reversing

[layer7 ctf] 리버싱 라이트업(총 2문제)

leesu0605 2022. 12. 19. 12:28

1. tea_time

일단 문제 파일을 열자마자 프로그램 루틴이 보였다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // al
  int i; // [rsp+8h] [rbp-188h]
  int v7; // [rsp+Ch] [rbp-184h]
  unsigned int v8; // [rsp+10h] [rbp-180h]
  unsigned int v9; // [rsp+14h] [rbp-17Ch]
  int v10; // [rsp+1Ch] [rbp-174h]
  int j; // [rsp+20h] [rbp-170h]
  int k; // [rsp+24h] [rbp-16Ch]
  char *v13; // [rsp+28h] [rbp-168h]
  int v14[12]; // [rsp+50h] [rbp-140h]
  char s[8]; // [rsp+80h] [rbp-110h] BYREF
  __int64 v16; // [rsp+88h] [rbp-108h]
  __int64 v17; // [rsp+90h] [rbp-100h]
  __int64 v18; // [rsp+98h] [rbp-F8h]
  __int64 v19; // [rsp+A0h] [rbp-F0h]
  __int64 v20; // [rsp+A8h] [rbp-E8h]
  __int64 v21; // [rsp+B0h] [rbp-E0h]
  __int64 v22; // [rsp+B8h] [rbp-D8h]
  __int64 v23; // [rsp+C0h] [rbp-D0h]
  __int64 v24; // [rsp+C8h] [rbp-C8h]
  __int64 v25; // [rsp+D0h] [rbp-C0h]
  __int64 v26; // [rsp+D8h] [rbp-B8h]
  __int64 v27; // [rsp+E0h] [rbp-B0h]
  __int64 v28; // [rsp+E8h] [rbp-A8h]
  __int64 v29; // [rsp+F0h] [rbp-A0h]
  __int64 v30; // [rsp+F8h] [rbp-98h]
  __int64 v31; // [rsp+100h] [rbp-90h]
  __int64 v32; // [rsp+108h] [rbp-88h]
  __int64 v33; // [rsp+110h] [rbp-80h]
  __int64 v34; // [rsp+118h] [rbp-78h]
  __int64 v35; // [rsp+120h] [rbp-70h]
  __int64 v36; // [rsp+128h] [rbp-68h]
  __int64 v37; // [rsp+130h] [rbp-60h]
  __int64 v38; // [rsp+138h] [rbp-58h]
  __int64 v39; // [rsp+140h] [rbp-50h]
  __int64 v40; // [rsp+148h] [rbp-48h]
  __int64 v41; // [rsp+150h] [rbp-40h]
  __int64 v42; // [rsp+158h] [rbp-38h]
  __int64 v43; // [rsp+160h] [rbp-30h]
  __int64 v44; // [rsp+168h] [rbp-28h]
  __int64 v45; // [rsp+170h] [rbp-20h]
  __int64 v46; // [rsp+178h] [rbp-18h]
  unsigned __int64 v47; // [rsp+188h] [rbp-8h]

  v47 = __readfsqword(0x28u);
  *(_QWORD *)s = 0LL;
  v16 = 0LL;
  v17 = 0LL;
  v18 = 0LL;
  v19 = 0LL;
  v20 = 0LL;
  v21 = 0LL;
  v22 = 0LL;
  v23 = 0LL;
  v24 = 0LL;
  v25 = 0LL;
  v26 = 0LL;
  v27 = 0LL;
  v28 = 0LL;
  v29 = 0LL;
  v30 = 0LL;
  v31 = 0LL;
  v32 = 0LL;
  v33 = 0LL;
  v34 = 0LL;
  v35 = 0LL;
  v36 = 0LL;
  v37 = 0LL;
  v38 = 0LL;
  v39 = 0LL;
  v40 = 0LL;
  v41 = 0LL;
  v42 = 0LL;
  v43 = 0LL;
  v44 = 0LL;
  v45 = 0LL;
  v46 = 0LL;
  printf("FLAG: ");
  __isoc99_scanf("%s", s);
  v7 = strlen(s);
  for ( i = 0; i < v7 / 8; ++i )
  {
    v13 = &s[8 * i];
    v8 = *(_DWORD *)v13;
    v9 = *((_DWORD *)v13 + 1);
    v10 = 32;
    for ( j = 0; v10--; v9 += (j + v8) ^ (16 * v8 - 1373085521) ^ ((v8 >> 5) + 821892524) )
    {
      j -= 1640531527;
      v8 += (j + v9) ^ (16 * v9 + 1461968204) ^ ((v9 >> 5) + 660145324);
    }
    *(_DWORD *)v13 = v8;
    *((_DWORD *)v13 + 1) = v9;
  }
  v14[0] = -1044660104;
  v14[1] = 247655037;
  v14[2] = -989523269;
  v14[3] = -745974710;
  v14[4] = 2003171038;
  v14[5] = 1264307023;
  v14[6] = -1131556505;
  v14[7] = 1311205716;
  v14[8] = -1276840000;
  v14[9] = -1336918999;
  for ( k = 0; (unsigned __int64)k <= 1; ++k )
  {
    if ( *(_DWORD *)&s[4 * k] != v14[k] )
    {
      v4 = 0;
      goto LABEL_13;
    }
  }
  v4 = 1;
