며칠 전 미리 준비된 쿼리 세트 중에서 선택할 수 있는 프로그램을 발견했는데, 선택한 쿼리의 답변을 가져오기 위해 서버와 통신하는 방식이었습니다. 그런데 서버 인증을 위한 비밀번호를 전혀 묻지 않는다는 사실을 깨닫고 의문이 들었습니다. 알고 보니 비밀번호가 프로그램에 내장되어 있었던 것입니다. 제작자는 1) 바이너리 파일이라 아무도 읽을 수 없고 2) 설령 읽을 수 있다 해도 비밀번호 문자열을 찾을 수 없을 거라며 괜찮다고 설명했습니다.
이 두 가지 이유 모두 타당하지 않으며, 본 글을 통해 그 점을 증명하고자 합니다. 제 예제 프로그램은 단순히 터미널에 "password"를 출력할 뿐이지만, 실제 운영 환경의 프로그램과의 유일한 차이는 악의적인 사용자가 검색해야 할 문자열의 수뿐입니다.
#include <stdio.h>
#include <string.h>
main ()
{
char password [9] = {"secret"};
printf ("This is the password: %sn", password);
}
|
| 그림 1 – 암호가 내장된 기본 프로그램 |
실행 시 표시됩니다
x1 이것이 비밀번호입니다: secret 준비됨 14:12:16 |
| 그림 2 – 기본 프로그램 실행 |
프로그램 모듈 내 문자열을 표시하기 위해 악의적인 사용자는 >system>gnu_library>bin 디렉토리의 strings 명령어를 사용할 수 있습니다. -n5 옵션은 출력을 5자 이상 문자열로 제한합니다. 표시를 간결하게 하기 위해 출력을 생략( . . . )했지만, 비밀번호가 가장 먼저 표시되는 문자열 중 하나임을 확인할 수 있습니다. 비밀번호가 "login"이나 "password" 같은 키워드, 또는 "admin", "root", "sql"과 같은 식별 가능한 사용자 ID와 근접해 있으면 검색이 더 쉬워집니다.
시스템>gnu_library>bin>strings -n5 x1.pm
bind, Release 17.1.beta.be
phx_vos#
Noah_Davids.CAC
사전 출시
이것이 비밀번호입니다: %s
비밀
s$start_c_program
_preemption_cleanup
_exit.
. . . .
|
| 그림 3 – GNU strings 명령어를 사용하여 프로그램 모듈 내 4자 이상인 모든 문자열 찾기 |
strings 명령어가 사용 불가능할 경우 악의적인 사용자는 프로그램 모듈을 단순히 표시할 수 있습니다. 우리 모두 실수로 이 작업을 해본 적이 있을 텐데, 보기 좋지는 않지만 비밀번호가 표시됩니다. 출력을 다시 잘랐지만 비밀번호를 확인할 수 있습니다.
d x1.pm
%phx_vos#m16_mas>SysAdmin>Noah_Davids>x1.pm 11-01-13 14:13:59 mst
`00`01`00`1Abind, Release 17.1.beta.be`00`00`00`00`00`00`00`04ctp
`00`00`00`00`0
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`
+#`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`
00`00Noa.
. . .
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`C
D'`8B`C0
+`00`00`00`00이것이 비밀번호입니다: %s
`00`00`00`00`00`00`00secret`00`00`00`00`00`00`00`04`B0`00`00`00`00`01`00
`00`00,`
+`00`00`04`00`00`00`03@`00`FF`F8`FF`FE`00`04main`00`00`83`EC`2C`C7D$$`00.
. . .
|
| 그림 4 – 비밀번호를 찾는 프로그램 모듈 표시 |
프로그램에 비밀번호를 내장하는 것 외에도 여러 대안이 있습니다.
비밀번호는 파일에 저장할 수 있으며 프로그램이 해당 파일을 읽을 수 있습니다. "실행 권한이 필요한" 사람만 해당 파일에 대한 읽기 권한을 부여받으며, 그 외 모든 사용자는 접근 권한이 없습니다. 물론 "실행 권한이 필요하다"는 것이 "비밀번호를 알아야 한다"는 것과 반드시 일치하지는 않습니다. 또한 누군가가 실수로 접근 권한을 변경할 위험은 항상 존재합니다.
대안은 프로그램 내에 비밀번호 문자열을 보관하고 프로그램 접근만 제한하는 것입니다. 이 해결책 역시 "접근 목록 변경이 쉽다"는 문제점을 안고 있습니다. 해당 프로그램이 누구나 실행할 수 있는 다른 프로그램들과 동일한 디렉터리에 보관될 경우 접근 목록 변경 가능성은 높아집니다.
마지막으로, 프로그램 모듈에 비밀번호를 보관하되 문자열을 어떤 방식으로든 난독화할 수 있습니다. 다음 프로그램은 이를 수행하는 간단한 방법을 보여줍니다.
#include <stdio.h>
#include <string.h>
main ()
{
char obfuscate [9] = {0x0c, 0x81, 0xe2, 0x94, 0xe6, 0x9c};
char key [9] = {127, 228, 129, 230, 131, 232, 133, 234, 135};
char password [9];
int i;
for (i = 0; i < strlen (obfuscate); i++)
password [i] = obfuscate [i] ^ key [i];
password [i] = 0x0;
printf ("This is the password: %sn", password);
}
|
| 그림 5 – 난독화된 비밀번호 문자열을 포함한 프로그램 |
보시다시피 이 프로그램은 난독화 문자열을 가져와 다른 키 문자열과 XOR 연산을 수행하여 암호를 생성합니다. 프로그램을 실행하면 첫 번째 프로그램과 동일한 출력이 생성됩니다.
x2 이것이 비밀번호입니다: secret 준비됨 14:12:51 |
| 그림 6 – 난독화된 프로그램 실행 |
그러나 문자열 검색을 해봐도 비밀번호처럼 보이는 것은 아무것도 나타나지 않습니다.
>system>gnu_library>bin>strings -n5 x1.pm bind, Release 17.1.beta.be phx_vos# Noah_Davids.CAC Pre-release This is the password: %s s$start_c_program _preemption_cleanup _exit. . . . |
| 그림 7 – strings 명령어가 더 이상 비밀번호를 표시하지 않음 |
파일을 표시하면 암호화된 비밀번호 문자와 키 문자가 나타나지만, 이를 찾으려면 정확히 어디를 살펴야 하는지, 그리고 이들이 어떻게 결합되어 비밀번호를 생성하는지 알아야 합니다.
d x2.pm
%phx_vos#m16_mas>SysAdmin>Noah_Davids>x2.pm 11-01-13 14:15:27 mst
. . . .
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`CD'`8B`
C0`00`0
+0`00`00이것이 비밀번호입니다: %s
`00`00`00`00`00`00`00`0C`81`E2`94`E6`9C`00`00`00`00`00`00`00`00`00`00`7F
`E4`81`
+E6`83`E8`85`EA`87`00`00`00`00`04`B0`00`00`00`00`01`00`00`00`00`00`00`0
4`01`B0
+`00`03@`00`FF`F0`00`08`FF`FE`00`04main`00`00`83`EC`89$X`89t$T`89|$P`C
7D$L`00
|
| 그림 8 – 프로그램 모듈 표시 시 인식 가능한 비밀번호가 더 이상 표시되지 않음 |
이것은 여전히 완벽한 해결책이 아닙니다. 프로그램 모듈을 디스어셈블하여 비밀번호 생성 방식을 파악할 수 있기 때문입니다. 다만 프로그램 디스어셈블에는 단순히 프로그램 모듈을 표시하는 것보다 훨씬 더 정교한 기술이 필요합니다.
암호화된 비밀번호 문자열을 파일에 저장한 후 액세스 목록으로 파일을 보호하는 방법에 대해 고민이 있습니다. 한편으로는 비밀번호를 변경해야 할 경우, 프로그램에 암호화된 문자열을 직접 내장하는 것보다 훨씬 나은 해결책입니다. 반면에, 파일에 다른 설정 옵션이 존재하더라도 해당 문자열을 포함시키면 악의적인 사용자가 조사해야 할 잠재적 비밀번호 문자열의 수가 줄어들 것이라고 생각합니다. 물론 악의적인 사용자는 여전히 해당 파일에 접근한 후 암호화 기법과 키를 알아내야 할 것입니다.
마지막으로, 난독화된 비밀번호 문자는 어떻게 생성했을까요? XOR 연산의 장점은 ((A XOR B) XOR B)가 A와 같다는 점입니다. 그래서 저는 원래 비밀번호를 제 키의 문자와 XOR 연산한 후 결과를 출력하는 간단한 프로그램을 작성했습니다.
#include <stdio.h>
#include <string.h>
main ()
{
char password [9];
char key [9] = {127, 228, 129, 230, 131, 232, 133, 234, 135};
char newPW [9];
int i;
strcpy (password, "secret");
for (i = 0; i < strlen (password); i++)
{
newPW [i] = password [i] ^ key [i];
printf ("%xn", newPW [i]);
}
}
|
| 그림 9 – 난독화된 비밀번호 생성 프로그램 |
