本题最终解数为42,因为题目难度不大,总体符合预期。题目是用rust写的代码,同时赛前夜里临时决定删除符号不给源码,一方面导致选手逆向难度很大,另一方面也让大部分选手把精力集中在动调上,避免陷入源码的细节。在出题过程中其实也没有漏洞和明确利用手法的考点,题目是一边学习一边调试的时候出出来的,再次把出题思路分享给大家,算是抛砖引玉。
这里还是首先给出源码( rustc 1.82.0-nightly (cefe1dcef 2024-07-22) ):
本题在构思的时候其实是想用不含unsafe的rust语言构造一个漏洞,因此一方面在RustSec上寻找合适的漏洞,另一方面发现了cve-rs项目。首先在RustSec找的漏洞不太适合出题,又限于出题时间和自身水平,最终还是选择用cve-rs内的原理,“盗用”了
UIUCTF 2024 Rusty Pointer题目的触发POC:
据我的理解,这段POC实际上是利用对变量静态生存周期的混淆,欺骗rust编译器不释放离开生存期的变量。
通过上述的技术原理,我们可以得到一个离开生存期仍然可用的指针(或者说对象),在此题中将其用到了栈上对象,因此我们可以获得一个get_ptr
函数内的一个栈对象msg
:
而每次add
的时候,由于函数调用的顺序不变,实际上每次申请得到的地址都是一样的。虽说这样让程序逻辑有些奇怪,但是也让堆的可控变小了。另外,Msg
的大小变化其实会导致栈布局,包括该离开生命周期的栈指针的能力发生变化,有时可以直接写到返回地址,这太简单了肯定不行:)
一血战队ACT的解法实际上跟我的预期解是一样的。通过show可以泄露栈上的堆地址、栈地址、ELF地址,通过edit
我们可以发现存在任意地址释放,但是问题在于怎样利用该能力实现栈地址写或者任意地址写。
我们现在手上有两个条件,首先是任意地址释放,其次是rust的vec
类似于C++,使用realloc
扩容,其指针数组也存储在堆上。
因此不难想到通过释放伪造堆块,将vec
的指针数组劫持到我们可控的位置,而我们可控的位置最直接的就是栈上0x50的空间,另一个是stdin的输入在堆上的缓冲区
实际上从选手做法来看,这两个位置都可以成功伪造堆块,实现控制vec
的指针。
这里给出我的exp:
实际上在输入choice
的时候输入的字符会开辟新的堆空间存储,因此也有队伍把栈上伪造的堆释放到tcache,接着实现栈溢出。
此外还有选手发现delete
的时候实际上会申请堆块,这我就没有往下深入研究了。
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!"
)
}
}
}
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!"
)
}
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)