오토핫키(Autohotkey L)동작 구조와 코드 노출에 대하여 #1

프로그래밍/Autohotkey 2017.09.17 댓글 Plorence

오토핫키를 한번쯤 해보고 배포도 해보신분이라면 알겁니다 내가 작성한 코드가 노출 된다는 겁니다.

오늘은 제가 시도한 난독화와 난독화 도중에 파악한 구조를 알려 드리고자 합니다. 설명이 부족하거나 어렵게 서술한 부분도 있을터이니 모르는 부분은 질문해주시면 감사하겠습니다 지적도 해주시면 감사하겠습니다.

제목에 #1 라고 쓰여 있는데, 내용이 길어질까봐 두 개로 나눠서 작성할 예정입니다.

시작에 앞서

저는 오핫을 제외한 C,JAVA(ANDROID) 정도만 다뤘으며, C++은 거의 모릅니다. 제가 틀린부분이 있다면 지적 해주시면 감사하겠습니다. 또한 제가 난독화 도중에 파악한 원리와 구조는 틀릴수도 있습니다. 오토핫키를 쓰시는분에게 조금이라도 도움이 되시고자 하여 작성하게 되었습니다. 또한 오토핫키가 어떤 방식으로 돌아가는지 궁금하시는 분들이 종종 계셔서 도움을 드리고자 합니다. 오토핫키 동작 구조라고 말해도 되는지 모르겠습니다.

오토핫키의 구조

구조 입니다. 난독화 하면서 가장 중요하게 생각했던건 오토핫키의 동작 구조 입니다. 그래야 난독화를 하든 뭘하든 할꺼니까요. 일단 오토핫키 L버전 프로그램에서 리소스 뷰어를 통하여 리소스를 보겠습니다. 오토핫키 프로그램 코드는 리소스 라는 곳에 보관됩니다. 리소스가 뭔지는 아래에 설명 해드리겠습니다. 

1
2
3
4
5
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
msgbox,잘보이나요?
cs

일단은 테스트를 위해 메세지박스를 띄우는 코드를 작성하였습니다. 리소스 뷰어로 확인해보면 이 코드가 그대로 보입니다. 단 주석처리된건 컴파일 도중에 코드에 포함시키지 않기 때문에, 주석은 보이지가 않습니다. 

잘보이시나요? 아까도 말했듯이 코드가 리소스에 올라갑니다. 리소스는 프로그램의 기본적인 데이터를 저장하는 공간으로 해석하셔도 됩니다. 프로그램 실행될때에는 리소스에 있는 코드를 통해 작동됩니다. 

그리고 왼쪽에 Icon Image,menu,Dialog,RC data,Icon Directory 등등 리스트가 보입니다. 이 리스트는 컴파일 도중에 코드와 같이 올라간 애들입니다. 프로그램의 아이콘이나,오토핫키 버전 등이 함께 올라가게 됩니다. 그중에 RC DATA라는 곳에 코드가 위치하여 있습니다.

그러면 코드를 보기 위해서는 RC DATA라는 곳에 접근해야합니다. RC DATA는 프로그램을 짤때 FileInstall한 파일들이 RC DATA에 함께 올라가게 됩니다. 예를들어서 FileInstall NanumGothic.ttf,% FileName 이라는 코드가 있을경우에 NaNumGothic.ttf 파일이 RC DATA에 올라가게 됩니다. 그리고 이 파일이 없을때는 리소스에서 내려받아 설치하는 식입니다. 물론 설치 하느냐 안하느냐는 개발자가 짠 코드에 따라 달라지게 됩니다. 

이젠 리소스에 대해 대충 알았으니 AHK 파일을 EXE 실행 프로그램으로 바꿔주는 오토핫키 컴파일러를 확인해보겠습니다. 우선 오토핫키 L버전 컴파일러는 오토핫키로 짜여져 있습니다. 다른 버전은 아마 C++로 짜여져 있는걸로 알고있습니다. 컴파일러는 공식 홈페이지에서 다운받아주시면 됩니다.(http://autohotkey.com/)

 우선 리소스를 올리기 위해서 어떤 함수를 사용하는지 알아야합니다. 그래야 리소스를 올리는 구간을 파악할수 있기 때문입니다. 

WINAPI UpdateResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648049(v=vs.85).aspx)

WINAPI BeginUpdateResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648030(v=vs.85).aspx)

WINAPI EndUpdateResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648032(v=vs.85).aspx)

위 3개의 함수들은 윈도우에서 제공하는 API이고 오토핫키에선 직접 함수를 호출할수 없습니다. 그래서 DllCall을 통하여 함수를 호출해야합니다. 위 3개의 API함수를 DllCall하는 함수를 찾으면 됩니다. 하지만 EndUpdateResource함수는 호출 안합니다. 처음 올리는거라 안해도 되는듯 합니다.

확인해보면 최종적으로 BundleAhkScript함수에서 BeginUpdateResource,UpdateResource를 호출하고 있습니다. 그래서 이 부분을 확인해봐야 합니다. 

