Generer selvstændige monteringsunderrutiner med opkald til Windows API-funktioner fra C/C ++

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har lavet nogle eksperimenter med udførelse af shellcode, hvor jeg skrev min egen shellcode, skriv den i målprogrammets hukommelse, hvor jeg vil have det henrettet og udføre det med enten en ny tråd eller trådkapning.


Dette virker godt, men manuelt at skrive shellcode er ret tidskrævende. Derfor søger jeg en metode til at skrive en funktion i C eller C ++, der vil være helt selvstændig, når den er udarbejdet. Det betyder, at en kompileret funktion skal kunne udføres selvstændigt. På denne måde kan jeg skrive det direkte til mit målprogram, der er klar til at udføres med WriteProcessMemory, for eksempel. Skubskoden skal derfor udføres med en kode som denne:


#include <Windows.h>
#include <iostream>

using namespace std;

BOOL MakeABeep() {
    return Beep(0x500, 0x500);
}
DWORD MakeABeepEnd() { return 0; }

int main() {
    DWORD pid = 0;
    cout << "PID: ";
    cin >> dec >> pid;
    HANDLE hProcess = OpenProcess(PROCESS\_ALL\_ACCESS, FALSE, pid);
    if (!hProcess) { cout << "OpenProcess failed GLE = " << dec << GetLastError() << endl; return EXIT\_FAILURE; }

    void* buf = VirtualAllocEx(hProcess, NULL, 4096, MEM\_COMMIT, PAGE\_EXECUTE\_READWRITE);
    if (!buf) { cout << "VirtualAllocEx failed GLE = " << dec << GetLastError() << endl; return EXIT\_FAILURE; }

    SIZE\_T size = (DWORD64)MakeABeep - (DWORD64)MakeABeepEnd;
    BOOL wpmStatus = WriteProcessMemory(hProcess, buf, MakeABeep, size, NULL);
    if (!wpmStatus) { cout << "WriteProcessMemory failed GLE = " << dec << GetLastError() << endl; return EXIT\_FAILURE; }

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD\_START\_ROUTINE)buf, NULL, NULL, NULL);
    if (!hThread) { cout << "CreateRemoteThread failed GLE = " << dec << GetLastError() << endl; return EXIT\_FAILURE; }
    WaitForSingleObject(hThread, INFINITE);

    VirtualFreeEx(hProcess, buf, 0, MEM\_RELEASE);
    return EXIT\_SUCCESS;
}


Hvis det kompileres med standardindstillingerne for MSVC compiler, kopieres kun en flok jmp instruktioner, som synes at være et springbord. For at undgå dette problem deaktiverede jeg inkremental sammenkædning i kompilatorindstillingerne, og nu er en kode i funktionen MakeABeep kopieret korrekt, med undtagelse af opkald til importerede funktioner.


I min shellkode passerer jeg argumenterne som krævet af opkaldskonventionen, og så sætter jeg adressen på den funktion, jeg vil kalde op i registret rax og endelig kalder jeg funktionen med call rax.


Er det muligt at få kompilatoren til at generere noget sådan?
Det vigtigste er, at den genererede binære skal have selvstændige, kun subrutiner, der kunne udføres uafhængigt.


For eksempel er dette samlingskoden genereret til funktionen MakeABeep:
MakeABeep Assembly code
For at kunne køre det direkte, i stedet for dette mov rax, QWORD PTR [rip+0x?], skal kompilatoren flytte den fulde adresse til bip-funktionen i rax i stedet. [12]


Venligst ignorere problemerne i forbindelse med de moduler, der muligvis ikke er indlæst eller indlæst på en anden adresse i målprogrammet. Jeg har kun til hensigt at kalde funktioner i kernel32 og ntdll, som sikkert er indlæst og på samme adresse i forskellige processer.


Tak for din hjælp.

Bedste reference


Kompilatoren kender ikke den fulde adresse til bip-funktionen. Beep-funktionen lever i kernel32.dll, og denne .DLL er markeret som ASLR-kompatibel og kan i teorien ændre sin adresse hver gang du kører programmet. Der er ingen kompilatorfunktion, der giver dig mulighed for at generere den rigtige adresse til en funktion i en .DLL, fordi en sådan funktion er temmelig ubrugelig. [13]


En mulighed, jeg kan tænke på, er at bruge magiske cookies, som du erstatter med de rigtige funktionsadresser i løbet af tiden:


SIZE\_T beepaddr = 0xff77ffffffff7001ull; // Magic value
((BOOL(WINAPI*)(DWORD,DWORD))beepaddr)(0x500, 0x500); // Call Beep()


kompilerer til


00011   b9 00 05 00 00   mov     ecx, 1280      ; 00000500H
00016   48 b8 01 70 ff ff ff ff 77 ff    mov     rax, -38280596832686079    ; ff77ffffffff7001H
00020   8b d1        mov     edx, ecx
00022   ff d0        call    rax


Du skal derefter skrive en wrapper omkring WriteProcessMemory, der ved, hvordan man finder og erstatter disse magiske værdier med den rigtige adresse.


Nogle shell-kode vil have sin egen mini-implementering af GetModuleHandle og GetProcAddress, hvor den ser modulet op i PEB-modullisten og derefter søger i eksportmappen. De bruger ofte en mini hashing-funktion til navnene, så de ikke behøver at håndtere strenge.


Hvis du injicerer en stor mængde kode, vil du nok blive træt af disse hacks og bare indlæse en .DLL i den eksterne proces som alle andre.