Binary 분석을 통해 어플리케이션에 포함된 숨겨진 데이터 찾아내기

일반적으로 PC Application 해킹에 대한 이야기는 리버싱으로 시작하여, 리버싱으로 끝나기 마련입니다. 대부분 디스어셈블러와 디버거를 이용하여 취약점을 분석하거나 내부 보안로직을 우회하는 등 프로그램에 대한 공격을 수행하게 됩니다.

다만 오늘은 리버싱에 대한 이야기 아니라 간단하게 Binary 파일 분석을 통해서 어플리케이션의 취약, 인증 우회 포인트를 찾는 방법에 대해 이야기할까 합니다.

대부분의 프로그램들은 매우 잘 만들어져 Binary 분석은 커녕 리버싱 자체로도 벅찬 경우가 있지만, 또 반대로 간단하게 Binary 분석을 통해 쉽게 풀어나갈 수 있는 프로그램도 있습니다.

Env

Source Code

아래 코드는 간단하게 Password 를 받아 비교하여, 결과를 주는 코드입니다. 코드를 보시면 알겠지만, strcmp 사용으로 취약할 수 있고, 단순한 코드여서 assemble를 통해 흐름을 바꾸어 쉽게 변조가 가능한 프로그램입니다.

#include <stdio.h>

#define db_id "admin"
#define db_pw "admin123"

void main() {
    char user_pw[644];
    printf("input PW: ");
    scanf("%s", user_pw);
    if (!strcmp(db_pw, user_pw)) {
        printf("\nOK Admin..\n");
    } else {
        printf("\nPW Error.\n");
    }
}

또한 추가로 간단한 문제점이 하나 있는데, 바로 #define을 사용하여 Password 값을 평문으로 저장하고 있는 것 입니다. 프로그램 내 인증이나, 패스워드 등 중요한 데이터를 포함하고 있을 경우 hex editor로 확인이 가능합니다.

물론 간단한 코드이기에 확인이 쉬운 것 뿐이지 큰 프로그램은 Binary로 분석하려면 많은 시간이 필요하기도 합니다 :(

Compile & Execute

일단 테스트를 위해 컴파일합니다.

gcc -o define_code define_code.c
ll

# -rwxr-xr-x  1 root   root   7287 11월 21 00:23 define_code
# -rw-r--r--  1 root   root    251 11월 21 00:23 define_code.c

실행하면 아래와 같이 Password를 물어보고, 맞으면 OK Admin, 틀리면 PW Error를 노출하게 됩니다.

./define_code
# input PW: www.codeblack.net
# PW Error.

Find Key

ghex(윈도우에서는 대표적으로 hxd가 많이 사용되죠)를 통해 한번 열어서 확인해보겠습니다. ELF로 시작하는 실행 파일입니다.

여기서 노출되었던 문자열 위주로 찾으면 조금 편합니다. PW Error 문자열 기준으로 검색해시면 아래와 같이 찾아낼 수 있습니다. (다 쓰기보단 나눠서 찾는게 좋아요)

찾아낸 PW Error 문자열 주변으로 PW가 맞았을 때 메시지와 아까 우리가 지정한 Password 문자열을 확인할 수 있습니다.

물론 코드를 모르는 상태에서 보면 저게 뭔가 싶겠지만, 스크립트를 짜서 나름대로 분석하거나, Fuzzing을 통해서 어느정도 유추할 수 있는 부분이기에 프로그램 입장에서는 취약한 포인트를 하나 더 가지고 있는 상태입니다.

저렇게 단순하게 하드 코딩된 데이터는 hex editor 이외에도 여러가지 툴들을 이용해 좀 더 쉽게 확인할 수 있습니다.

Bypass Logic

여담으로 테스트 코드로 대충 쓴거지만, 이 코드 자체가 매우 취약해서 objdump를 통해 확인했을 때 main 이외에 별다른게 확인되지 않고 흐름또한 단순하기 떄문에 gdb, edb 등을 이용해 쉽게 인증에 대해 통과할 수 있는 프로그램이 되겠네요.

objdump -d define_code
000000000040060c <main>:
  40060c:55                   push   %rbp
  40060d:48 89 e5             mov    %rsp,%rbp
  400610:48 81 ec 90 02 00 00 sub    $0x290,%rsp
  400617:bf 1c 07 40 00       mov    $0x40071c,%edi
  40061c:b8 00 00 00 00       mov    $0x0,%eax
  400621:e8 9a fe ff ff       callq  4004c0 <printf@plt>
  400626:48 8d 85 70 fd ff ff lea    -0x290(%rbp),%rax
  40062d:48 89 c6             mov    %rax,%rsi
  400630:bf 27 07 40 00       mov    $0x400727,%edi
  400635:b8 00 00 00 00       mov    $0x0,%eax
  40063a:e8 b1 fe ff ff       callq  4004f0 <__isoc99_scanf@plt>
  40063f:48 8d 85 70 fd ff ff lea    -0x290(%rbp),%rax
  400646:48 89 c6             mov    %rax,%rsi
  400649:bf 2a 07 40 00       mov    $0x40072a,%edi
  40064e:e8 8d fe ff ff       callq  4004e0 <strcmp@plt>
  400653:85 c0                test   %eax,%eax
  400655:75 0c                jne    400663 <main+0x57>
  400657:bf 33 07 40 00       mov    $0x400733,%edi
  40065c:e8 4f fe ff ff       callq  4004b0 <puts@plt>
  400661:eb 0a                jmp    40066d <main+0x61>
  400663:bf 3f 07 40 00       mov    $0x40073f,%edi
  400668:e8 43 fe ff ff       callq  4004b0 <puts@plt>

strcmp 이후에 바로 jne, jmp가 있는 구간이 확인되고, 흐름을 바꾼다면 쉽게 가짜 패스워드로 통과할 수 있겠지요. :)

(gdb) set disassembly intel
(gdb) disas main
Dump of assembler code for function main:
   0x000000000040060c <+0>:push   rbp
   0x000000000040060d <+1>:mov    rbp,rsp
   0x0000000000400610 <+4>:sub    rsp,0x290
   0x0000000000400617 <+11>:mov    edi,0x40071c
   0x000000000040061c <+16>:mov    eax,0x0
   0x0000000000400621 <+21>:call   0x4004c0 <printf@plt>
   0x0000000000400626 <+26>:lea    rax,[rbp-0x290]
   0x000000000040062d <+33>:mov    rsi,rax
   0x0000000000400630 <+36>:mov    edi,0x400727
   0x0000000000400635 <+41>:mov    eax,0x0
   0x000000000040063a <+46>:call   0x4004f0 <__isoc99_scanf@plt>
   0x000000000040063f <+51>:lea    rax,[rbp-0x290]
   0x0000000000400646 <+58>:mov    rsi,rax
   0x0000000000400649 <+61>:mov    edi,0x40072a
   0x000000000040064e <+66>:call   0x4004e0 <strcmp@plt>
   0x0000000000400653 <+71>:test   eax,eax
   0x0000000000400655 <+73>:jne    0x400663 <main+87>
   0x0000000000400657 <+75>:mov    edi,0x400733
   0x000000000040065c <+80>:call   0x4004b0 <puts@plt>
   0x0000000000400661 <+85>:jmp    0x40066d <main+97>
   0x0000000000400663 <+87>:mov    edi,0x40073f
   0x0000000000400668 <+92>:call   0x4004b0 <puts@plt>
   0x000000000040066d <+97>:leave 
   0x000000000040066e <+98>:ret