[tokyo 2016] ReverseBox

CTF 2016. 9. 12. 22:41

하하하 이것은 쓸만함 나한테는 재미있는데 다른 사람에게는 ㅋㅋㅋ


w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box

usage: ./reverse_box flag

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box AAAAAAAAAAAAA

e0e0e0e0e0e0e0e0e0e0e0e0e0

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box A

e0

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box AA

e0e0

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box AB

e04f

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box AC

e079

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ ./reverse_box BC

4f79

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$



파일의 정보를 보면

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$ file reverse_box

reverse_box: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5403acba0427c34695b1ebda8f0c678905b33456, stripped

w00t@ubuntu:~/Desktop/ctf-tokyo/reverse$  


그다음은 아이다에서 열고

이렇게 보니까 쉬워보이는데 그 당시는 매우 어려워 보였음


풀이 방법은 크게 2가지임

1. 로직을 분석해서 파이썬 코드로 변환한 사람

2. gdb에 디버깅 걸어서 1~256 대입한 사람

3. LiLi 가 푼 방법


오늘 소개할 방법은 LiLi가 푼 무식한 방법과

gdb 의 x 옵션을 걸어서 나이스하게 푼 방법을 소개함

나이스한 방법은 https://github.com/TeamContagion/CTF-Write-Ups/tree/master/TokyoWesterns-2016/Reverse%20Box 에 있는 내용을 한글로 번역함

이런 방법을 지금까지 몰랐다는 것은 함정!


LiLi가 푼 방법

프로그램을 분석해 보니 

프로그램에서 1바이트만 가지고 와서 일정 위치의 바이트를 가져온다고 알고 있음

그래서 프로그램을 패치한 다음 255번 돌림

위를 몇 바이트 고쳐서 아래와 같이 패치함 그러면 입력된 길이만큼이 아니라 255 바이트 만큼 출력하도록 함

그래서 파이썬으로 프로그램을 읽어서 다시 1바이트 패치를 한 다음 출력하도록 함 이렇게 255번을 돌림

import struct


#대상 프로그램을 읽음

f=open('reverse_box_p','rb')

buf=f.read()


#1바이트 패치함, 시드값의 자리

def mk(k):

  f2=open('reverse_box_p_01','wb')

  f2.write(buf[:0x5B5]+struct.pack("B",k)+buf[0x5B6:])


#고고 함수

def gogo(h):

  mk(h)


  #패치된 프로그램을 실행하며 변경된 메모리값을 1~255까지 출력하게 함

  ans=''

  #filters output

  import subprocess

  proc = subprocess.Popen(['./reverse_box_p_01','A'],stdout=subprocess.PIPE)

  while True:

    line = proc.stdout.readline()

    if line != '':

      #the real code does filtering here

      ans = line.rstrip()

    else:

      break


  #키 값을 메모리에서 특정값을 읽어오는 offset 값임

key='95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a'

  print ans

  i=0

  flag=''

  while i<len(key):

     k = key[i:i+2]

     a = find_c(k,ans)

     if a>0:

         flag += chr(a)

     else:

         break

     i+=2

  #플래그 값을 출력함

  print flag


#메모리(버퍼)에서 오프셋값을 읽어오는 함수

#복호화 하는 함수 이므로 암호값이 몇번째에 있었으냐가 플레인의 값임

def find_c(s,ans):

   i=0

   while i<256:

       if (s==ans[i:i+2]):

           return i/2

       i+=2

   return -1


#0부터 254까지 시드 값을 입력함

for k in range(255):

  gogo(k)


위 프로그램을 실행한 결과 플래그가 출력됨

=




2^

TWCTF{5UBS717U710N_C1PH3R_W17H_R4ND0M123D_5-B0X}

{FD{WT


N

B



아이고 되다. 그리고 나이스하게 푼 쪽은 아래과 같다. 추가 설명은 영어가 있으니 생략한다.

 # gdb ./reverse_box -x solve.py


Breakpoint 2, 0x080486db in ?? ()


Breakpoint 1, 0x080485b1 in ?? ()


Breakpoint 3, 0x080486e0 in ?? ()

[Inferior 1 (process 46588) exited normally]

TWCTF{5UBS717U710N_C1PH3R_W17H_R4ND0M123D_5-B0X}

(gdb) q


위에서 사용한 solve.py 소스

 

import gdb
import tempfile
# We know that the following string was produced by the binary when the flag
# was given as input
desired = '95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a'
# The binary looks up the bytes of the input in a lookup table and returns the
# bytes found there. However, the lookup table is randomized, so our task is to
# find the correct table. We know the flag begins with 'TWCTF', so if we
# encrypt that string the beginning of the output should match.
desired_prefix = desired[:len('TWCTF')*2]
seed_set = '*0x80485b1' # Instruction after the seed for the table has been chosen
before_build = '*0x80486db' # instruction just before calling the table build function
after_build = '*0x80486e0' # instruction just after table build function returns
# Disable all existing breakpoints so we can predict where we will stop
for bp in gdb.breakpoints() or []:
bp.enabled = False
my_bps = []
my_bps.append(gdb.Breakpoint(seed_set))
gdb.execute('set pagination off')
# Iterate through the possible seeds (1 to 255, because it is ANDed with 0xff
# and 0 is not accepted) to find the one that matches
outfile = tempfile.NamedTemporaryFile()
for seed in range(1,256):
# Run program sending output to outfile
gdb.execute('run TWCTF{foo} > %s' % outfile.name)
# Just after seed is randomly chosen by program, overwrite it with our
# selection
gdb.execute('set $eax = %d' % seed)
gdb.execute('continue')
# Read the program output and see if it matches our desired encryption of
# 'TWCTF'
outfile.seek(0)
out = outfile.read()
if out.startswith(desired_prefix):
print 'seed is %d' % seed
break
outfile.close()
# Now we need to capture the lookup table generated from this seed
my_bps.append(gdb.Breakpoint(before_build))
my_bps.append(gdb.Breakpoint(after_build))
gdb.execute('run TWCTF{foo} > /dev/null')
# Figure out where the lookup table is stored
stack = gdb.parse_and_eval('(unsigned char **)$esp')
lookup = stack.dereference()
gdb.execute('continue')
# override seed
gdb.execute('set $eax = %d' % seed)
gdb.execute('continue')
# Table has been constructed, read it out byte by byte into a reverse lookup
# dict
table = {}
for i in range(256):
table[int(lookup[i])] = i
# Clean up gdb state
for bp in my_bps:
bp.delete()
gdb.execute('continue')
# Iterate through the desired output, looking up the bytes in the table
vals = []
i = 0
while i < len(desired):
byte = int(desired[i:i+2], 16)
val = table[byte]
vals.append(val)
i += 2
print ''.join(chr(x) for x in vals)


'CTF' 카테고리의 다른 글

[tum ctf 2016] haggis - crpyto  (0) 2016.10.03
[SCTF 2016] pwn2 한땀 한땀 ROP read /bin/sh  (0) 2016.09.16
[tokyo ctf 2016]greeting  (0) 2016.09.06
[plaidCTF]butterfly  (0) 2016.04.19
bctf2016-bcloud  (0) 2016.03.21
Posted by goldpapa
,