본문으로 바로가기

* 이 글은 Return Oriented Programming (ROP) 에서 이어지는 글입니다.

* 컴파일 command line: gcc test.c -o test -m32 -Wl,-z,relro -no-pie -fno-pie -fno-stack-protector -mmanual-endbr -O0 -ggdb

 

 

Last update: 6/25/2020

 

cdecl

x86에서 함수는 일반적으로 cdecl 호출 규약을 따른다. 각 인자는 push 명령어로 stack에 저장되며, caller가 stack을 정리한다.

다음 예시를 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int function1(int x) {
    return 1;
}

int function2(int xint y) {
    return 2;
}

int function3(int xint yint z) {
    return 3;
}

int function4(int xint yint zint w) {
    return 4;
}

int main() {
    function1(1);
    function2(0x110x12);
    function3(0x210x220x23);
    function4(0x310x320x330x34);
    return 0;
}

 

gdb-peda$ pd main
Dump of assembler code for function main:
   0x0804919e <+0>:     push   ebp
   0x0804919f <+1>:     mov    ebp,esp
   0x080491a1 <+3>:     push   0x1
   0x080491a3 <+5>:     call   0x8049176 <function1>
   0x080491a8 <+10>:    add    esp,0x4
   0x080491ab <+13>:    push   0x12
   0x080491ad <+15>:    push   0x11
   0x080491af <+17>:    call   0x8049180 <function2>
   0x080491b4 <+22>:    add    esp,0x8
   0x080491b7 <+25>:    push   0x23
   0x080491b9 <+27>:    push   0x22
   0x080491bb <+29>:    push   0x21
   0x080491bd <+31>:    call   0x804918a <function3>
   0x080491c2 <+36>:    add    esp,0xc
   0x080491c5 <+39>:    push   0x34
   0x080491c7 <+41>:    push   0x33
   0x080491c9 <+43>:    push   0x32
   0x080491cb <+45>:    push   0x31
   0x080491cd <+47>:    call   0x8049194 <function4>
   0x080491d2 <+52>:    add    esp,0x10
   0x080491d5 <+55>:    mov    eax,0x0
   0x080491da <+60>:    leave  
   0x080491db <+61>:    ret    
End of assembler dump.

 

call 실행 이후 esp를 증가시킨다.

 

최적화 옵션을 끈 상태에서 컴파일하면, 함수는 인자에 접근하기 위해 ebp 레지스터를 사용한다. (최적화 옵션을 키면 esp를 사용한다)

다음 예시로 이 사실을 확인할 수 있다. ebp+0 은 SFP를 가리키며, ebp+4는 RET이다.

 

1
2
3
4
5
6
7
8
int add(int xint y) {
    return x+y;
}

int main() {
    add(12);
    return 0;
}

 

gdb-peda$ pd add
Dump of assembler code for function add:
   0x08049176 <+0>:     push   ebp
   0x08049177 <+1>:     mov    ebp,esp
   0x08049179 <+3>:     mov    edx,DWORD PTR [ebp+0x8]
   0x0804917c <+6>:     mov    eax,DWORD PTR [ebp+0xc]
   0x0804917f <+9>:     add    eax,edx
   0x08049181 <+11>:    pop    ebp
   0x08049182 <+12>:    ret
End of assembler dump.

 

ROP 32bit :: Basic

ROP로 함수를 1개 호출하는 과정을 살펴보자.

최적화 옵션을 끄면 함수가 인자에 접근하기 위한 기준으로 ebp가 사용된다. 함수 프롤로그 push ebp ; mov ebp, esp 으로 ebp가 기존의 esp가 되기 때문에, 아래처럼 payload를 구성해서 인자를 전달할 수 있다.

 

 

아래 코드는 ROP로 write를 사용해 문자열을 출력한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdint.h>
#include <unistd.h>

char *buf = "Hello ROP\n";

void function() {
    uint32_t s[1];
    s[2] = write;
    s[3] = 0xAAAAAAAA;
    s[4] = 1;
    s[5] = buf;
    s[6] = 0xA;
}

int main() {
    function();
    return 0;
}

 

ROP 32bit :: ROP Chain

64bit와 다르게 인자가 stack으로 전달되기 때문에, 함수를 연속해서 호출하려면 esp의 위치를 맞춰 줘야 한다. 이것은 pop [reg] ; ret gadget을 사용해 해결할 수 있다.

 

아래 그림은 ROP로 인자가 각각 3개, 2개인 함수 2개를 호출하는 것을 도식화한 것이다.

 

 

아래 코드는 read로 문자열을 입력받고 printf로 출력한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

char buf[0x10];

void gadget() {
    __asm (
        "pop %ebx\n\t"
        "pop %ecx\n\t"
        "pop %ebp\n\t"
        "ret\n\t"
    );
}

void function() {
    uint32_t s[1];
    s[2] = read;
    s[3] = (uint32_t)gadget + 3;
    s[4] = 0;
    s[5] = buf;
    s[6] = 0x10;
    s[7] = printf;
    s[8] = 0xAAAAAAAA;
    s[9] = "%s\n";
    s[10] = buf;
}

int main() {
    function();
    return 0;
}

 

Additional ROP Gadgets

32bit에서 gadget의 용도는 오로지 esp를 증가시키는 것이기 때문에, 항상 pop을 쓸 필요는 없다.

rp++으로 gadget 목록을 보면, 다음과 같이 add esp, 0x8 이나 add esp, 0x10 이 보인다. pop 개수가 부족하다면 esp를 증가시켜주는 gadget을 사용해 stack을 정리하는 방법을 사용할 수 있다.

 

syine@MinetaLinux:~/Desktop/test rp-lin-x86 -r 5 -f ./test | grep "add esp"
0x080492ad: add byte [eax], al ; add esp, 0x08 ; pop ebx ; ret  ;  (1 found)
0x080492a9: add ebx, 0x00002D57 ; add esp, 0x08 ; pop ebx ; ret  ;  (1 found)
0x0804901f: add esp, 0x08 ; pop ebx ; ret  ;  (1 found)
0x080492af: add esp, 0x08 ; pop ebx ; ret  ;  (1 found)
0x0804927d: add esp, 0x0C ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)
0x08049122: add esp, 0x10 ; leave  ; ret  ;  (1 found)
0x0804916f: add esp, 0x10 ; leave  ; ret  ;  (1 found)
0x0804901a: sal byte [edx+eax-0x01], 0xFFFFFFD0 ; add esp, 0x08 ; pop ebx ; ret  ;  (1 found)