본문 바로가기

REVERSING

Trend Micro CTF 2020 / reversing-2 200

요즘 바빠서 블로그에 뭐 쓸 게 없네요ㅠ. KISA에 취약점 제보한 것들 비밀유지 기한이 끝나면 쓸 것들이 좀 많아질 듯합니다.

 

Gameboy를 기반으로 만들어진 간단한 게임이 주어집니다. 해당 게임은 점프 높이나 몬스터 때문에 정상적으로 클리어할 수 없기 때문에 ROM 파일을 리버싱하여 치트 코드를 알아내야 합니다.

 

 

 

 

 

리버싱에는 2가지 툴을 사용했습니다. Ghidra에서 어셈블리를 디컴파일 해주기 때문에 일반적인 ELF 파일 리버싱과 비슷한 느낌으로 하면 됩니다.

Ghidra : GhidraBoy를 설치하면 ROM 파일의 어셈블리를 디컴파일해줍니다. 윈도우에서는 설치가 안 되서 리눅스에서 Ghidra를 설치하여 사용했습니다.

bgb : 동적 디버깅을 위해 사용했습니다.

 

 

 

치트코드를 입력할 때 코드의 길이가 3이면 check_routin 함수가 호출됩니다.

 

/* WARNING: Control flow encountered bad instruction data */
/* WARNING: Removing unreachable block (ram,0x0462) */
/* WARNING: Removing unreachable block (ram,0x045f) */
/* WARNING: Removing unreachable block (ram,0xc1a8) */
/* WARNING: Removing unreachable block (ram,0x0465) */

void FUN_ram_03d8(char param_1,undefined param_2)

{
  byte in_F;
  byte bVar1;
  undefined uVar2;
  char *pcVar3;
  undefined2 *puVar4;

  if ((bool)(in_F >> 7)) {
                    /* WARNING: Bad instruction - Truncating control flow here */
    halt_baddata();
  }
  bVar1 = (param_1 == '\x03') << 6;
  if (param_1 == '\x03') {
    check_routin();
    if ((bVar1 >> 6 & 1) == 0) {
      unkown(CONCAT11(0x14,param_2),0x9840,0x48e);
    }
    else {
//생략
  }
  else {
    unkown(CONCAT11(0x14,param_2),0x9840,0x4a2);
  }
  unkown(CONCAT11(0x14,param_2),0x9880,0x50c);
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

 

 

check_routin 함수에서는 check1,2,3 함수로 입력값을 검증합니다. 해당 함수들의 return 값이 True가 되는 입력값들을 찾으면 치트코드 3개를 구할 수 있습니다. 이때 모든 함수의 인자로 0xc1aa의 주소가 들어갑니다. bgb로 디버깅하면 해당 주소에는 (inp[0]-0x41,inp[1]-0x41,inp[2]-0x41)가 들어가있는 것을 확인할 수 있습니다.

 

 

undefined check_routin(void)

{
  byte mabye_return;
  
  check1(&DAT_ram_c1aa);
  if ((bool)(mabye_return >> 6 & 1)) {
    return 0;
  }
  check2(&DAT_ram_c1aa);
  if ((bool)(mabye_return >> 6 & 1)) {
    return 1;
  }
  check3(&DAT_ram_c1aa);
  if ((bool)(mabye_return >> 6 & 1)) {
    return 2;
  }
  return 0xff;
}

 

 

check1 함수는 매우 간단합니다. [0x15+0x41, 0x1 +0x41 , 0x12+0x41] —> VBS를 입력하면 됩니다.

 

 

undefined check1(char *param_1)//&DAT_ram_c1aa

{
  if (*param_1 != '\x15') {
    return 0x15;
  }
  if (param_1[1] != '\x01') {
    return 1;
  }
  return 0x12;
}

 

 

ghidra에서 check2 함수를 정확하게 디컴파일해주지 않습니다. bgb를 통해 디버깅하면 입력값 3바이트를 각각 특정한 값과 xor한 뒤 어떤 값과 일치하는지 검사합니다. 디버깅으로 값을 확인한 후 입력값을 맞춰주면 됩니다.

check3 함수는 입력값에 대해 몇 번 산술연산을 거친 후, 그 결과값을 특정 바이트와 비교합니다. 해당 로직을 파이썬으로 포팅하면 아래와 같습니다.

 

 

test=0
inp="TTT"
inp=[ord(inp[0])-0x41,ord(inp[1])-0x41,ord(inp[2])-0x41]
for x in range(3):
    test = test*0x12
    test = test+inp[x]
print(hex(test))

 

 

아래 스크립트로 치트 코드를 구할 수 있습니다.

 

from itertools import product
def gen(inp):
    test=0
    for x in range(3):
        test = test*0x12
        test = test+inp[x]
    return test


chars=""
for x in range(0x20):
    chars+=chr(x)

bf= product(chars, repeat=3)

for t in bf:
    x,y,z= map(ord,t)
    if gen([x,y,z])==0x1922:
        print("find : %c%c%c"%(x+0x41,y+0x41,z+0x41))

 

 

check1,2,3 함수를 통해서 얻은 치트코드를 모두 입력한 후 게임을 클리어하면 flag의 나머지 부분을 줍니다.

 

 

 

 

 

 

기드라로 디컴파일만 했으면 쉽게 풀 수 있었던 문제 같습니다. 

'REVERSING' 카테고리의 다른 글

윈도우 악성코드 분석  (0) 2020.02.29
PINTOOL for CTF / codegate 2020 prequal simple machine  (2) 2020.02.26