2021. 3. 2. 04:26ㆍ0x07 CTF/[스스로 푼 것]
문제 카테고리: Rev
TL;DR:
-
바이너리 속 유추할 수 있는 규칙을 통해 암호화 된 섹션을 복구해야한다.
-
External에 존재하는 모듈을 연계지어 생각하면 분석이 용이하다.
-
AES CBC 임을 유추해야한다.
-
time() 을 어떻게 사용할 것인지 고민해야한다.
바이너리 분석:
-
파일 크기: 38400000
파일 크기가 큰 것으로 보아, 덤프파일임을 유추할 수 있다.
-
메인 함수에 3개의 서브 함수가 존재하나, loc_1691의 경우 올바르게 디스어셈블 되지 못하고 있는 상황임을 확인할 수 있다. 이에 따른 원인은 sub_172A 함수에 진입하면 보다 더 자세히 알 수 있게 된다.
-
sub_172A 함수
.text 섹션 unk_13A9 + 895까지 복호화 시키는 루틴이다. 범위는 0x13A9부터 0x1728이다. 즉, 위에 언급 된 loc_1691 역시 암호화 된 영역에 속하게 되어 디스어셈블이 원활하게 이루어지지 않은 것이다.
검은 박스의 경우는 실 없는 루틴이라 판단하였다.
-
XOR Key에 사용되는 32바이트 알아내는 방법
unk_13A9로 접근하여 데이터들을 파악하다보면, 암호화 되어 있지 않은 정상적인 함수 역시 찾아볼 수 있게 된다.
이를 undefined 하게 되면, OPCODE를 살펴볼 수 있게 된다.
아래에 보이는 0xB1839F3F의 경우 [0x13A9:0x13AC]의 OPCODE이다.
Dump 파일이기 때문에, XOR 키 값은 이미 바이너리에 존재한다.
하드코딩 되어있는 XOR값이 박혀있는 오프셋을 알았으니 간단하게 코드를 작성한다.
#-*-encoding:utf-8-*-
import sys
FILENAME = "dump"
DUMPNAME = "dec_dump"
# 암호화 된 부분 0x13A9 ~ 0x1728
text_section_offset = 0x13A9
text_section_range = 0x13a9+896
# 일부 key를 모르는 상태이지만, dump 파일임에 주목해야한다 ! (32byte)
'''
0x13A9 주소로 부터 시작되는데 xor 해서 x0428c81c5ea13e0c2.......5e가 나와야 한다.
0x428c81c5 - endbr64
0xea13e0c2 - push rbp / mov rbp, rsp
0x5E - leave
'''
#key = [0x42, 0x8c, 0x81, 0xc5, 0xea, 0x13, 0xe0, 0xc2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e]
j=0
with open(FILENAME,"rb") as f:
bytes_read = f.read()
key = bytes_read[0x4BA74:0x4BA74+32]
write_byte = bytearray(bytes_read)
key_byte = bytearray(key)
print(len(key_byte))
#for i in range(0,len(write_byte)):
for i in range(text_section_offset,text_section_range):
write_byte[i] ^= key_byte[j%32]
j=j+1
# print(write_byte[i])
with open(DUMPNAME,"wb") as f:
f.write(write_byte)
복호화 된 바이너리 모습이다.
-
sub_1691
모듈 이름들을 적절히 배치하면 다음과 같다.
현재 빌드 시간에 맞추어 랜덤 데이터를 생성하고,
기존에 존재하는 flag.txt 파일을 파일입출력을 통해 불러와서 암호화 시키고자 하는 부분이다.
-
allocation_sub_13A9
a1는 볼 필요 없고, a2가 사이즈를 의미한다. 현재 시작점이 0x5060이므로, 0x5060:0x5060+0x80이 flag 파일을 복사한 영역이 된다.
아래의 사진에서 0x5060을 기준으로 보여지는 데이터를 확인할 수 있다.
-
시간을 구하는 방법
ls -al을 이용하면, 바이너리 시간을 대략적으로 알 수 있다.
가정
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
int main()
{
int n = 1614261060; // time (2021 02 25 13:51:00)
int a = 0;
srand(n);
a = rand() % 0x7FF;
printf("%d\n",a);
return 0;
}
항상 값이 같게 나온다. 즉 time을 정확하게 정해두면 시드에 따라 항상 고정 값이 나온다는 것을 알 수 있다.
-
enc_sub_13FE
32바이트와 16바이트로 할당하는 것으로 보아 AES 알고리즘을 사용하고 있음을 추정할 수 있다.
64회 반복하는 코드의 경우, flag 파일의 내용을 복사하기 위해 사용 된 동적할당 알고리즘이 똑같이 적용되고 있다.
아래의 사진을 보면 allocation_sub_13A9는 총 10번 반복되게 되고, 해당 함수에서의 a2가 전역변수 qword_5040의 갯수를 계속 증가시키고 있음을 확인할 수 있다. 그렇다면, 앞서 살펴본 rand함수의 반환 값을 오프셋에 꾸준히 더하는 행위를 한다는 것이다.
v2를 알기 위해서는 rand() % 2047 값을 알아내야하는데, 위에서 추측한 ‘수정 시간'을 통해 알아낸 값을 통해 브루트포싱을 진행해보면 된다.
Solve Code Logic
Heap 오프셋 : 0x5060 / 첫 블록 오프셋 0x80 |
시간 from 0 to 24hour 24min 60sec (87900) |
rand() % 0x7FF의 반환 값으로 블록 오프셋 크기 변경 (64회 반복) |
Key 획득 루틴 (data[:0x10]) |
Key 데이터 할당 크기 만큼 블록 오프셋 크기 변경 (0x20) |
rand() % 0x7FF의 반환 값으로 블록 오프셋 크기 변경 (64회 반복) |
IV 획득 루틴 (data[:0x10]) |
IV 데이터 할당 크기 만큼 블록 오프셋 크기 변경 (0x10) |
rand() % 0x7FF의 반환 값으로 블록 오프셋 크기 변경 (64회 반복) |
임의 데이터 할당 크기 만큼 블록 오프셋 크기 변경 (0xC0) |
rand() % 0x7FF의 반환 값으로 블록 오프셋 크기 변경 (64회 반복) |
If Heap 오프셋 + 최종 블록 오프셋 == text 섹션 XOR 복호화 키 오프셋 올바른 Key, IV 획득 성공 |
# linux python script
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from ctypes import CDLL
heap_offset = 0x5060
data = open("./dump","rb").read()
xor_key_offset = 0x4BA74
enc_flag = data[heap_offset:heap_offset+0x80]
libc = CDLL("/usr/lib/x86_64-linux-gnu/libc.so.6")
# linux time stamp (bruteforce)
# timestamp = 1614261060
ts = 1614261060
for i in range(0, 87900): # convert to second (24hour:24min:60sec)
blockpos = 0x80 # To skip the flag offset
libc.srand(ts + i)
# Follow the codes
for _ in range(0, 64):
blockpos += libc.rand() % 2047
aeskey = data[heap_offset + blockpos:heap_offset + blockpos + 0x10]
blockpos += 0x20
for _ in range(0, 64):
blockpos += libc.rand() % 2047
aesiv = data[heap_offset + blockpos:heap_offset + blockpos + 0x10]
blockpos += 0x10
for _ in range(0, 64):
blockpos += libc.rand() % 2047
blockpos += 0xc0
for _ in range(0, 64):
blockpos += libc.rand() % 2047
if(heap_offset + blockpos == xor_key_offset):
print("Found candidate %d" % i)
break
c = AES.new(aeskey, AES.MODE_CBC, aesiv)
print(c.decrypt(enc_flag))
'0x07 CTF > [스스로 푼 것]' 카테고리의 다른 글
HacktheBox CTF coding problem (0) | 2021.04.24 |
---|---|
zer0pts 2021 - not beginners rev (0) | 2021.03.12 |
[REV] COMPEST - CreeptiCity (0) | 2020.09.12 |
HexionCTF XOR - Crypto (0) | 2020.04.18 |
ISITDTU CTF Reversing inter (0) | 2018.08.03 |