AeroCTF2021 Dummyper

2021. 3. 2. 04:260x07 CTF/[스스로 푼 것]

728x90

문제 카테고리: Rev

 

TL;DR: 

  1. 바이너리 속 유추할 수 있는 규칙을 통해 암호화 된 섹션을 복구해야한다.

  2. External에 존재하는 모듈을 연계지어 생각하면 분석이 용이하다.

  3. AES CBC 임을 유추해야한다.

  4. 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