LABEL_13:
  if ( v4 )
    puts("Correct!");
  else
    puts("Wrong...");
  return 0;
}

대충 사용자로부터 문자열을 입력받고, 4바이트씩 묶어 인접한 한 쌍씩 특정 xor 연산을 수행하고 그렇게 나온 값을

이 값들과 비교하는 루틴인 것 같다.
xor연산을 역순으로 다시 진행해주면 원래 값을 복호화 할 수 있다.

//ex.cpp
#include <iostream>
#include <stack>
using namespace std;

int main(){
    unsigned int data[10]={0xC1BBC078, 0xEC2EA7D, 0xC50512BB, 0xD389544A, 0x7765F6DE, 0x4B5BCB4F, 0xBC8DD167, 0x4E276954, 0xB3E4F7C0, 0xB0503C29};
    stack<int> flag;
    for(int i=4;i>=0;i--){
        unsigned int fcur=data[i*2];
        unsigned int scur=data[i*2+1];
        unsigned int ns=0;
        for(int j=0;j<32;j++)
            ns-=1640531527;
        for(int j=0;j<32;j++){
            scur-=(ns+fcur)^(16*fcur-1373085521)^((fcur>>5)+821892524);
            fcur-=(ns+scur)^(16*scur+1461968204)^((scur>>5)+660145324);
            ns+=1640531527;
        }
        flag.push(scur);
        flag.push(fcur);
    }
    while(!flag.empty()){
        int cur=flag.top();
        for(int i=0;i<4;i++){
            cout<<(char)(cur&0xff);
            cur>>=8;
        }
        flag.pop();
    }
}

flag : LAYER7{33f2e4d2c33bd374a51521a378f68123}

 

2. vMalware

프로그램을 열었는데 어셈블리 코드 중간중간 이상한 값들이 섞여 있어 단순히 어셈블리 코드만 보고 해석하는 건 불가능했다.

따라서 pwndbg를 열어 ni 명령어로 프로그램을 일일이 실행시키며 코드를 분석했다.
다행히 바이너리 루틴과 변수 사용은 vm 문제 치고 복잡하지 않아 심각한 분석력은 요구하지 않았다.

main 함수는 플래그 입력, 0x290짜리 배열 할당, init_cpu, run_cpu 호출, run_cpu에서의 연산 결과값 비교 등과 같은 역할을 하는 것 같고,
run_cpu 코드에선 stub 배열에 들어있는 opcode에 따라 다른 명령어를 수행해 main함수에서 할당한 배열과 사용자로부터 입력받은 플래그에 각각 다른 연산을 진행하는 루틴이 보였다.

참고로 각 함수에서 쓰이는 변수는 다음과 같다.

main-----

[rbp-0x48] = [rbp-0x40] -> malloc(0x290) -> input from 0x90 -> argument of run_cpu
[rbp-0x50] -> rax
[rbp-0x30] -> input_array
[rbp-0x38] -> points input_array
[rbp-0x54] -> loop counter

run_cpu----

[rbp-0x28] -> allocated array from main
[rbp-0x10] -> rax
[rbp-0x17] -> first operand
[rbp-0x16] -> second operand
[rbp-0x15] -> third operand(byte)
[rbp-0x8] -> third operand(qword, dword)
[rbp-0x14] -> stub_iterator
stub -> opcode

따라서 run_cpu의 어셈블리 코드를 분석해 각각의 opcode에 따라 어떤 연산이 수행되는지 구해왔고, 그에 따라 연산을 직접 구현해 z3 solver로 문제를 자동으로 풀었다.

이 문제는 딱히 막힌 부분은 없었으나 z3 solver로 BitVec 객체를 64비트로 선언해 연산할 때 byte, dword, qword ptr에 따라 비트를 옆으로 미는 방식으로 구현하다 보니 자잘한 구현 실수들이 있어 시간이 좀 더 걸렸다.