BundleAhkScript함수를 보면 BeginUpdateResource,UpdateResource를 호출 하고 있습니다. BeginUpdateResource함수는 전달해야하는 인자가 2개 뿐이니 간단하게 설명 하겠습니다.

 BeginUpdateResource함수는 첫번째 전달 인자의 프로그램의 핸들값을 반환합니다.리소스를 바꾸거나 올릴려면 해당 경로에 있는 프로그램의 핸들값을 받아와야 합니다. 첫번째 전달 인자가 바로 프로그램 이름을 포함한 경로입니다. 아래의 코드를 봐주시면 됩니다. 두 번째는 신경 안쓰시고 전달인자로 false를 전달하면 됩니다.

1
2
module := DllCall("BeginUpdateResource""str", ExeFile, "uint"0"ptr")
;ExeFile이라는 변수는 컴파일할 프로그램의 경로 입니다. 예)바탕화면\닭\오토핫키.exe
cs

먼저 위 함수가 전달하는건 간단하게 설명 드렸습니다. 리소스를 올리거나 바꿀려면 해당 프로그램의 핸들값을 받아와야 하는데,바꾸거나 올리기 위해 BeginUpdateResource함수를 호출하였습니다. 그래서 Dllcall하여 프로그램의 핸들값을 반환합니다. 실제로 module 이라는 변수에 프로그램의 핸들값이 존재합니다. 이제 이 핸들값을 통해 리소스를 조작하게 됩니다.

1
2
3
if !DllCall("UpdateResource""ptr", module, "ptr"10"str", IcoFile ? ">AHK WITH ICON<" : ">AUTOHOTKEY SCRIPT<"
"ushort"0x409"ptr",&RESdata, "uint",Len, "uint")
;UpdateResource는 성공과 실패에 따라 0 또는 1을 반환합니다.
cs

사실상 파악하기위해선 오토핫키 컴파일러 코드에서 위 함수 2개를 제외하곤 안봐도 됩니다만 리소스외에 컴파일러를 수정하고 싶으시다면 확인해보셔야 합니다.

UpdateResource함수의 첫 번째 전달 인자 : module은 아까 BeginUpdateResource를 통하여 반환하였습니다. 즉 전달해야 할것은 핸들값입니다. 이 핸들값을 가지고 있는 프로그램을 조작합니다.

UpdateResource함수의 두 번째 전달 인자 : 전달 인자로 10을 썼는데 이 10은 MAKEINRESOURCE 매크로로 전달한겁니다. 즉 MAKEINRESOURCE(10)인데요 10은 RT_RCDATA 를 의미합니다. 어디에 올릴건지를 정하는 리소스 타입인거죠.

UpdateResource함수의 세 번째 전달 인자 : 삼항 연산자 같은데, 간단하게는 RCDATA에 올리는데 데이터를 저장한 이름을 정하는겁니다. 변수와 똑같습니다. 무언가 저장할려면 그 저장할 공간의 이름이 필요한데 그게 바로 이 부분 입니다. var = 10처럼 >AUTOHOTKEY SCRIPT< 라는 공간에 코드가 저장되게 됩니다. 그래서 리소스를 올릴때 정할 이름입니다.

UpdateResource함수의 네 번째 전달 인자 : 이거는 코드의 언어 형태인것 같은데,그렇게 중요한건 아닌것 같습니다. 0x409는 두 번째 전달인자와 마찬가지로 MAKEINID 매크로 입니다. 0x409은 영어로 알고있습니다.

UpdateResource함수의 다섯 번째 전달 인자 : 가장 중요합니다. 리소스에 올라갈 데이터입니다. 저는 컴파일러를 수정해서 &RESdata이지만,원래는 &BinScriptBody가 맞습니다. 실제로 BinScriptBody에 코드가 담겨있습니다. 궁금하시면 메세지박스로 출력해서 확인해보세요.

UpdateResource함수의 여섯 번째 전달 인자 : 리소스에 올라갈 데이터의 길이 입니다. 길이 만큼만 리소스에 올립니다. 만약에 데이터의 길이는 1000인데 100 만 전달해줬으면 나머지 900은 잘려서 올라가질 않습니다. 저것도 원래 Len이 아니라 BinScriptBody_Len이 맞습니다.

