[Malware Analysis] Lokibot 분석 ~ing (DLL Injection)
File name: PO NO5532.bin
분석 환경: Windows 10 x64 ver.20H2
ErrorMode를 지정하고, GetVersion()에서 Windows의 version을 받아오는데, Win 8.1 이거나 Win 10이면 ax=206을 반환하는데 현재 분석 환경은 WIn 10이므로 해당 분기문을 실행한다.
v1에 off_409228[2 * a1]를 넘기고 있는데, a1은 0이므로 0x409228에 들어있는 값을 v1에 대입한다. v1에는 KERNEL32가 들어간다. v2에서는 KERNEL32에서 로드된 handle을 반환받는다. 밑에 if문은 handle을 성공적으로 반환받았을 때 실행한다. (GetModuleHandleA가 실패하면 return value는 0이다.) 다음은 v2, off_40922c를 인자로 갖는 GetProcAddress()를 call한다. 0x400922c에는 SetDefaultDllDirectories가 들어간다.
다음은 do-while 문을 실행한다. *v1이 0이 아닐 때까지 sub_406065에 "UXTHEME"를 인자로 넘긴다.
GetSystemDirectoryA(Buffer, 0x104)를 call 하는데 해당 함수의 reference는 다음과 같다.
그리고 길이가 0x104보다 크면 v1 = 0을 실행하는데 해당 분기문은 당연히 무시된다. 그리고 비트 연산을 수행한 값을 v2에 넣고 인자를 다시 v4로 넘긴다. 이제 해당 인자를 가지고 wsprintfA를 실행하는데 결과는 다음과 같다. 그냥 단순히 dll로 library load하는 부분이다.
저기 보이는 dll을 모두 call하면 *v0 포인터의 값이 0이 되고 do-while문이 종료된다.
이번에는 아까 위에서 실행한 sub_4060D3에 인자가 13이 들어갔다. 인자가 들어가면 아까 KERNEL32를 가리키던 값이 추가된 인자를 더한 어떠한 문자열을 가리킬 것이다. 13이 인자로 들어가면 VERSION을 가리킨다.
밑에 있는 sub_4060D3(11)도 동일하다.
다음은 공통 컨트롤을 위한 InitCommonControls()가 call 되고, OleInitialize(0)의 return value를 dword_42ED18에 넣는다. OleInitialize()는 밑에 사진에 나타난 기능을 사용하기 위해 call 되는 것으로 보인다. return value는 사용하지 않으므로 패스한다.
SHGetFileInfoA()의 reference는 다음과 같으며 파일, 폴더, 디렉터리 또는 드라이브 루트와 같은 파일 시스템의 개체에 대한 정보를 검색하는 함수다.
pszPath: A pointer to a null-terminated string of maximum length MAX_PATH that contains the path and file name.
dwFileAttributes: file attributes flag로 IfuFlagsdoes not include theSHGFI_USEFILEATTRIBUTESflag, this parameter is ignored 된다.
*psfi: SHFILEINFO의 구조체로부터 파일 정보를 받아온다.
cbFileInfo: psfi의 size
uFlags: The flags that specify the file information to retrieve. This parameter can be a combination of the following values.
실제 넘어가는 인자는 다음과 같다.
근데 왜 실행하면 다 NULL임? ㅇㅅㅇ << 이 부분은 uFLags 때문인 것 같다.
다음으로 sub_405D43은 이렇게 생겼다.
우리가 아는 strcpyn과 작동 방식이 동일하다.
다음은 sub_405D43(&sz, v2)를 호출하는데, v2는 위에서 GetCommandLineA()의 return value다. GetCommandLineA()는현재 프로세스의 명령줄을 검색하는 역할을 한다. return value는 The return value is a pointer to the command-line string for the current process이다. 즉 다음과 같은 값을 return 한다.
이 값을 &sz(0x434000)로 복사한다. 다음 실행할 GetModuleHandleA()는 인자로 NULL이 들어가면 호출한 프로세스의 handle값이 반환된다.
그리고 해당 분기문이 참이므로, unk_434001을 v3에 넣는다.
sub_405861을 실행할 인자로 v3, v18을 넘기는데 각각 unk_434001, 34를 담고 있다.
초기값은 아까 들어간 문자열의 포인터이고, *result가 0이 아니면서 34(")가 아닐 때까지 1씩 더한다. 그냥 포인터의 위치를 옮기는 함수인 듯하다.
그리고 v5를 다음 문자로 다시 넣어주니까? 문자열의 끝을 대입한다. (NULL Byte 위치)
다시 그 값을 lpString2에 넣는다.
v5는 당연하게도 NULL을 가리키고 있으니 LABEL_26으로 점프한다.
그러고 나서 GetTempPathA(0x400, PathName)을 실행하는데, 먼저 GetTempPathA의 reference를 봐보자.
임시 폴더를 검색하는 역할을 하고, return value로 그 경로를 반환한다.
밑에 if문에서 sub_40325D를 call 한다.
또다시 sub_405FA5(PathName)을 call 하는데..
일단 v1에 PathName이 들어간다. 그리고 PathName의 값을 검사하는데 당연히 만족할리 없다. 이제 밑에 분기문을 실행하는데, *v1에 값이 들어있고 sub_4058A3(v1)이 0이 아니면 v1 += 2를 하는데.. 저 함수는 또 뭘까?
일단 v1 = *a1 | 0x20이니 99가 들어갈 것이고, 뭐.. 저런 식의 결과를 return 하는데 정말 누가봐도 1을 return 하게 생김.
왜냐면 v1이 저 범위 안에 있고, PathName[1]이 ":"이기 때문이다. 그렇다면 v1 += 2가 수행되어 처음 "\"를 가리킬 것이다. "\"는 v2로 옮기고 주소는 v3에 넣는다. 이제 for문에서 초기값이 PathName 포인터고, eax가 가리키는 값을 v2에 넣고 v2가 0이 아닐 때까지 반복한다.
해당 반복문 안에서 if문이 있는데, v2 > 0x1F고(printable 하지 않은 특수문자 판별 용도), *sub_405861(asc_409410, v2)의 값이 참이면 밑에 분기를 수행하는데..
솔직히 이정도 짬이면 저기 해당하는 특수문자 들어가는지 안 들어가는지 확인하는 함수라고 추측할 수 있다.
정확히는 특수문자를 만나는 위치를 return 하는 함수였다. ㅇㅎ 그니까 대충 특수문자를 기준으로 문자열을 나누는 함수인 듯하다. 일단 아까 분기문에서 수행하는 루틴을 살펴보자면, v5 = PathName++ 해주고 sub_4059DB(i, v1, v5-v1)을 call 한다. 저건 또 뭘까..
v3에 a1넣고, v4에 i 넣고, a3가 0보다 크면 생각하다가 굳이 여길 분석할 필요가 있을까? 라고 생각해서 다음 do-while문 까지 넘겼다. 사실 return value만 보면 되니까..
해당 반복문은 문자열 끝까지 돌아가니까 문자열의 마지막을 0으로 대입한다. ( *i = 0 )
그 다음 do-while은 문자열의 끝에서 부터 진행되는데 공백이나, \\가 있으면 break한다. 근데 해당 반복문은 입구컷.
이제 sub_4058A3(PathName)을 실행한다. 근데 이 친구는 아까 1을 return 하는 걸 확인 했으니 다음 분기문을 실행할 것이다. sub_405836(PathName)은 이렇게 생겼다.
PathName 길이를 계산하고, PathName 마지막에서 1칸 뒤로 간 것이 "\\"가 아닌지 검사하는데 당연하게도 lstrcatA(lpString, asc_409010)을 실행한다.
그냥 뒤에 백슬래시 하나 더 붙이는 함수다.
다음은 sub_40556E(PathName)을 call 한다.
CreateDirectoryA는 해당 경로에 디렉터리를 만드는 함수다. 두번쨰 인자가 0이면 If lpSecurityAttributes is NULL, the directory gets a default security descriptor.라고 한다. 근데 이미 해당 디렉터리가 있으니까 패스.
그리고 나서 마지막으로 sub_405A49(FileName, PathName)을 call 한다.
v2(esi)를 FileName의 주소로 바꾸고, v3에 100을 넣는다. 이제 반복문이 돌아가는데, result가 1이거나 v3이 0이면 break 한다. 일단 반복문에서는 v3를 1 감소시키고, PrefixString에 'asn'을 넣는다. 근데 이게 무슨 함수냐?
그냥 이렇게 tmp 파일 만드는 함수다. 어떻게 알았는지는 짬에서 나온다. ㅎ0ㅎ 넘어가보도록 하자.
드디어 다시 여기로 돌아왔다. sub_40325D에서 최종적으로 0이 아닌 값을 반환하므로 해당 분기문을 수행한다.
DeleteFileA(FileName)에서는 아까 만든 파일을 다시 지운다. 그리고 sub_402CA5(Buffer)을 call 하는데 여기서 Buffer의 값은 0이다.
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;
sub_405D43(byte_434C00, ExistingFileName);
v3 = (const CHAR *)sub_40587D(byte_434C00);
sub_405D43(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 == -559038737 && v15 == 1953721929 && v14 == 1952870259 && v13 == 1819047246 )
{
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;
sub_405A49((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;
}
? 왜 이렇게 길어 이거.. ㅠ 천천히 따라가보자
일단 첫 줄에서 GetTickCount() + 1000한 값을 가져오는데, 이건 아까 위에서 tmp 만든 것 처럼 random value를 가져오기 위함이다. 그 값을 dword_42EC6C에 저장한다. 그리고 GetModuleFileNameA(0, ExistingFIleName, 0x400)을 call 하는데 reference는 다음과 같다.
hModule: NULL or 현재 실행되고 있는 모듈의 handle
LPSTR: return buffer
nSize: buffer size
이다. 즉, 다음과 같은 값을 받아온다.
이제 밑에서 sub_405A1A(ExistingFileName, 0x80000000, 3u)을 실행하는데.. 저건 또 뭘까 벌써 무섭네~
v3에 GetFileAttributesA(ExistingFileName)의 결과를 넣는다. return value는 다음과 같다.
FIle Attribute Constants는 여기 들어가보세요..
URL: docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
File Attribute Constants (WinNT.h) - Win32 apps
File Attribute Constants In this article --> File attributes are metadata values stored by the file system on disk and are used by the system and are available to developers via various file I/O APIs. For a list of related APIs and topics, see the See Also
docs.microsoft.com
나는 이 값을 받아왔다.
이제 CreateFileA(lpFileName, dwDesiredAccess, 1u, 0, dwCreationDisposition, v3 != -1 ? v3 : 0, 0)를 실행한다. reference는 다음과 같다.
lpFileName: file name
dwDesiredAccess: 파일에 대한 액세스 권한
dwShareMode: 파일의 공유 모드
lpSecurityAttributes: 파일의 보안 속성을 지정하는 SECURITY_ATTRIBUTES 구조체의 포인터
dwCreationDisposition: 파일을 생성할 것인지 열 것인지를 지정하는 flag
dwFlagsAndAttributes: 생성할 파일의 속성
hTemplateFIle: 생성될 파일의 속성을 제공할 템플릿 파일
즉, [esp] 이름을 가진 파일을 뭐 저런 옵션을 가지고 읽기 공유 모드로 파일 또는 장치가 있는 경우에만 기본 옵션으로 만든다? 네 그냥 일단 저런 파일을 만드네요.
그리고 다음 명령을 차례로 수행한다. sub_405D43(byte_434C00, ExistingFileName); sub_40587D(byte_434C00) sub_405D43(byte_436000, v3) 아까 위에서 봤듯이 sub_405D43은 lstrcpyA 함수로 strcpyn 명령어와 작동 방식이 동일하다. 즉, ExistingFileName의 값을 byte_434C00으로 복사한다. sub_40587D는 이렇게 생겼다.
그리고 분석하면서 LPSTR이 도대체 뭘까 하고 찾아봤는데 C++에서 사용하는 char *형이라고 나와있다. 계속 진행하자면, v1은 lpString[strlen(lpString)]이므로 문자열 끝에 NULL Byte의 주소를 가리킨다. 그리고 v1이 "\\" 이거나 v1이 lpString보다 작을 때까지 do-while문을 반복한다. 아마도 밑에 사진에서 맨 나중에 나오는 "\\"에 걸릴 것이다.
정확하다. 그리고 v1이 가리키는 값을 0으로 바꾸니까.. 다음과 같이 바뀐다. 아마도 실행되고 있는 파일의 이름을 추출하는 함수인 듯하다. return value로는 v1 + 1이니까 파일의 이름을 가리키는 주소를 return 한다.
이제 v3는 위에서 return 받은 주소를 갖고 있고 이 값을 byte_436000으로 복사한다.
다음 명령은 GetFileSize(v1, 0)이다. 현재 v1에는 sub_405A1A를 실행해서 얻은 handle 값을 갖고있다. GetFIleSize의 return value는 다음과 같다.
이제 return 값을 다시 v4(esi)에 넣는다. 그리고 파일의 크기가 0보다 크다면다음 분기문을 실행한다. 만약 파일 크기가 0 이하일 경우는 GetFileSize에서 오류가 발생했다는 것을 알 수 있지만? 우리는 성공했으니 신경 쓸 필요 없다.
일단 v4(File size)의 값을 v5에 넣는다. 그리고 삼항연산을 수행하여 참이라면 밑에 삼항연산도 수행하는데.. 이부분은 동적 분석으로 확인해보겠다.
보니까 if문을 실행한다. 여기서 v5의 값이 0x200으로 변한다. 다음 분기문은 sub_403214(&unk_420C78, v5) 값이 거짓이라면 break 한다.
v2에 아까 넘긴 0x200 값을 넣고, ReadFile을 실행하는데 reference는 다음과 같다.
hFile: device의 handle
lpBuffer: 파일이나 장치로 부터 받은 데이터를 저장하기 위한 버퍼를 가리키는 포인터
nNumberOfBytesToRead: 읽을 최대 바이트 크기
lpNumberOfBytesRead: 읽어들인 데이터의 바이트 수를 넘긴다.
lpOverlapped: 비동기 입출력을 위한 OVERLAPPED 구조체의 포인터. hFile이 FILE_FLAG_OVERLAPPED 플로그로 열렸다면, 반드시 이 구조체를 사용해야 한다. 비동기 입출력이 아니라면 NULL을 사용한다.
인자는 이렇고, 실제 무슨 값이 넘어가나 확인해보자.
hObject는 전에 받아왔던 handle 값이고, 저장은 unk_420C78에 한다. size는 200만큼 읽어오고 0x19FC9C에 넘긴다고 한다. 실행하면 어떠한 결과가 나타날지 봐보자.
정상적으로 값을 읽어왔다. ReadFile 함수는 정상적으로 값을 읽어보면 nonzero(TRUE) 값을 return 한다. 즉, break가 걸려서 while 문을 탈출할 리는 없다는 것이다 ㅠㅠ.. 다음은 dwBytes가 참인지 검사한다.
네 참이 아니네요 :D 그럼 이제 else 문에 있는 sub_4059DB(&v11, &unk_420C78, 28)를 실행한다. v11에는 스택 값이 들어가있다.
위에서 본 함수와 동일하다. v3에 a1을 넣고 v4에 28을 넣는다. 그리고 a3(28)이 0보다 크니까 do-while 문을 실행한다. 반복문에서는 v3[a2-a1]한 값이 들어가는데, a1이 DWORD로 casting 되어 있다. 보아하니 아까 ReadFile 함수로 얻어온 정보를 v3에 적고 v3은 1 증가, v4는 1 감소시키고 있다. 그렇다는 것은 총 28자를 v11(0x19FDB0)에 복사한다는 뜻이다.
이제 뭔가 복잡해 보이는 분기문을 지날 차례다.
흠.. v11을 검사하는 식은 일단 패스고, v12가 0xDEADBEEF이면서, v13~v15가 NullsoftInst 여야 한다. 근데 v12 부터 값이 틀리네 ㅇㅅㅇ 이 분기문은 패스한다. 즉, 밑에 있는 if ( v4 < dword_428C78 ) 분기로 넘어가는데 dword_428C78에는 파일 사이즈가 들어가있고, v4는 dword_428C78에서 0x200 만큼 뺀 값이 들어가있다. 즉, if문이 참이므로 sub_406142(v19, &unk_420C78, v5)를 call 한다.
int __stdcall sub_406142(int a1, unsigned __int8 *a2, int a3)
{
signed int v3; // ecx
unsigned int v4; // eax
signed int v5; // esi
int v6; // edx
unsigned int v7; // eax
unsigned __int8 *v8; // ecx
if ( !dword_42C65C )
{
v3 = 0;
do
{
v4 = v3;
v5 = 8;
do
{
v4 = ((v4 & 1) != 0 ? 0xEDB88320 : 0) ^ (v4 >> 1);
--v5;
}
while ( v5 );
dword_42C658[v3++] = v4;
}
while ( v3 < 256 );
}
v6 = a3;
v7 = ~a1;
if ( a3 )
{
v8 = a2;
do
{
v7 = dword_42C658[*v8++ ^ (unsigned __int8)v7] ^ (v7 >> 8);
--v6;
}
while ( v6 );
}
return ~v7;
}
? 이게 뭐노.. 일단 dword_42C85C가 NULL 이면 분기를 수행한다.
ㅋㅋㄹ 분기를 수행한다. v3에 0을 넣고 v4가 256 이상일 때까지 do-while 문을 실행한다. 굳이 256으로 제한하는 걸 보니까 비트로 장난질 하려는 것 같다. 일단 v4에 v3 값을 넣고 v5에는 8을 넣는다. 그리고 v5가 0 일때까지 do-while 문을 실행한다. 일단 8번을 실행한다는 것을 알 수 있는데 저 요상한 삼항연산이 문제다. 그런데 생각해보니 v4는 0이다. 즉, 8번을 실행한다고 해도 v4가 계속 0이므로 해당 반복문은 아무런 행동(?)을 수행하지 않는다. 라고 생각했는데 밑에 v3++을 해주는 부분이 있넹? ㅎ0ㅎ 저 짓을 256번 반복한다. 이런 부분은 동적 분석으로 무슨 결과값이 나왔는지 보는 게 ㄹㅇ 훨씬 이득이다. 분기문이 끝나는 곳까지 수행하고, dword_42C658가 어떻게 바뀌었나 봐보도록 하자.
총 1,008개의 알 수 없는 문자가 생성되었다. 일단은 넘어가보자.
이제 v6에 0x200을 넣고, v7은 a1의 bit를 반전한 값을 넣는다. a1을 반전한 값은 0xFFFFFFFF이다. 다음 분기문에서는 a3가 참이어야 수행하는데, a3는 0x200이므로 당연히 참. 분기에서는 a2(0x420C78)를 v8에 넣고 v6이 0이 될때까지 do-while문을 수행한다. 근데 이거 뭔가 냄새가 난다. 아까 알 수 없는 문자열에서 특정값을 비트연산하여 v7에 넣는다. 최종적으로 v7에 무슨 값이 들어가나 동적분석으로 확인해보자.
그런데 이 값을 반전해서 return value로 사용한다. 일단 반전한 값은 0x885D7A2F이다. 다시 아까 실행한 함수로 돌아와서 이 부분을 실행한다.
여기서 v5는 0x200 이었고 dword_420C68에도 0x200이 들어갔으니까 0x400이 된다. 그리고 v4 값에서 0x200을 빼는데 v4에는 파일 사이즈에서 0x200 만큼 뺀 값이 들어가 있었다. 여기서 또 0x200을 빼니 원래 파일에서 0x400을 뺀 값이 들어갈 것이다. 그리고 v4가 0이하면 LABEL_23으로 이동하는데, 절대 그럴리 없으니 패스~ 그리고 이 짓을 while 문으로 계속 반복한다. 우선 이 반복문을 탈출해보자.
탈출했더니 0x420C78의 문자열이 바뀌어있다. 일단 계속 진행해보자. sub_420C06(1)은 이렇게 생겼다.
일단 result에 아까 바뀐 그 문자열을 넣는다. 그리고 DestroyWindow(dword_420C74)를 call 하는데...
얘는 인자로 넘어간 hWnd를 파괴하는 역할을 한다. return-value로는 성공 시 nonzero가 return 된다.. 라고 분석 했는데 알고보니까 dword_420C74는 0이다.. ㅋㅎ.. 다시 dword_420C74에 0을 넣고, result 를 return 한다. 여기서 result는 바뀌지 않았으므로 0이다. 별 볼일 없는 함수다. 이제 우리는 이 명령으로 이동한다. v6 = GlobalAlloc(0x40u, dwBytes);
GlobalAlloc 함수는 Heap 할당을 위해 사용하는 함수라는데, 이 함수는 약 10년 전쯤 쓰였다고 한다.. :P return value로는 새롭게 할당된 memory object의 handle을 return 한다고 한다. 참고로 uFlags가 0x40이므로 값은 0으로 초기화된다.
그리고 뭐를 하는지는 모르겠으나 대입 연산을 수행한다. 마지막 친구는 자기 주소에 자기 주소를 넣는다.
그리고 sub_405A49(&FileName, PathName)을 실행한다. PathName에는 Temp 경로가 들어가있다.
그리고 이 함수 아까 어디서 많이 봤다 했더니 특정 범위 안에 random string을 뽑는 함수였다.
이 함수가 실행되면 tmp 파일이 하나 생성될 것이다.
그리고 CreateFileA(&FileName, 0xC0000000, 0, 0, 2u, 0x4000100u, 0) 명령을 수행한다. 파일을 만드는 함수인 것은 위에서 봤으니 그냥 실행해보겠다. hFile에는 handle 값이 들어갈 것이다. (0x308) 에러를 검사하는 if문은 가볍게 통과~
다음은 sub_403246(dwBytes + 28)을 수행한다. 해당 함수는 이렇게 생겼다.
SetFilePointer는 원하는 파일의 파일 포인터를 옮기는 함수다. lDistanceToMove에 다음 값을 넘기고 있다.
hObject는 맨 처음 tmp 에서 얻어온 파일의 handle이다. 현재 파일 포인터의 위치는 0x881C 만큼 이동한 상태다.
dword_428C7C에는 0x881C가 저장된다. 그리고 이 값에 비트 연산을 수행한 값을 뺀다. (연산 값: 0x6EF2) 다음으로는 sub_402F6D(-1, 0, v6, dwBytes)가 들어가는데 현재 v6에는 아까 GlobalAlloc으로 할당받은 Heap 영역이 들어있다.
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이므로 첫 분기문은 패스한다. 그리고 sub_403098(4)의 return value를 result에 넣는다.
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 ( !sub_403214(&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(&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에는 4 + dword_420C6C(0) - lDistanceToMove(0) 가 들어간다. dword_42EC6C에는 random 값이 들어간다.
여기서 v1은 4이므로 분기문을 수행한다. sub_403246(dword_428C7C)는 아까 위에서 봤던 파일 포인터를 옮기는 함수다. (이제 슬슬 라벨링좀 해야할 듯) dword_428C7C는 0x881C이다. 그리고 밑에서는 hFile의 파일 포인터를 변경한다.
dword_428C78(파일 사이즈)에는 v1을, dword_420C68에는 0을 넣는다. 근데 이거 또 분석하다 보니까 밑에는 함수 콜 하는 부분이 sub_402C06 말고는 없어서 굳이 분석할 필요가 없다고 느꼈다. 바로 가볍게 패스하자. 다시 아까 함수로 돌아가서 분기문에 있는 ReadFile을 수행한다.
0x19FC90에 뭐가 적히는지 봐보자.
일단 읽는데는 성공했기 때문에 밑에 분기문이 실행된다. dword_420C6C(0x0)에 4를 더하고 sub_403098을 실행한다.
근데 이 함수 아까 그 별로 필요 없어보이는 함수임.. ㅇㅇ 그래서 가볍게 넘겨줬다. 이제 밑에서 v8에 0을 넘겨준다.
당연히 분기문은 패스되지만 밑에 if(!lpBuffer)에서 걸려 분기문을 실행하지 않는다. lpBuffer는 현재 0x2974이고 이 값을 v6에 넘긴다. 그리고 if ( Buffer >= (signed int)NumberOfBytesWritten )에서 값이 둘 다 동일하므로, v6을 다시 NumberOfBytesWritten으로 덮는데 값이 동일하므로 별 의미는 없다.
마지막으로 ReadFile을 또 다시 실행한다.
Hmm.. 일단 읽어오는데 성공했으므로 밑에 분기문을 실행한다. NumberOfBytesRead는 0x2974이므로 dword_420C6C에 더해주고 이 값을 return value로 사용한다. 다시 위에 함수로 돌아가자.
v7은 dwBytes와 동일하므로 해당 분기문은 넘어간다.
이제 아까 ReadFile로 읽어온 값의 주소 dword_42EC70에 넣고 값은 dword_42EC78에 넣는다. 다음 분기문은 조건이 맞지 않아 넘어간다. 이제 v8에 아까 넣은 주소 + 17를 한 값을 넣고 v9에는 8을 대입한다. 그리고 v9가 0이 될때까지 do-while 문을 실행하는데 현재 v8에는 주소 + 17한 값이 들어가있고, v6에는 주소 원본 값이 들어가있다. 일단 v8을 먼저 2 감소시키고, v6 주소를 v8이 가리키는 곳에 넣고 v9를 1 감소한다. 즉, 이 행동을 8번 반복한다는 것인데, 이런 식으로 값이 바뀌게 된다. 이 값을 따라가보니까 어떤 코드의 시작점을 가리키는 듯한데 일단은 넘어가도록 하겠다.
그리고 dword_420C6C 값을 v6[15]에 넣는데 32bit 이므로 v6[15]는 0x5DC174를 가리킨다. (원본 주소: 0x5DC138)
마지막으로 sub_4059DB(&dword_42EC80, (int)(v6 + 1), 64)을 수행하는데 위에서 본 함수다. dword_42EC80에 위에서 얻었던 값 64byte가 복사된다. 이제 이 함수도 끝..
이제 다시 EntryPoint가 있는 함수로 돌아간다..
v17은 위에 분석했던 함수에서 0을 return 하였으므로 패스, dword_42EC7C는 0이므로 패스.
이제 dword_42ED0C에 -1을 넣고 sub_403796 함수를 실행한다.
signed int 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))sub_4060D3(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 = byte_42DC01;
*(_BYTE *)sub_405861(byte_42DC01, 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);
sub_405D43(&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 ( !sub_406065("RichEd20") )
sub_406065("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(0x5DC138) 값을 넣는다. 이 값은 위에서 포인터로 8번 복사했던 그 부분이다. 그리고 sub_4060D3(3)이라는 오랜만에 보는 반가운 함수를 call 한다.
off_409228[6] 에는 KERNEL32 문자열을 담고있다.
여기서 GetUserDefaultUILanguage를 push하고 GetProcAddress로 DLL의 handle 값을 return 받는다.
v1에는 현재 handle 값이 들어가있고, if(v1)을 가볍게 통과한 다음에 v1를 함수 포인터로 하여 실행한다. 이렇게 되면 v2에는 우리가 시스템에서 사용하고 있는 시스템 언어의 ID 값이 들어갈 것이다.
와~ 정답이에요~! 그 다음으로는 sub_405CA1(FileName, v2)를 실행한다. 현재 FileName에는 tmp 파일의 경로가 들어가있다.
wsprintfA로 포멧을 바꿔주고 있다. 그냥 단순한 hex to dec로 1042로 변경된다. 다음은 sub_403A5F()를 실행한다.
int sub_403A5F()
{
signed __int16 v0; // bx
__int16 v1; // ax
int v2; // esi
unsigned __int16 *v3; // ecx
const CHAR *v4; // eax
int 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 = (const CHAR *)sub_405D65(chText, -2);
SetWindowTextA(dword_42A0A0, v4);
result = dword_42EC8C;
v6 = dword_42EC88;
if ( dword_42EC8C )
{
v7 = dword_42EC8C;
do
{
result = *(_DWORD *)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)한 결과를 넣는다. 현재 FileName은 시스템에서 사용하는 언어 ID로 변경된 상태다.
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에 FileName의 주소를 넣는다. v2에는 0, v9에는 1, v3에는 10, v4에는 57을 넣는다. (Korean 기준)
먼저, *a1 값이 45인가 검사하는데, 우리는 49이므로 패스한다. 마찬가지로 48도 패스다. 즉, 밑에 있는 반복문으로 진입한다. 반복문 첫줄에서 *v1++한 값을 v6에 넣는다. 즉, 다음 문자를 넣는다. 우리는 "1042" 이므로 "0"을 가리킨다. "0"은 분기문 조건(v6 >= 48 && v6 <= 57)을 만족하므로, 48 - 48한 값을 v7에 넣고 LABEL_16으로 간다. LABEL_16에서는 v7 + v2 * v3한 값을 v2에 넣는데 우린 0이 나온다. 그리고 다시 반복문으로 돌아가 "4"를 가리키고...를 반복하면 결국엔 NULL Byte를 가리킬 것이다. NULL Byte를 만나면 if문에 걸려 v2 * v9한 값을 return 하게 된다.
를 한 값에 +2를 하고 다시 1을 곱한 값을 return 한다. 즉, 최종적으로 0x412가 return 되는데 우리가 처음에 알아낸 ID값과 동일하다. 이제 위 함수에 있는 v1에는 0x412가 들어가게 된다. 그리고 다시 반복문을 만난다. v2에 dword_42ECA4(0x1) 의 값을 넣고 분기문을 실행한다. v3에는 (unsigned __int16 *)(dword_42ECA0 + dword_42ECA4 * *(_DWORD *)(dword_42EC70 + 100)) 값을 넣는다. 천천히 해석하자면, dword_42ECA0에는 0x5DE9D2(빈 공간)이 들어가있고 dword_42ECA4에는 0x1이, *(dword_42EC70 + 100)에는 0xDA가 들어가있다. 다음 사칙연산을 수행하면 0x5DEAAC라는 값이 들어가게 된다.
그리고 또 내부 반복문을 만나는데, (unsigned __int16 *)((char *)v3 - *(_DWORD *)(dword_42EC70 + 100))한 값을 v3에 넣는다. 아까 *(_DWORD *)(dword_42EC70 + 100)값은 0xDA였으니까 v3-0xDA한 값이 v3이 될 것이다. 즉, v3는 0x5DE9D2(위에서 넘겨준 빈 공간)라는 값을 갖는다. 밑에서는 v2를 1 감소하는데, 원래 v2는 1이었으므로 밑에 분기문 조건 if(!v2)를 만나 LABEL_8로 이동한다. LABEL_8에서는 v0이 -1이면 v0의 값을 1023으로 셋하거나, 아니라면 0으로 셋한다. 여기서는 v0의 값이 -1이므로 1023으로 바뀐다. 근데 뭐 딱히 중요한 부분이 아닌 것 같아서 반복문을 탈출할 때까지 실행했다.
밑에 dword_42E43C = (int)(v3 + 5)에서는 0x5DE9DC라는 주소 값이 들어가게 되고, sub_405CA1(FileName, *v3)를 만나 FileName의 포멧을 변경하게 된다. 근데 이거 아무리 봐도 시스템 언어가 English 인지 아닌지 검사하는 것 같은뎅 ㅎ-ㅎ
밑에서는 sub_405D65(chText, -2)를 넣어주고 있는데, chText는 NSIS Error라는 문자열을 가리키고 있다.
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 ( dword_42EC64 )
{
if ( v22 && !dword_42EC64(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
sub_405D43(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 = sub_405D43(lpString1, &Buffer);
return result;
}
음.. 아마 중요한 함수 같은데.. 일단 코드를 분석해보자.
v2에 -2를 넣고, 분기문을 만족하므로 *(_DWORD *)(dword_42E43C - (4 * a2 + 4))값이 들어간다. 해당 수식을 해석하자면 0x5DE9DC - (-4) 이므로 0x5DE9E0이 될 것이다. 밑에서는 (v2 + dword_42EC98) => (0x4A8 + 0x5DE280)이 될 것이고 이 값이 v3로 넘어간다.
다음은 &Buffer 값을 result에 넣고 있는데, Buffer의 주소값은 0x42DC00이다. edi에는 0x435000("1033")가 들어가있다. (여기서 의문인게 왜 lpString1 - &Buffer가 0x860이란 값을 갖는지 모르겠음.. 근데 아무튼 가지네 ㅇㅇ) 쨋든 이 분기문은 패스된다.
이제 반복문을 만나는데 *v3의 값을 v16에 넣어준다. 현재 v3에는 0x5DE728("installer caption")이란 값이 들어가있다. 근데 1byte만 넣는거라서 i만 들어간다. (즉, EDX = 0x860 -> 0x869) 다음 분기문에서 두 조건을 검사하는데, 일단 !*v3은 무조건 0이라서 다음 조건만 보면 된다. v5 - &Buffer는 자신에서 자신을 빼는 것이므로 0이 나와 해당 분기문을 패스하게 된다. 다음으로는 v3에 값을 1 증가시켜 v23에 넣는다. 다음 조건문에서는 v16 <= 0xFC여야 하는데, 이 조건 또한 만족한다. (참고로 0xFC를 검사하는 이유는 ASCII 255에서 printable 하게 표현할 수 있는 문자열이기 때문이라고 감히 추측해본다 ㅎ..) 그리고 밑에서 v16 == -4인지 검사하는데, 당연히 아니므로 else문을 실행한다. else 문에서는 *v5에 v16을 넣고 v5을 1 증가시킨다. 현재 v5의 값은 0x42DC00이고 v16의 값은 0x69(i) 이다.
그리고 v5의 포인터를 1 증가시킨다. (다음 공간을 가리킴) 이제 이거를? "install caption"을 옮길 때까지 반복한다.
이제 반복문을 탈출하고, *v5에 0을 넣는다. (문자열 NULL Byte 삽입) 그리고 분기문에서 lpString1이 당연히 0이 아니므로, sub_405D43(lpString1, &Buffer)을 실행한다. sub_405D43은 lstrcpynA 함수다. 즉, Buffer에 들어있는 값을 lpString1에 넣는다. 참고로 return value로는 result 값을 반환하기 때문에 lpString1의 주소를 반환한다.
이제 다시 원래 분석하던 함수로 가보자.
SetWindowTextA(dword_42A0A0, v4)를 실행하는데, hWnd의 text 값을 바꾸는 역할을 한다.
그런데 생각해보니까 handle 자리에 들어가야 할 0x42A0A0의 값이 0인데? 그럼 이거 False 반환하는데..???
흠.. 일단 다음으로 dword_42EC8C(0x4)의 값을 result에 넣고 dword_42EC88(0x68c474)의 값은 v6에 넣는다.
분기문은 당연하게 실행이 될 것이다. 분기문에서 dword_42EC8C의 값을 다시 v7로 넣고, v7이 0이 될때까지 do-while문을 실행한다. 현재 v7은 4이므로 총 4번 반복할 것이다. 반복문에서는 *v6 값을 result에 넣고 그 값이 참이면 특정 루틴을 수행하는데, 일단 0x48C474는 이렇게 생겼다.
일단 처음은 참이므로, sub_405D65((LPSTR)(v6 + 24), *(_DWORD *)v6)를 실행할 것이다. 근데 이 함수는 아까 위에서 install caption을 옮길 때 사용했던 그 함수다. 이번에는 lpString1의 인자로 v6 + 24 값이, a2의 인자로는 64가 들어간다.
똑같은 함수이므로 위에 코드를 참고하자. 이번에는 v2에 0x64(d)가 들어간다. 이번에는 a2가 0보다 크므로 밑에 수식은 계산하지 않는다. 그리고 v3에 (v2 + dword_42EC98)한 값을 넣는데, 대충 이렇게 생겼다.
이제 둘을 연산하면 0x68E274가 나온다. 근데 여기서 ㄹㅇ 유레카인점.. 드디어 악성행위를 하는 코드를 발견했다는 것이다. 위에 v3를 연산할 때 dword_42EC98에 v2를 더한 것을 볼 수 있는데 dword_42EC98이 Lokibot이 탐색할 경로들의 집합이고, v2가 인덱스의 번호라고 생각하면 편하다.
이제 v5에 Buffer의 주소를 넣고, if문을 만난다. 이번 분기문에서는 lpString1 - &Buffer의 값이 월등하게 크므로 분기문을 패스하고 바로 반복문을 만나게 된다. v16에 *v3(Mozilla Firefox Installation에 "M") 값을 넣고, 다음 분기문을 검사한다. 당연히 !*v3은 0이 될 것이고, v5(&Buffer) - &Buffer의 값은 0이 될 것이다. 즉 break 되지 않고 다음으로 넘어간다. 다음으로는 v3에 1을 증가시킨 값을 v23에 넣는다. v16은 위에 포인터 연산으로 인해 1 증가했으므로 "o"와 0xFC를 연산하는데, 사실 이는 ASCII-255에서 대부분의 값보다 크므로 참이될 수 밖에 없다. 이제 내부 분기문인 (v16 == -4)를 검사하는데, 당연히 될 리가 없으니까 v16의 값을 *v5에 넣고 v5를 1 증가시킨다. 이런 방식으로 쭉 Mozilla Firefox Installation을 &Buffer에 복사한다. (install caption을 복사했던 것과 동일)
마찬가지로 NULL Byte 넣어주고, lpString1에 이 문자열을 복사하고 result 값을 return 한다.
이제 아까 분석하던 do-while문으로 다시 돌아가자. sub_405D65 함수가 끝났으므로, v6의 값을 1048 증가시킨다. 현재 v6에는 0x68C474가 들어가있다. (0x68C474 -> 0x68C88C) 원래 4번 반복했던 함수를 방금 1번 실행했으니 3번 더 반복한다. 여기서 복사하는 문자열은 다음과 같다.
1st try: install caption
2nd try: Mozilla Firefox Installation
3rd try: Mozilla Thunderbird Installation
4th: Prog 3
5th: Prog 4
리버싱 하면서 알게된 점인데, do-while문을 돌면서 생기는 0x64나 0xAA가 인덱스였다. (이 값은 좀 예전에 만들어짐)
아뭍느 총 1+4번을 수행하면 do-while 문이 종료된다. 이제 ㅋㅋㅋㅋㅋ.. 원래 분석하던 함수로 다시 넘어가자. 너무 오래되서 분석하던 부분을 다시 올리겠다.
dword_42EC78(0x81) & 0x20한 값을 dword_42ECE0에 넣는다. 값은 0이다. 그리고 밑에서 dword_42ECFC에 0x10000을넣는다. 바로 밑에 분기문에서 sub_405917(&byte_434400)을 실행하는데 return value가 0이어야 분기문을 실행한다.
우선 lpString2(0x434400)은 아무 값도 들어있지 않아, byte_42B4D0도 NULL 값으로 복사된다. 그리고 v1에 sub_4058CA(byte_42B4D0)의 return value를 넣는다.
근데 여기서 재밌는건 지금 lpsz에는 NULL로 초기화 되어 있어서 어떤 행동을 할지가 참 의문이다 ㅇㅇ..
일단 result에는 lpsz에서 2칸 뒤 값이 들어가는데.. 라고 생각하다가 MSDN을 딱 봤다.
NULL Byte를 가리키면 lpsz를 return 한단다.. 즉, result에는 0x42B4D0이 들어가게 된다. 일단 NULL Byte를 가리키므로 if문도 당연히 패스할 것이고, lpsz가 23644라는 말도 안되는 if문도 스킵하네? 그럼 결론은 return 0만 수행하게 된다.
마저 하자면 이제 방금 전 함수에서 얻은 return value를 갖고 v2에 다시 넘긴다. 그런데 현재 v1는 0이므로 return 0을 만나 이 함수도 그냥 종료된다. 즉, 아까 아까 분석하던 if ( !sub_405917(&byte_434400) )는 return value가 0이므로 분기문을 실행한다. 이제 분기문에서 *(_DWORD *)(v0 + 72) 값을 v3에 넣는데, 현재 v0에는 0x42EC70이 들어가있다. 즉, 0x42EC70에 들어있는 0x5BC0E8 + 72를한 값이 v3에 들어가게 된다. 값은 0이다. 그러므로 다음 분기문에서 v3는 0이므로 분기문을 패스할 것이다.
다음 분기문인 ( !sub_405917(&byte_434400) )은 아까 결과와 마찬가지로 0을 return 하므로 분기문을 실행한다. 분기문에서는 sub_405D65(&byte_434400, *(_DWORD *)(v0 + 280))를 실행하는데 byte_434400에는 여전히 빈 공간이, *(v0+280)에는 0이 들어가있다.
근데 이 함수? 아까 우리가 갇혀있던 그 함수지만 인자의 조건이 맞지 않아 break 된다. 그리고 밑에 (HICON)LoadImageA(hInstance, (LPCSTR)0x67, 1u, 0, 0, 0x8040u)를 실행하는데.. reference를 보도록 하자.
icon, cursor, bitmap을 불러오는 함수다. return value로는 handle값을 가져온다. Flag 값을 분석해보니 아이콘을 불러오는 용도로 쓰이는 듯하다. 와~ 근데 return value가 0이네요..? 함수 실행 실패! 일단 다음으로 넘어갑시다.
dwNewLong에 return value를 넣는다. 근데 여기서 부터 뭔가 느낌이 안 좋죠? 함수 실패를 2번이나 했으니.. 그리고 분기문을 만나는데 *(v0 + 80) 값이 -1이 아니면 실행된다.
-1이므로 분기문을 패스한다. 그리고 나서 sub_40140B(0)를 수행하는데 return value가 참이면 2를 return 한다.
dword_42EC70(0x63C1A0) + 4 * 0 + 108 = *(0x63C20C) == -1이고 이 값과 0을 가지고 sub_401389를 실행한다.
네.. 벌써 느낌이 매우 안 좋죠? v2에는 a1의 값인 -1이 들어간다. 그러나 밑에 분기문에서 바로 return 0을 만나 함수가 종료된다. 즉 0x40140B의 return value는 0이다. 일단 return 2;는 피했다.
그리고 나서 sub_403A5F를 실행한다. 이 함수도 위에서 봤던 함수다. 그냥 빠르게 return value만 받아오자.
다음으로 dword_42EC00의 값이 참이면 분기를 수행하는데 값이 0이다. 분기문은 패스한다. 그리고? ShowWindow(dword_42A0A0, 5)를 실행하는데 dword_42A0A0.. 이거 어디서 많이 봤던 주소다. 바로 NULL이 들어가있던 주소.. 역시 아무런 행동도 하지 않은채 0을 return 했다.. 또 실패! 다음으로 sub_406065("RichEd20")을 실행하는데..
이렇게 생긴 초반에 봤던 함수다. 일단 v1에 Buffer에 디렉터리의 경로가 적히고 다음 인자를 갖고 wsprintfA를 실행한다.
그리고 LoadLibraryExA(Buffer, 0, 8u)를 실행하고 return 된다. RichEd20.dll이 있으므로 분기문은 패스한다.
바로 밑 분기문에서는 GetClassInfoA(0, "RichEdit", &WndClass)를 수행한다.
일단 RichEdit20A의 handle 값을 받아왔다. 값을 정상적으로 받아왔으므로 분기문은 패스한다. 이제 DialogBoxDialogBoxParamA(hInstance, (LPCSTR)(unsigned __int16)(dword_42E440 + 105), 0, sub_403B2C, 0)를 실행할 차례다. reference는 다음과 같다.
자.. 이제 이 함수를 실행하면 lpDialogFunc의 위치인 0x403B2C로 이동할 것이다. BP를 걸고 확인해보자.
INT_PTR __stdcall sub_403B2C(HWND hWndInsertAfter, UINT a2, WPARAM wParam, LPARAM lParam)
{
HWND v4; // edi
HWND v6; // eax
HWND v7; // edi
WPARAM v8; // eax
_DWORD *v9; // esi
int v10; // ebx
HWND v11; // eax
int v12; // ebx
HMENU v13; // eax
int v14; // ST18_4
int v15; // eax
HWND v16; // eax
HWND v17; // eax
struct tagRECT Rect; // [esp+10h] [ebp-10h]
HWND wParama; // [esp+2Ch] [ebp+Ch]
if ( a2 == 272 || a2 == 1032 )
{
v8 = wParam;
v4 = hWndInsertAfter;
dword_42A0AC = wParam;
if ( a2 == 272 )
{
hwnd = hWndInsertAfter;
::wParam = GetDlgItem(hWndInsertAfter, 1);
dword_429088 = GetDlgItem(hWndInsertAfter, 2);
sub_403FFF(hWndInsertAfter, 28, -1);
SetClassLongA(hWndInsertAfter, -14, dwNewLong);
dword_42E42C = sub_40140B(4);
v8 = 1;
dword_42A0AC = 1;
}
v9 = (_DWORD *)(dword_42EC80 + (dword_4091CC << 6));
if ( dword_4091CC < 0 )
goto LABEL_62;
if ( v8 == 1 && sub_401389(*(_DWORD *)(dword_42EC80 + (dword_4091CC << 6) + 16), 0) )
{
SendMessageA(hDlg, 0x40Fu, 0, 1);
return dword_42E42C == 0;
}
if ( *v9 )
{
LABEL_62:
sub_40404B(0x40Bu);
while ( 1 )
{
do
{
dword_4091CC += dword_42A0AC;
v9 += 16 * dword_42A0AC;
if ( dword_4091CC == dword_42EC84 )
sub_40140B(1);
if ( dword_42E42C || dword_4091CC >= (unsigned int)dword_42EC84 )
{
DestroyWindow(hDlg);
hwnd = 0;
EndDialog(hWndInsertAfter, nResult);
goto LABEL_56;
}
v10 = v9[5];
sub_405D65(byte_436800, v9[9]);
sub_403FFF(hWndInsertAfter, -999, v9[8]);
sub_403FFF(hWndInsertAfter, -997, v9[7]);
sub_403FFF(hWndInsertAfter, -998, v9[10]);
v11 = GetDlgItem(hWndInsertAfter, 3);
wParama = v11;
if ( dword_42ECEC )
LOWORD(v10) = v10 & 0xFEFD | 4;
ShowWindow(v11, v10 & 8);
EnableWindow(wParama, v10 & 0x100);
sub_404021(v10 & 2);
v12 = v10 & 4;
EnableWindow(dword_429088, v12);
v13 = GetSystemMenu(hWndInsertAfter, 0);
EnableMenuItem(v13, 0xF060u, v12 == 0);
SendMessageA(wParama, 0xF4u, 0, 1);
if ( dword_42ECEC )
{
SendMessageA(hWndInsertAfter, 0x401u, 2u, 0);
sub_404034((WPARAM)dword_429088);
}
else
{
sub_404034((WPARAM)::wParam);
}
custom_strcpyn(byte_42A0C8, chText);
v14 = v9[6];
v15 = lstrlenA(byte_42A0C8);
sub_405D65(&byte_42A0C8[v15], v14);
SetWindowTextA(hWndInsertAfter, byte_42A0C8);
}
while ( sub_401389(v9[2], 0) || !*v9 );
if ( v9[1] != 5 )
break;
if ( dword_42ECEC || !dword_42ECE0 )
return 0;
}
DestroyWindow(hDlg);
dword_429898 = (int)v9;
if ( *v9 > 0 )
{
v16 = CreateDialogParamA(
hInstance,
(LPCSTR)(unsigned __int16)(dword_42E440 + *(_WORD *)v9),
hWndInsertAfter,
*(&lpDialogFunc + v9[1]),
(LPARAM)v9);
hDlg = v16;
if ( v16 )
{
sub_403FFF(v16, 6, v9[11]);
v17 = GetDlgItem(hWndInsertAfter, 1018);
GetWindowRect(v17, &Rect);
ScreenToClient(hWndInsertAfter, (LPPOINT)&Rect);
SetWindowPos(hDlg, 0, Rect.left, Rect.top, 0, 0, 0x15u);
sub_401389(v9[3], 0);
if ( dword_42E42C )
return 0;
ShowWindow(hDlg, 8);
sub_40404B(0x405u);
}
}
goto LABEL_56;
}
}
else
{
v4 = hWndInsertAfter;
if ( a2 == 71 )
SetWindowPos(dword_42A0A0, hWndInsertAfter, 0, 0, 0, 0, 0x13u);
if ( a2 == 5 )
ShowWindow(dword_42A0A0, wParam != 1 ? 5 : 0);
if ( a2 == 1037 )
{
DestroyWindow(hDlg);
hDlg = (HWND)wParam;
LABEL_56:
if ( !dword_42B0C8 )
{
if ( hDlg )
{
ShowWindow(v4, 10);
dword_42B0C8 = 1;
}
}
return 0;
}
if ( a2 == 17 )
{
SetWindowLongA(hWndInsertAfter, 0, 0);
return 1;
}
if ( a2 != 273 )
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
v6 = GetDlgItem(hWndInsertAfter, (unsigned __int16)wParam);
v7 = v6;
if ( !v6 || (SendMessageA(v6, 0xF3u, 0, 0), IsWindowEnabled(v7)) )
{
if ( (unsigned __int16)wParam == 1 )
{
sub_403FD8(1u);
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
}
if ( (unsigned __int16)wParam == 3 )
{
if ( dword_4091CC > 0 )
{
sub_403FD8(0xFFFFFFFF);
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
}
goto LABEL_26;
}
if ( (unsigned __int16)wParam != 2 )
{
LABEL_26:
SendMessageA(hDlg, 0x111u, wParam, lParam);
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
}
if ( dword_42ECEC )
{
sub_40140B(2);
nResult = 2;
LABEL_22:
sub_403FD8(0x78u);
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
}
if ( !sub_40140B(3) )
{
nResult = 1;
goto LABEL_22;
}
return sub_404066(a2, (HDC)wParam, (HWND)lParam);
}
}
return 0;
//히히
일단 첫 분기문부터 탈락이니까 바로 else 문으로 넘어간다. h4에 hWndInsertAfter 값을 넣고, a2가 71인지 분기문에서 검사한다. 하지만 우리 a2는 0x30이므로 (a2 != 273) 분기문까지 패스한다. 해당 분기문으로 가면 sub_404066(a2, (HDC)wParam, (HWND)lParam)을 수행하는데, ㅇㄱㄹㅇ 딱 들어가보면 그냥 별거 아니다. return value만 보자.
0을 return 하므로 이 값을 가지고 0x403FCE로 이동한다. 사실 여기서부터는 user32.dll와 comctl32.dll 영역이다.
아 졸려.. 일단 내일 패킷분석 해봐야지
C&C: 52.6.206.192/zero/zero5/fre.php
흠.. 많은 정보를 보내지는 않네? DLL을 설치하고 다시 분석해봐야겠다.