문제 파일을 다운로드 받으면 64bit ELF 파일임을 확인할 수 있었습니다. 먼저 프로그램을 실행할 경우 아래 내용을 출력해준 뒤 동작을 종료하는 것을 확인할 수 있었습니다.
persian@Code-PAPA /tmp ./handray
flag를 뛰어넘었습니다!
인자를 전달해야 하는 문제인가 싶어 임의의 값도 입력해 보았으나 유효한 결과를 얻진 못하였습니다.
GDB를 통해 먼저 사용되는 함수의 목록을 살펴보았으나 main()
함수 외에 특별한 함수는 찾을 수 없었습니다. main()
함수를 분석하던 중 flag와 관련되어 보이는 값을 확인할 수 있었습니다.
0x0000000000400577 <+81>: mov esi,0x6010e0
0x000000000040057c <+86>: mov edi,0x400638
0x0000000000400581 <+91>: mov eax,0x0
0x0000000000400586 <+96>: call 0x400400 <printf@plt>
위 명령을 실행하기 위해서는 몇 가지 조건을 맞춰줄 필요가 있었으며 사용되는 인자를 바탕으로 아래 값임을 유추할 수 있었습니다.
flag is A]`j?NCz?eiHb:R^CkdA.jaP+F+..jb!}
이 값에서 마지막 문자가 '}'로 끝나는 것을 보아 encoding 된 flag 값이지 않을까 생각하게 되었고 프로그램의 흐름을 역으로 따라가며 분석하였습니다.
0x0000000000400544 <+30>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000400547 <+33>: cdqe
0x0000000000400549 <+35>: movzx eax,BYTE PTR [rax+0x6010e0]
0x0000000000400550 <+42>: mov edx,eax
0x0000000000400552 <+44>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000400555 <+47>: cdqe
0x0000000000400557 <+49>: mov eax,DWORD PTR [rax*4+0x601060]
0x000000000040055e <+56>: add eax,edx
0x0000000000400560 <+58>: mov edx,eax
0x0000000000400562 <+60>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000400565 <+63>: cdqe
0x0000000000400567 <+65>: mov BYTE PTR [rax+0x6010e0],dl
0x000000000040056d <+71>: add DWORD PTR [rbp-0x8],0x1
0x0000000000400571 <+75>: cmp DWORD PTR [rbp-0x8],0x1e
0x0000000000400575 <+79>: jle 0x400544 <main+30>
위 코드에서 기존 encoding 값과 비슷한 위치에서 참조하는 다른 값이 있는 점을 확인할 수 있었고 확인한 결과 4 Byte 단위로 다른 값이 있는 것을 보아 int 값임을 추측할 수 있었습니다.
gdb-peda$ x/50wx 0x601060
0x601060 <array>: 0x00000007 0x00000004 0x00000003 0x00000001
0x601070 <array+16>: 0x00000004 0x00000006 0x00000003 0x00000001
0x601080 <array+32>: 0x00000009 0x0000000a 0x0000000b 0x0000000c
0x601090 <array+48>: 0x0000000d 0x0000000e 0x0000000f 0x00000010
0x6010a0 <array+64>: 0x00000001 0x00000001 0x00000001 0x00000002
0x6010b0 <array+80>: 0x00000002 0x00000002 0x00000003 0x00000004
0x6010c0 <array+96>: 0x00000005 0x00000002 0x00000005 0x00000002
0x6010d0 <array+112>: 0x00000002 0x00000002 0x00000002 0x00000002
0x6010e0 <string>: 0x6a605d41 0x7a434e3f 0x4869653f 0x5e523a62
0x6010f0 <string+16>: 0x41646b43 0x50616a2e 0x2e2b462b 0x21626a2e
0x601100 <string+32>: 0x0000007d
두 변수를 호출하는 것과 add와 같이 연산하는 과정이 있는 것을 토대로 연관성 있는 값이라고 판단되어 가설을 바탕으로 exploit code를 작성하였습니다.
Exploit code를 작성할 때 가장 처음의 가설은 실제 flag 값에서 array의 값을 각 글자에 더한 값이 string의 값이라고 추정하였습니다. 그 근거로 array에는 총 32개의 숫자가, string에는 33자가 있기에 마지막 '}' 문자는 영향을 받지 않았을 거라 판단했기 때문입니다.
따라서 처음에는 string에서 array의 값을 뺄셈으로 진행하였습니다. 그러나 그럴듯한 값을 얻을 수 없었고 이에 덧셈으로 진행하여 flag 값을 획득할 수 있었습니다. Flag 형식에 맞는 값을 구하였음에도 인증에 실패하여 다시 확인한 결과 cmp DWORD PTR [rbp-0x8],0x1e
명령으로 인해 30(0x1e)번째까지만 encoding을 진행하는 점을 확인할 수 있었습니다.
따라서 이러한 내용들을 모두 반영하여 최종적으로 아래와 같은 exploit code를 작성하였습니다.
#!/usr/bin/python3
import struct
_string_offset = 0x000010D8+0x8
_array_offset = 0x00001054+0xc
salt = [0]*33
dec_flag = ""
with open("/tmp/handray", "rb") as f:
data = f.read()
enc_flag = data[_string_offset:_string_offset+33].decode('utf-8')
array = data[_array_offset:_array_offset+120]
def toTheNumeric(array):
for i in range(int(len(array)/4)):
salt[i] = struct.unpack("<I", array[i*4:i*4+4])[0]
def decoding(enc_flag, salt):
enc = list(enc_flag)
global dec_flag
for i in range(len(enc_flag)):
dec_flag += chr(ord(enc[i]) + salt[i])
toTheNumeric(array)
decoding(enc_flag, salt)
print(dec_flag)
위 코드에서 offset의 경우 hexedit을 통해 구하였습니다.