Wargame/study

[HackCTF] BabyMIPS

maple19out 2022. 2. 22. 20:54

1. Description

babymips라는 파일이 하나 주어진다. ubuntu에서 file 명령어를 통해 살펴보면 다음과 같다.

babymips

32-bit MIPS 아키텍처에서 실행되는 프로그램임을 알 수 있다. MIPS는 학교에서 컴퓨터 아키텍처 과목을 수강할 때 pipelining 등을 설명하기 위해 예시로 항상 나오던 cpu 아키텍처였는데, 실제로는 처음 보는 것 같다. 현재 실행 중인 pc는 x86-64 기반의 cpu이므로 동적 분석을 하기 위해서는 qemu 같은 도구를 이용해야 할 것 같다.

 

2. Analysis

우선 동적분석을 하기에 앞서 ida로 babymips 파일을 열어보았다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [sp+18h] [+18h]
  int v5; // [sp+1Ch] [+1Ch]
  int j; // [sp+20h] [+20h]
  int k; // [sp+24h] [+24h]
  int m; // [sp+28h] [+28h]
  char v9[28]; // [sp+2Ch] [+2Ch] BYREF

  memset(v9, 0, 0x1Au);
  printf("Password > ");
  _isoc99_scanf("%s", v9);
  if ( strlen(v9) == 25 )
  {
    for ( i = 0; i < 6; ++i )
    {
      for ( j = 24; j >= 0; --j )
      {
        if ( j )
        {
          if ( j == 24 )
          {
            v5 = v9[24];
            v9[24] = v9[23];
          }
          else
          {
            v9[j] = v9[j - 1];
          }
        }
        else
        {
          v9[0] = v5;
        }
      }
    }
    for ( k = 0; k < 25; ++k )
    {
      if ( (data[k] ^ k ^ v9[k]) != comp[k] )
      {
        puts("try again");
        return 0;
      }
    }
    for ( m = 0; m < 25; ++m )
      IO_putc((char)comp[m], stdout);
    puts(byte_E1C);
    return 0;
  }
  else
  {
    puts("len try again");
    return 0;
  }
}

코드는 매우 간단하다. 크게 다음과 같은 흐름이다.

(1) 입력받은 비밀번호의 길이가 25인지 검사한다.

(2) 이중 for 문으로 비밀번호에 대해 rotate right를 6만큼 수행한다.

(3) data 배열과 index(==k) 과 비밀번호 배열을 XOR 한 결과가 comp 배열과 같은지 비교한다.

 

따라서 본래 찾고자 하는 비밀번호는 data ^ index ^ comp를 해준 후, rotate left를 6만큼 해준 문자열이 될 것이다. data 배열과 comp 배열은 각각 다음과 같다. 이때, mips가 big endian 아키텍처이며 data와 comp 배열의 각 원소는 4byte임에 주의하자.

 

- data 배열

data

- comp 배열

comp

 

3. Solving

문제 풀이를 위해 동적분석까지 접근할 필요는 없을 것 같다. 분석한 내용을 바탕으로 다음과 같은 파이썬 스크립트를 작성했다.

comp = \
[
  67,
  111,
  110,
  103,
  114,
  97,
  116,
  122,
  95,
  89,
  111,
  117,
  95,
  70,
  111,
  117,
  110,
  100,
  95,
  70,
  108,
  97,
  103,
  33,
  33
]

data = \
[
  114,
  9,
  4,
  16,
  73,
  25,
  58,
  28,
  52,
  59,
  38,
  42,
  21,
  48,
  82,
  27,
  13,
  12,
  18,
  56,
  73,
  4,
  2,
  105,
  75
]

flag = list()
for i in range(25):
    flag.append(chr(i ^ data[i] ^ comp[i]))
flag = flag[6:] + flag[:6]
print("".join(flag))

스크립트를 실행하면 다음과 같이 flag를 획득할 수 있다.

flag

4. Comment

사실 문제 자체는 동적 분석을 필요로 할 만큼 복잡하지 않기 때문에 ida나 ghidra 같은 툴의 디컴파일 뷰만 살펴보고도 flag를 획득할 수 있다.

 

5. etc

다른 문제풀이를 해주신 분들의 글을 많이 볼 수 있었지만, qemu를 통해 주어진 바이너리 파일을 실행시킨 글은 거의 보지 못했다. 이번 풀이를 통해 qemu를 실행시켜보면서 다른 아키텍처의 환경 세팅을 해보는 연습을 해보았다.

 

