이 문제는 직접 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()

+ Recent posts