---------------------------------------------------------------------------- Format String Attacks by Tim Newsham , Guardent Inc. Mon Sep 11 2000 Format String Attacks Tim Newsham Guardent, Inc. September 2000 Copyright (c) 2000. All Rights Reserved format string Ãë¾àÁ¡ÀÇ ¿øÀΰú ¿µÇâ¿¡ ´ëÇؼ­ ¾Ë¾Æº¾´Ï´Ù. ½ÇÁ¦ ¿¹Á¦¸¦ »ç¿ëÇÏ¿© ¿ø¸®¸¦ ±¸ÇöÇØ º¸¾Ò½À´Ï´Ù. INTRODUCTION ³ª´Â ÀÌ°ÍÀÌ ´ç½Å¿¡°Ô ÀϾ°í Àִٴ°ÍÀ» ¾Ë°í ÀÖ½À´Ï´Ù. ÀÌ°ÍÀº ¾î¶»°Ôµç ¿ì¸® ¸ðµÎ¿¡°Ô ÀϾ°í ÀÖ½À´Ï´Ù. ´ç½ÅÀº Àú³áÆÄƼ¿¡ ¿ÍÀÖ½À´Ï´Ù. °Ý¾çµÈ Ä£±¸ÀÇ ¸ñ¼Ò¸®Áß¿¡¼­, ´ç½ÅÀº "format string attack." À̶õ ¸»À» µè½À´Ï´Ù. "Format string attack? Format string attack ÀÌ ¹¹¿©?" ¶ó°í ´ç½ÅÀº Àǹ®À» °¡Áý´Ï´Ù. µ¿·áµé¿¡°Ô´Â ´ç½ÅÀÇ ¹«Áö°¡ µå·¯³¯±îºÁ ºÒÆíÇÑ ¿ôÀ½À» ÁöÀ¸¸ç °í°³¸¦ ²ô´ö°Å¸®°í Àß ¾Ë°í ÀÖ´Â È­Á¦°Å¸®°¡ ³ª¿À±â¸¦ ±â´Ù¸³´Ï´Ù. ±×·±´ë·Î ¸¶¹«¸®°¡ µÇ¸é ĬÅ×ÀÏÀÌ ¸îÀÜ µ¹°í È­Á¦´Â ´Ù¸¥°ÍÀ¸·Î ¹Ù²î°í, ´©±¸¿¡°Ôµµ ¹ßÀüÀº ¾ø°Ô µË´Ï´Ù. ÀÚ~ ´õÀÌ»ó °ÆÁ¤ÇÏÁö ¸¶¼¼¿ä. ÀÌ ¹®¼­´Â ´ç½ÅÀÌ ¾Ë°í½Í¾îÇß´ø, ±×·¯³ª Áú¹®ÇÏÁö ¸øÇß´ø format string attack ¿¡´ëÇÑ ¸ðµç°ÍÀ» ¾Ë·ÁÁÙ°ÍÀÔ´Ï´Ù. WHAT IS A FORMAT STRING ATTACK? format string ¹ö±×°¡ »ý±ä ÀÌÀ¯´Â ´Ù¸¥ security hole µé°ú ´Ù¸¦°Ô ¾ø½À´Ï´Ù. ¹Ù·Î ÇÁ·Î±×·¡¸ÓÀÇ °ÔÀ¸¸§ÀÌÁÒ. ´ç½ÅÀÌ ÀÌ ±ÛÀ» Àаí ÀÖ´Â Áö±Ýµµ ¾îµð¼±°¡´Â ÇÁ·Î±×·¡¸Ó°¡ Äڵ带 ÀÛ¼ºÇÏ°í ÀÖ ½À´Ï´Ù. ±×°¡ ÇÒÀÏÀº ½ºÆ®¸µÀ» Ãâ·ÂÇϰųª ±×°ÍÀ» ¹öÆÛ¿¡ º¹»çÇسִ °Í ÀÔ´Ï´Ù. ±×°¡ ÇÏ·Á°í ÇÏ´Â ÀÏÀº ´ÙÀ½°ú °°À» °Ì´Ï´Ù: printf("%s", str); ±×·¯³ª ±×´Â ÀÌ·¸°Ô ÇÏ´Â ´ë½Å¿¡ ½Ã°£, ³ë·Â ±×¸®°í 6¹ÙÀÌÆ® ºÐ·®ÀÇ ¼Ò½ºÄÚµå ŸÀÌÇÎÀ» ÁÙÀ̱â·Î °áÁ¤Çß½À´Ï´Ù: printf(str); ¿Ö ¾ÈµÉ±î¿ä? ¿Ö ¿©ºÐÀÇ printf ÀÎÀÚ·Î ¿ì¸®°¡ ¼ö°í¸¦ ÇؾßÇÒ±î¿ä? ±× ¸ÛûÇÑ format À¸·Î ºÐ¼®ÇØ ³»´Âµ¥´Â ½Ã°£ÀÌ °É¸³´Ï´Ù. ¾î·µç! printf ÀÇ Ã¹¹ø° ÀÎÀÚ(argument) ¸¸À¸·Îµµ ½ºÆ®¸µÀº Ãâ·ÂµË´Ï´Ù. ¿Ö³ÄÇϸé ÇÁ·Î±×·¡¸Ó´Â security hole ÀÌ »ý±ä´Ù´Â°ÍÀ» ¸ð¸£°í ÀÌ°ÍÀº °ø°ÝÀÚ°¡ ÇÁ·Î±×·¥ÀÇ ½ÇÇàÀ» Á¦¾îÇÒ¼ö ÀÖµµ·Ï ÇÒ ¼ö ÀÖ½À´Ï´Ù. ÀÌ°ÍÀÌ ±× ÀÌÀ¯ÀÔ´Ï´Ù. ÇÁ·Î±×·¡¸Ó°¡ À߸øÇÑ °ÍÀº ¹«¾ùÀԴϱî? ±×´Â Ãâ·ÂÇÏ°íÀÚÇÏ´Â ½ºÆ®¸µÀ» °£·«ÇÏ°Ô ³Ñ°ÜÁÖ¾ú½À´Ï´Ù. ±× ½ºÆ®¸µÀº printf ÇÔ¼ö¿¡ ÀÇÇØ format string À¸·Î °£ÁֵǾî interpret µË´Ï´Ù. "%d" °°Àº Ưº°ÇÑ format ¹®ÀÚ¸¦ °¨½ÃÇÏ´Ù°¡ format ÀÌ ³ª¿À°Ô µÇ¸é ÀÎÀÚÀÇ °ªÀ» ½ºÅÿ¡¼­ °¡Á®¿À°Ô µË´Ï´Ù. °ø°ÝÀÚ´Â ½ºÅÿ¡ ÀúÀåµÇ¾î ÀÖ´Â °ªÀ» ÀÌ·±½ÄÀ¸·Î Ãâ·ÂÇÔÀ¸·Î¼­ ÇÁ·Î±×·¥ÀÇ ¸Þ¸ð¸®¸¦ µé¿©´Ùº¼ ¼ö ÀְԵ˴ϴÙ. ÀÌ °£´ÜÇÑ ½Ç¼ö°¡ ½ÇÇàÁßÀÎ ÇÁ·Î±×·¥ÀÇ ¸Þ¸ð¸®¿¡ ÀÓÀÇÀÇ °ªÀ» ¾µ¼ö ÀÖµµ·Ï ¸¸µéÁöµµ ¸ð¸¨´Ï´Ù. PRINTF - WHAT THEY FORGOT TO TELL YOU IN SCHOOL ¿ì¸®ÀÇ ¸ñÀû¿¡ printf ¸¦ ¾î¶»°Ô ¾Ç¿ëÇÒ ¼ö ÀÖ´ÂÁö¿¡´ëÇؼ­ ÀÚ¼¼È÷ µé¾î°¡±â Àü¿¡, printf °¡ Á¦°øÇÏ´Â ±â´É¿¡´ëÇØ È®½ÇÈ÷ ¾Ë¾Æ¾ß ÇÕ´Ï´Ù. ÀÌ ±ÛÀ» Àд µ¶ÀÚ´Â printf ÇÔ¼ö¸¦ »ç¿ëÇØ º¸¾Ò°í, ¾î¶»°Ô Á¤¼ö¿Í ½ºÆ®¸µ(¹®ÀÚ¿­)À» Ãâ·ÂÇÏ´ÂÁö, ¶Ç ¾î¶»°Ô ½ºÆ®¸µÀÇ ÃÖ´ë, ÃÖ¼Ò ÆøÀ» ÁöÁ¤ÇÏ´ÂÁö °°Àº ±âº»ÀûÀÎ formatting ±â´Éµé¿¡ ´ëÇØ ¾Ë°íÀÖ´Ù°í °¡Á¤ÇÕ´Ï´Ù. ÀÌ·± º¸ÅëÀÇ ±â´Éµé¿¡, Àß ¾Ë·ÁÁöÁö ¾ÊÀº ±â´ÉµéÀÌ ¸î°³ ÀÖ½À´Ï´Ù. format string ÀÇ ¾î´À ÁöÁ¡¿¡¼­°Ç Ãâ·ÂµÈ ¹®ÀÚÀÇ ¼ö¸¦ ¾ò´Â °ÍÀÌ °¡´ÉÇÕ´Ï´Ù. format string ¿¡¼­ "%n" format ÀÌ ³ª¿À°Ô µÇ¸é %n ÀÌ ³ª¿À±â Àü¿¡ Ãâ·ÂµÈ ¹®ÀÚÀÇ ¼ö°¡ ´ÙÀ½¿¡ ³ª¿À´Â ÀÎÀÚÀÇ ÁÖ¼Ò¿¡ ÀúÀåµË´Ï´Ù. ¿¹¿¡¼­´Â, µÎ format µÈ ¼öÀÇ offset À» ±¸ÇÒ ¼ö ÀÖ½À´Ï´Ù: int pos, x = 235, y = 93; printf("%d %n%d\n", x, &pos, y); printf("The offset was %d\n", pos); "%n" format Àº ½ÇÁ¦·Î Ãâ·ÂµÈ ¹®ÀÚÀÇ ¼ö°¡ ¾Æ´Ñ Ãâ·ÂµÇ·Á°í Çß´ø ¹®ÀÚÀÇ ¼ö¸¦ ¸®ÅÏÇØÁÝ´Ï´Ù. ½ºÆ®¸µÀ» °íÁ¤µÈ Å©±âÀÇ ¹öÆÛ¿¡ formatting ÇÑ´Ù¸é, ½ºÆ®¸µÀº ¹öÆÛÀÇ Å©±â¿¡ ¸Â°Ô µÞºÎºÐÀº Àß·Á ³ª°¥°ÍÀÔ´Ï´Ù. ÀÌ·¸°Ô Àß·Á³ª°¡´õ¶óµµ "%n" format ÀÌ ¸®ÅÏÇÏ´Â offset Àº ½ºÆ®¸µÀÌ Àß·Á³ª°¡Áö ¾Ê¾Ò´Ù°í °¡Á¤ÇÏ°í ±× ±æÀ̸¦ ¹Ý¿µÇÕ´Ï´Ù. À̺κÐÀÇ ¿¹Á¦¸¦ º¸°Ú½À´Ï´Ù. ´ÙÀ½ ÄÚµåÀÇ Ãâ·Â°ªÀº "20" ÀÌ ¾Æ´Ï°í "100" ÀÌ µÉ °ÍÀÔ´Ï´Ù: char buf[20]; int pos, x = 0; snprintf(buf, sizeof buf, "%.100d%n", x, &pos); printf("position: %d\n", pos); A SIMPLE EXAMPLE ÀÌ·± Ãß»óÀûÀÎ ¿¹±â¸»°í, ÀÌÁ¦´Â ¾Õ¿¡¼­ ³íÀÇ µÆ´ø ¿ø¸®µéÀ» º¸¿©ÁÙ¼ö ÀÖ´Â ±¸Ã¼ÀûÀÎ ¿¹Á¦¸¦ »ç¿ëÇϵµ·Ï ÇÏ°Ú½À´Ï´Ù. ¾Æ·¡¿¡ ³ª¿À´Â °£´ÜÇÑ ÇÁ·Î±×·¥À¸·Îµµ ÃæºÐÇÕ´Ï´Ù: /* * fmtme.c * Format a value into a fixed-size buffer */ #include int main(int argc, char **argv) { char buf[100]; int x; if(argc != 2) exit(1); x = 1; snprintf(buf, sizeof buf, argv[1]); buf[sizeof buf - 1] = 0; printf("buffer (%d): %s\n", strlen(buf), buf); printf("x is %d/%#x (@ %p)\n", x, x, &x); return 0; } ÀÌ ÇÁ·Î±×·¥¿¡ ´ëÇØ ¸î°¡Áö ¾Ë¾Æº¸°Ú½À´Ï´Ù. ¸ÕÀú, »ç¿ë¹ýÀº °£´ÜÇÕ´Ï´Ù: Ä¿¸Çµå ¶óÀÎÀ» ÅëÇؼ­ Àü´ÞµÈ °ªÀº °íÁ¤µÈ ±æÀÌÀÇ ¹öÆÛ¿¡ format µË´Ï´Ù. ¹öÆÛ´Â ³ÑÄ¡Áö ¾Êµµ·Ï ÇØ ³õ¾Ò½À ´Ï´Ù. ¹öÆÛ°¡ format µÈ ÈÄ¿¡ ±×°ÍÀ» Ãâ·ÂÇÕ´Ï´Ù. ÀÎÀÚ(argument)¸¦ formating ÇÑ ´ÙÀ½¿¡ Á¤¼öÇü º¯¼ö¸¦ ¼³Á¤ÇÏ°í ÈÄ¿¡ ÀÌ°ÍÀ» Ãâ·ÂÇÕ´Ï´Ù. ÀÌ º¯¼ö´Â ÈÄ¿¡ °ø°ÝÀÇ ´ë»óÀ¸·Î »ç¿ëµË´Ï´Ù. Áö±ÝÀº Ãâ·ÂµÇ´Â °ªÀÌ Ç×»ó 1ÀÏ °ÍÀÔ´Ï´Ù. ÀÌ ±Û¿¡ ÀÖ´Â ¸ðµç ¿¹Á¦µéÀº x86 BSD/OS 4.1 ¹Ú½º¿¡¼­ ¼öÇàÀÌ µË´Ï´Ù. ¸¸¾à ´ç½ÅÀÌ ÃÖ±Ù 20³âµ¿¾È ¸ðÀáºñÅ© ¼±±³È°µ¿À» ÇÏ´À¶ó°í x86 ÀÌ Ä£¼÷ÇÏÁö ¾Ê´Ù¸é, x86 Àº ¸®Æ²¿£µð¾È(littel-endian) ¸Ó½ÅÀÔ´Ï´Ù. ÀÌ°ÍÀº ¿¹Á¦»ó¿¡¼­ multi-precision number °¡ ¹ÙÀÌÆ®°ªÀÇ ¿¬¼ÓÀ¸·Î Ç¥ÇöµÉ ¶§ ¹Ý¿µµÉ °ÍÀÔ´Ï´Ù. ¿©±âÀÇ »ç¿ëµÈ ½ÇÁ¦ÀÇ ¼ýÀÚ´Â ´Ù¸¥ ¾ÆÅ°ÅØó, OS, ȯ°æ ±×¸®°í Ä¿¸Çµå¶óÀÎÀÇ ±æÀÌµî ½Ã½ºÅÛ¿¡ µû¶ó ´Ù¸¦°ÍÀÔ´Ï´Ù. ¿¹Á¦µéÀº Á¶±Ý¸¸ ¼Õº¸¸é x86 ¸Ó½Å¿¡¼­ µ¿ÀÛÇϵµ·Ï ÇÒ ¼ö ÀÖ½À´Ï´Ù. ¾à°£ÀÇ ³ë·Â°ú »ý°¢À¸·Î ´Ù¸¥ ¾ÆÅ°ÅØó¿¡¼­µµ Àß µ¿ÀÛÇϵµ·Ï ¸¸µé¼ö ÀÖÀ»°ÍÀÔ´Ï´Ù. FORMAT ME! ÀÌÁ¦ ¿ì¸®ÀÇ °ËÀº¸ðÀÚ(black hat) ¸¦ ¾²°í °ø°ÝÀÚÀÇ ÀÔÀå¿¡¼­ »ý°¢ÇØ¾ß ÇÒ ¶§ÀÔ´Ï´Ù. ¿ì¸®¿¡°Ô´Â Å×½ºÆ® ÇÁ·Î±×·¥ÀÌ ÀÖ½À´Ï´Ù. ¿ì¸®´Â ÀÌ°ÍÀÌ Ãë¾àÇÏ´Ù´Â °ÍÀ» ¾Ë°íÀÖ°í ÇÁ·Î±× ·¡¸Ó°¡ ¾îµð¼­ ÀÌ·± ½Ç¼ö¸¦ ÀúÁö¸£´ÂÁö ¾Ë°íÀÖ½À´Ï´Ù. ¿ì¸®´Â printf ÇÔ¼ö¿¡ ´ëÇÑ Áö½ÄÀ» °¡ Áö°í ÀÖ°í ±×°ÍÀÌ ¿ì¸®¿¡°Ô ¹«¾ùÀ» ÇØÁÙÁö ¾Ë°í ÀÖ½À´Ï´Ù. ÀÚ ÀÌÁ¦ ÀÌ ÇÁ·Î±×·¥À¸·Î »ðÁú(¤Ñ¤Ñ;;)À» Çغ¾½Ã´Ù. ½ÃÀÛÀº °£´ÜÇÏ°Ô, Á¤»óÀûÀÎ ÀÎÀÚ(argument) ·Î ÇÁ·Î±×·¥À» ½ÇÇàÇØ º¾½Ã´Ù: % ./fmtme "hello world" buffer (11): hello world x is 1/0x1 (@ 0x804745c) Ưº°ÇÑ°ÍÀº ¾ø½À´Ï´Ù. ÇÁ·Î±×·¥Àº ¿ì¸®°¡ ÀÔ·ÂÇÑ ½ºÆ®¸µÀ» ¹öÆÛ¿¡ format ÇÏ¿´°í ±×°ÍÀÇ ±æÀÌ¿Í °ªÀ» Ãâ·ÂÇÏ¿´½À´Ï´Ù. ¶Ç 1ÀÇ °ª(10Áø¼ö¿Í 16Áø¼ö·Î Ãâ·ÂÀ̵ÆÁÒ) À» °¡Áö°í ÀÖ´Â º¯¼ö x µµ Ãâ·ÂÀÌ µÇ¾ú°í, ±×°ÍÀº 0x804745c ¿¡ ÀúÀåÀÌ µÇÀÖ½À´Ï´Ù. À̹ø¿¡´Â format ¸í·ÉµéÀ» ³Ö¾îº¾½Ã´Ù. ÀÌ ¿¹Á¦¿¡¼­´Â format string ÀÌ ¾Æ´Ï¶ó ½ºÅÿ¡ÀÖ´Â Á¤¼öµéÀÌ Ãâ·ÂµÉ°ÍÀÔ´Ï´Ù: % ./fmtme "%x %x %x %x" buffer (15): 1 f31 1031 3133 x is 1/0x1 (@ 0x804745c) ÇÁ·Î±×·¥À» Àá±ñ¸¸ ºÐ¼®ÇØ º¸¸é sprintf °¡ È£ÃâµÆÀ»¶§ ½ºÅûóÀÇ ·¹À̾ƿôÀ» ¾Ë ¼ö ÀÖÀ»°ÍÀÔ´Ï´Ù. Address Contents Description fp+8 Buffer pointer 4-byte address fp+12 Buffer length 4-byte integer fp+16 Format string 4-byte address fp+20 Variable x 4-byte integer fp+24 Variable buf 100 characters ÀÌÀüÀÇ Å×½ºÆ®¿¡¼­ Ãâ·ÂµÈ 4°³ÀÇ °ªÀº ½ºÅÿ¡¼­ format string ÀÇ µÚ¿¡ ÀÖ´Â ³×°³ÀÇ ÀÎÀÚ ÀÔ´Ï´Ù: º¯¼ö x, ±×¸®°í ÃʱâÈ­µÇÁö¾ÊÀº buf ·ÎºÎÅÍ °¡Á®¿Â Á¤¼ö 3°³. ÀÌÁ¦ Á¤¸®¸¦ ÇÒ ½Ã°£ÀÔ´Ï´Ù. ÀÚ~ °ø°ÝÀÚ¶ó°í »ý°¢À» ÇսôÙ. ¿ì¸®´Â ¹öÆÛ¿¡ ÀúÀåµÉ °ªµéÀ» ¸¶À½´ë·Î ÇÒ ¼ö ÀÖ½À´Ï´Ù. ÀÌ °ªµéÀº sprintf È£ÃâÀÇ ÀÎÀÚ·Î »ç¿ëµË´Ï´Ù. Àá½Ã È®ÀÎÀ» ÇØ º¾½Ã´Ù: % ./fmtme "aaaa %x %x" buffer (15): aaaa 1 61616161 x is 1/0x1 (@ 0x804745c) ¿½! ¿ì¸®°¡ ÀÔ·ÂÇÑ 'a' ³×°³°¡ ¹öÆÛ·Î º¹»çµÇ¾ú°í sprintf ¿¡ÀÇÇØ 0x61616161 ('a' ÀÇ ¾Æ½ºÅ° °ªÀÌ 0x61 ÀÔ´Ï´Ù) °ªÀ» °®´Â Á¤¼öÇü ÀÎÀÚ·Î interpret µÇ¾ú½À´Ï´Ù. X MARKS THE SPOT ¸ðµç Á¶°¢µéÀÌ Á¦ÀÚ¸®¸¦ ã¾Æ°¡°í ¾Ò½À´Ï´Ù! ÀÌÁ¦ ¼öµ¿ÀûÀÎ Å×½ºÆ®º¸´Ù´Â Á»´õ ´Éµ¿Àû À¸·Î ÇÁ·Î±×·¥ÀÇ »óŸ¦ º¯°æÇغ¼ ½Ã°£ÀÔ´Ï´Ù. º¯¼ö 'x' ¸¦ ±â¾ïÇϽÃÁÒ? ÀÚ ÀÌ°ÍÀÇ °ªÀ» ¹Ù²ãº¾½Ã´Ù. ÀÌ°É ÇÒ·Á¸é ¸ÕÀú x ÀÇ ÁÖ¼Ò¸¦ sprintf ÀÇ ÀÎÀÚ·Î ÀÔ·ÂÇØ ÁÖ¾î¾ß ÇÕ´Ï´Ù. ±×´ÙÀ½ sprintf ÀÇ ÀÎÀÚÇϳª(ÀÌ°Ç º¯¼ö x) ¸¦ °Ç³Ê ¶Ù¾î¾ß °ÚÁÒ. ±×¸®°í ¸¶Áö¸·À¸·Î "%n" format À» »ç¿ëÇÏ¿© ¿ì¸®°¡ ÁöÀåÇÑ ÁÖ¼Ò¿¡ ¾²±â¸¦ ÇÕ´Ï´Ù. Áö±ÝÀº ¾î·Æ°Ô µé¸®°ÚÁö¸¸ ¿¹Á¦¸¦ º¸¸é ÀÌÇØ°¡ µÉ°ÍÀÔ´Ï´Ù. [Note: ¿ì¸®°¡ ÇÊ¿äÇÑ °ªÀ» Ä¿¸Çµå ¶óÀÎÀÇ ÀÎÀÚ·Î ¿¡¼­ ½±°Ô ÀÔ·Â ÇÒ ¼ö ÀÖµµ·Ï PERL À» »ç¿ëÇؼ­ ÇÁ·Î±×·¥À» ½ÇÇàÇÏ°Ú½À´Ï´Ù.]: % perl -e 'system "./fmtme", "\x58\x74\x04\x08%d%n"' buffer (5): X1 x is 5/x05 (@ 0x8047458) xÀÇ °ªÀÌ ¹Ù²î¾ú½À´Ï´Ù. ±Ùµ¥ Á¤È®È÷ ¹«½¼ÀÏÀÌ ÀϾ°É±î¿ä? sprintf ÀÇ ÀÎÀÚ´Â ´ÙÀ½°ú °° À»°ÍÀÔ´Ï´Ù: snprintf(buf, sizeof buf, "\x58\x74\x04\x08%d%n", x, 4 bytes from buf) ¸ÕÀú sprintf ´Â óÀ½ ³×¹ÙÀÌÆ®¸¦ ¹öÆÛ¿¡ º¹»çÇÕ´Ï´Ù. ´ÙÀ½ "%d" format À» Àоî¿Í¼­ x ÀÇ °ªÀ» Ãâ·ÂÇÕ´Ï´Ù. ³¡À¸·Î "%n" ¸í·É¿¡ À̸£°Ô µÇ¸é ½ºÅÿ¡¼­ ´ÙÀ½°ªÀ» °¡Áö°í ¿À°Ô µÇÁÒ. ±× °ªÀº buf ÀÇ Ã³À½ 4¹ÙÀÌÆ®°¡ µÉ°ÍÀÔ´Ï´Ù. ÀÌ 4¹ÙÀÌÆ®´Â Àü¿¡ "\x58\x74\x04\x08" ·Î ä¿ö Á³ÁÒ(Á¤¼ö°ªÀ¸·Î ÇѸé 0x08047458). sprintf ´Â Ãâ·ÂµÈ¹ÙÀÌÆ® ÀÇ ¼ö ¸¦ ±×°÷¿¡´Ù°¡ ÀúÀåÇÕ´Ï´Ù. ±× °ªÀº 5°¡µÇ°ÚÁÒ. Á» µ¹·Á¼­ »ý°¢Çغ¾½Ã´Ù. ±× ÁÖ¼Ò´Â º¯¼ö x ÀÇ ÁÖ¼ÒÀÔ´Ï´Ù. ÀÌ°ÍÀº ¿ì¿¬ÀÇ ÀÏÄ¡°¡ ¾Æ´Õ´Ï´Ù. ÀÌÀüÀÇ Å×½ºÆ®¸¦ Âü°íÇؼ­ 0x08047458 À̶ó´Â »ç¿ëÇÏ°Ô µÇ¾ú½À´Ï´Ù. ÀÌ °æ¿ì¿¡´Â ÇÁ·Î±×·¥ÀÌ ¿ì¸®¿¡°Ô ÇÊ¿äÇÑ ÁÖ¼Ò¸¦ Ãâ·ÂÇØÁ༭ µµ¿òÀÌ µÇ¾ú½À´Ï´Ù¸¸ º¸ÅëÀº µð¹ö°Å¸¦ »ç¿ëÇÏ¿© ±× °ªÀ» ã¾Æ³»¾ß ÇÕ´Ï´Ù. ÀÚ~ ¸ÚÁöÁÒ! ÀÓÀÇÀÇ ÁÖ¼Ò°ª (ÁÖ¼Ò°ªÀÌ ³Î¹®ÀÚ¸¦ Æ÷ÇÔÇÏÁö ¾Ê´ÂÇÑ) À» ã¾Æ³»¼­ ±×°÷¿¡ °ªÀ» ¾µ ¼ö ÀÖ½À´Ï´Ù. ±×·±µ¥ ±×°÷¿¡ À¯¿ëÇÑ °ªÀ» ¾µ ¼ö ÀÖÀ»±î¿ä? Sprintf ·Î´Â Ãâ·ÂµÈ ¹®ÀÚÀÇ ±æÀ̸¸À» ¾µ¼ö ÀÖ½À´Ï´Ù. ¸¸¾à ¾µ·Á°í ÇÏ´Â °ªÀÌ 4º¸´Ù ÀÛÀº °ªÀ̶ó¸é ¹æ¹ýÀº ¾ÆÁÖ °£´ÜÇÕ´Ï´Ù. ¿ì¸®°¡ ¿øÇÏ´Â °ªÀÌ µÉ¶§±îÁö format string À¸·Î ä¿ö ³ÖÀ¸¸é µÉ°ÍÀÔ´Ï´Ù. ±×·¯³ª Å«°ªÀº? "%n" ÀÌ ¹®ÀÚÀÇ °¹¼ö¸¦ Çì¾Æ¸±¶§´Â Ãâ·ÂÇÒ¶§ Àß·Á³ª°£ °ÍÀÌ ¾ø´Ù°í °¡Á¤ÇÕ´Ï´Ù. ¿ì¸®´Â ÀÌ°ÍÀ» ÀÌ¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù: % perl -e 'system "./fmtme", "\x54\x74\x04\x08%.500d%n" buffer (99): %0000000 ... 0000 x is 504/x1f8 (@ 0x8047454) "%n" ¿¡ ÀÇÇØ ¾²¿©Áø x ÀÇ °ª 504´Â ½ÇÁ¦·Î buf ·Î º¸³»Áø 99 ¹ÙÀÌÆ®º¸´Ù ÈξÀ Å«°ªÀÔ´Ï´Ù. ¿ì¸®´Â ÇʵåÆøÀ» Å©°Ô ÁöÁ¤Çؼ­ ÀÓÀÇÅ©±âÀÇ °ªÀ» ³ÖÀ» ¼ö ÀÖ½À´Ï´Ù [1]. ±×·¯¸é ÀÛÀº °ªÀº ¾î¶»°Ô? ÀÌ°Ç ºÎºÐ¾²±â¸¦ ¿©·¯¹ø ÇÔÀ¸·Î½á ¿øÇÏ´Â °ª(0 ±îÁöµµ)À» ¸¸µé¾î ³¾ ¼ö ÀÖ½À´Ï´Ù. 1¹ÙÀÌÆ® offset À¸·Î ³×°³ÀÇ ¼ýÀÚ¸¦ write ÇÑ´Ù¸é, ³×°³ÀÇ ÃÖÇÏÀ§ ¹ÙÀÌÆ®·Î ÀÌ·ç¾îÁø ¿øÇÏ´Â °ªÀ» ¸¸µé¾î³¾ ¼ö ÀÖÀ»°ÍÀÔ´Ï´Ù. ÀÌ°ÍÀ» Ç¥·Î ¸¸µé¾îº¸¾Ò½À´Ï´Ù. ¾Æ·¡ÀÇ Ç¥¸¦ º¸°í Àß »ý°¢ ÇØ º¸¼¼¿ä: Address A A+1 A+2 A+3 A+4 A+5 A+6 Write to A: 0x11 0x11 0x11 0x11 Write to A+1: 0x22 0x22 0x22 0x22 Write to A+2: 0x33 0x33 0x33 0x33 Write to A+3: 0x44 0x44 0x44 0x44 Memory: 0x11 0x22 0x33 0x44 0x44 0x44 0x44 ³×¹øÀÇ write°¡ ³¡³­ÈÄ ¸Þ¸ð¸® ÁÖ¼Ò A ÀÇ Á¤¼öÇü °ªÀº, ³×¹ø ¾²¿©Áø °ªÀÇ ¸¶Áö¸· ¹ÙÀÌÆ®·Î ÀÌ·ç¾îÁø 0x44332211 ÀÌ µÇ¾îÀÖÀ»°Ì´Ï´Ù. ÀÌ Å×Å©´ÐÀ¸·Î ¿ì¸®´Â À¯¿¬¼º ÀÖ°Ô °ªÀ» write ÇÒ ¼ö ÀÖ½À´Ï´Ù. ±×·¯³ª ÀÌ ¹æ¹ý¿¡µµ ¹®Á¦°¡ Á» ÀÖ½À´Ï´Ù: ÀÌ ¹æ¹ýÀº ¿øÇÏ´Â °ªÀ» ³Ö±â À§ÇØ ³×¹øÀ̳ª write¸¦ ÇؾßÇÕ´Ï´Ù. ±×·¯¸é ´ë»ó ÁÖ¼ÒÀÇ ÁÖº¯ 3¹ÙÀÌÆ®¸¦ °Çµå¸®°Ô µË´Ï´Ù. ¶Ç ¼¼¹øÀÇ unaligned write ¸¦ ÇؾßÇϴµ¥, ÃÖ±Ù ¸î¸î ¾ÆÅ°ÅØÃĵéÀº unaligned write ¸¦ Áö¿øÇÏÁö ¾Ê½À´Ï´Ù. ±×·¡¼­ ÀÌ Å×Å©´ÐÀ» ¸ðµç °÷¿¡ Àû¿ëÇÒ ¼ö ´Â ¾ø½À´Ï´Ù. SO WHAT? ±×·¡¼­ ¹¹? ¹¹? ¾î¼¶ó°í? ±×·¡¼­ ´ç½ÅÀÌ ¿øÇÏ´Â (°ÅÀÇ ´ëºÎºÐÀÇ) ¸Å¸ð¸® ÁÖ¼Ò¿¡´Ù°¡ ÀÓÀÇÀÇ °ªÀ» ¾µ ¼ö ÀÖ½À´Ï´Ù!! ´ç½ÅÀº ÀÌ°É ¾îµð´Ù À¯¿ëÇÏ°Ô »ç¿ëÇÒÁö »ý°¢Çس¾ ¼ö ÀÖÀ»°ÍÀÔ ´Ï´Ù. ´ÙÀ½À» º¾½Ã´Ù: ÀúÀåµÈ UID ¸¦ overwrite ÇÁ·Î±×·¥ÀÌ ³ôÀº ±ÇÇÑÀ» ÁÖµµ·Ï ÇÒ ¼ö ÀÖ½À´Ï´Ù. ½ÇÇàµÈ ¸í·ÉÀ» overwrite ÇÒ ¼ö ÀÖ½À´Ï´Ù. ¸®ÅϾîµå·¹½º¸¦ overwrite ÇÏ¿© ±×°ÍÀÌ ½©Äڵ尡 ÀÖ´Â ¹öÆÛ¸¦ °¡¸®Å°µµ·Ï ÇÒ ¼ö ÀÖ½À´Ï´Ù. °£´ÜÇÏ°Ô ¸»Çϸé: ´ç½ÅÀº ±× ÇÁ·Î±×·¥À» Áö¹èÇÒ ¼ö ÀÖ½À´Ï´Ù. ÁÁ½À´Ï´Ù. ±×·³ ¿À´Ã ¿ì¸®°¡ ¹è¿î°ÍÀº? printf ´Â ´ç½ÅÀÌ ÀÌÀü¿¡ »ý°¢Çß´ø°Íº¸´Ù ÈξÀ °­·ÂÇÏ´Ù. ±ÍÅüÀÌ°¡ Àß·Á³ª°£ °ÍÀº °áÄÚ °ªÀ» ¹ÞÀ» ¼ö ¾ø´Ù. ¹«ÇØÇغ¸ÀÌ´Â »ý·«Àº °ø°ÝÀÚ°¡ ´ç½ÅÀÇ ÇϷ縦 ¸ÁÄ¡°Ô ÇÒ¼öµµ ÀÖ´Ù. ÃæºÐÇÑ ½Ã°£°ú ³ë·ÂÀÌ ÀÖ´Ù¸é, Áö³­ obfuscated-C contest ÀÇ ÀÔ»óÀÛó·³ º¸ÀÌ´Â input string À¸·Î ´©±º°¡ÀÇ ÀÛÀº ½Ç¼ö¸¦ ¿¬ÇÕ´º½º°Å¸®·Î ¸¸µé ¼ö ÀÖ½À´Ï´Ù. [1] ÇöÀç ³ª¿ÍÀÖ´Â glibc ÀÇ printf ´Â ±¸Çö»ó¿¡ ¹®Á¦°¡ ÀÖ½À´Ï´Ù. ÇʵåÆøÀ» Å« °ªÀ¸·Î ¼³Á¤Çϸé printf ´Â ³»ºÎ ¹öÆÛ¿¡ underflow °¡ ¹ß»ýÇÏ¿© ÇÁ·Î±×·¥ÀÌ Á¤ÁöÇÏ°Ô µË´Ï´Ù. ±×·¡¼­ ÇöÀç ¹öÀüÀÇ ¸®´ª½º»ó¿¡¼­ ÇÁ·Î±×·¥À» °ø°ÝÇÒ¶§ ¼öõ (several thousand) º¸´Ù Å« °ªÀ» ÇʵåÆøÀ¸·Î ÁöÁ¤ÇÏ´Â °ÍÀº ºÒ°¡´ÉÇÕ´Ï´Ù. ¿¹¸¦µé¸é, ´ÙÀ½°ú °°Àº ÄÚµå´Â À̹®Á¦·Î segmentation fault ¸¦ ÀÏÀ¸Åµ´Ï´Ù: printf("%.9999d", 1); * ÀÌ ±Û(english ver. °ú korean ver. ¸ðµÎ) ÀÇ ÀúÀÛ±Ç(copyright) Àº Guardent ÀÇ Tim Newsham ¿¡°Ô ÀÖ½À´Ï´Ù. * ------------------------------------------------------------------- - ÀÌÇØ°¡ ¾ÈµÇ´ÂºÎºÐÀº ¾ðÁ¦³ªÃ³·³ ¿ø¹®À» Âü°í ÇÏ½Ã°í ±×·¡µµ ÀÌÇØ°¡ ¾ÈµÇ½Ã¸é Çѹø ´õ Àо½Ã°í ±×·¡µµ ÀÌÇØ°¡ ¾ÈµÉ°æ¿ì ´Ù¸¥ Æ÷¸Ë½ºÆ®¸µ°ü·Ã ±ÛÀ» ÈÈ¾î º» ´ÙÀ½ ÀÐ¾î º¸½Ã°í ±×·¡µµ ÀÌÇØ°¡ ¾ÈµÇ½Ã¸é ¹öÆÛ¿À¹öÇÃ·Î¿ì °ü·Ã±ÛµéÀ» ¸¶½ºÅÍÇϽŠ´ÙÀ½ Àо½Ã°í.. ±×·¡µµ ÀÌÇØ°¡ ¾ÈµÇ½Å´Ù¸é.. Çä.. ¤Ñ¤Ñ;;»ßÁú - ¼öÁ¤ÇÒ ºÎºÐÀÖÀ¸¸é ¸áÁÖ¼¼¿ä.. °íÄ¥µ¥ ¸¹À»°Í °°Àºµ¥.. ------------------------------------------------------------------- - ¿ø¹®À» ÷ºÎÇÕ´Ï´Ù. Format String Attacks by Tim Newsham , Guardent Inc. Mon Sep 11 2000 Format String Attacks Tim Newsham Guardent, Inc. September 2000 Copyright (c) 2000. All Rights Reserved The cause and implications of format string vulnerabilities are discussed. Practical examples are given to illustrate the principles presented. INTRODUCTION I know it has happened to you. It has happened to all of us, at one point or another. You're at a trendy dinner party, and amidst the frenzied voices of your companions you hear the words "format string attack." "Format string attack? What is a format string attack?" you ask. Afraid of having your ignorance exposed among your peers you decide instead to break an uncomfortable smile and nod in hopes of appearing to be in the- know. If all goes well, a few cocktails will pass and the conversation will move on, and no one will be the wiser. Well fear no more! This paper will cover everything you wanted to know about format string attacks but were afraid to ask! WHAT IS A FORMAT STRING ATTACK? Format string bugs come from the same dark corner as many other security holes: The laziness of programmers. Somewhere out there right now, as this document is being read, there is a programmer writing code. His task: to print out a string or copy it to some buffer. What he means to write is something like: printf("%s", str); but instead he decides that he can save time, effort and 6 bytes of source code by typing: printf(str); Why not? Why bother with the extra printf argument and the time it takes to parse through that silly format? The first argument to printf is a string to be printed anyway! Because the programmer has just unknowingly opened a security hole that allows an attacker to control the execution of the program, that's why! What did the programmer do that was so wrong? He passed in a string that he wanted printed verbatim. Instead, the string is interpreted by the printf function as a format string. It is scanned for special format characters such as "%d". As formats are encountered, a variable number of argument values are retrieved from the stack. At the least, it should be obvious that an attacker can peek into the memory of the program by printing out these values stored on the stack. What may not be as obvious is that this simple mistake gives away enough control to allow an arbitrary value to be written into the memory of the running program. PRINTF - WHAT THEY FORGOT TO TELL YOU IN SCHOOL Before getting into the details of how to abuse printf for our own purposes, we should have a firm grasp of the features printf provides. It is assumed that the reader has used printf functions before and knows about its normal formatting features, such as how to print integers and strings, and how to specify minimum and maximum string widths. In addition to these more mundane features, there are a few esoteric and little-known features. Of these features, the following are of particular relevance to us: It is possible to get a count of the number of characters output at any point in the format string. When the "%n" format is encountered in the format string, the number of characters output before the %n field was encountered is stored at the address passed in the next argument. As an example, to receive the offset to the space between two formatted numbers: int pos, x = 235, y = 93; printf("%d %n%d\n", x, &pos, y); printf("The offset was %d\n", pos); The "%n" format returns the number of characters that should have been output, not the actual count of characters that were output. When formatting a string into a fixed-size buffer, the output string may be truncated. Despite this truncation, the offset returned by the "%n" format will reflect what the offset would have been if the string was not truncated. To illustrate this point, the following code will output the value "100" and not "20": char buf[20]; int pos, x = 0; snprintf(buf, sizeof buf, "%.100d%n", x, &pos); printf("position: %d\n", pos); A SIMPLE EXAMPLE Rather than talking in vagaries and abstractions, we will use a concrete example to illustrate the principles as they are discussed. The following simple program will suffice for this purpose: /* * fmtme.c * Format a value into a fixed-size buffer */ #include int main(int argc, char **argv) { char buf[100]; int x; if(argc != 2) exit(1); x = 1; snprintf(buf, sizeof buf, argv[1]); buf[sizeof buf - 1] = 0; printf("buffer (%d): %s\n", strlen(buf), buf); printf("x is %d/%#x (@ %p)\n", x, x, &x); return 0; } A few notes about this program are in order. First, the general purpose is quite simple: A value passed on the command line is formatted into a fixed-length buffer. Care is taken to make sure the buffer limits are not exceeded. After the buffer is formatted, it is output. In addition to formatting the argument, a second integer value is set and later output. This variable will be used as the target of attacks later. For now, it should be noted that its value should always be one. All examples in this document were actually performed on an x86 BSD/OS 4.1 box. If you have been on a mission to Mozambique for the last 20 years and are unfamiliar with the x86, it is a little-endian machine. This will be reflected in the examples when multi-precision numbers are expressed as a series of byte values. The actual numbers used here will vary from system to system with differences in architecture, operating system, environment and even command line length. The examples should be easily adjusted to work on other x86 machines. With some effort and thought, they may be made to work on other architectures as well. FORMAT ME! It is now time to put on our black hats and start thinking like attackers. We have in our hands a test program. We know that it has a vulnerability and we know where the programmer made his mistake. We are also armed with a thorough knowledge of the printf function and what it can do for us. Let's get to work by tinkering with our program. Starting off simple, we invoke the program with normal arguments. Let's begin with this: % ./fmtme "hello world" buffer (11): hello world x is 1/0x1 (@ 0x804745c) There's nothing special going on here. The program formatted our string into the buffer and then printed its length and the value out. It also told us that the variable x has the value one (shown in decimal and hex) and that it was stored at the address 0x804745c. Next lets try providing some format directives. In this example we'll print out the integers on the stack above the format string: % ./fmtme "%x %x %x %x" buffer (15): 1 f31 1031 3133 x is 1/0x1 (@ 0x804745c) A quick analysis of the program will reveal that the stack layout of the program when the snprintf function is called is: Address Contents Description fp+8 Buffer pointer 4-byte address fp+12 Buffer length 4-byte integer fp+16 Format string 4-byte address fp+20 Variable x 4-byte integer fp+24 Variable buf 100 characters The four values output in the previous test were the next four arguments on the stack after the format string: the variable x, then three 4-byte integers taken from the uninitialized buf variable. Now it is time for an epiphany. As an attacker, we control the values stored in the buffer. These values are also used as arguments to the snprintf call! Let's verify this with a quick test: % ./fmtme "aaaa %x %x" buffer (15): aaaa 1 61616161 x is 1/0x1 (@ 0x804745c) Yup! The four 'a' characters we provided were copied to the start of the buffer and then interpreted by snprintf as an integer argument with the value 0x61616161 ('a' is 0x61 in ASCII). X MARKS THE SPOT All the pieces are falling into place! It is time to step up our attack from passive probes to actively altering the state of the program. Remember that variable "x"? Let's try to change its value. To do this, we will have to enter its address into one of snprintf's arguments. We will then have to skip over the first argument to snprintf, which is the variable x, and finally, use a "%n" format to write to the address we specified. This sounds more complicated than it actually is. An example should clarify things. [Note: We're using PERL here to execute the program which allows us to easily place arbitrary characters in the command line arguments]: % perl -e 'system "./fmtme", "\x58\x74\x04\x08%d%n"' buffer (5): X1 x is 5/x05 (@ 0x8047458) The value of x changed, but exactly what is going on here? The arguments to snprintf look something like this: snprintf(buf, sizeof buf, "\x58\x74\x04\x08%d%n", x, 4 bytes from buf) At first snprintf copies the first four bytes into buf. Next it scans the "%d" format and prints out the value of x. Finally it reaches the "%n" directive. This pulls the next value off the stack, which comes from the first four bytes of buf. These four bytes have just been filled with "\x58\x74\x04\x08", or, interpreted as an integer, 0x08047458. Snprintf then writes the amount of bytes output so far, five, into this address. As it turns out, that address is the address of the variable x. This is no coincidence. We carefully chose the value 0x08047458 by previous examination of the program. In this case, the program was helpful in printing out the address we were interested in. More typically, this value would have to be discovered with the aid of a debugger. Well, great! We can pick an arbitrary address (well, almost arbitrary; as long as the address contains no NUL characters) and write a value into it. But can we write a useful value into it? Snprintf will only write out the number of characters output so far. If we want to write out a small value greater than four then the solution is quite simple: Pad out the format string until we get the right value. But what about larger values? Here is where we take advantage of the fact that "%n" will count the number of characters that should have been output if there was no truncation: % perl -e 'system "./fmtme", "\x54\x74\x04\x08%.500d%n" buffer (99): %0000000 ... 0000 x is 504/x1f8 (@ 0x8047454) The value that "%n" wrote to x was 504, much larger than the 99 characters actually emitted to buf. We can provide arbitrarily large values by just specifying a large field width [1]. And what about small values? We can construct arbitrary values (even the value zero), by piecing together several writes. If we write out four numbers at one-byte offsets, we can construct an arbitrary integer out of the four least-significant bytes. To illustrate this, consider the following four writes: Address A A+1 A+2 A+3 A+4 A+5 A+6 Write to A: 0x11 0x11 0x11 0x11 Write to A+1: 0x22 0x22 0x22 0x22 Write to A+2: 0x33 0x33 0x33 0x33 Write to A+3: 0x44 0x44 0x44 0x44 Memory: 0x11 0x22 0x33 0x44 0x44 0x44 0x44 After the four writes are completed, the integer value 0x44332211 is left in memory at address A, composed of the least-significant byte of the four writes. This technique gives us flexibility in choosing values to write, but it does have some drawbacks: It takes four times as many writes to set the value. It overwrites three bytes neighboring the target address. It also performs three unaligned write operations. Since some architectures do not support unaligned writes, this technique is not universally applicable. SO WHAT? So what? So what!? SO WHAT!#@?? So you can write arbitrary values to (almost any) arbitrary addresses in memory!!! Surely you can think of a good use for this. Let's see: Overwrite a stored UID for a program that drops and elevates privleges. Overwrite an executed command. Overwrite a return address to point to some buffer with shell code in it. Put into simpler terms: you OWN the program. Ok, so what have we learned today? printf is more powerful than you previously thought. Cutting corners never pays off. An innocent looking omission can provide an attacker with just enough leverage to ruin your day. With enough free time, effort, and an input string that looks like the winning entry in last year's obfuscated-C contest, you can turn someone's simple mistake into a nationally syndicated news story. [1] There is an implementation flaw in printf in certain versions of glibc. When large field widths are specified, printf will underflow an internal buffer and cause the program to crash. Because of this, it is not possible to use field widths larger than several thousand when attacking programs on certain versions of Linux. As an example, the following code will cause a segmentation fault on systems with this flaw: printf("%.9999d", 1); * ÀÌ ±Û(english ver. °ú korean ver. ¸ðµÎ) ÀÇ ÀúÀÛ±Ç(copyright) Àº Guardent ÀÇ Tim Newsham ¿¡°Ô ÀÖ½À´Ï´Ù. *