1) (host) qemu 설치

qemu은 다른 아키텍쳐의 환경을 가상화시켜주는 에뮬레이터다. 윈도우 pc에서 리눅스를 사용하기 위해 vmware를 사용할 수 있다. 이때 설치되는 리눅스는 호스트와 게스트의 아키텍처가 동일하다. 만약 이번 문제처럼 MIPS 아키텍처에서 돌아가는 바이너리를 실행시키기 위해서는 qemu와 같은 도구를 사용해야 한다.

 

ubuntu에서는 다음과 같은 명령어로 qemu 설치가 가능하다.

sudo apt-get install qemu-kvm qemu

 

2) (host) 커널, 이미지 다운로드

qemu를 통해 실행시킬 커널과 이미지 파일을 다운로드하아야 한다. mips의 경우 아래 링크에서 wget으로 다운로드할 수 있다.

https://people.debian.org/~aurel32/qemu/mips/

 

Index of /~aurel32/qemu/mips

 

people.debian.org

README.txt 파일을 읽어보면서 원하는 환경에 맞는 파일을 다운로드하여주면 된다. 그런데 구글링을 하다 보면 squeeze mips의 경우 지원이 중단되었다는 글을 종종 발견할 수 있었다. 따라서 wheezy mips를 사용하기로 하고, babymips가 32-bit 환경에서 실행되므로 이에 대응되는 파일을 받아주기로 했다.

wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta
wget https://people.debian.org/~aurel32/qemu/mips/debian_wheezy_mips_standard.qcow2

 

3) (host) qemu 실행

이제 qemu를 실행시킬 준비는 다 되었다. 실행시킬 명령어의 인자가 꽤 복잡한데 하나씩 나누어 보면 이해하기 쉽다.

qemu-system-mips \
-M malta -kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_wheezy_mips_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \

다운로드한 커널과 이미지를 통해 가장 기본적인 옵션만 설정하고 qemu를 실행할 수 있지만, ssh 접속 등 동적 분석을 편리하게 하기 위해 몇 가지 옵션을 다음과 같이 추가하자.

qemu-system-mips \
-m 256 \
-M malta -kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_wheezy_mips_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-net user,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:5555-:1234 \
-net nic,model=e1000

- m : 램(RAM) 크기를 설정하는 부분이다. (32-bit MIPS에서는 기본 128m, 최대 256m 인식)

- net : 포트 포워딩을 설정하는 부분이다. ip는 로컬 호스트로 설정하고 포트의 경우 2222 -> 22 (ssh)로, 5555 -> 1234 (gdbserver)로 설정해준다. (이전의 -redir 옵션은 deprecated 됐다고 한다. 위와 같은 형태로 옵션을 주자)

 

성공적으로 실행이 되었으면 root/root 혹은 user/user로 로그인이 가능하다.

 

4) (guest) gdbserver, gdb 설치

apt-get update를 해도 패키지를 잘 못 찾아오는 것을 확인할 수 있다. /etc/apt/sources.list의 모든 내용을 주석처리하고 다음 라인을 추가해주자.

 

deb http://archive.debian.org/debian/ wheezy main contrib non-free

 

이후에 apt-get install gdbserver gdb로 gdbserver와 gdb를 설치해주자.

 

5) (guest) gdbserver 실행

scp로 호스트에서 게스트로 babymips 파일을 복사 후, gdbserver를 실행시켜주자.

scp -P 2222 babymips root@127.0.0.1:/root
gdbserver localhost:1234 ./babymips

 

6) (host) gdb-multiarch 실행

호스트에서 gdb-multiarch를 실행해준 후, target remote localhost:5555 명령어를 통해 게스트에서 실행되고 있는 gdbserver에 접속한다.

target remote localhost:5555

gdb-multiarch

위와 같이 gdb-multiarch를 이용해 호스트에서 MIPS 동적 디버깅이 가능해진다.

 

6. Reference

https://stackoverflow.com/questions/55043135/qemu-system-arm-redir-invalid-option/55045534

https://pr0cf5.github.io/ctf/2019/07/16/mips-userspace-debugging.html

https://goseungduk.tistory.com/29

https://serverfault.com/questions/704294/qemu-multiple-port-forwarding

반응형