Files
2024-07-20 11:41:25 +01:00

355 lines
14 KiB
C++

// dllmain.cpp : Defines the entry point for the DLL application.
#include <process.h>
#include <iostream>
#include <Windows.h>
#include <dbghelp.h>
#include <tlhelp32.h>
#include <string>
#include <psapi.h>
#include <tchar.h>
#include "Performance.h"
#pragma comment(lib, "dbghelp.lib")
#include "hde64.h"
typedef hde64s HDE;
#ifdef _DEBUG
#define DPRINT(...) { printf(__VA_ARGS__); }
#else
#define DPRINT(...) {}
#endif
int LoadPrivilege(void) {
HANDLE hToken;
LUID Value;
TOKEN_PRIVILEGES tp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return(GetLastError());
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Value))
return(GetLastError());
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = Value;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
return(GetLastError());
CloseHandle(hToken);
return 1;
}
bool check_number(std::string str) {
for (int i = 0; i < str.length(); i++)
if (isdigit(str[i]) == false)
return false;
return true;
}
#define DBGHELP_TRANSLATE_TCHAR
void Analyze(int pid)
{
DWORD nAlerts = 0;
TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
//const HANDLE hProcess = ::GetCurrentProcess();
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
printf("[X] Can't get handle to process.\n");
return;
}
}
DPRINT("[Information] Analyzing PID: %d\n", pid);
HMODULE hMod;
DWORD cbNeeded;
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
//DWORD pid = GetProcessId(hProcess);
//DPRINT("ERROR OpenProcess: %d\n", GetLastError());
//const HANDLE hThread = ::GetCurrentThread();
//const HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, 16780);
//DPRINT("ERROR OpenThread: %d\n", GetLastError());
if (hProcess == NULL) {
DPRINT("[X] Can't get handle to process. Check that PID exists and that you have proper permissions.\n");
return;
}
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
{
GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(TCHAR));
}
hMod = NULL;
cbNeeded = NULL;
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h != INVALID_HANDLE_VALUE)
{
THREADENTRY32 te;
te.dwSize = sizeof(te);
if (Thread32First(h, &te))
{
do
{
if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID))
{
// Suspend all threads EXCEPT the one we want to keep running
if (te.th32OwnerProcessID == pid)
{
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
DPRINT("Analyzing ThreadID: %d\n", te.th32ThreadID);
if (hThread == NULL) {
DPRINT("[X] Can't get handle to thread. Check that you have proper permissions.\n");
exit(0);
}
SuspendThread(hThread);
GetThreadContext(hThread, &context);
ResumeThread(hThread);
STACKFRAME64 stack = { 0 };
stack.AddrPC.Offset = context.Rip;
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Offset = context.Rsp;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Offset = context.Rsp;
stack.AddrFrame.Mode = AddrModeFlat;
SymInitialize(hProcess, NULL, TRUE);
SymSetOptions(SYMOPT_LOAD_LINES);
SYMBOL_INFO* pSymbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + sizeof(TCHAR) * 255, 1);
//std::string last_funcname;
//last_funcname = pSymbol->Name;
//std::string stack_trace;
for (int frame = 0; ; ++frame)
{
BOOL result = StackWalk64(IMAGE_FILE_MACHINE_AMD64, hProcess, hThread, &stack, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL);
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = 256;
DWORD64 displacement;
SymFromAddr(hProcess, stack.AddrPC.Offset, &displacement, pSymbol);
if (!result) {
break;
}
else {
// Resolve the return address of the function
// Once resolving the return address, use HDE/Hacker Disassembly engine to go back 1 instruciton
// Check if instruction is a call
// If instruction is a call, validate it is a call to last_funcname by resolving name based on address
// If instruction is not a call, alert
// If the callstack doesnt contain the called function, alert
// Alert on RtlUserThreadStart HAVING a return address
}
//if (!stack_trace.empty()) stack_trace.append(1, L'\n');
//stack_trace.append(pSymbol->Name);
//DPRINT("Analyzing Function Frame: %s\n PC Address: 0x%p\n Stack Address: 0x%p\n Return Address: 0x%p\n", pSymbol->Name, (ULONG64)stack.AddrPC.Offset, (ULONG64)stack.AddrStack.Offset, (ULONG64)stack.AddrReturn.Offset);
if ((LPVOID)stack.AddrReturn.Offset != 0) {
int valid = 0;
SIZE_T read = 0;
void* bytes = malloc(64);
ReadProcessMemory(hProcess, (LPBYTE)stack.AddrReturn.Offset - 0x20, bytes, 64, &read);
LPVOID baseLoc = (LPVOID)((LPBYTE)bytes + 0x20);
LPBYTE Validate = (LPBYTE)baseLoc;
HDE hs;
HDE hsLongCall;
HDE hsShortCall;
HDE hsRelativeCallShort;
HDE hsCallRegister;
HDE hsRelativeCall;
HDE hsSyscall;
hde64_disasm(Validate - 0x07, &hsLongCall); // 48 FF MODR/M Rex.W 1
hde64_disasm(Validate - 0x06, &hsShortCall); // FF MODR/M Rex.W 0
hde64_disasm(Validate - 0x03, &hsRelativeCallShort); // FF 2 BYTES ADDRESS
hde64_disasm(Validate - 0x02, &hsCallRegister); // FF Call to register
hde64_disasm(Validate - 0x05, &hsRelativeCall); // E8 4 BYTES ADDRESS
hde64_disasm(Validate - 0x02, &hsSyscall); // 0F05 Syscall
hde64_disasm(Validate, &hs); // Opcode at return address
if (hsLongCall.opcode != 0xff && hsShortCall.opcode != 0xff && hsRelativeCallShort.opcode != 0xff && hsCallRegister.opcode != 0xff && hsRelativeCall.opcode != 0xE8 && hsSyscall.opcode != 0x0f) { // Observed functions causing FP's
DPRINT("\nAnalyzed Function Frame: %s\n PC Address: 0x%p\n Stack Address: 0x%p\n Return Address: 0x%p\n", pSymbol->Name, (ULONG64)stack.AddrPC.Offset, (ULONG64)stack.AddrStack.Offset, (ULONG64)stack.AddrReturn.Offset);
DPRINT("[X] Possibly spoofed return address observed! There is a false caller observed here.\n");
DPRINT("[!] This alert was generated because a call was not observed before the return address, therefore it is incredibly unlikely that this return address is valid.\n UNLESS this is a JIT process (such as a C Sharp Process). Then FP's are expected.\n");
DPRINT("[Information] Opcode observed at return address is: 0x%02X\n", hs.opcode);
//_tprintf(TEXT("[!][%s] Potentially tampered return address. Reason (0x%02X) opcode identified as return address\n"), szProcessName, hs.opcode);
//_tprintf(TEXT("[!][%s] Function Frame: %s\n PC Address: 0x%p\n Stack Address: 0x%p\n Return Address: 0x%p\n"), szProcessName, pSymbol->Name, (ULONG64)stack.AddrPC.Offset, (ULONG64)stack.AddrStack.Offset, (ULONG64)stack.AddrReturn.Offset);
nAlerts++;
if (hs.opcode == 0xff) {
DPRINT("[Information] Opcode argument observed at return address is: 0x%02X\n", Validate[1]);
if (hs.opcode == 0xff) {
DPRINT("[!] 0xFF Instruction found. This opcode is generally used for performing calls and jumps. Investigate the argument in a utility like https://defuse.ca/online-x86-assembler.htm and trace the jmp to observe the real caller.\n");
}
}
}
free(bytes);
}
// RtlUserThreadStart has a return address of 0. Where would it return to!
if ((LPVOID)stack.AddrReturn.Offset != 0 && strcmp(pSymbol->Name, "RtlUserThreadStart") == 0) {
DPRINT("This is an alert for an invalid RtlUserThreadStart return address. RtluserThreadStart should never contain a return address.\n");
// printf("[!][%s] Tampered RtlUserThreadStart Address", szProcessName);
nAlerts++;
}
//DPRINT("Last function called is: %s\n", last_funcname.c_str());
/*if ((LPVOID)stack.AddrReturn.Offset != 0) {
SIZE_T read = 0;
void* bytes = malloc(10);
ReadProcessMemory(hProcess, (LPBYTE)stack.AddrReturn.Offset-0x07, bytes, 10, &read);
LPBYTE Validate = (LPBYTE)bytes + 0x07;
Validate = Validate - 0x05;
HDE hs;
hde64_disasm(Validate, &hs);
// Turn this into a case statement using like bytes[7]
if (hs.opcode == 0xE8) {
// Regular call, E8 with relative 4 byte address after
//DPRINT("Opcode is 0x%02X\n", hs.opcode);
//DPRINT("Regular relative call.\n\n");
//DPRINT("[Information] Opcode observed is: 0x%02X\n", hs.opcode);
//DPRINT("[Information] Valid relative call instruction found.\n");
}
else {
//DPRINT("[Information] Opcode observed at -0x05 was: 0x%02X\n", hs.opcode);
// Long Call QORD PTR, REX.W is 1 (or 48 hex) FF Opcode then MODR/M
Validate = Validate - 0x02;
hs = { 0 };
hde64_disasm(Validate, &hs);
if (hs.opcode == 0xff && hs.rex_w == 1) {
//DPRINT("Opcode is 0x%02X\n", hs.opcode);
//DPRINT("rex_w is 0x%02X\n", hs.rex_w);
//DPRINT("Long call.\n\n");
//DPRINT("[Information] Opcode observed is: 0x%02X\n", hs.opcode);
//DPRINT("[Information] rex_w observed is: 0x%02X\n", hs.rex_w);
//DPRINT("[Information] Valid long call qword ptr instruction found.\n");
}
else {
//DPRINT("[Information] Opcode observed at -0x07 was: 0x%02X\n", hs.opcode);
// Short Call QWORD PTR, 2 bytes, FF Opcode then MODR/M
Validate = Validate + 0x1;
hs = { 0 };
hde64_disasm(Validate, &hs);
if (hs.opcode == 0xff && hs.rex_w == 0) {
//DPRINT("Opcode is 0x%02X\n", hs.opcode);
//DPRINT("rex_w is 0x%02X\n", hs.rex_w);
//DPRINT("Short call.\n\n");
//DPRINT("[Information] Opcode observed is: 0x%02X\n", hs.opcode);
//DPRINT("[Information] Valid short call qword ptr instruction found.\n");
}
else {
//DPRINT("[Information] Opcode observed at -0x06 was: 0x%02X\n", hs.opcode);
Validate = Validate + 0x4;
hs = { 0 };
hde64_disasm(Validate, &hs);
if (hs.opcode == 0xff) {
//DPRINT("[Information] Opcode observed is: 0x%02X\n", hs.opcode);
//DPRINT("[Information] Valid call/jmp opcode found.\n");
}
else if (hs.opcode == 0x0f && hs.opcode2 == 0x05) {
//DPRINT("[Information] Opcode observed is: 0x%02X%02X\n", hs.opcode, hs.opcode2);
//DPRINT("[Information] Valid syscall opcode found.\n");
}
else {
DPRINT("Analyzed Function Frame: %s\n PC Address: 0x%p\n Stack Address: 0x%p\n Return Address: 0x%p\n", pSymbol->Name, (ULONG64)stack.AddrPC.Offset, (ULONG64)stack.AddrStack.Offset, (ULONG64)stack.AddrReturn.Offset);
DPRINT("[X] Spoofed return address observed! There is a false caller observed here.\n");
DPRINT("[!] This alert was generated because a call was not observed before the return address, therefore it is incredibly unlikely that this return address is valid.\n");
Validate = Validate + 0x02;
hs = { 0 };
hde64_disasm(Validate, &hs);
DPRINT("[Information] Opcode observed at return address is: 0x%02X\n", hs.opcode);
if (hs.opcode != Validate[1] && hs.opcode == 0xff) {
DPRINT("[Information] Opcode argument observed at return address is: 0x%02X\n", Validate[1]);
if (hs.opcode == 0xff) {
DPRINT("[!] 0xFF Instruction found. This opcode is generally used for performing calls and jumps. Investigate the argument in a utility like https://defuse.ca/online-x86-assembler.htm and trace the jmp to observe the real caller.\n\n");
}
}
else {
DPRINT("\n");
}
}
}
}
}
free(bytes);
}*/
}
free(pSymbol);
//DPRINT("%s", stack_trace.c_str());
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
if (NULL != hProcess) {
CloseHandle(hProcess);
}
if(nAlerts > 0){
_tprintf(TEXT("[!][%s] Suspicious return address. Alerts: (%u)\n"), szProcessName, nAlerts);
}
else {
_tprintf(TEXT("[+][%s] CLEAN!\n"), szProcessName);
}
return;
}
int main(int argc, char* argv[]) {
LARGE_INTEGER timer = ::timer();
int nAlerts = 0;
int ObtainedDebug = LoadPrivilege();
if (ObtainedDebug != 1) {
DPRINT("[X] Failed to get debug privileges, can only handle owned procs.\n");
}
if (argc >= 2) {
std::string pidS = argv[1];
if (check_number(pidS) != true) {
DPRINT("[X] Must provide an integer as a pid.");
exit(0);
}
DWORD pid = atoi(argv[1]);
Analyze(pid);
}else{
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
{
return 1;
}
cProcesses = cbNeeded / sizeof(DWORD);
for (i = 0; i < cProcesses; i++)
{
if (aProcesses[i] != 0)
{
DPRINT("[*] Analyzing PID: %u\n", aProcesses[i]);
Analyze(aProcesses[i]);
}
}
elapsed_time(timer);
}
return 0;
}