오토핫키(Autohotkey L)암호화/복호화 컴파일러? #2

프로그래밍/Autohotkey 2017.09.21 댓글 Plorence

이번에는 암호화/복호화에 대하여 설명 해보도록 하겠습니다. 저번 #1편에 분명히 난독화라고 하였지만,난독화보다는 암호화에 가까워서 암호화 라고 말하겠습니다. 암호화와 복호화는 반드시 따라오는 한 쌍이며,하나라도 빠지면 의미가 없는 존재들입니다. 암호화는 코드나 어떤 문자열을 개발자 말곤 아무도 모르는 암호로 바꾼다고 하여 암호화고 복호화는 암호화된 코드를 해독하는 겁니다.

들어가기 전에

먼저 본격적인 설명을 하기전에 #1편의 내용이 들어가 있습니다. #2편을 읽으실려면 반드시 #1편보고 와주세요.(http://plorence.kr/266) 그리고 원래는 Dll,프로젝트,컴파일러를 배포할려고 했지만 다른 분들의 의견을 통해서 프로젝트에서 가장 핵심인 파일,컴파일러만 배포 할 예정입니다. 그 이유는 분명히 모든걸 배포하면 악용해서 판매하시는 분들이 계실까봐 그런겁니다. 빌드 방법은 뮤텍스 수정방법(http://plorence.kr/226)을 참고해주시면 감사하겠습니다. 다운로드 링크에서 전부다 다운로드 한다고 돌아가는건 아닙니다. Dll은 알아서 짜세요. 할줄 아신다면 컴파일러만 보시고 어떤걸 해야하는지 감이 잡힐겁니다. 그리고 기본적인 프로그래밍 지식을 요구합니다.


AutoHotKeySC.bin파일의 중요성

컴파일러를 사용하다보면 지독하게 따라오는놈이 하나 있습니다. 바로 AutohotkeySC.bin이라는 파일인데요. 이 파일은 컴파일 이후 이 파일 자체가 오토핫키 프로그램으로 복사되어 동작하게 됩니다.중요한 소스코드를 제외한 모든것이 AutohotKeySC.bin에서 복사됩니다. 간단하게 말하면 소스코드 말고 아무것도 없는 껍데기 프로그램에서 AutohotKeySC.bin을 복사하여 완성된겁니다.오핫에서 쓰던 명령어나 GUI가 생성되는거 까지 담겨있습니다.

(네이버 카페 오토** *** 의 회원이신 L*****님의 댓글입니다.)

들어가기전에 위에 이미지 한번만 읽어보시고 넘어가주시면 이해해 도움이 되실겁니다. 물론 제가 글을 어렵게 썼지만요 이제 그 지겹도록 따라온 AutohotkeySC.bin을 건드릴겁니다. #1편을 이어서 하는거니 프로젝트 다운,실행은 다 할줄아는걸로 간주하고 넘어가겠습니다.

먼저 암호화시킨 오토핫키 실행파일입니다. 지금 상태는 바이너리 뷰어고 오른쪽에 121/23/82/177 이런식으로 되어있는게 기존의 코드를 암호화 시킨것들입니다.100%리소스 보호는 불가능하기에 아예 못알아보도록 암호화를 시킨겁니다. 물론 암호화된건 못뚫는건 아닙니다. 어떻게 해서 가능한지는 밑에서 설명 해드리겠습니다.

함수 정의를 제외한 가장 중요한 부분입니다. 먼저 맨위에 주석친 1990줄부터 한줄씩 해석해보도록 하겠습니다.

1
#pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console")  
cs

이 코드는 중간에 콘솔창을 실행하는건데,디버깅이 불가능하니(가능은 한데 귀찮음)아예 콘솔창을 띄워서 변수값들을 출력하려는 목적이었습니다. 주석친 printf함수도 마찬가지입니다. 그러면 한줄 한줄씩 해석해보도록 하겠습니다.

1
2
3
4
5
TextMem::Buffer textbuf(NULL0false);
.
.
.
char * data = (char *)textbuf.mBuffer;
cs

먼저 다른 파일에 TextMem이라고 정의된 클래스가 있습니다. 이 클래스는 리소스상에 올라간 소스코드와 길이를 담는 클래스입니다.1번째 줄에 TextMem클래스를 사용하기위해 객체 생성을 하고 소스코드는 mBuffer라는 멤버변수,길이는 Length이라는 멤버변수에 담겨 있습니다. 현재 소스코드는 textbuf.mBuffer에 담겼고 작업을 하기위해 char * data(자료형이 char인 포인터) 을 선언해서 선언과 동시에 욺겨줬습니다. (char *)라고 쓴 이유는 담을 공간은 char *인데 넣을려고 하는건 buffer라 서로 타입이 일치하지 않아 형 변환 이라는걸 해줬습니다.

