CTF

[Swing CTF] Reversing - Randsomware

오호츠크해 기단 2022. 12. 2. 02:07
728x90

Randsomware

문제 링크: http://swingctf.hspace.io:9090/challenges#Randsomware-9

IDA Pro 프로그램을 이용할 것이다.
IDA? (처음 써보는 프로그램이라 간단하게 사용법부터 정리하겠다..)
디스어셈블러의 일종으로, 디스어셈블러는 바이너리 파일을 역으로 어셈블리어로 재구성해주는 툴이다.

인터페이스
좌: functions window, 우: 해당 함수에 대한 description

Pseudo Code 보기
변환을 원하는 함수를 선택한 후 F5를 누르면 된다. 그러면 창에 Pseudocode라는 창이 새로 생기면서 프로그래밍 변환된 걸 보여준다. 어셈블리어 코드를 다시 pseudocode로 자동적으로 바꾼 것이기 때문에 예를 들어 __int64 -> int, _isoc99_scanf -> scanf로 눈치껏 해석하는 것이 필요하다..

새로운 창이 생긴 모습





Write Up

랜덤값이 buf에 담기고, rand()로 무작위 시드를 뽑은 후 16바이트 키를 만든다.

unsigned __int64 init()
{
  int i; // [rsp+8h] [rbp-18h]
  int fd; // [rsp+Ch] [rbp-14h]
  __int64 buf; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  fd = open("/dev/urandom", 0);
  buf = 0LL;
  read(fd, &buf, 8uLL);
  close(fd);
  srand(buf);
  for ( i = 0; i <= 15; ++i )
    key[i] = rand();
  return v4 - __readfsqword(0x28u);
}

그 후 16바이트 키를 가져와서 XOR을 통해 flag.png를 암호화 한다.

정말 무작위 값이기 때문에 그 값을 유추하는 것은 불가능하다.

하지만 XOR연산에는 하나의 특성이 있다.

A ⊕ B = C
C ⊕ A = B

이 특성을 이용해서 문제를 풀어줄 것이다. png 헤더는 값이 일정하기 때문에 원래 png 헤더값과 암호화된 png 헤더값을 XOR 하면 원본 키 16바이트를 구할 수 있을 것이다.

header ⊕ key = enc
enc ⊕ header = key


png 파일의 헤더 시그니처: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 (밑줄 친 부분은 png의 헤더 시그니처)
암호화 된 png 파일의 헤더 시그니처: 3D BB CA 2D 54 C7 82 20 D4 F9 3B E9 C2 91 81 A4

암호화 된 문제 png 파일의 헤더 부분

우선 문제를 다운받은 prob 파일 내에 파이썬 파일(.py)을 하나 생성한다.
prob 파일 아래에 파이썬 파일을 생성하는 이유는 암호화 된 png 파일을 파이썬에서 더 쉽게 불러오기 위해서이다!


open()
: open(filename, mode)
mode

  • r(read), w(write), a(append)
  • 인코딩 모드
  • t(text): 자동 인코딩/디코딩 모드
  • read(): 반환타입 str
  • write(): str으로 자동 변환
  • b(binary): 바이너리 모드
  • read(): 반환 타입은 bytes
  • write(): bytes으로 자동 반환

ex) rt(read+text), rb, wt, wb, at, ab
png 헤더(header)와 암호화된 png파일의 헤더(enc) 읽는다.

header = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52]
enc = open("./enc_verysecret.png", 'rb').read() #파일 전체를 byte 형식으로 읽기->'rb'



암호화 된 png의 헤더 중 16바이트(0x10)를 temp 배열에 저장한다. 파일의 헤더 부분이므로 앞에서 16바이트를 저장해주면 된다. -> [:0x10]
그리고 원래 png헤더와 암호화 된 png 헤더를 XOR(^) 연산을 통해서 key 값을 구해준다.

temp = enc[:0x10] #암호화 된 png 헤더
 
key = []
for i in range(0x10): #키 값 구하기
    key.append(header[i] ^ temp[i])



이제 위에서 구한 key를 가지고 enc(enc_verysecret.png)를 복호화를 해준다.

solve = []
for i in range(len(enc)): #암호화 된 png의 길이만큼 반복
    solve.append(enc[i] ^ key[i % 0x10])



join()
join()은 매개변수로 들어온 리스트에 있는 요소 하나하나를 합쳐서 하나의 문자열로 바꾸어 변환하는 함수이다.

''.join(리스트)
['a', 'b', 'c'] -> 'abc'

'구분자'.join(리스트)
['a', 'b', 'c'] -> "a_b_c"




chr()
아스키코드를 문자열로 변환하는 함수이다. (<-> ord())
chr(아스키코드)

solve = ''.join(chr(_) for _ in solve)
open("./flag.png", 'wb').write(solve.encode('iso-8859-1'))

print("완료")




<전체 코드>

header = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52]
enc = open("./enc_verysecret.png", 'rb').read()
temp = enc[:0x10]

key = []
for i in range(0x10):
    key.append(header[i] ^ temp[i])
    
solve = []
for i in range(len(enc)):
    solve.append(enc[i] ^ key[i % 0x10])
    
solve = ''.join(chr(_) for _ in solve)
open("./flag.png", 'wb').write(solve.encode('iso-8859-1'))

print("완료") #함수 실행 완료 시, '완료' 출력

SWING{Secure_rand_ransomware}

flag.png