1
VarSetCapacity(BinScriptBody, BinScriptBody_Len := StrPut(ScriptBody, "UTF-8"- 1)
cs

마지막 "uint"는 DllCall해서 함수의 반환값의 타입인데 아까 0 또는 1을 반환한다고 했으니 정수겠죠? 반환형은 Unsigned int, 줄여서 Uint입니다. 신경 안쓰셔도 됩니다.

정리 해보면

"따로 수정을 안했다면 리소스 RCDATA -> ">AUTOHOTKEY SCRIPT<" 라는 곳에 코드가 올라가 있고,코드를 올리기 위해선 BeginUpdateResource함수와 UpdateResource함수가 필요하다"

이제 컴파일러를 통하여 어떻게 리소스가 올라가는지 확인하였습니다. 이제는 프로그램에서 어떻게 확인하는지 내려받는지 확인하겠습니다. 마찬가지로 공식 홈페이지(https://autohotkey.com/)에서 다운로드 하시면됩니다. 아마 깃허브로 이동할겁니다

지금 빨간줄 쳐진 파일이 오토핫키 프로젝트를 열수 있는 파일입니다. 옆에 Microsoft Visual Studio Solution이라고 친절하게 나와있습니다. 저는 비주얼 스튜디오 2017으로는 빌드 실패 하길래 비주얼 C++ 2010과 비주얼 스튜디오 2015을 설치하였습니다. (둘 중에 아무거나 쓰셔도 상관없습니다만 비주얼 스튜디오 2015를 추천드리겠습니다.)

먼저 실제로 어떻게 가져오는지 파악하기 전에 알아야 하는 함수들입니다. 함수에 대한 설명은 밑에서 하도록 하겠습니다. 실제로 가져오는 부분은 Script.cpp파일에 위치하여 있습니다. 해당 프로그램에서 올라간 리소스를 가져오는 함수는 총 4개로 이루어져 있으며, 마찬가지로 4개 다 서로 연관성을 가지는 함수들입니다.

WINAPI FindResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648042(v=vs.85).aspx)

WINAPI SizeOfResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648048(v=vs.85).aspx)

WINAPI LoadResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648046(v=vs.85).aspx)

WINAPI LockResource(https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms648047(v=vs.85).aspx)

4개의 함수중에 가장 먼저 해야할 일은 FindResource함수로 핸들값을 가져오는겁니다. BeginUpdateResource함수와 비슷합니다.

그러니 CTRL+F를 통하여 FindResource를 검색하고 일치하는 부분이 바로 리소스 데이터를 가져오는 중요한 부분입니다.

먼저 ">AHK WITH ICON<"이 아닌 그 위에있는 FindResource를 기준으로 알려드리겠습니다.

1
if (hRes = FindResource(NULL, _T(">AUTOHOTKEY SCRIPT<"), RT_RCDATA))
cs

FindResource함수의 첫번째 전달 인자는 프로세스의 모듈값을 의미하는데 NULL이면 자기 자신의 모듈값입니다. 그다음은 두 번째 전달인자는 데이터타입안에 어떤 데이터이름인지 입니다. 세 번째 전달 인자는 RT_RCDATA인데 이러면 RCDATA안에 ">AUTOHOTKEY SCRIPT<"의 핸들값을 가져옵니다. 실패시 NULL를 반환합니다. 앞으로 SizeOfResource,LoadResource,LockResource함수에서 중요하게 쓰일 함수입니다.

1
textbuf.mLength = SizeofResource(NULL, hRes)
cs

SizeOfResource함수의 첫번째 인자 전달은 beginUpdateResource함수의 반환값을 의미하는것 같은데 NULL면 자기 자신인가 봅니다. 두 번째 전달인자는 FindResource함수의 반환값인 핸들값을 전달합니다. 이 함수는 리소스의 길이를 반환합니다.

1
hResData = LoadResource(NULL, hRes)
cs

LoadResource함수의 첫번째 인자 전달은 SizeOfResource와 마찬가지입니다. 두 번째 전달인자는 FindResource함수의 반환값인 핸들값을 인자로 전달하면 됩니다. 이 함수는 리소스를 로드하는 함수입니다 실제 포인터가 반환됩니다.

1
textbuf.mBuffer = LockResource(hResData)
cs

LockResource함수의 첫번째 인자 전달로 LoadResoucre함수의 반환값을 전달 하면됩니다. 이 함수는 리소스의 실제 포인터를 리턴합니다. 그래서 문자열의 첫번째 주소값을 리턴하게되고 그게 textbuf.mBuffer에 저장하게 됩니다. 실제로 textbuf.mBuffer의 값을 메세지 박스로 출력해보면 코드가 보입니다.

mBuffer또는 mLength를 우클릭해서 정의로 이동(G)을 해보시면 TEXTIO.cpp에 위치하여 있습니다. TEXT는 말 그대로 TEXT고 IO는 I는 input(입력),O는 output(출력)을 의미하는것 같습니다.

이때까지의 과정을 그림으로 그려서 표현해봤습니다. 사용한 함수는 제외 했으며 해당 게시글에서 다룬 함수는 총 6개입니다. (EndUpdateResource함수는 제외) 리소스에서 코드를 가져와서 동작하는 구조라 SizeOfResource,LoadResource,LockResource 3개중에 1개만 실패해도 프로그램 동작이 불가능합니다. 빌드 방법은 저번에 올렸던 뮤텍스 수정 방법을 참고해주시면 됩니다. http://plorence.kr/226


알고리즘 순서도는 여기서

마치며

이렇게 길게 쓴 글은 아마 처음이 아닐까 싶습니다. 맞춤법 틀린 부분이 있으면 알려주시면 감사하겠습니다. 부족한 설명 보셔서 감사하고 수고하셨습니다. 이해가 어려우신분은 댓글로 달아주시면 답변 해드리겠습니다. 다음은 난독화에 대해 설명 해드리겠습니다.


댓글