export fn main() void = { signal::handle(signal::SIGINT, &handler); let idx: u8 = 0; let opt: []u8 = []; let notes: [8]note = [ note { title = [0...], content = [0...], init = false}... ]; let notep: *[*]note = ¬es; assert(bufsz == len(notes)); for (true) { fmt::printf( "1) Move note pointer forward 2) Move note pointer backward 3) Add note 4) Delete note 5) Read note 6) Exit > ")!; bufio::flush(os::stdout)!; opt = bufio::scanline(os::stdin)! as []u8; defer free(opt); switch (strings::fromutf8(opt)!) { case "1" => ptr_forward(&idx); case "2" => ptr_back(&idx); case "3" => note_add(¬ep[idx]); case "4" => note_delete(¬ep[idx]); case "5" => note_read(¬ep[idx]); case "6" => break; case => fmt::println("Invalid option")!; }; }; };
We have the source code of the binary which writtern in wuffs language.
There is a bug in the function ptr-back:
1 2 3 4 5 6 7 8
fn ptr_back(p: *u8) void = { if (*p - 1 < 0) { fmt::println("error: out of bounds seek")!; } else { *p -= 1; }; return; };
Because the type of p is unsigned, *p-1 never be less than 0.
I will take advantage of this bug to set idx = 0xa. So we can overwrite saved $RIP.
Checksec tells us that the binary has RWX segment. But when I check in gdb, there is no RWX segment so we must forcus on ROP attack.
Almost gadgets are end of with leave ;ret , so it’s so hard to use these gadgets to create a useful rop chain.
But when analyzing functions list, I found the rt_restore_si function:
So I will use SROP technique to exploit this binary. Because there is no /bin/sh string in the binary, I must find the way to store /bin/sh on .bss segment.
This is my stratergy:
Create a SROP chain that makes stack pivot to .bss segment.
Create a SROP chain that calls execve("/bin/sh",0,0) (We can store /bin/sh after the chain to ensure that the address of string /bin/sh are always fixed).