mirror of
https://github.com/toneillcodes/dll-research
synced 2026-08-09 13:13:50 +00:00
Adding atomic DLL components for locating the image base and PEB walking
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# DLL Development
|
||||
## Component: `hellow-dll.c`
|
||||
## Component: `hellow-dll`
|
||||
|
||||
`hellow-dll.c` is a minimalistic Dynamic Link Library (DLL) template designed to verify basic DLL loading dynamics and export triggering. Unlike more complex implementations that decouple execution via background threads, this component serves as a direct, synchronized test payload to confirm immediate entry-point execution and explicit export accessibility.
|
||||
- [Detailed Documentation](/documentation/hellow-dll.md)
|
||||
@@ -15,7 +15,7 @@ To compile the source code into a DLL using the Microsoft Visual C++ compiler (`
|
||||
cl.exe /LD hellow-dll.c /Fe:hellow.dll
|
||||
```
|
||||
|
||||
## Component: `notabeacon.c`
|
||||
## Component: `notabeacon`
|
||||
|
||||
`notabeacon.c` is a benign, non-malicious C-based DLL designed to mimic the execution flow and timing footprints typically associated with command-and-control (C2) agents or beacons. It serves as a safe testing utility for verifying detection pipelines, telemetry collection, and binary side-loading or manual mapping techniques without introducing actual risk to the environment.
|
||||
- [Detailed Documentation](/documentation/notabeacon.md)
|
||||
@@ -31,4 +31,79 @@ To compile the source code into a DLL using the Microsoft Visual C++ compiler (`
|
||||
|
||||
```cmd
|
||||
cl.exe /LD notabeacon.c /Fe:notabeacon.dll
|
||||
```
|
||||
|
||||
## Component 'whereami'
|
||||
|
||||
`whereami-lib.c` todo: write summary
|
||||
- Detailed Documentation
|
||||
|
||||
`whereami.c` todo: write summary
|
||||
- Detailed Documentation
|
||||
|
||||
### Compilation
|
||||
First, compile the whereami-lib source code into a DLL using the Microsoft Visual C++ compiler (`cl.exe`), execute the following command in a Developer Command Prompt:
|
||||
|
||||
|
||||
## Component 'whereami'
|
||||
|
||||
`pebwalk-lib.c` todo: write summary
|
||||
- Detailed Documentation
|
||||
|
||||
`pebwalk.c` todo: write summary
|
||||
- Detailed Documentation
|
||||
|
||||
### Compilation
|
||||
First, compile the whereami-lib source code into a DLL using the Microsoft Visual C++ compiler (`cl.exe`), execute the following command in a Developer Command Prompt:
|
||||
|
||||
```cmd
|
||||
c:\dev\dll-research\dll-development>cl /LD whereami-lib.c
|
||||
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27054 for x64
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
whereami-lib.c
|
||||
Microsoft (R) Incremental Linker Version 14.16.27054.0
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
/out:whereami-lib.dll
|
||||
/dll
|
||||
/implib:whereami-lib.lib
|
||||
whereami-lib.obj
|
||||
Creating library whereami-lib.lib and object whereami-lib.exp
|
||||
|
||||
c:\dev\dll-research\dll-development>
|
||||
```
|
||||
|
||||
Second, compile the wheream source code into an executable using the Microsoft Visual C++ compiler (`cl.exe`), execute the following command in a Developer Command Prompt:
|
||||
|
||||
```cmd
|
||||
c:\dev\dll-research\dll-development>cl.exe whereami.c
|
||||
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27054 for x64
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
whereami.c
|
||||
Microsoft (R) Incremental Linker Version 14.16.27054.0
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
/out:whereami.exe
|
||||
whereami.obj
|
||||
|
||||
c:\dev\dll-research\dll-development>
|
||||
```
|
||||
|
||||
### Execution
|
||||
|
||||
```cmd
|
||||
c:\dev\dll-research\dll-development> whereami.exe
|
||||
[test] Loading whereami-lib.dll...
|
||||
[test] DLL loaded at: 00007FF849740000
|
||||
[test] Resolving target function
|
||||
[test] Target function found at: 00007FF849741010
|
||||
[test] Calling WhereAmI...
|
||||
|
||||
[whereami] Found my base address: 00007FF849740000
|
||||
|
||||
[test] Done.
|
||||
|
||||
c:\dev\dll-research\dll-development>
|
||||
```
|
||||
@@ -0,0 +1,262 @@
|
||||
// cl.exe /LD pebwalk-lib.c
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pebwalk-lib.h"
|
||||
|
||||
#define LOCAL_PROCESS_HANDLE ((HANDLE)(LONG_PTR)-1)
|
||||
|
||||
typedef struct _FULL_LDR_DATA_TABLE_ENTRY {
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
ULONG Flags;
|
||||
WORD ObsoleteLoadCount;
|
||||
WORD TlsIndex;
|
||||
LIST_ENTRY HashLinks;
|
||||
ULONG TimeDateStamp;
|
||||
} FULL_LDR_DATA_TABLE_ENTRY, *PFULL_LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// Custom layout ensuring BaseDllName is present regardless of SDK versions
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY_COMPAT {
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
} LDR_DATA_TABLE_ENTRY_COMPAT, *PLDR_DATA_TABLE_ENTRY_COMPAT;
|
||||
|
||||
#pragma intrinsic(__readgsqword)
|
||||
|
||||
__declspec(dllexport)
|
||||
void WalkPEB(void)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
PPEB peb = (PPEB)__readgsqword(0x60);
|
||||
#else
|
||||
PPEB peb = (PPEB)__readfsdword(0x30);
|
||||
#endif
|
||||
|
||||
if (!peb || !peb->Ldr) {
|
||||
printf("[walkpeb] Failed to read PEB or Ldr.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
LIST_ENTRY* head = &peb->Ldr->InMemoryOrderModuleList;
|
||||
LIST_ENTRY* curr = head->Flink;
|
||||
|
||||
printf("[walkpeb] Walking PEB module list:\n");
|
||||
|
||||
while (curr != head)
|
||||
{
|
||||
PFULL_LDR_DATA_TABLE_ENTRY entry =
|
||||
CONTAINING_RECORD(curr, FULL_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
|
||||
|
||||
printf(" Base: %p | Name: %ws\n",
|
||||
entry->DllBase,
|
||||
entry->BaseDllName.Buffer);
|
||||
|
||||
curr = curr->Flink;
|
||||
}
|
||||
}
|
||||
|
||||
PVOID GetPebAddressGeneric(HANDLE hProcess) {
|
||||
// --- Bootstrap our own API resolution via the local PEB ---
|
||||
// Get the local PEB base address silently
|
||||
if (hProcess == NULL || hProcess == LOCAL_PROCESS_HANDLE || hProcess == GetCurrentProcess()) {
|
||||
#ifdef _WIN64
|
||||
return (PVOID)__readgsqword(0x60);
|
||||
#else
|
||||
return (PVOID)__readfsdword(0x30);
|
||||
#endif
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Seamlessly handles memory read operations for both local and remote execution
|
||||
BOOL ReadMemoryInternal(HANDLE hProcess, PVOID baseAddress, PVOID localBuffer, SIZE_T size) {
|
||||
if (hProcess == NULL || hProcess == LOCAL_PROCESS_HANDLE) {
|
||||
__try {
|
||||
memcpy(localBuffer, baseAddress, size);
|
||||
return TRUE;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return FALSE;
|
||||
}
|
||||
} else {
|
||||
SIZE_T bytesRead = 0;
|
||||
return ReadProcessMemory(hProcess, baseAddress, localBuffer, size, &bytesRead) && (bytesRead == size);
|
||||
}
|
||||
}
|
||||
|
||||
PVOID GetModuleBaseManualGeneric(HANDLE hProcess, PVOID pebAddr, const char* targetModuleName) {
|
||||
PEB localPeb = { 0 };
|
||||
if (!ReadMemoryInternal(hProcess, pebAddr, &localPeb, sizeof(PEB))) return NULL;
|
||||
if (!localPeb.Ldr) return NULL;
|
||||
|
||||
PEB_LDR_DATA localLdr = { 0 };
|
||||
if (!ReadMemoryInternal(hProcess, localPeb.Ldr, &localLdr, sizeof(PEB_LDR_DATA))) return NULL;
|
||||
|
||||
PVOID remoteListHead = (BYTE*)localPeb.Ldr + offsetof(PEB_LDR_DATA, InMemoryOrderModuleList);
|
||||
LIST_ENTRY currentEntry = localLdr.InMemoryOrderModuleList;
|
||||
|
||||
WCHAR targetNameWide[MAX_PATH] = { 0 };
|
||||
MultiByteToWideChar(CP_ACP, 0, targetModuleName, -1, targetNameWide, MAX_PATH);
|
||||
|
||||
while (currentEntry.Flink != remoteListHead) {
|
||||
PVOID tableEntryAddr = CONTAINING_RECORD(currentEntry.Flink, LDR_DATA_TABLE_ENTRY_COMPAT, InMemoryOrderLinks);
|
||||
LDR_DATA_TABLE_ENTRY_COMPAT moduleEntry = { 0 };
|
||||
|
||||
if (!ReadMemoryInternal(hProcess, tableEntryAddr, &moduleEntry, sizeof(LDR_DATA_TABLE_ENTRY_COMPAT))) break;
|
||||
|
||||
if (moduleEntry.BaseDllName.Buffer && moduleEntry.BaseDllName.Length < (MAX_PATH * sizeof(WCHAR))) {
|
||||
WCHAR localNameBuffer[MAX_PATH] = { 0 };
|
||||
|
||||
if (ReadMemoryInternal(hProcess, moduleEntry.BaseDllName.Buffer, localNameBuffer, moduleEntry.BaseDllName.Length)) {
|
||||
localNameBuffer[moduleEntry.BaseDllName.Length / sizeof(WCHAR)] = L'\0';
|
||||
|
||||
if (_wcsicmp(localNameBuffer, targetNameWide) == 0) {
|
||||
return moduleEntry.DllBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentEntry = moduleEntry.InMemoryOrderLinks;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// PE validation function to parse and verify DOS and NT headers safely
|
||||
BOOL GetPEHeaders(HANDLE hProcess, PVOID moduleBase, IMAGE_DOS_HEADER* outDos, IMAGE_NT_HEADERS* outNt) {
|
||||
if (!ReadMemoryInternal(hProcess, moduleBase, outDos, sizeof(IMAGE_DOS_HEADER))) return FALSE;
|
||||
if (outDos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;
|
||||
|
||||
PVOID ntHeadersAddr = (BYTE*)moduleBase + outDos->e_lfanew;
|
||||
if (!ReadMemoryInternal(hProcess, ntHeadersAddr, outNt, sizeof(IMAGE_NT_HEADERS))) return FALSE;
|
||||
if (outNt->Signature != IMAGE_NT_SIGNATURE) return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName) {
|
||||
return GetModuleBaseManualGeneric(LOCAL_PROCESS_HANDLE, (PVOID)pebObject, targetModuleName);
|
||||
}
|
||||
|
||||
PVOID GetProcAddressManualGeneric(HANDLE hProcess, PVOID moduleBase, const char* functionName, WORD ordinal) {
|
||||
IMAGE_DOS_HEADER dosHeader = { 0 };
|
||||
IMAGE_NT_HEADERS ntHeaders = { 0 };
|
||||
if (!GetPEHeaders(hProcess, moduleBase, &dosHeader, &ntHeaders)) return NULL;
|
||||
|
||||
IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (exportDataDir.VirtualAddress == 0) return NULL;
|
||||
|
||||
IMAGE_EXPORT_DIRECTORY exportDir = { 0 };
|
||||
PVOID exportDirAddr = (BYTE*)moduleBase + exportDataDir.VirtualAddress;
|
||||
if (!ReadMemoryInternal(hProcess, exportDirAddr, &exportDir, sizeof(IMAGE_EXPORT_DIRECTORY))) return NULL;
|
||||
|
||||
if (functionName == NULL) {
|
||||
// Resolve via Ordinal
|
||||
DWORD functionIndex = ordinal - exportDir.Base;
|
||||
if (functionIndex >= exportDir.NumberOfFunctions) return NULL;
|
||||
|
||||
DWORD funcRVA = 0;
|
||||
PVOID funcRVAAddr = (BYTE*)moduleBase + exportDir.AddressOfFunctions + (functionIndex * sizeof(DWORD));
|
||||
if (!ReadMemoryInternal(hProcess, funcRVAAddr, &funcRVA, sizeof(DWORD))) return NULL;
|
||||
|
||||
return (BYTE*)moduleBase + funcRVA;
|
||||
}
|
||||
|
||||
// Resolve via Name (Binary Search)
|
||||
DWORD* nameTable = (DWORD*)malloc(exportDir.NumberOfNames * sizeof(DWORD));
|
||||
WORD* ordinalTable = (WORD*)malloc(exportDir.NumberOfNames * sizeof(WORD));
|
||||
if (!nameTable || !ordinalTable) {
|
||||
free(nameTable); free(ordinalTable);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ReadMemoryInternal(hProcess, (BYTE*)moduleBase + exportDir.AddressOfNames, nameTable, exportDir.NumberOfNames * sizeof(DWORD));
|
||||
ReadMemoryInternal(hProcess, (BYTE*)moduleBase + exportDir.AddressOfNameOrdinals, ordinalTable, exportDir.NumberOfNames * sizeof(WORD));
|
||||
|
||||
int low = 0;
|
||||
int high = exportDir.NumberOfNames - 1;
|
||||
PVOID functionAddress = NULL;
|
||||
|
||||
while (low <= high) {
|
||||
int mid = low + (high - low) / 2;
|
||||
char currentName[256] = { 0 };
|
||||
|
||||
PVOID nameAddress = (BYTE*)moduleBase + nameTable[mid];
|
||||
ReadMemoryInternal(hProcess, nameAddress, currentName, sizeof(currentName) - 1);
|
||||
|
||||
int cmp = strcmp(functionName, currentName);
|
||||
if (cmp == 0) {
|
||||
WORD ordinalValue = ordinalTable[mid];
|
||||
DWORD funcRVA = 0;
|
||||
PVOID funcRVAAddr = (BYTE*)moduleBase + exportDir.AddressOfFunctions + (ordinalValue * sizeof(DWORD));
|
||||
|
||||
if (ReadMemoryInternal(hProcess, funcRVAAddr, &funcRVA, sizeof(DWORD))) {
|
||||
if (funcRVA >= exportDataDir.VirtualAddress && funcRVA < (exportDataDir.VirtualAddress + exportDataDir.Size)) {
|
||||
printf("[!] Warning: Forwarded export detected.\n");
|
||||
break;
|
||||
}
|
||||
functionAddress = (BYTE*)moduleBase + funcRVA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cmp < 0) high = mid - 1;
|
||||
else low = mid + 1;
|
||||
}
|
||||
|
||||
free(nameTable);
|
||||
free(ordinalTable);
|
||||
return functionAddress;
|
||||
}
|
||||
|
||||
void* GetProcAddressManual(HANDLE hProcess, void* moduleBase, const char* functionName)
|
||||
{
|
||||
return GetProcAddressManualGeneric(
|
||||
hProcess,
|
||||
moduleBase,
|
||||
functionName,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
__declspec(dllexport)
|
||||
void DemoKernel32Exports(void)
|
||||
{
|
||||
PPEB peb = (PPEB)GetPebAddressGeneric(GetCurrentProcess());
|
||||
// todo: stack-based string construction
|
||||
char k32libraryName[] = { 'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
|
||||
void* kernelBase = GetModuleBaseManual(peb, k32libraryName);
|
||||
|
||||
// todo: hash values
|
||||
// "LoadLibraryA" on the stack (13 bytes total)
|
||||
char loadLibraryAName[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
|
||||
void* loadLib = GetProcAddressManual(
|
||||
LOCAL_PROCESS_HANDLE,
|
||||
kernelBase,
|
||||
loadLibraryAName
|
||||
);
|
||||
|
||||
// todo: hash values
|
||||
// "GetProcAddress" on the stack (15 bytes total)
|
||||
char getProcAddressName[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '\0' };
|
||||
void* getProc = GetProcAddressManual(
|
||||
LOCAL_PROCESS_HANDLE,
|
||||
kernelBase,
|
||||
getProcAddressName
|
||||
);
|
||||
|
||||
printf("LoadLibraryA: %p\n", loadLib);
|
||||
printf("GetProcAddress: %p\n", getProc);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// pebwalk-lib.h
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
__declspec(dllexport)
|
||||
void WalkPEB(void);
|
||||
|
||||
__declspec(dllexport)
|
||||
void DemoKernel32Exports(void);
|
||||
|
||||
PVOID GetPebAddressGeneric(HANDLE hProcess);
|
||||
PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName);
|
||||
PVOID GetModuleBaseManualGeneric(HANDLE hProcess, PVOID pebAddr, const char* targetModuleName);
|
||||
BOOL ReadMemoryInternal(HANDLE hProcess, PVOID baseAddress, PVOID localBuffer, SIZE_T size);
|
||||
@@ -0,0 +1,31 @@
|
||||
// cl.exe pebwalk.c
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void (*FnWalkPEB)(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("[test] Loading pebwalk-lib.dll...\n");
|
||||
|
||||
HMODULE hPebWalkMod = LoadLibraryA("pebwalk-lib.dll");
|
||||
if (!hPebWalkMod) {
|
||||
printf("[test] Failed to load pebwalk DLL.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[test] pebwalk-lib.dll DLL loaded at: %p\n", hPebWalkMod);
|
||||
|
||||
FnWalkPEB WalkPEB = (FnWalkPEB)GetProcAddress(hPebWalkMod, "WalkPEB");
|
||||
if (!WalkPEB) {
|
||||
printf("[test] Failed to resolve WalkPEB export.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[test] Calling WalkPEB...\n\n");
|
||||
WalkPEB();
|
||||
|
||||
printf("\n[test] Done.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// cl.exe /LD whereami-lib.c
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
__declspec(noinline) void* GetCurrentAddress() {
|
||||
return _ReturnAddress();
|
||||
}
|
||||
|
||||
__declspec(dllexport)
|
||||
void WhereAmI(void)
|
||||
{
|
||||
BYTE* cursor = (BYTE*)GetCurrentAddress();
|
||||
|
||||
// Walk backward until we find 'MZ'
|
||||
while (cursor > (BYTE*)0x10000) // avoid low memory
|
||||
{
|
||||
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)cursor;
|
||||
|
||||
if (dos->e_magic == IMAGE_DOS_SIGNATURE) // 'MZ'
|
||||
{
|
||||
// Validate NT header
|
||||
IMAGE_NT_HEADERS* nt =
|
||||
(IMAGE_NT_HEADERS*)(cursor + dos->e_lfanew);
|
||||
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) // 'PE\0\0'
|
||||
{
|
||||
printf("[whereami] Found my base address: %p\n", cursor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cursor--;
|
||||
}
|
||||
|
||||
printf("[whereami] Failed to locate image base.\n");
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved)
|
||||
{
|
||||
if (reason == DLL_PROCESS_ATTACH)
|
||||
DisableThreadLibraryCalls(hinst);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// cl.exe whereami.c
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void (*FnWhereAmI)(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("[test] Loading whereami-lib.dll...\n");
|
||||
|
||||
HMODULE hMod = LoadLibraryA("whereami-lib.dll");
|
||||
if (!hMod) {
|
||||
printf("[test] Failed to load DLL.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[test] DLL loaded at: %p\n", hMod);
|
||||
printf("[test] Resolving target function\n");
|
||||
FnWhereAmI WhereAmI = (FnWhereAmI)GetProcAddress(hMod, "WhereAmI");
|
||||
if (!WhereAmI) {
|
||||
printf("[test] Failed to resolve WhereAmI export.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("[test] Target function found at: %p\n", WhereAmI);
|
||||
|
||||
printf("[test] Calling WhereAmI...\n\n");
|
||||
|
||||
WhereAmI();
|
||||
|
||||
printf("\n[test] Done.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// cl.exe loader.c
|
||||
// cl.exe pe-mapper.c
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
Reference in New Issue
Block a user