#ex.py
from z3 import *

data=[0x0C78FBB2FDAFD6293, 0x0A08D029FE351A82C, 0x9FC3BF1CFE9041BB, 0x0A788079CB806FE25, 0x0DBC6BA4FAC9741BC]
stub=[0x9e, 0x01, 0x07, 0x05, 0x00, 0x00, 0x00, 0x9e,
0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x01,
0x09, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x02, 0x0c,
0x1d, 0x9a, 0x65, 0xda, 0xae, 0x66, 0xbd, 0xc3,
0x9e, 0x04, 0x0a, 0x09, 0xa1, 0x02, 0x0c, 0x60,
0x11, 0xa8, 0x11, 0x3a, 0x19, 0xbd, 0xa1, 0xe3,
0x02, 0x0c, 0x5d, 0xcf, 0xb1, 0x8b, 0x66, 0x0c,
0x6f, 0x0c, 0xc6, 0x0c, 0xf3, 0x00, 0x0a, 0x0c,
0x9e, 0x06, 0x09, 0x0a, 0xa1, 0x01, 0x08, 0x01,
0x00, 0x00, 0x00, 0xa1, 0x01, 0x09, 0x08, 0x00,
0x00, 0x00, 0x55, 0x07, 0x08, 0x05, 0x02, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc]

s=Solver()
flag=[0]*18
inp=[BitVec("FLAG_%d"%i, 64) for i in range(5)]
pr=[x for x in inp]

it=0
while stub[it]!=0xcc:
    if stub[it]==0x9e:
        if stub[it+1]==0x01:
            flag[stub[it+2]]=stub[it+3]
            it+=7
        elif stub[it+1]==0x02:
            adder=0
            for i in range(8):
                adder|=stub[it+3+i]<<(i<<3)
            flag[stub[it+2]]=adder
            it+=0x0b
        elif stub[it+1]==0x04:
            flag[stub[it+2]]=0
            for i in range(8):
                flag[stub[it+2]]|=(((inp[(((flag[stub[it+3]])&0xff)+i)>>3]>>((((flag[stub[it+3]]&0xff)+i)&7)<<3)))&0xff)<<(i<<3)
            it+=4
        elif stub[it+1]==0x06:
            for i in range(8):
                bit=(2**64)-1
                for j in range(8):
                    bit^=(1<<((((flag[stub[it+2]]+i)&7)<<3)+j))
                inp[(flag[stub[it+2]]+i)>>3]&=bit
                inp[(flag[stub[it+2]]+i)>>3]|=((flag[stub[it+3]]>>(i<<3))&0xff)<<(((flag[stub[it+2]]+i)&7)<<3)
            it+=4
    elif stub[it]==0xa1:
        if stub[it+1]==0x02:
            adder=0
            for i in range(8):
                adder|=stub[it+3+i]<<(i<<3)
            flag[stub[it+2]]+=adder
            it+=0x0b
        elif stub[it+1]==0x01:
            adder=0
            for i in range(4):
                adder|=stub[it+3+i]<<(i<<3)
            flag[stub[it+2]]+=adder
            it+=7
    elif stub[it]==0xe3:
        if stub[it+1]==0x02:
            adder=0
            for i in range(8):
                adder|=stub[it+3+i]<<(i<<3)
            flag[stub[it+2]]-=adder
            it+=0x0b
    elif stub[it]==0xc6:
        flag[stub[it+1]]=~flag[stub[it+1]]
        it+=2
    elif stub[it]==0xf3:
        if stub[it+1]==0x00:
            flag[stub[it+2]]^=flag[stub[it+3]]
            it+=4
    elif stub[it]==0x55:
        if flag[stub[it+1]]==flag[stub[it+2]]:
            flag[0x10]=1
        else:
            flag[0x10]=0
        if flag[stub[it+1]]>flag[stub[it+2]]:
            flag[0x11]=1
        else:
            flag[0x11]=0
        it+=3
    elif stub[it]==0x05:
        if stub[it+1]==0x02:
            if flag[0x10]==1:
                it+=0xa
            else:
                it=stub[it+2]

for i in range(5):
    s.add(inp[i]==data[i])
if s.check()==sat:
    print("sat")
    r=s.model()
    for i in pr:
        print(r[i].as_long().to_bytes(8, "little").decode(), end="")
    print()
else:
    print("unsat")

flag : LAYER7{a12491d0cdb4aa3798dcb2a5dcb33262}