//
// dumpster_ordinals.cpp
// https://pastebin.com/C3qcNMbh
//
// See Homies Twee t relating to cli obfus in Windows
/*
https://x.com/sixtyvividtails/status/1795410561775095816
https://x.com/sixtyvividtails/status/1797667842185372084
https://x.com/sixtyvividtails/status/1825951974153662711
https://x.com/malmoeb/status/1825888362265182578
*/
// Compile via msvc:
// cl /O2 /GS- /Brepro dumpster_ordinals.cpp /link /dll /nocoffgrpinfo /noentry /out:dumpster_ordinals.dll
//
// Self-test via rundll32:
// rundll32.exe %__CD__%\dumpster_ordinals.dll,test
//
#define _NO_CRT_STDIO_INLINE
#include <stdio.h>
#include <Windows.h>
#pragma comment(lib, "ntdllp") // you might need wdk for this
#pragma comment(lib, "user32")
#pragma comment(lib, "kernel32")
#pragma execution_character_set("utf-8")
// round 1
#pragma comment(linker, "/export:A=advapi32.dll.#1212,@1") // with dll extension (and we enforce our ordinal)
#pragma comment(linker, "/export:B=advapi32.#+4294968508!") // overflows uint32 into 1212
// "advapi32.#▲ ♫1212*1337", linker directive quoted due to " "; RtlCharToInteger skips leading chars with ords <= 0x20
#pragma comment(linker, "\"/export:C=advapi32.#\x1E \x0E" "1212*1337\"")
#pragma comment(linker, "/export:D=advapi32.#-0x44FFFFFB44") // overflows and then gets negated
// round 2
#pragma comment(linker, "/export:E=advapi32.dll::$DATA.#1212") // use ads streams (here unnamed default)
#pragma comment(linker, "/export:F=C:\\windows\\system32\\advapi32.#1212") // full path
#pragma comment(linker, "/export:G=\\windows\\system32\\advapi32.#1212") // ok only when current drive is C:
#pragma comment(linker, "/export:H=C:advapi32.#1212") // 👎 doesn't work even if cwd is %system32%
// round 2 with editing own export (only coz msvc linker doesn't allow to build such file statically)
#pragma comment(linker, "/export:J=dumpster_ordinals.#") // 👎 means ordinal is 0; but 0 ordinal doesn't work
#pragma comment(linker, "/export:K=dumpster_ordinals.#0x13370001") // not an overflow(!): ok when ordinalBase 0x13370001
// for rundll32.exe test
#pragma comment(linker, "/export:test")
#pragma comment(linker, "/alternatename:test=_test@16") // needed for x32 only
extern "C"
IMAGE_DOS_HEADER __ImageBase; // linker-defined
static void test_round2()
{
HMODULE ownImage = (HMODULE)&__ImageBase;
char funcName[2]{"E"};
void* funcs[6];
for (int i = 0; i < _countof(funcs); ++i, ++funcName[0])
funcs[i] = GetProcAddress(ownImage, funcName);
// prepare to edit our own export table, to change ordinal base first to 0, and then to 0x13370001;
// it doesn't have to be edited dynamically, it's just msvc linker doesn't allow us to build it statically
auto* peHeader = (const IMAGE_NT_HEADERS*)(PBYTE(ownImage) + __ImageBase.e_lfanew);
UINT exportRva = peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
auto* exports = (IMAGE_EXPORT_DIRECTORY*)(PBYTE(ownImage) + exportRva);
ULONG dummyOldProtect;
VirtualProtect(exports, sizeof(*exports), PAGE_READWRITE, &dummyOldProtect);
UINT origOrdinalBase = exports->Base;
void* funcsX[2];
exports->Base = 0; // 'J' still won't resolve, but only because LdrpGetProcedureAddress bans ordinal 0
funcsX[0] = GetProcAddress(ownImage, "J");
exports->Base = 0x13370001; // 'K' should resolve now
funcsX[1] = GetProcAddress(ownImage, "K");
// note if module has OrdinalBase larger than 0xFFFF in its export table, you won't be able to resolve any of its
// funcs by ordinals via GetProcAddress; however, you can use native ntdll!LdrGetProcedureAddressForCaller for that
exports->Base = origOrdinalBase;
wchar_t buf[0x740];
swprintf_s(buf, _countof(buf),
LR"(Secret round2 discovered!
%p "E" > advapi32.dll::$DATA.#1212
%p "F"
> C:\windows\system32\advapi32.#1212
%p "G"
> \windows\system32\advapi32.#1212
should work iff "C:" is current drive
%p "H" > C:advapi32.#1212
does NOT work even if %%system32%% is cwd
%p "J" > dumpster_ordinals.#
%p "J" > dumpster_ordinals.#
^ OrdinalBase is 0 - still does NOT work
%p "K" > dumpster_ordinals.#0x13370001
%p "K" > dumpster_ordinals.#0x13370001
^ OrdinalBase is 0x13370001 - should work okay)",
funcs[0], funcs[1], funcs[2], funcs[3],
funcs[4], funcsX[0],
funcs[5], funcsX[1]);
MessageBoxW({}, buf, L"dumpster ordinals", MB_OK|MB_ICONINFORMATION|MB_SETFOREGROUND);
}
extern "C"
void __stdcall test(HWND, HINSTANCE, LPSTR, int)
{
void* funcs[4];
char funcName[2]{"A"};
unsigned exportsResolved = 0;
for (int i = 0; i < _countof(funcs); exportsResolved += !!funcs[i], ++i, ++funcName[0])
funcs[i] = GetProcAddress((HMODULE)&__ImageBase, funcName);
// may only retrieve image base like that once we've resolved forwarded exports
HMODULE advapi = GetModuleHandleA("advapi32");
void* testFuncs[3]{};
if (advapi)
{
testFuncs[0] = GetProcAddress(advapi, "CryptGenRandom");
testFuncs[1] = GetProcAddress(advapi, (LPCSTR)1212);
testFuncs[2] = GetProcAddress(advapi, (LPCSTR)1213);
}
wchar_t buf[0x400];
swprintf_s(buf, _countof(buf),
LR"(%p advapi32.dll
%p dumpster_ordinals.dll
------------------------------------------------
Sanity test; exports resolved via
GetProcAddress(advapi32):
%p "CryptGenRandom"
%p #1212
%p #1213
------------------------------------------------
------------------------------------------------
Actual test; exports resolved via
GetProcAddress(dumpster_ordinals):
%p "A" > advapi32.dll.#1212
%p "B" > advapi32.#+4294968508!
%p "C" > advapi32.#▲ ♫1212*1337
%p "D" > advapi32.#-0x44FFFFFB44
------------------------------------------------
RESULT: %u/4 weird exports resolved.)",
advapi, &__ImageBase,
testFuncs[0], testFuncs[1], testFuncs[2],
funcs[0], funcs[1], funcs[2], funcs[3], exportsResolved);
// Note we cheat a bit with visual representation of variant C - we use higher Unicode chars. That doesn't affect
// our export table, sequence "\x1E\x20\x0E" there still consists of just 3 bytes. !▲ ♫! << Unicode
// This comment line contains actual lower ansi chars just to test your environment. ! ! << Ansi
int rez = MessageBoxW({}, buf, L"dumpster ordinals",
MB_CANCELTRYCONTINUE|MB_DEFBUTTON2|MB_ICONINFORMATION|MB_SETFOREGROUND);
if (rez == IDTRYAGAIN)
test_round2();
}
Comments
0 B
|0 👍
/0 👎