1、查看安全策略
root@kali:~/ctf/Other/pwn/heap# checksec stkof
[*] '/root/ctf/Other/pwn/heap/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以看到是64位系统,同时开启了canary和NX
2、静态分析
[0x00400c58]> afl
0x00400840 1 42 entry0
0x004007c0 1 6 sym.imp.__libc_start_main
0x00400750 1 6 sym.imp.free
0x00400760 1 6 sym.imp.puts
0x00400770 1 6 sym.imp.fread
0x00400780 1 6 sym.imp.strlen
0x00400790 1 6 sym.imp.__stack_chk_fail
0x004007a0 1 6 sym.imp.printf
0x004007b0 1 6 sym.imp.alarm
0x004007d0 1 6 sym.imp.fgets
0x004007e0 1 6 sym.imp.atoll
0x00400800 1 6 sym.imp.malloc
0x00400810 1 6 sym.imp.fflush
0x00400820 1 6 sym.imp.atol
0x00400830 1 6 sym.imp.atoi
0x00400c58 21 250 main
0x00400910 8 122 -> 90 entry.init0
0x004008f0 3 28 entry.fini0
0x00400870 4 50 -> 41 fcn.00400870
0x004007f0 1 6 loc.imp.__gmon_start
0x00400936 6 178 fcn.00400936
0x004009e8 13 287 fcn.004009e8
0x00400b07 8 162 fcn.00400b07
0x00400ba9 11 175 fcn.00400ba9
0x00400720 3 26 fcn.00400720
0x0040092a 5 134 -> 67 fcn.0040092a
调用的函数里,有几个函数可以注意一下
-
malloc
和free
涉及到堆[0x00400c58]> axt sym.imp.malloc fcn.00400936 0x40097c [CALL] call sym.imp.malloc [0x00400c58]> axt sym.imp.free fcn.00400b07 0x400b7a [CALL] call sym.imp.free
-
alarm
,alarm的libc地址加上5就可以得到system函数。 -
puts
,经常用来泄露libc基址
下面来梳理一下程序的功能
-
首先会读取一个长度小于10的字符串,然后把这个字符串转化为整数,我们暂且把这个数命名为input_NUM
-
判断input_NUM的值
if input_NUM == 1: res = handle_1() # fcn.00400936() elif input_NUM == 2: res = handle_2() # fcn.004009e8() elif input_NUM == 3: res = handle_3() # fcn.00400b07() elif input_NUM == 4: res = handle_4() # fcn.00400ba9() else: res = -1
-
判断res的值
if res == 0: puts("OK") else: puts("FAIL")
-
接下来分别看下几个分支的函数的作用
-
handle_1
:用户输入一个数字,然后程序会使用malloc分配对应大小的内存,分配成功的话返回0,否则返回-1;之后程序会打印本次创建的chunk的序号 -
Handle_2
:offset = 用户输入数字 if input_NUM < 0x100001: if 地址 [0x602140 + offset * 8] 储存的值 != 0: inputCount = 用户输入数字 逐个字节把inputCount个字节的内容写入对应的chunk中 写入成功则return 0 else: ret = -1
-
handle_3
: 用户输入一个数字offset,如果地址[0x602140 + offset * 8] 储存的值不为0,则释放[0x602140 + offset * 8]地址对应的chunk,并且将地址[0x602140 + offset * 8]置为0。 -
handle_4
: 用户输入一个数字offset,如果地址[0x602140 + offset * 8] 储存的值不为0,则计算[0x602140 + offset * 8]地址开始的字符串长度,如果长度小于4,则输出//TODO
,否则输出...
。
-
-
动态调试
利用gdb动态调试程序,首先明确了几个函数的功能,
- 用户输入数字1~4选择功能
- 功能1 - 分配内存:进入功能后,用户输入一个数字来定义malloc()分配的大小,并返回创建的chunk的序号;
- 功能2 - 写入数据:进入功能或,用户首先输入chunk的序号来选择要写入的目标,然后输入一个数字表示要写入的字节数目,最后输入要写入的字符串;
- 功能3 - 释放内存:进入功能后,用户输入要释放的chunk的序号来执行free(chunk)的操作;
- 功能4 - 数据长度:进入功能后,用户输入目标chunk的序号,程序会计算字符串的长度,长度小于4输出
//Todo
,否则输出...
。
- 使用一个数组储存每个创建的chunk的指针,我们暂且称这个数组为global
程序启动后的堆分配如下所示
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0xe05000 Size: 0x1011 Top chunk | PREV_INUSE Addr: 0xe056a0 Size: 0x20961
alloc了一个chunk后会额外添加一个缓冲,此后再分配的时候会依次往后添加
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0xe05000 Size: 0x1011 Allocated chunk | PREV_INUSE Addr: 0xe06010 Size: 0x111 Allocated chunk | PREV_INUSE Addr: 0xe06120 Size: 0x411 Allocated chunk | PREV_INUSE Addr: 0xe06530 Size: 0x41 Allocated chunk | PREV_INUSE Addr: 0xe06570 Size: 0x91 Top chunk | PREV_INUSE Addr: 0xe06600 Size: 0x20a01
- 用户输入数字1~4选择功能
-
payload
from pwn import * class EXP(): def __init__(self): self.conn = process('./stkof') def _chunk_ptr(self, seq): return 0x602140 + seq * 8