作为pwn,确实是很基础的堆题,就是用rust写的有点恶心人。。。
分析
F5随便搜一搜,可以发现有后门函数,对后门函数分析后(才学rust两天,很多都不知道全靠猜。。。),存在UAF可以利用
运行程序发现是标准菜单题,注意到这儿可以show(泄露地址),edit(改fd/bk)
而且这个题是libc2.27,思路就很简单了,放一个unsortedbin的chunk,通过UAF泄露mainarena拿到libcbase,然后edit可以改fd为free_hook(因为这儿有delete)为system,然后写binsh(本来想改成onegadget的,但为什么就是打不通???)
其他的点
- 由于是rust写的,所以ida打开几乎不能看,但是这个题可能出于人道主义给了符号表,可以在动调调试的时候结合f5的结果来重新整理一下程序
源码
赛后wp出题人还很贴心的给出了源码
官方wp:https://xz.aliyun.com/t/12485#toc-28
use anyhow::Result;
use std::io::{self, Read, Write};
use std::vec::Vec;
use nix::unistd::alarm;
const MAX_HOUSE: usize = 8;
struct HouseTbl {
houses: [Option<Vec<u8>>; MAX_HOUSE],
}
fn show_menu() -> Result<()> {
println!("1. add a house");
println!("2. show a house");
println!("3. edit a house");
println!("4. delete a house");
println!("5. exit");
print!(">>> ");
io::stdout().flush()?;
Ok(())
}
fn add(tbl: &mut HouseTbl) -> Result<()> {
let mut i = 0;
for x in tbl.houses.iter() {
if !x.is_none() {
i += 1;
} else {
break;
}
}
if i == MAX_HOUSE {
println!("full!");
return Ok(());
}
println!("idx is {}", i);
print!("now the size: ");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let size = buffer.trim().parse::<usize>()?;
if size >= 0x200 {
println!("too large");
return Ok(());
}
let mut content: Vec<u8> = vec![0xcc as u8; size];
// let mut content: Vec<u8> = Vec::with_capacity(size);
print!("next the content: ");
io::stdout().flush()?;
io::stdin().read_exact(&mut content)?;
tbl.houses[i] = Some(content);
Ok(())
}
fn show(tbl: &mut HouseTbl) -> Result<()> {
print!("house index: ");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let index = buffer.trim().parse::<usize>()?;
if index >= MAX_HOUSE {
println!("invalid!");
return Ok(());
}
match tbl.houses[index].as_ref() {
Some(house) => io::stdout().write_all(house)?,
None => println!("no house"),
}
Ok(())
}
fn edit(tbl: &mut HouseTbl) -> Result<()> {
print!("house index: ");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let index = buffer.trim().parse::<usize>()?;
if index >= MAX_HOUSE {
println!("invalid!");
return Ok(());
}
match tbl.houses[index].as_mut() {
Some(house) => {
print!("content(size {}): ", house.len());
io::stdout().flush()?;
io::stdin().read_exact(house)?;
}
None => println!("no house"),
}
Ok(())
}
fn backdoor(tbl: &mut HouseTbl, i: i32) {
let idx = i - 1953723762;
if idx < MAX_HOUSE as i32 && idx >= 0 {
if let Some(house) = tbl.houses[idx as usize].as_mut() {
unsafe {
let _ = Vec::from_raw_parts(house.as_mut_ptr(), house.len(), house.capacity());
}
}
}
}
fn delete(tbl: &mut HouseTbl) -> Result<()> {
print!("house index: ");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let index = buffer.trim().parse::<usize>()?;
backdoor(tbl, index as i32);
if index >= MAX_HOUSE {
println!("invalid!");
return Ok(());
}
match tbl.houses[index].take() {
Some(_) => println!("success"),
None => println!("no house"),
}
Ok(())
}
fn main_process() -> Result<()> {
let mut tbl = HouseTbl {
houses: Default::default(),
};
loop {
show_menu()?;
let mut choose = String::new();
io::stdin().read_line(&mut choose)?;
let choose_number = choose.trim().parse::<u8>();
_ = match choose_number {
Ok(1) => add(&mut tbl),
Ok(2) => show(&mut tbl),
Ok(3) => edit(&mut tbl),
Ok(4) => delete(&mut tbl),
Ok(5) => break,
_ => {
println!("invalid!");
Ok(())
}
};
}
println!("Bye~");
Ok(())
}
fn main() {
alarm::set(60);
_ = main_process();
}