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