그래서 여기까진 data라는 변수에 담긴거나 texbuf.mBuffer에 담긴 값이 똑같습니다.

1
2
3
4
5
char RESdata[5000000= { 0 };
.
.
.
memcpy(RESdata, textbuf.mBuffer, strlen(data) + 1);
cs

그다음 줄인 memcpy함수입니다. memcpy는 메모리를 카피(복사)해주는 함수이며 해석해보면 textbuf.mbuffer를 RESdata에 복사한다. 단! 길이는 strlen(data) + 1 (정수)까지 이다. 라는 의미입니다. 즉 textbuf.mBuffer의 내용이 RESdata에 그대로 복사된다는 겁니다. 뒤에 + 1해준 이유는 strlen함수가 널을 포함하지 않아 + 1해줬습니다. 굳이 이렇게 안짜도 1~2줄 줄이기는 가능합니다. data라는 포인터 변수는 strlen함수를 쓰기 위함이었고 앞으로 작업해야할 데이터는 RESdata배열에 담겨 있는겁니다.

1
BYTE * mBuffer = new BYTE[5000000];
cs

BYTE라는 클래스(?)가 있으며 변수 선언과 같은 존재입니다. 즉 배열인데 길이가 5000000입니다. 이때 BYTE라는건 unsigned char(MSB부호를 값표현으로 쓰며 음수는 넣을 수 없으며 양수의 범위가 늘어납니다.)형과 마찬가지입니다. 

그러면 왜 unsigned char로 선언을 하였냐? 인데요.바로 그 이유는 사용할 암호화 알고리즘인 AES 알고리즘에 있는데요. AES 알고리즘은 0부터 255까지 랜덤이기때문에 그냥 char로 써버리면 초과됩니다. 그래서 범위가 255인 Unsigned Char를 선언한 이유입니다. 그리고 이 mBuffer라는 배열은 암호화된 코드를 복호화 시킨 값을 담을려는 목적으로 선언된 배열입니다.

1
BYTE Key[] = { 0x2b0x7e0x150x160x280xae0xd20xa60xab0xf70x150x880x090xcf0x4f0x3c };
cs

AES알고리즘의 특징인 키가 있어야 키를 바탕으로 암호화/복호화를 한다는겁니다. 즉 이 키가 없거나 틀리면 생각하지 못한 값들이 나오게 됩니다. 즉 의도치 않은 값이 나온거죠. 그래서 중요합니다. Key라는 배열의 길이는 총 16이며 0~255까지 마음대로 쓰시면 됩니다. 문고리와 열쇠로 비유를 한다면 복호화가 문고리문고리 열쇠가 키입니다. 잠겨있는 문을 키가 없으면 못열잖아요? 그거랑 마찬가지 인겁니다.

1
memset(mBuffer, 0sizeof(mBuffer));
cs

memset함수는 메모리 공간에 할당된 것을 전부다 초기화 시킨다고 보시면됩니다. memset함수를 해석하면 mBuffer배열을 0으로 초기화 시킨다. 단! sizeof(mBuffer)만큼만. 왜냐하면 메모리 공간에는 10까지 할당했는데 100이라고 써주면 할당된 메모리 공간의 밖을 침범하므로 런타임에러나 심각한 문제가 생기게 됩니다. 즉 이 함수는 복호화된 코드를 담을 배열을 0으로 초기화 시킨다고 보시면 됩니다.

1
2
3
4
5
BYTE arr[5000000= { 0 };
.
.
.
int i = CipherToInt(RESdata, arr);
cs
CipherToInt라는 함수는 제가 직접 만든 함수이고,RESdata의 담긴 문자열을 정수로 바꿔 arr라는 배열에 담습니다. 컴파일 단계에서 문제였던건 문자열화 하지 못하여 암호화된 정수 값 하나만 리소스 상에 올라간다는겁니다.그래서 컴파일 단계에서 정수 -> 문자열화 하여서 리소스 공간에 올려줬고 마찬가지로 실행 단계에서 문자열 -> 정수로 바꾸는 과정이 필요합니다. 이 과정이 CipherToInt함수를 통해 해결합니다.
i라는 변수는 반복한 횟수를 반환하여 복호화할때 쓰일 예정입니다.
1
AES_ECB_Decrypt((LPCBYTE)arr, Key, mBuffer, i);
cs
이 함수는 와,문자열->정수로 변경된 배열,복호화된 코드를 담을 배열,길이를 필요로 합니다.암호화/복호화 컴파일러에서 복호화에 해당하는 함수입니다. arr는 복호화할 코드가 담긴 배열이고 Key는 복호화에 쓰일 키 mBuffer는 복호화된 코드를 담을 배열이고 i는 길이입니다.
1
textbuf.mBuffer = mBuffer;
cs
AES_ECB_Decrypt함수를 통해 복호화된 코드를 사용해야합니다. textbuf.mBuffer에 복호화된 코드를 집어넣는 과정입니다.
1
2
3
4
5
    TextMem tmem, *fp = &tmem;
    // NOTE: Ahk2Exe strips off the UTF-8 BOM.
    tmem.Open(textbuf, TextStream::READ | TextStream::EOL_CRLF | TextStream::EOL_ORPHAN_CR, CP_UTF8);
#endif
    textbuf.mBuffer = NULL;
cs

마지막 5번째 줄에서는 화면에 안나왔습니다. 일단 내버려 두고 타입이 TextMem인 변수를 선언,그리고 타입이 TextMem인 포인터 변수를 선언합니다. 그리고 선언과 동시에 tmem 변수의 주솟값을 반환하여 포인터 변수 fp에 대입합니다. fp는 밑에서 쓰는것으로 생각되며 TextMem 클래스에  Open이라는 메서드가 있는데 이 메서드를 통해 프로그램이 돌아가도록 하게 합니다. textbuf.mBuffer = NULL;은 이제 이코드는 의미없으니 NULL시켜줘서 디버깅에서 최소한이라도 범위를 줄이고자 했습니다.

암호화된걸 어떻게 뚫는다는거에요?

아까 말했던걸 답하겠습니다. 우선은 모든 변수와 객체생성된 변수는 메모리 공간에 할당됩니다.메모리 공간에 할당되면 디버거나 치트엔진을 통해 볼 수 있으며 이걸 못보게 할순 없습니다. 다만 탐지하기 어렵게는 가능합니다. 디버거는 신(GOD)과 같은 존재이며 막을수는 없습니다.그래서 메모리 공간에 올라가면 찾으면 다 보인다는겁니다. 근데 찾기 어렵습니다.(물론 고수는 다릅니다) 

그래서 이구간을 디버거나 치트엔진을 통해서 찾기만 하면됩니다. 암호화/복호화(겉)은 끝났으니 이제는 어떻게든 찾기 힘들게 해야합니다.(안)은 방법으로 Anti-Debugging을 통해 아예 탐지되면 꺼버리게 하거나,FindWindow로 찾아서 디버거가 보이면 프로그램이 종료되게 하시면 됩니다. 일단 메모리 공간에 올라가기만 하면 무조건 볼수있으며 가장 중요한건 이걸 찾는겁니다.

마치며

고수분들이 보시기엔 딱히 쓸모없는 코드가 1~3줄정도 들어갑니다. 이걸 암호화/복호화 라고 말해도 가능한 수준인지 모르겠습니다. 먼저 이걸 보시고 궁금증이나 물어보고 싶은게 있으시면 댓글로 써주시면 감사하겠습니다. 아까도 말씀 드렸다 시피 수정한 모든걸 배포할려고 했지만 분명히 악용 해서 판매하시는 분이 있을 것 같아 다른분의 의견을 참고하여 중요한 Script.cpp,AHk2exe.ahk만 배포할 계획입니다. 주말이 오기까진 아직 남았지만 화이팅 하시고 열심히 찾아보시면 충분히 가능하실겁니다.(msdn 참고)

원래 #2편으로 마무리를 할 예정이었지만 #3편은 암호화/복호화와 딱히 상관없는 번외편을 준비 하도록 하겠습니다. 이 번외편의 내용은 저의 오토핫키를 사용한 계기와 지금까지, 그리고 지금 이후로는 어떻게 하고있는지 왜 이런걸 알려주는지에 대해 말씀드리도록 하겠습니다. 이때까지 어렵게 쓴글 봐주셔서 감사합니다. 하루 좋게 마무리하세요

다운로드는 https://github.com/zxc010613/Ahk2Exe-encrypt-and-Script.cpp-Decryption- 깃허브를 통해 다운받으세요.


댓글