1.SandBox분석
먼저 SandBox 분석 결과를 확인해보자.
1개의 http Post request가 확인된다. 현재 C&C서버가 죽어 있는 상태이므로 response는 확인되지 않는다. Process 정보를 보면 아래와 같다.
현재 분석대상파일인 po no5532.bin이 실행되면 다시한번 po no5532.bin이 실행되는 것을 확인할 수 있다.
먼저 앞서서 실행되는 po no5532.bin process의 상세 정보를 확인해보자.
해당 프로세스에서 "C:\Users\admin\AppData\Local\Temp 경로의 PO NO5532.bin.exe 파일을 실행하는 것을 확인할 수 있다.
해당 process가 load하는 다음 PO NO5532.bin.exe process의 정보도 확인해 보자.
현재 4종류의 danger와 2종류의 Warning이 감지되는데 Lokibot이 탐지되는 것을 알 수 있고 info stealer 계열의 악성코드인 만큼 personal data나 credential을 steal하는 것을 확인 할 수 있다.
Modified File section을 보면 총 두개의 event가 발생했다. 두 event모두 dropped from process 종류이다. MD5값이 같은 것으로 보아 같은 file이며 정확하게 어떤 역할을 하는지는 정확하게 확인이 불가능 하다.
다음으로 registry change section에서 registry값의 변화를 확인할 수 있다. Cache와 cookie History에 접근하는 것을 확인할 수 있다.
앞서 확인했던 dropping부분을 상세히 확인해보자.
C:\Users\admin\AppData\Local\Temp\nsk8470.tmp 경로에 59qv0p.dll을 파일을 드랍하고 해당 dll로부터 PO NO5532.bin.exe를 실행하는 것을 알 수 있다.
2.행위분석
Process monitor툴을 이용해 분석 대상인 PO NO 5532.exe file을 실행한 log이다. 앞서 SandBox 분석에서 확인 한 것처럼 parent-child process로 PO NO5532.exe가 두개 생성된 것을 확인 할 수 있다.
위 사진은 PO NO5532.exe의 캡처 된 동작의 일부분이다. RegOpenKey,RegQueryKey등 registry에 접근하기 위한 동작들과 CreatFlie, ReadFile과 같은 파일 생성에 관여되는 동작들을 확인 할 수 있다.
QueryNetworkOpenInformationFile Operation을 통해 network 상으로 file을 전송하는 것 역시 확인 가능하다.
3. 정적분석
먼저 Exeinfo PE툴을 이용해 패킹 여부 등 여러 정보들을 확인 가능하다. EP section에 .text로 나타나는 것으로 보아 패킹 되지 않았음을 알 수 있다. 32bit exe파일임도 확인 가능하다. 또한 NSIS를 통해 빌드 된 것 역시 확인 가능하다.
패킹 여부는 pe view 툴을 이용하는 방식으로도 확인 가능하다. IMAGE_SECTION_HEADER를 확인하면 패킹 여부판단에 필요한 값들을 확인할 수 있다.
Virtual Size와 Size of Raw Data의 크기를 비교하면 패킹 여부를 판단할 수 있는데 만약 Virtual Size가 Size of Raw Data의 크기가 월등히 크다면 패킹의 징후라고 판단할 수 있다. 위의 경우 약간의 차이는 있으나 유의미한 정도의 차이는 아니므로 패킹이 되지 않았음을 알 수 있다. 약간의 차이가 발생하는 이유는 하드디스크의 offset 구조에서 메모리 영역으로 로딩되면서 NULL Padding의 크기 차이가 발생하기 때문에 약간의 차이는 발생할 수 있다.
4.동적분석
먼저 x32에 분석 대상파일을 올리면 초기에 ntdll.dll에서 시작하는 것을 알 수 있다. 따라서 해당부분을 넘기고 실제 분석대상 파일이 동작을 시작하는 부분인 0x0040328E부분에 break point를 걸자. 해당 부분은 hex ray 상에서 start로 자동적으로 잡히는 것을 확인 할 수 있다.
또한 pe view에서 IMAGE-OPTIONAL-HEADER에서도 Image Base와 Address of Entry Point의 합을 통해 확인이 가능하다.
Hex ray 상에서 동작을 확인해보자.
Start(0x0040328E)
초기 동작을 보면 함수의 프로로그가 지나가고 register가 세팅된 뒤 SetErrorMode함수와 GetVersion함수를 호출하는 것을 확인가능 하다. 해당 부분을 IDA의 hex ray를 통해 확인해보자.
위의 c코드를 보면 push를 통해 변수들에 필요한 값들을 세팅 해주고 SetErrorMode함수와 Getversion함수의 return값을 통해 if문에 넣기 위한 세팅들을 확인할 수 있다.
SetErrorMode함수는 잘못된 연산등의 메시지를 관리하는 함수이다. 인자값의 자세한 정보는 아래와 같다.
다음으로 GetVersion함수의 return 값이 6이 아닌경우에 if문 내부 로직이 실행되는 것을 알 수 있다. X32상에서 실행시켜보면 cmp ax 6 에서 ZF가 1로 세팅되지 않는것으로 보아 if문이 실행될 것으로 예상된다. If문 에 들어가면 sub_4060D3함수가 실행된다. sub_4060D3함수를 살펴보자.
sub_4060D3
해당 함수를 보면 GetModuleHandle과 GetProcAddress함수를 확인할 수 있다. GetModuleHandle 함수는 실행파일(exe, dll 등)의 Module값을 가져오는데 사용하는 함수이다. 현재 해당 함수의 인자로 off_409228 배열의 0번 index를 인자로 넣어주는데 해당 값을 x32로 확인해보면 KERNEL32가 들어가는 것을 확인할 수 있다. 해당 인자 값에 확장자가 주어지지 않은 경우 자동으로 .dll로 인식한다. 해당 함수가 실행되면 v2에 kernel32.dll의 handle이 반환된다
이후 if문으로 특정 조건을 만족할 경우 GetProcAddress함수를 호출한다. 해당 함수를 호출하기 위해 앞서 v2에 저장된 kerenl32의 handle과 off_40922C[0]의 값이 인자로 세팅되어 GetProcAddress함수가 호줄된다.
인자로 세팅 되는 값은 위와 같은데 GetProcAddress함수가 실행되면 kernel32.dll에 이는 SetDefaultDllDirectories의 함수의 주소 값이 반환된다.
따라서 sub_4060D3함수는 dll 내부의 API의 address를 가져오는 함수임을 알 수 있다. 따라서 GetAPIAdrdress로 lableing하겠다.
이후 if문이 하나 더 있는데 v0값이 0이 아니기 때문에 실행되어야 할 것 같은 로직인 것 같은데 실행되지 않고 넘어간다
이후 do-while문을 통해 sub_406065함수가 호출되며 v1을 인자로 준다. v1은 "UXTHEME"로 문자열로 세팅 되어있다. 이후 lstrlenA함수를 통해 멀티 바이트 문자열의 길이를 구하는 로직을 수행한다. sub_406065함수를 살펴보자.
sub_406065
GetSystemDirectoryA함수를 호출하여 Buffer 배열에 system Directory의 path를 넣어준다. V1에 들어가는 반환 값은 buffer에 들어간 값의 길이가 반환된다. v1이 0x104보다 크다면 즉 GetSystemDirectory에 인자로 준 배열 크기보다 크다면 v1을 0으로 세팅한다. v2는 bool 변수인데 v1이 0이 아니거나 복잡한 연산을 한 값이 92라면 참을 대입하고 아니면 거짓을 대입한다. 이후 wsprintfA함수를 호출한다.
해당 로직이 실행되고 나면 eax에 eax:"C:\\Windows\\system32\\UXTHEME.dll"이 세팅 되는데 해당 값이 buffer에 저장된 값이다. 이후 LoadLibraryExA함수가 호출되고 해당 함수의 return값이 반환된다. LoadLibrary 함수는 지정된 dll을 현재 process에 loading시키고 HMODULE을반환한다. 따라서 해당 함수가 실행되면 return되는 값은 UXTHEME.dll의 handle이다. 또한 가운데 인자는 반드시 null로 설정되어야 하며 8은 아래와 같은 내용을 가진다.
해당 로직은 do-while문 형태이므로 UXTHEME.dll이후에도 다른 여러 dll들에 대한 handle을 얻어온다. 목록은 아래와 같다. 따라서 sub_406065함수는 GetDllHandle로 labeling할 수 있다.
이처럼 lstrlenA함수이후 1을 더해주는 이유는 .을 이용해 dll 명들을 구분하기 위한 값으로 보인다.
이후 sub_4060D3함수를 호출하는 루틴이 두 번 보인다.
앞서 설명하였듯 해당함수를 실행시키면 특정 dll에 대한 handle을 얻어와서 내부의 특정 함수의 address를 얻어온다. 해당 함수의 인자로 현재 13과 11이 들어가는데 앞서 0인 경우 kernerl32.dll에 대한 handle을 얻어왔다. 13이 들어간 경우에는 version.dll에 GetfileVersionInfoA함수에 대한 handle을 얻어오는 것을 확인 할 수 있다.
또한 11의 경우 아래와 같다
이후 SHGetFolderPathA의 주소를 dword_42EC64에 저장하고 InitCommonControls함수를 호출한다 InitCommonControls함수는 common control dll을 로드하기 위한 함수이다.
해당 함수를 지나고 나면 dword_42ED18변수에 OleInitialize함수에 인자 0을 주어 호출해 return값을 저장한다. OleInitialize 함수는 OLE객체에 접근하기 위해 사용하는 사전 준비 역할을 하는 함수이다. 이후 SHGetFileInfoA 함수에 pszPath,0등의 인자를 주어 함수를 실행시킨다. OleInitialize함수는 아래와 같은 기능을 이요하기 위해 사용한다.
위 push 명령들이 SHgetFileInfoA함수의 인자 값들이 세팅 되는 부분이다. 위 두번째 사진은 스택에 세팅된 인자값들이다.
먼저 SHGetFileInfo함수는 file system 내부의 file, folder, directory등을 검색해 정보를 검색해주는 함수이다. 현재 3번째 인자 &psfi에 file information을 반환해주는데 해당 함수가 실행되고 난 이후 0x0018FE2C를 확인해보면 아무것도 들어있지 않다. 따라서 일단은 넘어가도록하자.
이후 sub_405D43 함수를 호출한다.
Sub_405D43함수를 확인해보면 lstrcpyn함수를 호출해 lpstring1이 destination buffer이고 lpstring2가 source string임을 알 수 있다. 함수 이름에서도 알 수 있듯 String을 복사해주는 함수이다. 따라서 그냥 lstrcpyn함수로 labeling하겠다. 총 두 번 호출되고 각각의 인자 값이 다른데 aNsisError는 단지 “NSIS error” 문자열으르 chText에 복사한다. 두번째 호출에서는 &sz에 v2의 값을 복사해준다. V2에는 GetComandLine의 return값이 저장될텐데 해당 함수는 현재 process에서 command-line string을 검색하는 함수이며 return값은 현재 process에 대한 command-line string을 pointer형태로 반환해준다. 따라서 sz에 command-line string이 담겨 있을 것이다.
따라서 lstrcpyn함수가 실행되면서 아래와 같이 string이 저장된다.
이후 hInstance에 GetModuleHandle 함수의 반환값을 넣고 앞서 가져온 commandLineString을의 주소값을 v3변수에 넣어준다. GetModuleHandle의 reference는 아래와 같다.
따라서 hInstance에는 GetModuleHandle함수의 인자값이 0이므로 exe file의 calling process를 만들기위한 것으로 보인다.
이후 commandLineStirng이 34인 경우 if문 내부로 들어가 v18에 34를 대입하고 v3에 &unk_434001을 넣는데 이때 들어가는 값은 아래와 같다.
현재 분석 대상파일이 존재하는 경로가 세팅되어 v3에 들어간다. 원래 v3에 commandLineString이 존재 했는데 사실 동일한 값이 들어간다.
이후 sub_405861함수가 호출되며 해당함수의 return을 다시 CharNext()에 넣어 v5변수에 넣고 v5를 lpString2변수에 저장한다.
Sub_405861
해당 함수에서는 단지 앞서서 받은 인자 두개를 기반으로 연산을 진행하는데 현재 인자로 들어가는 lpsz에는 "C:\\Users\\smlij\\Desktop\\PO NO5532.bin\""값이 a2에는 0x22가 들어가있을 것이다. result변수를 lpsz로 초기화하고 증감식의 경우 CharNext()를 통해 string내부에서 다음 character에 대한 pointer를 반환하는 형태이다. 조건식은 *result가 0이 아니고 0x22(")가 아닐때 까지 연산을 진행한다. 아마 문자열 맨끝 부분으로 포인터를 옮기는 함수인것 같다. 따라서 MoveStringPointer라고 labeling하겠다.
이후 v5에 v4를 인자로 주어 CharNext()를 호출하는데 위에서 본것처럼 v4가 문자열 맨끝을 가르키는 포인터이기 때문에 v5는 null이있는 주소를 가르키는 포인터가 될것이다.
이후 while문에 걸리는데 *v5가 0일경우 goto 문을 통해 LABEL_26으로 이동한다. 앞서 말한것 처럼 CharNext(v4)를 통해 v5는 null을 가르키는 포인터이므로 여기서 if문 내부로 들어갈것이다. 현재 v5는 0x00434026을 가르키고 있다.
Cmp cl,bl을 통해 jne 문에 걸리는 데 둘다 0으로 세팅되어있기 때문에 무시하고 jmp 문이 실행되는데 여기가 goto LABEL_26이다. 따라서 LABEL_26을 확인해보자.
Line 95번줄에서 임시파일의 path를 알아내는 것을 알 수 있다. GetTempPath의 인자와 return은 아래와 같다.
따라서 0x400은 PathName에 들어갈 크기이고 lpBuffer인 PathName에는 temporary file path가 담기게 된다.
x32상에서 위 코드를 확인할 수 있었다.
해당 함수 GetTempPath()가 실행되면 레지스터에 임시파일의 경로가 저장 되어있는 것을 알 수 있다. 아마 악성코드를 drop하거나 악성행위 동작을 위해 컴퓨터의 경로가 어떤 형태인지 확인하기 위한 로직인 것 같다.
GetTempPath함수 이후 if문 로직에서 sub_40325D함수 및 GetWindowsDirectory함수를 이용하는 것을 볼 수 있다.
if 문에서는 sub_40325D()가 True이거나 GetWindowsDirectory()로 PathName에 새로운 값을 할당하고 lstrcat을 통해 PathName+String2를 하고 다시 sub_40325D()를 호출해 True인 경우 if문 내부 로직으로 넘어간다. lstrcat을 통해 String2를 이어붙이는데 이는 “Temp”임을 확인 할 수 있다.
그리고 다시 sub_40325D()가 실행된다. 실제로는 if문 내부 로직에서 or 연산이기 때문에 분석환경에서는 GetWindowsDirectory() 이용해 새롭게 PathName을 세팅하지 않아도 sub_40325D자체가 True이기 때문에GetWindowsDirectory는 실행되지 않고 넘어간다.
sub_40325D함수를 살펴보자.
sub_405FA5(PathName)이후 result에 sub_4058A3()의 return을 대입하고 result가 0이 아닐 경우 sub_405836(),sub40556E를 호출한 후 sub_405A49에 FileaName과 PathName을 넣어 result를 return 한다.
sub_40325D()에서 호출하는 함수들이 많은에 전부 하나씩 차례대로 보도록하자.
sub_405FA5()
먼저 v1에 인자로 들어온 lpsz 즉 PathName을 대입해준다. 그리고 lpsz에 들어있는 문자열을 검사하는데 \\\\?\\ 형태가 되어야 if문 내부로 들어갈텐데 앞서서 x32를 통해 PathName에 temp file path가 정상적으로 세팅되는 것이 확인되었기때문에 해당 if문은 넘어간다. 그리고 또 PathName이 들어있는 v1을 인자로 주어 sub4058A3()을 호출하는데 이함수도 확인해보자.
sub4058A3()
인자로 들어온 값인 a1 즉 43(c)과 을 32와 bit or 연산을 진행하면 v1에 99가 들어갈것이다. 이후 return하게 되는데 99는 97(a)보다 크고 99(z) 보다 작고 현재 a[1]은 PathName의 두번째 index인데 PathName이 C:\\user~~~와 같은 형태이기 때문에 True를 return할것으로 예상된다.
이후 if문 내부로 들어가 v1=+2를 수행하면 포인터가 옮겨가 C:\\부분에서 앞쪽 \를 가르키게될것이다. 이후 v2변수가 "\"를 가르키고 v3에 "\"의 주소를 대입해준다.
그리고 for문으로 들어가는데 2증가한 PathName이 초기화식 부분에 들어가고 조건식은 v2 증감식은 v2=*v6이다. 그리고 if문을 통해 v2가 0x1F보다 크고 앞서 확인했던 MoveStringPointer()에 인자를 주고 참이 아닌경우에 if문 내부로 들어가는 것을 확인할 수 있다. 인자로 들어가는 asc_409410을 확인해보면 아래와 같다.
또한 ascii talbe을 확인해보면 0x1F까지가 printable하지 않은 값들이므로 아마 이 부분은 특수문자와 printable하지 않은 값까지 포인터를 이동시켜주는 로직인것 같다. 이후 do-while문을 확인해보자.
v7에 CharPrev()를 통해 이전 포인터를 대입해주는데 break를 통해 탈출하는 if문의 조건이 ' '공백이 아니고나 '\\'이 아닌 경우에 탈출한다. 결과적으로 PathName에 들어가는 result는 아래와 같다.
이후 sub_4058A3(PathName)을 호출해 return을 result변수에 저장해준다.
앞서 확인 한것처럼 해당함수는 맨첫값이 a~z 사이라면 true를 반환해주는 함수이다. 따라서 이번에도 True가 return 될것이므로 바로 아래 if문이 실행되며 내부로 들어간다.
if문 내부에서는 sub_405836(),sub_40556E()가 차례로 호출되며 result에 대입하는 return으로 sub_405A49((int)FileName, PathName)의 return을 넣어준다.
먼저 sub_405836()을 확인해보자
해당 함수에서는 인자로 들어가는 PathName을 lstrlen으로 길이를 계산해 v1변수에 대입한다. 그리고 CharPrev()를 통해 lpstring의 맨마지막에서 마지막이 92 즉 "\\"가 아닌지 확인하고 lstrcat을 이용해 lpstring에 asc_409010을 붙여준다.
그냥 \하나가 더붙는 함수인것같다. add_backslash로 labeling하도록 하자.
sub_40556E()
인자로 들어가는 PathName을 이용해 CreatDirectory()를 호출한다.
따라서 PathName 경로에 디렉토리를 만들어준다. 두번쨰 인자인 0은 그냥 default security descriptor을 설정해준다.
만약 디렉토리가 정상적으로 생성되었다면 0이 아닌 값을 반환하기 때문에 result를 0으로 설정 할 것이고 실패시 GetLastError를 통해 위 return code를 result에 넣어준다.
현재 PathName은 위 와 같은 것을 확인 할 수 있다.
마지막으로 sub_405A49()를 호출한다. 인자로 들어가는 값은 FileName,PathName임을 인데 해당 함수를 확인해보자.
먼저 v2에 인자로 들어가는 FileName을 v3에 100을 넣어준다. 이후 while문에 걸린 루프를 돈다 prefixString에 "asn"을 넣어주고 BYTE2로 prefixString을 형변환하고 GetTickCount()%26 + 97 한 값을 넣어준다. 또한 result에 GeTempFilename()을 호출한다. 각각의 API 함수들을 먼저 알아보자
GetTickCount함수는 단순히 난수생성을 위해 사용하고 %26은 난수가 알파뱃 범위에 들어가게 하고 97을 더하는 것은 해당 값을 ascii 코드 상에서 알파벳으로 만들기 위한 루틴이다.
GetTempFileName()을 호출하면 임시 파일의 이름을 생성해주는데 이 이름이 unique한 경우라면 해당 이름의 empty file이 생성된다.
각각의 인자값들을 조금더 자세히 확인해보자.
lpPathName : 파일이름에 대한 디렉토리 경로
lpPrefixString : 앞부분에 고정할 3자리 character. 여기서는 asn으로 세팅되어있다.
uUnique : 0으로 설정되어있으므로 현재 systemtime을 기반으로 파일 생성을 시도한다.
lpTempFilename : 임시파일의 이름을 받을 buffer에 대한 pointer이다.
정상적으로 실행되었다면 0이 아닌값이 return되므로 if문을 통해 break가 실행되어 탈출할것이다. 결과적으로 sub_40325D()에서 반환되는 값은 아래와 같을 것이다.
또한 해당 경로에 nsw58F7.tmp 파일이 생성된것 역시 확인가능하다. return값 역시 0이 아니므로 뒷부분의 GetWindowsDirectory()를 호출하는 부분도 넘어간다.
이후 DeleteFile()을 호출한다.
이후 DeleteFlie함수에 FileName을 인자로 주어 file을 삭제하는 것을 알 수 있다.
실제로 다음 DeleteFile()을 호출하고 나면 nsw58F7.tmp는 삭제되어있다.
다음으로 sub_402CA5()를 호출한다.
sub_402CA5()
char *__stdcall sub_402CA5(int Buffer)
{
void *v1; // edi
const CHAR *v3; // eax
signed int v4; // esi
DWORD v5; // edi
int *v6; // esi
int v7; // eax
_DWORD *v8; // eax
signed int v9; // ecx
CHAR FileName; // [esp+Ch] [ebp-128h]
int v11; // [esp+110h] [ebp-24h]
int v12; // [esp+114h] [ebp-20h]
int v13; // [esp+118h] [ebp-1Ch]
int v14; // [esp+11Ch] [ebp-18h]
int v15; // [esp+120h] [ebp-14h]
SIZE_T dwBytes; // [esp+124h] [ebp-10h]
int v17; // [esp+128h] [ebp-Ch]
int v18; // [esp+12Ch] [ebp-8h]
int v19; // [esp+130h] [ebp-4h]
v19 = 0;
v18 = 0;
dword_42EC6C = GetTickCount() + 1000;
GetModuleFileNameA(0, ExistingFileName, 0x400u);
v1 = (void *)sub_405A1A(ExistingFileName, 0x80000000, 3u);
hObject = v1;
if ( v1 == (void *)-1 )
return aErrorLaunching;
lstrcpyn(byte_434C00, ExistingFileName);
v3 = (const CHAR *)sub_40587D(byte_434C00);
lstrcpyn(byte_436000, v3);
dword_428C78 = GetFileSize(v1, 0);
v4 = dword_428C78;
if ( dword_428C78 > 0 )
{
while ( 1 )
{
v5 = v4;
if ( v4 >= (::dwBytes != 0 ? 0x8000 : 512) )
v5 = ::dwBytes != 0 ? 0x8000 : 512;
if ( !sub_403214(&unk_420C78, v5) )
break;
if ( ::dwBytes )
{
if ( !(Buffer & 2) )
sub_402C06(0);
}
else
{
sub_4059DB(&v11, (int)&unk_420C78, 28);
if ( !(v11 & 0xFFFFFFF0) && v12 == 0xDEADBEEF && v15 == 0x74736E49 && v14 == 0x74666F73 && v13 == 0x6C6C754E )
{
Buffer |= v11;
dword_42ED00 |= Buffer & 2;
::dwBytes = dword_420C68;
if ( v17 > v4 )
return aInstallerInteg;
if ( !(Buffer & 8) && Buffer & 4 )
goto LABEL_23;
++v18;
v4 = v17 - 4;
if ( v5 > v17 - 4 )
v5 = v17 - 4;
}
}
if ( v4 < dword_428C78 )
v19 = sub_406142(v19, &unk_420C78, v5);
dword_420C68 += v5;
v4 -= v5;
if ( v4 <= 0 )
goto LABEL_23;
}
sub_402C06(1);
return aInstallerInteg;
}
LABEL_23:
sub_402C06(1);
if ( !::dwBytes )
return aInstallerInteg;
if ( v18 )
{
sub_403246(dword_420C68);
if ( !sub_403214(&Buffer, 4u) || v19 != Buffer )
return aInstallerInteg;
}
v6 = (int *)GlobalAlloc(0x40u, dwBytes);
dword_414C60 = (int)&unk_40CC58;
dword_414C5C = (int)&unk_40CC58;
dword_40B0B8 = 8;
dword_40B5D4 = 0;
dword_40B5D0 = 0;
dword_414C58 = (int)&dword_414C58;
CreatTempFile((int)&FileName, PathName);
hFile = CreateFileA(&FileName, 0xC0000000, 0, 0, 2u, 0x4000100u, 0);
if ( hFile == (HANDLE)-1 )
return aErrorWritingTe;
dword_428C7C = sub_403246(::dwBytes + 28);
dword_420C70 = dword_428C7C - (~(_BYTE)v11 & 4) + v17 - 28;
v7 = sub_402F6D(-1, 0, v6, dwBytes);
if ( v7 != dwBytes )
return aInstallerInteg;
dword_42EC70 = (int)v6;
dword_42EC78 = *v6;
if ( v11 & 1 )
++dword_42EC7C;
v8 = v6 + 17;
v9 = 8;
do
{
v8 -= 2;
*v8 += v6;
--v9;
}
while ( v9 );
v6[15] = dword_420C6C;
sub_4059DB(&dword_42EC80, (int)(v6 + 1), 64);
return 0;
}
장난아니게 긴데 여기서는 안보이지만 IDA에서보면 119줄이다......
앞서서 인자 세팅부가 거의 끝난것으로 보이기 때문에 아마 핵심적인 부분일것 같다.
먼저 int형 변수인 v19,18을 0으로 초기화 해준다. 이후 dword_42EC6C에 앞서서 본것과 유사하게 GetTickCount() + 1000을 이용해 난수를 생성한다. 그리고 GetModuleFileName()을 호출하는데 해당함수를 알아보자.
해당 함수를 호출하게되면 특정 module을 포함하는 전체 경로를 검색한다. 이때 module은 현재 proccess에 load되어있어야한다.
함수에 인자로 들어가는 값들 역시 확인해보자.
hModule : 요청된 module에 대한 handle이다. 현재 0으로 세팅되어있으므로 현재 process의 executable file의 경로를 검색해준다.
lpFilename : 해당 필드는 module에 대한 전제 경로를 받을 buffer의 pointer이다.
nSize: lpFilenaem buffer의 크기이다.
따라서 lpFilename에는 현재 실행하고 있는 PO NO 5532.bin의 전체 경로가 저장될것이다.
이후 sub_405A1A()를 호출한다..
v3에 GetFileAttributes()의 return을 넣고 CreatFile()을 호출한다.
GetFileAttributes()를 실행하면 file에 대한 attributes가 반환되어 나온다. 자세한 constant값은 아래 링크에서 확인 가능하다.
https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
함수의 이름에서도 알 수 있듯 file을 만들거나 여는 함수이다. 해당함수를 실행한 후 return은 file에 접근할 수 있는 handle을 반환해준다.
lpFileName: file name 여기서는 앞서 얻었던 ExistingFileName에 들어있는 PO NO 5532.bin의 전체 경로이다.
dwDesiredAccess: 파일에 대한 액세스 권한
dwShareMode: 파일의 공유 모드
lpSecurityAttributes: 파일의 보안 속성을 지정하는 SECURITY_ATTRIBUTES 구조체의 포인터
dwCreationDisposition: 파일을 생성할 것인지 열 것인지를 지정하는 flag
dwFlagsAndAttributes: 생성할 파일의 속성 여기서 3항 연산자를 이용해 v3가 -1이 아닌 경우에는 -1, -1이라면 0을 넣어준다.
hTemplateFIle: 생성될 파일의 속성을 제공할 템플릿 파일
각 인자마다 정보가 너무 많아서 자세한 내용은 링크로 남기도록 하겠다....
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
v1에는 PO NO 5532.bin 에 대한 handle이 들어가 있을 것이다.
이후 v1이 -1이라면 aErrorLauching을 반환하는데 오류처리를 위한 부분이니까 그냥 넘어가자,
이후 lstrcpyn을 이용해 ExistingFileName의 값을 byte_434C00에 복사해준다.
그리고 sub_40587D()를 호출한다. 그리고 해당 함수의 return을 v3에 넣어준다.
해당 함수에서는 v1에 &lpstring[lstrlenA(lpString)]이 들어가는데 이는 문자열 마지막의 null byte를 가르킬것이다. 그리고 do-while문을 이용해 루프를 수행하는데 break 조건은 v1이 92(\\)이거나 v1이 lpString보다 클때 이다. 그렇지 않다면 v1에 CharPrev()를 이용해 앞 문자열에 대한 포인터를 넣어준다.
결과적으로 마지막에 있는 백슬래시에 걸리기 때문에 파일이름까지만 루프를 돈다.
따라서 해당 함수를 ExtractFileName으로 labeling 하자.
그리고 *v1=0으로 만들어주는데 이는 \\를 제거하기 위한 부분이다. 즉 깔끔한 file name 세팅이 가능해진다.
이후 v1+1을 return해 filename부분만 반환해준다. 따라서 v3에 파일이름이 들어간다.
그리고 lstrcpyn()을 이용해 추출된 filename이 저장된 v3를 byte_436000에 복사해준다.
이후 GetFileSize()를 호출해 앞서 받은 v1과 0을 호출한다. 앞서 확인한것처럼 v1에는 PO NO 5532.bin에 대한 handle이 들어있다.
현재 hfile인자에 file의 handle을 정상적으로 넣었고 lpFileSizeHigh에 0을 넣었으므로 appication이 high-order doubleword를 필요로 하지 않는다.
return되는 값은 low order doubleword 형태로 file의 크기가 반환된다. 이후 반환된 값은 dword_428C78에 저장되었다가 v4에 저장된다.
반환되는 값이 filesize와 정확히 일치하는 것을 확인 할 수 있다.
이후 if문을 통해 file size가 들어있는 dword_428C78가 0보다 크다면 if 내부 로직으로 들어간다.
해당 먼저 v5에 v4 즉 file size를 넣어준다. 그리고 if문을 이용해 3항 연산을 수행한다. 해당 연산이 참이라면 한번더 3항 연산이 수행된다. 0x8000과 512가 뭘 의미하는지 모르겠어서 일단 동적으로 확인해보자.
sub403214()를 호출하기 전에 v5가 0x200으로 변한다. 즉 3항연산에서 false가 나왔다는것이다. 이제 sub_403214()를 확인해보자.
인자로 들어가는 unk_420C78과 0x200은 각각 lpBuffer와 nNumberofByteToRead이다.
hFile: device의 handle 여기서는 hobject 즉 앞서 만든 파일 즉 PO NO 5532.bin의 handle이다.
lpBuffer: 파일이나 장치로 부터 받은 데이터를 저장하기 위한 버퍼를 가리키는 포인터로 unk_420C78의 주소가 들어간다
nNumberOfBytesToRead: 읽을 최대 바이트 크기 0x200이 들어간다.
lpNumberOfBytesRead: 읽어들인 데이터의 바이트 수를 넘긴다.
lpOverlapped: 비동기 입출력을 위한 OVERLAPPED 구조체의 포인터. hFile이 FILE_FLAG_OVERLAPPED 플래그로 열렸다면, 반드시 이 구조체를 사용해야 한다. 비동기 입출력이 아니라면 NULL을 사용한다.
function이 성공적으로 실행되면 True를 반환한다. 그리고 v2에 0x200이 담기고 해당 값과 nNumberofByteToRead이 동일한 경우 True를 return한다. 일단 ReadFile 수행시 어떤 일이 일어나는지 x32를 통해 확인해보자.
ReadFile()를 호출하기전에 세팅되는 인자의 값은 위와 같다.
eax가 1로 세팅된것으로 보아 정상적으로 file을 읽어왔음을 알 수 있다. 따라서 위 while문에서 break에 걸리지않고 다음으로 넘어간다.
다음으로 dwByte가 참일 경우 if문 내부로 들어가는것을 확인 할 수 있다. dwByte는 앞서 0x200으로 세팅되는 것을 확인 할 수 있기때문에 당연히 내부로 들어온다.
그리고 buffer와 2를 bit and 연산한 값이 참이 아닌경우 sub_402C06()를 호출한다. buffer는 sub_402CA5에서는 인자로 들어온 값인데 정확하게 뭐가 들어가는지 판단이 안되어서 그냥 동적으로 확인하자.
그냥 0인것을 확인 할 수 있다. 아마 start에서 buffer를 0으로 초기화하는데 그 이후로 아무런 조작이 없는것 같다.
따라서 프로그램은 else문으로 분기한다. else문에서는 sub_4059DB()를 호출한다.
보면 포인터 v11의 주소 unk_420C78의 주소 28을 각각의 인자 a1,a2,a3로 넘긴다. unk_420C78 여기는 앞서 ReadFile을 통해 읽은 정보의 위치를 가르키고 있다.
먼저 v3에 인자로 받은 a1 즉 v11의 주소를 넣고 v4에 28을 저장한다. 이후 28은 0보다 크므로 당연히 if문 내부로 들어가는데 do-while에 의해 loop를 돌고있다. break조건은 v4가 0이 될때 이다.
루프 내부를 확인해보면 v3[a2-a1]한 값이 들어가는데, a1이 DWORD로 캐스팅 되어 있다. 보아하니 아까 ReadFile 함수로 얻어온 정보를 v3에 적고 v3은 1 증가, v4는 1 감소시키고 있다. 즉 총 28자를 v11(0x18FDC8)에 복사한다는 뜻이다.
따라서 sub_4059DB()는 CopyInfo_28로 labeling 하겠다.
이후 뭔가 화나게 생긴 if문이 걸려있는데 딱 생긴게 절대 통과 못하게 생겼다. 동적으로 확인해보면 그냥 제일 첫번째 0xFFFFFFF0 and 연산에서 jump가 걸려서 넘어간다.
이후 filesize가 dword_428C78보다 작으면 if문 내부로 들어간다. 앞서 if문 전체가 넘어가면서 filesize변수에 변화가 없으므로 여기도 넘어간다.
이후 dword_420C68에 v5만큼 더해주고 filesize에서 v5만큼 뺴준다. v5에는 앞서 확인 한것처럼 0x200이 들어있다.
위 어셈블리가 더하고 빼는 부분의 코드이다. 각각의 레지스터를 확인해 연산결과를 확인해보자.
결과적으로 esi즉 filesize는 0x200이 빠진 값인 0x26CF2가 들어가게 된다. jg는 앞의 오퍼랜드가 클때 점프한다. 즉 0x200만큼 계속 빼준다는 소리이다. 많이 돌아야하므로 그냥 흐름상 sub_402C06(1) 를 호출하는데 브레이크를 걸고 분석을 진행하였다.
먼저 sub_402C06()을 호출 하는 것을 확인할 수 있다 해당 함수를 확인해보자.
HWND __cdecl sub_402C06(int a1)
{
HWND result; // eax
int v2; // eax
CHAR String; // [esp+4h] [ebp-40h]
if ( a1 )
{
result = dword_420C74;
if ( dword_420C74 )
result = (HWND)DestroyWindow(dword_420C74);
dword_420C74 = 0;
}
else if ( dword_420C74 )
{
result = (HWND)sub_40610F(0);
}
else
{
result = (HWND)GetTickCount();
if ( (unsigned int)result > dword_42EC6C )
{
if ( hwnd )
{
if ( dword_42ED14 & 1 )
{
v2 = sub_402BEA();
wsprintfA(&String, aD, v2);
result = (HWND)sub_40502F(0, &String);
}
}
else
{
dword_420C74 = CreateDialogParamA(hInstance, (LPCSTR)0x6F, 0, DialogFunc, 0);
result = (HWND)ShowWindow(dword_420C74, 5);
}
}
}
return result;
}
sub_402C06()에서 result에 dword_420C74를 대입해준다. 해당 변수가 어디서 사용되었는지 알 수없어 그냥 동적으로 확인해보았다.
dword_420C74에 0이 담겨 해당 if문을 실행되지 않는다. 따라서 해당 함수에는 별다른 동작을 하지 않고 넘어간다.
이후 if문에서 dwByte가 0이면 aInstallerInteg을 return한다. 현재 dwByte는 0이 아니기때문에 (0x2974이다.) 넘어가고 이후 v18이 0이 아닌경우에 if내부로 들어가는데 v18은 0으로 초기화 되어있고 더이상의 조작은 없었다. 따라서 GlobalAlloc으로 흐름이 넘어가게 된다. GlobalAlloc이 뭐하는 함수인지 확인해보자.
heap영역에 특정 크기만큼을 할당하는 함수이다.
인자로 들어가는 0x40은 메모리 content를 0으로 initialize하는 것을 확인 할 수 있다.
dwBytes는 현재 0x2974인데 할당할 크기임을 알 수있다.
함수가 성공적으로 호출된다면 return은 새롭게 할당된 영역에 대한 handle이며 해당 값은 v6변수에 저장된다.
그리고 각각의 dword변수에 특정값들을 할당해준다.
그리고 sub_405A49()를 호출한다.
해당 함수에 들어가는 인자는 각각 FileName의 주소와 PathName이다.
앞서 확인한 함수인데 임시file을 생성하는 함수였다. 따라서 Path에 임시파일이 생성될것이다.
그리고 CreatFile을 호출한다. 해당함수가 파일을 만드는것은 앞서서 확인했기 때문에 어떤 값들이 들어가는지만 확인해보자.
return이 0이 아니므로 성공적으로 수행된것을 알 수 있다. eax에 든값이 방금 생성한 file에 대한 handle이다. 또한 if에 걸린 -1과 비교하는 로직 역시 넘어간다.
이제 sub_403246을 호출한다. 인자 값은 dwByte+28이다.
해당함수에서는 SetFilePointer을 호출한다. dwbyte+28은 lDistanceToMove이다.
특정 파일의 pointer를 lpdistanceToMove값만큼 이동시키는 함수이다. 0,0으로 들어가는 인자는 그냥 Highorder이 필요하지 않고 starting point가 file의 시작점이라는 의미이다.
여기서 hFile에 들어가는 hObject는 제일 처음 만들었던 Temp file의 handle이다.
인자로 세팅되는 값은 위와 같다.
return은 앞서 본것 처럼 lpDistanceToMoveHigh가 0이므로 새파일 pointer에 대한 Dword형태의 pointer이다.
해당 pointer를 dword_428C7C에 저장해준다. 그리고 다음과 같은 연산을 수행한 값을 dword_420C70에 넣어준다.
dword_428C7C - (~(_BYTE)v11 & 4) + v17 - 28
이후 sub_402F6D(-1, 0, v6, dwBytes)를 호출한다. v6는 앞서 GlobalAlloc을 통해 할당받은 heap영역이다. dwByte는 현재 0x2974이다.
int __stdcall sub_402F6D(int Buffer, HANDLE hFile, LPVOID lpBuffer, DWORD NumberOfBytesWritten)
{
int result; // eax
int v5; // edi
DWORD v6; // eax
int v8; // [esp+Ch] [ebp-8h]
DWORD NumberOfBytesRead; // [esp+10h] [ebp-4h]
if ( Buffer >= 0 )
{
dword_420C6C = dword_42ECB8 + Buffer;
SetFilePointer(::hFile, dword_42ECB8 + Buffer, 0, 0);
}
result = sub_403098(4);
if ( result < 0 )
return result;
if ( ReadFile(::hFile, &Buffer, 4u, &NumberOfBytesRead, 0) && NumberOfBytesRead == 4 )
{
dword_420C6C += 4;
result = sub_403098(Buffer);
v8 = result;
if ( result < 0 )
return result;
if ( !lpBuffer )
{
if ( Buffer > 0 )
{
while ( 1 )
{
v5 = 0x4000;
if ( Buffer < 0x4000 )
v5 = Buffer;
if ( !ReadFile(::hFile, &unk_41CC68, v5, &NumberOfBytesRead, 0) || v5 != NumberOfBytesRead )
break;
if ( !WriteFile(hFile, &unk_41CC68, NumberOfBytesRead, &NumberOfBytesWritten, 0) || NumberOfBytesWritten != v5 )
return -2;
v8 += NumberOfBytesRead;
Buffer -= NumberOfBytesRead;
dword_420C6C += NumberOfBytesRead;
if ( Buffer <= 0 )
return v8;
}
return -3;
}
return v8;
}
v6 = Buffer;
if ( Buffer >= (signed int)NumberOfBytesWritten )
v6 = NumberOfBytesWritten;
if ( ReadFile(::hFile, lpBuffer, v6, &NumberOfBytesRead, 0) )
{
dword_420C6C += NumberOfBytesRead;
return NumberOfBytesRead;
}
}
return -3;
}
일단 인자로 들어온 값들은 buffer가 -1 hfile이 0 lpBuffer가 v6 NumberOfBytesWritten은 dwByte이다.
현재 buffer가 -1이므로 제일 앞의 if문에 걸리지 않고 넘어간다. 그리고 result변수에 sub_403098(4)의 return을 넣어준다.
sub403098()
signed int __stdcall sub_403098(int a1)
{
int v1; // esi
DWORD v2; // edi
int v3; // esi
DWORD NumberOfBytesWritten; // [esp+10h] [ebp-4h]
v1 = a1 + dword_420C6C - lDistanceToMove;
dword_42EC6C = GetTickCount() + 500;
if ( v1 > 0 )
{
sub_403246(dword_428C7C);
SetFilePointer(hFile, lDistanceToMove, 0, 0);
dword_428C78 = v1;
dword_420C68 = 0;
while ( 2 )
{
v2 = 0x4000;
if ( dword_420C70 - dword_428C7C <= 0x4000 )
v2 = dword_420C70 - dword_428C7C;
if ( !FileRead(&unk_41CC68, v2) )
return -1;
dword_428C7C += v2;
dword_40B0A8 = (int)&unk_41CC68;
dword_40B0AC = v2;
while ( 1 )
{
if ( dword_42EC70 && !dword_42ED00 )
{
dword_420C68 = lDistanceToMove + dword_428C78 - dword_420C6C - a1;
sub_402C06(0);
}
dword_40B0B0 = (int)&unk_414C68;
dword_40B0B4 = 0x8000;
if ( sub_4061B0((unsigned __int8 **)&dword_40B0A8) < 0 )
return -3;
v3 = dword_40B0B0 - (_DWORD)&unk_414C68;
if ( (_UNKNOWN *)dword_40B0B0 == &unk_414C68 )
break;
if ( !WriteFile(hFile, &unk_414C68, dword_40B0B0 - (_DWORD)&unk_414C68, &NumberOfBytesWritten, 0)
|| v3 != NumberOfBytesWritten )
{
return -2;
}
lDistanceToMove += v3;
if ( !dword_40B0AC )
goto LABEL_18;
}
if ( dword_40B0AC || !v2 )
return -3;
LABEL_18:
if ( a1 + dword_420C6C - lDistanceToMove > 0 )
continue;
break;
}
SetFilePointer(hFile, dword_420C6C, 0, 0);
}
sub_402C06(1);
return 0;
}
일단 v1에 인자로 들어온 a1 즉 4 + dword_420C6C - lDistanceToMove를 수행한다. 결과적으로 v1에는 4가 담기게 된다. 그리고 dword_42EC6C를 호출해서 GetTickCount+500을 대입해준다. 난수생성하는 부분이다. 이후 v1은 4로 0보다 크기때문에 if문 내부 로직으로 들어간다. 그리고 앞서 확인했던 함수인 sub_403246을 호출해 hObject의 pointer를 0x881c 만큼 옮겨준다. 보면 계속 file pointer를 옮기는 로직밖에 수행하지 않는다 그래서 그냥 이전 함수로 넘어가자.
이후 ReadFile을 호출해 return을 통해 if문에 걸린다.수행한 이후 lpBuffer의 값을 확인해보자
ReadFile이 성공적으로 수행되었으므로 아래 로직으로 넘어간다. 중간 if문 다넘어가고 아래 부분에 흐름이 걸리게 된다
다시한번 ReadFile이 실행되는데 인자값은 아래와 같다.
lpBuffer 0x005F06B0을 확인해보면 아래와 같다.
그리고 NumberOfBytesRead로 2974를 return한다.
이전함수로 돌아오자. (sub_402CA5)
if문에 v7과 dwBytes가 다른 경우 aInstallerInteg를 return한다.
v7은 앞서 return된 0x2974가 들어있을 것이다. dwByte역시 0x2974이므로 해당 if문은 패스한다.
이후 앞서 할당받은 heap 영역의 주소가 들어있는 v6를 dword_42EC70에 넣어준다. 또한 dword_42EC78에 v6의 첫번쨰 값을 넣어준다.
heap 영역의 주소는 0x5306B0 이다.
첫번쨰 값은 0x81이다.
이후의 if문은 True가 아니므로 넘어간다.
이후 do-while에 걸리는데 v8을 2만큼 감소시키고 v8에 v6를 넣어준다. 그리고 v9를 1만큼 감소시킨다.
즉 2바이트만큼 v8을 이동시키며 v6의 값을 복사시켜주는 로직이다. v9는 앞서 8로 세팅되기 때문에 총 9번 수행되게 된다.
해당 v8의 주소부분을 dump떠보면 위와 같다.
그리고 v6[15]에 dword_420C6C를 대입한다.
0x78이 v6[15]에 대입되게 된다.
그리고 앞서 lableing했던 CopyInfo_28이 호출된다.
0x42EC80에 앞서 확인했던 0x002606B4부터 인코딩된 값이 복사된것을 확인할 수 있다.
드이어 start함수로 돌아왔다.
v17에서 0이 return되었으므로 if문 내부로 들어간다. 그리고 dword_42EC7C가 0이라면 내부로 들어간다. 그리고 sub_403796함수를 호출한다.
INT_PTR sub_403796()
{
int v0; // esi
int (*v1)(void); // eax
unsigned __int16 v2; // ax
int v3; // ecx
const CHAR *v4; // edi
const CHAR *v5; // eax
DWORD v6; // eax
const CHAR *v7; // eax
HICON v8; // eax
INT_PTR v10; // esi
CHAR ClassName[4]; // [esp+10h] [ebp-14h]
int pvParam; // [esp+14h] [ebp-10h]
int Y; // [esp+18h] [ebp-Ch]
int v14; // [esp+1Ch] [ebp-8h]
int v15; // [esp+20h] [ebp-4h]
v0 = dword_42EC70;
v1 = (int (*)(void))GetAPIAddress(3);
if ( v1 )
{
v2 = v1();
sub_405CA1(FileName, v2);
}
else
{
*(_DWORD *)FileName = 30768;
sub_405C2A(HKEY_CURRENT_USER, "Control Panel\\Desktop\\ResourceLocale", 0, (DWORD)&byte_42A0C8, 0);
if ( !byte_42A0C8 )
sub_405C2A(HKEY_USERS, ".DEFAULT\\Control Panel\\International", "Locale", (DWORD)&byte_42A0C8, 0);
lstrcatA(FileName, &byte_42A0C8);
}
sub_403A5F();
dword_42ECE0 = dword_42EC78 & 0x20;
dword_42ECFC = 0x10000;
if ( !sub_405917(&byte_434400) )
{
v3 = *(_DWORD *)(v0 + 72);
if ( v3 )
{
v4 = &Buffer;
sub_405C2A(
*(HKEY *)(v0 + 68),
(LPCSTR)(dword_42EC98 + v3),
(LPCSTR)(dword_42EC98 + *(_DWORD *)(v0 + 76)),
(DWORD)&Buffer,
0);
if ( Buffer )
{
if ( Buffer == 34 )
{
v4 = sz;
*MoveStringPointer(sz, 34) = 0;
}
v5 = &v4[lstrlenA(v4) - 4];
if ( v5 > v4 && !lstrcmpiA(v5, aExe) )
{
v6 = GetFileAttributesA(v4);
if ( v6 == -1 || !(v6 & 0x10) )
sub_40587D(v4);
}
v7 = (const CHAR *)sub_405836(v4);
lstrcpyn(&byte_434400, v7);
}
}
}
if ( !sub_405917(&byte_434400) )
sub_405D65(&byte_434400, *(_DWORD *)(v0 + 280));
v8 = (HICON)LoadImageA(hInstance, (LPCSTR)0x67, 1u, 0, 0, 0x8040u);
dwNewLong = (LONG)v8;
if ( *(_DWORD *)(v0 + 80) != -1 )
{
WndClass.hIcon = v8;
strcpy(ClassName, "_Nb");
WndClass.lpfnWndProc = sub_401000;
WndClass.hInstance = hInstance;
WndClass.lpszClassName = ClassName;
if ( !RegisterClassA(&WndClass) )
return 0;
SystemParametersInfoA(0x30u, 0, &pvParam, 0);
dword_42A0A0 = CreateWindowExA(
0x80u,
ClassName,
0,
0x80000000,
pvParam,
Y,
v14 - pvParam,
v15 - Y,
0,
0,
hInstance,
0);
}
if ( sub_40140B(0) )
return 2;
sub_403A5F();
if ( dword_42ED00 )
{
if ( StartAddress(0) )
{
if ( !dword_42E42C )
sub_40140B(2);
return 2;
}
sub_40140B(1);
return 0;
}
ShowWindow(dword_42A0A0, 5);
if ( !GetDLLHandle((int)"RichEd20") )
GetDLLHandle((int)"RichEd32");
if ( !GetClassInfoA(0, "RichEdit20A", &WndClass) )
{
GetClassInfoA(0, "RichEdit", &WndClass);
WndClass.lpszClassName = "RichEdit20A";
RegisterClassA(&WndClass);
}
v10 = DialogBoxParamA(hInstance, (LPCSTR)(unsigned __int16)(dword_42E440 + 105), 0, sub_403B2C, 0);
sub_40140B(5);
sub_4036E6(1);
return v10;
}
아 너무 기네.....
일단 앞부분 부터 차근차근 확인해보자.
일단 v0에 dword_42EC70을 넣고 v1에 GetAPIAddress를 호출한다. dword_42EC70에는 임시파일의 경로가 들어간다.
그리고 들어가는 API는 GetUserDefaultUILanguage이다.
그럼 v1에 정상적으로 api에 대한 handle이 들어갔으므로 if 문 내부로 들어간다. 또한 v1을 호출하게 되면 GetUserDefaultUILanguage의 return이 v2에 들어가게된다.
해당 api를 호출하면 현재 사용자에 대한 UI 언어에 대한 ID를 반환해준다.
현재 분석환경인 English - UnitedStates인것을 확인할 수 있다. else문의 경우 해당 api호출이 실패했을때 레지스트리 접근을 통해 UI language를 확인하는 부분인것 같다. 그리고 sub_405CA1(FileName, v2)가 호출된다. 해당 함수는 wsprintf함수가 들어있는 함수인데 FileName 부분에 v2만큼을 넣어준다,
이후 sub_403A5F()가 호출된다.
LPSTR sub_403A5F()
{
signed __int16 v0; // bx
__int16 v1; // ax
int v2; // esi
unsigned __int16 *v3; // ecx
const CHAR *v4; // eax
LPSTR result; // eax
int v6; // esi
int v7; // edi
v0 = -1;
v1 = sub_405CBA(FileName);
while ( 1 )
{
v2 = dword_42ECA4;
if ( dword_42ECA4 )
{
v3 = (unsigned __int16 *)(dword_42ECA0 + dword_42ECA4 * *(_DWORD *)(dword_42EC70 + 100));
while ( 1 )
{
v3 = (unsigned __int16 *)((char *)v3 - *(_DWORD *)(dword_42EC70 + 100));
--v2;
if ( !((unsigned __int16)v0 & (unsigned __int16)(v1 ^ *v3)) )
break;
if ( !v2 )
goto LABEL_8;
}
dword_42E440 = *(_DWORD *)(v3 + 1);
dword_42ED08 = *(_DWORD *)(v3 + 3);
if ( v3 != (unsigned __int16 *)-10 )
break;
}
LABEL_8:
if ( v0 == -1 )
v0 = 1023;
else
v0 = 0;
}
dword_42E43C = (int)(v3 + 5);
sub_405CA1(FileName, *v3);
v4 = sub_405D65(chText, -2);
SetWindowTextA(dword_42A0A0, v4);
result = (LPSTR)dword_42EC8C;
v6 = dword_42EC88;
if ( dword_42EC8C )
{
v7 = dword_42EC8C;
do
{
result = *(LPSTR *)v6;
if ( *(_DWORD *)v6 )
result = sub_405D65((LPSTR)(v6 + 24), *(_DWORD *)v6);
v6 += 1048;
--v7;
}
while ( v7 );
}
return result;
}
v0에 -1을 대입하고 v1에 sub_405CBA(FileName)의 return을 넣는다. 여기서 FileName에는 앞서 확인했던 Language ID인 1033이 들어있을 것이다.
int __stdcall sub_405CBA(_BYTE *a1)
{
_BYTE *v1; // ecx
int v2; // edi
char v3; // al
char v4; // bl
char v5; // dl
signed int v6; // edx
int v7; // edx
signed int v9; // [esp+Ch] [ebp-4h]
v1 = a1;
v2 = 0;
v9 = 1;
v3 = 10;
v4 = 57;
if ( *a1 == 45 )
{
v1 = a1 + 1;
v9 = -1;
}
if ( *v1 == 48 )
{
v5 = *++v1;
if ( *v1 >= 48 && v5 <= 55 )
{
v3 = 8;
v4 = 55;
}
if ( (v5 & 0xDF) == 88 )
{
v3 = 16;
++v1;
}
}
while ( 1 )
{
v6 = (char)*v1++;
if ( v6 >= 48 && v6 <= v4 )
{
v7 = v6 - 48;
goto LABEL_16;
}
if ( v3 != 16 || (signed int)(v6 & 0xFFFFFFDF) < 65 || (signed int)(v6 & 0xFFFFFFDF) > 70 )
return v2 * v9;
v7 = (v6 & 7) + 9;
LABEL_16:
v2 = v7 + v2 * v3;
}
}
먼저 v1에 인자로 들어오 a1을 넣는다. 그리고 각각의 변수를 세팅해준다. 이후 if문에서 v1의 값이 48인지 확인하는데 앞서 확인했던것처럼 0x31이므로 넘어간다. 계속 앞서 얻은 UI language ID에 대한 검증으로 봐서 사용하는 언어가 영어인지 확인하는 함수로 보인다. 그냥 넘어가자
이전함수에서도 계속 if문이 걸려있는데 연산이 복잡해서 그냥 넘어가고 sub_405CA1 함수 호출부를 확인해보자.
아까와 똑같은 값이 세팅된다. 아마 FileName 초기부분을 무조건 1033으로 맞추려고 만든 함수인것 같다.
sub_405D65(chText, -2)를 호출한다.
LPSTR __stdcall sub_405D65(LPSTR lpString1, int a2)
{
int v2; // eax
CHAR *v3; // ecx
LPSTR result; // eax
CHAR *v5; // edi
int v6; // eax
int v7; // ecx
int v8; // esi
int v9; // ebx
int v10; // ebx
int v11; // ecx
signed int v12; // esi
signed int v13; // eax
BOOL v14; // ebx
int v15; // eax
CHAR v16; // dl
int csidl; // [esp+0h] [ebp-18h]
int v18; // [esp+4h] [ebp-14h]
int v19; // [esp+8h] [ebp-10h]
int v20; // [esp+Ch] [ebp-Ch]
LPITEMIDLIST ppidl; // [esp+10h] [ebp-8h]
BOOL v22; // [esp+14h] [ebp-4h]
int v23; // [esp+24h] [ebp+Ch]
int v24; // [esp+24h] [ebp+Ch]
v2 = a2;
if ( a2 < 0 )
v2 = *(_DWORD *)(dword_42E43C - (4 * a2 + 4));
v3 = (CHAR *)(v2 + dword_42EC98);
result = (LPSTR)&Buffer;
v5 = (CHAR *)&Buffer;
if ( (unsigned int)(lpString1 - &Buffer) < 0x800 )
{
v5 = lpString1;
lpString1 = 0;
}
while ( 1 )
{
v16 = *v3;
if ( !*v3 || v5 - &Buffer >= 1024 )
break;
v23 = (int)++v3;
if ( (unsigned __int8)v16 <= 0xFCu )
{
if ( v16 == -4 )
*v5++ = *v3++;
else
*v5++ = v16;
}
else
{
v6 = v3[1];
v7 = *v3;
v8 = v7 & 0x7F | ((v6 & 0x7F) << 7);
v9 = v7;
v24 = v23 + 2;
BYTE1(v9) |= 0x80u;
csidl = v9;
v10 = v7;
v11 = v6;
v18 = v10;
BYTE1(v11) |= 0x80u;
v19 = v11;
v20 = v6;
switch ( v16 )
{
case -2:
v12 = 2;
v13 = GetVersion();
v22 = v13 >= 0 || (_WORD)v13 == 23044 || v20 == 35 || v20 == 46;
if ( dword_42ECE4 )
v12 = 4;
if ( (v10 & 0x80u) != 0 )
{
sub_405C2A(
HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion",
(LPCSTR)(dword_42EC98 + (v10 & 0x3F)),
(DWORD)v5,
(HKEY)(v10 & 0x40));
if ( !*v5 )
{
sub_405D65(v5, v20);
goto LABEL_30;
}
goto LABEL_31;
}
if ( v10 == 37 )
{
GetSystemDirectoryA(v5, 0x400u);
}
else
{
if ( v10 == 36 )
{
GetWindowsDirectoryA(v5, 0x400u);
v12 = 0;
}
while ( v12 )
{
--v12;
if ( SHGetFolderPath )
{
if ( v22 && !SHGetFolderPath(hwnd, *(&csidl + v12), 0, 0, v5) )
break;
}
if ( !SHGetSpecialFolderLocation(hwnd, *(&csidl + v12), &ppidl) )
{
v14 = SHGetPathFromIDListA(ppidl, v5);
CoTaskMemFree(ppidl);
if ( v14 )
break;
}
*v5 = 0;
}
}
LABEL_30:
if ( *v5 )
{
LABEL_31:
if ( v20 == 26 )
lstrcatA(v5, "\\Microsoft\\Internet Explorer\\Quick Launch");
}
LABEL_33:
sub_405FA5(v5);
break;
case -3:
if ( v8 == 29 )
sub_405CA1(v5, (int)hwnd);
else
lstrcpyn(v5, &byte_42F000[1024 * v8]);
if ( (unsigned int)(v8 - 21) < 7 )
goto LABEL_33;
break;
case -1:
sub_405D65(v5, -1 - v8);
break;
}
v15 = lstrlenA(v5);
v3 = (CHAR *)v24;
v5 += v15;
result = (LPSTR)&Buffer;
}
}
*v5 = 0;
if ( lpString1 )
result = lstrcpyn(lpString1, &Buffer);
return result;
}
어음 매우 중요해 보이긴한다.
이후 분석을 진행하기 위해 x32를 통해 분석을 진행하던 중 아래의 라인에서 오류가 발생하면 자동적으로 디버거가 꺼지는 현상이 발생하였다.
위와 같이 break point를 걸어둔 부분인 sub_403796함수를 호출하고 다음 라인으로 넘어가려고 하면 디버거가 멈춘다. 해당 함수를 hex ray를 통해 확인해보자.
sub_403796함수
위 사진은 sub_403796함수의 부분이다. HKEY_CURRENT-USER과 같은 부분이 보이는 것으로 보아 레지스터의 값들을 통해 필요한 정보를 세팅하는 로직을 수행하는 것 같다.
일단 어디서 꺼지는지 확인하기 위해서 x32상에서 winapi함수를 제외하고 샘플 파일내부에서 정의된 함수들을 전부 하나 확인해 보려고 한다.
확인 결과 아래 라인에서 debugger가 꺼지는 것을 확인 할 수 있었다.
DialogBoxParam함수에서 꺼지는 것으로 예상된다. 해당 오류까지 넘어가다sub_405D65에서 exploer의 version을 획득하는 로직을 발견 할 수 있었다.
Sub_405CA2함수에 인자들을 주기 때문에 Sub_405CA2함수역시 확인해보자.
Sub_405CA2 에서는 RegOpenKeyExA, RegQueryValueExA, RegCloseKey 함수들을 호출 하는 것을 알 수 있다.
RegOpenKeyExA함수는 지정된 레지스트리 key를 open하는 역할을 하고 RegQueryValueExA을 통해 레지스트리 key를 이용해 data를 획득한다. RegCloseKey 함수는 앞서 RegOpenKeyEx open 했던 handler를 닫는 역할을 한다.