문제 소개

상용 안드로이드 어플리케이션을 개발했지만, 불행히도 개발은 멈췄고 출시를 하지 못하게 되었습니다. 하지만 유효한 키를 찾아서 프리미엄 기능을 열어달라고 합니다.

 

APK 분석

apk 를 제공해 주므로, apktool 을 이용하여 디컴파일을 진행합니다.

.
├── THC
│   ├── AndroidManifest.xml
│   ├── apktool.yml
│   ├── original
│   ├── res
│   └── smali
└── THC.apk

apk 를 디컴파일해보면, 여러 구성 요소들이 보입니다.

 

AndroidManifest.xml 파일을 확인해 보면, com.thc.bestpig.serial.MainActivity 가 메인 엑티비티 인 것을 알 수 있습니다.

 

MainActiviy->onCreate() 부터 살펴 봅니다. onCreate() 에서 하는 게 많지만, 핵심은 마지막 부분에 위치합니다.

Id가 0x7f070023 View 를 가져와서 버튼인지 확인을 하고, MainActivity$3의 <init> 함수를 호출하고 버튼에 Click Listener 을 설정합니다.

 

<public type="id" name="buttonActivate" id="0x7f070023" />

Id 가 0x7f070023 인 것을 확인해 보면, buttonActivate 라는 이름을 확인할 수 있습니다.

 

더 확인해보면, 텍스트로 "Activate Software" 를 가진 버튼임을 확인할 수 있습니다.

 

다시 MainActivity$3 으로 돌아와서, 버튼을 click 하게 되면 위의 method 가 호출됩니다.

입력한 값을 가져와서 문자열로 바꾸고, 이 문자열을 MainActivity의 checkPassword() 메소드의 인자로 하여 호출합니다.

 

checkPassword 메소드에서는 이 문자열을 다시 validateSerial 메소드의 인자로 하여 호출을 하고, 결과값이 1이 아니면 인증 실패 루틴으로 이동하게 됩니다. 따라서 validateSerial 메소드에서 1을 반환할 조건을 분석해 봅니다.

 

validateSerial 메소드에서는 v0 의 값을 return 하게 되는데, 이 값이 1이 되는 경우는 아래에 더 존재하는 모든 조건을 충족하는 경우만 있습니다. 따라서 이 조건들을 정리해 보면 아래와 같습니다.

 

.line 18    len(flag) == 0x13
.line 20    flag[0x04] == flag[0x09] == flag[0x0e] == 0x2d
.line 22    flag[0x05] == flag[0x06] + 0x01
.line 25    flag[0x05] == flag[0x12]
.line 28    flag[0x01] == (flag[0x12] % 0x04) * 0x16
.line 30    flag[0x0a] == flag[0x03] * flag[0x0f] / flag[0x11] - 0x01
.line 32    flag[0x01] == flag[0x0a]
.line 34    flag[0x0d] == flag[0x0a] + 0x05
.line 36    flag[0x0a] == flag[0x05] - 0x09
.line 38    0x5a0 == (flag[0x00] % flag[0x07]) * flag[0x0b]
.line 40    flag[0x02] - flag[0x08] + flag[0x0c] == flag[0x0a] - 0x09
.line 42    (flag[0x03] + flag[0x0c]) / 0x02 == flag[0x10]
.line 44    flag[0x00] - flag[0x02] + flag[0x03] == flag[0x0c] + 0x0f
.line 46    flag[0x03] == flag[0x0d]
.line 48    flag[0x10] == flag[0x00]
.line 50    flag[0x07] + 0x01 == flag[0x02]
.line 52    flag[0x0f] + 0x01 == flag[0x0b]
.line 54    flag[0x0b] + 0x03 == flag[0x11]
.line 56    flag[0x07] + 0x14 == flag[0x06]

이 조건들로 시리얼을 직접 구할 수도 있지만, SMT solver 중 하나인 Z3 를 이용하여 solver 를 만듭니다.

 

Serial 구하기

위에서 찾은 식을 조건에 추가하고, 조건을 충족하는 결과 값을 출력합니다.

 

위에서 구한 serial 값을 입력하면, 활성화에 성공한 것을 확인할 수 있습니다.

'0x00 Reversing > 0x02 CTF' 카테고리의 다른 글

InCTF 2019 :: cliche_crackme  (0) 2019.10.05

문제 소개

설명을 보면 뉴비를 위한 뻔한 crackme 문제라고 한다. 바이러니의 checksum 비교를 위하여 hash 값을 비교해 본다.

 

hash 값은 동일하며, 맥에서 사용되는 Mach-0 파일임을 알 수 있다.

 

함수 분석

__int64 start()
{
  void *v0; // ST30_8
  __int64 result; // rax
  char v2; // [rsp+40h] [rbp-30h]
  __int64 v3; // [rsp+68h] [rbp-8h]

  v0 = calloc(0x29BuLL, 4uLL);
  scanf("%s", &v2);
  result = sub_100000DB0(sub_100000D00, sub_100000C80, sub_100000BA0, sub_100000C10, &v2, v0);
  if ( __stack_chk_guard == v3 )
    result = 0LL;
  return result;
}

ida 로 열어보면, start 함수에서는 v0 에 0x29B 만큼 calloc 해주고 v2 에 입력값을 받는다.

그 후 sub_100000DB0 함수에서 여러 인자들로 result 값을 만든다.

 

__int64 __fastcall sub_100000DB0(
  void (__fastcall *a1)(__int64, __int64),          // sub_100000D00
  unsigned int (__fastcall *a2)(__int64, void *),   // sub_100000C80
  unsigned int (__fastcall *a3)(__int64),           // sub_100000BA0
  unsigned int (__fastcall *a4)(__int64),           // sub_100000C10
  __int64 a5,                                       // scanf address
  __int64 a6)                                       // calloc(0x29BuLL, 4uLL)
{
  unsigned int (__fastcall *v6)(__int64);           // sub_100000BA0
  __int64 v7;                                       // scanf address
  __int64 v9;                                       // calloc(0x29BuLL, 4uLL)
  unsigned int (__fastcall *v10)(__int64);          // sub_100000C10

  v6 = a3;
  v10 = a4;
  v7 = a5;
  v9 = a6;
  a1(a6, a5);
  if ( v6(v7) && v10(v9) && a2(v9, &unk_100001040) )
    printf("Congratz - You have earned it!");
  else
    printf("%s", "You've got to do better");
  return 0LL;
} 

sub_100000DB0 함수로 들어가 보면, 인자들을 로컬변수에 넣고 그 값들을 이용하여 입력값이 올바른지 확인한다.

이를 요약하면 아래의 함수들로 정리할 수 있다.

  • al(a6,a5) => sub_100000D00(v0,&v2)
  • v6(v7) => sub_100000BA0(&v2)
  • v10(v9) => sub_100000C10(v0)
  • a2(v9, &unk_100001040) => sub_100000C80(v0,&unk_100001040)

 

__int64 __fastcall sub_100000D00(__int64 a1, const char *a2)
{
  __int64 result; // rax
  int j; // [rsp+0h] [rbp-20h]
  int i; // [rsp+4h] [rbp-1Ch]
  int v5; // [rsp+8h] [rbp-18h]
  int v6; // [rsp+Ch] [rbp-14h]

  v6 = strlen(a2);
  v5 = 0;
  for ( i = 0; i < v6; ++i )
  {
    for ( j = i + 1; j < v6; ++j )
      *(_DWORD *)(a1 + 4LL * v5++) = a2[j] + a2[i];
  }
  result = a1;
  *(_DWORD *)(a1 + 2668) = 0;
  return result;
}

sub_100000D00 함수에서는 a1 을 이용하여 a2 값을 생성한다.

 

_BOOL8 __fastcall sub_100000BA0(__int64 a1)
{
  signed int i; // [rsp+4h] [rbp-18h]
  int v3; // [rsp+8h] [rbp-14h]

  v3 = 0;
  for ( i = 0; i < 37; ++i )
    v3 += *(char *)(a1 + i);
  return v3 == 3504;
}

sub_100000BA0 함수에서는 a1의 배열 값들을 더해서 3504 가 맞으면 True 값을 리턴해준다.

 

_BOOL8 __fastcall sub_100000C10(__int64 a1)
{
  signed int i; // [rsp+4h] [rbp-18h]
  int v3; // [rsp+8h] [rbp-14h]

  v3 = 0;
  for ( i = 0; i < 667; ++i )
    v3 += *(_DWORD *)(a1 + 4LL * i);
  return v3 == 126144;
}

sub_100000C10 함수에서는 a1의 배열 값들을 더해서 126144 가 맞으면 True 값을 리턴해준다.

 

_BOOL8 __fastcall sub_100000C80(__int64 a1, __int64 a2)
{
  signed int v3; // [rsp+0h] [rbp-1Ch]

  v3 = 0;
  while ( v3 < 666 )
  {
    if ( *(_DWORD *)(a1 + 4LL * v3) == *(_DWORD *)(a2 + 4LL * v3) )
      ++v3;
  }
  return v3 >= 666;
}

마지막으로 sub_100000C80 함수에서는 a1의 값과 a2의 값이 같은지 확인하는데, 그 길이가 666인지 확인한다.

여기서 a1은 입력값을 기반으로 만든 배열로 a2의 길이가 길이가 666일 경우, a1의 길이가 37인 것을 알 수 있다.

 

위의 함수들을 정리해 보면

  • 입력값의 길이는 37
  • 입력값을 이용하여 만든 배열을 바이너리에 있는 값과 비교
  • 입력값의 문자열들의 총합은 3504

인 것을 알 수 있다.

이제 이를 구하는 간단한 파이썬 코드를 작성한다.

 

flag 구하기

with open('cliche_crackme','rb') as rb:
    rb.seek(0x1040)
    data = rb.read(666*4)
    for i in range(0x21,0x80):
        try:
            rev = chr(i)
            for j in range(0,len(data),4):
                rev += chr(data[j]-ord(rev[0+(len(rev)-1)//37]))
        except:
            a5,chk = rev[:37],0
            for j in range(len(a5)): chk += ord(a5[j])
            if chk == 3504: print('input :',a5)

플래그를 구할때는 거꾸로, 바이너리에서 데이터를 읽어오고 그 값에서 임의의 값을 빼서 문자열을 추측해 본다.

임의의 값을 기반으로 생성한 문자열의 값들을 합한 값이 3504 인지 확인하고, 맞을 경우 이를 출력해 주는 코드이다.

 

숫자, 대문자, 소문자를 거쳐 i로 시작하는 경우에만 그 문자열의 합이 3504이며 플래그를 확인할 수 있다.

'0x00 Reversing > 0x02 CTF' 카테고리의 다른 글

THC CTF 2018 :: Android Serial (with Z3 solver)  (0) 2019.10.13

+ Recent posts