W ostatnim poście pisałem o tym jak napisać shellcode na Windowsa (32 bitowego). Aby sprawdzić nasz kod posłużyłem się programem asmloader autorstwa Gynvaela Coldwinda, został on napisany aby pomóc początkującym w nauce assembly (x86). Spróbujmy napisać taki program samemu, aby dopełnić dzieło. Więc co taki program będzie robić ? Pomysł jest prosty :

  • alokacja pamięci (wraz z flagą PAGE_EXECUTE_READWRITE, która m.in. ustawi atrybut wykonywalności strony pamięci w której znajduje się nasz zaalokowany obszar)
  • otwarcie pliku z naszym shellcodem oraz skopiowanie go do wcześniej zaalokowanej pamięci.
  • skok programu w nasz zaalokowany obszar.
#include <windows.h>
#include <stdio.h>
int main (int argc, char ** argv)
{
  if (argc < 2)
  {
    puts ("Usage : runShellcode.exe <shellcode.bin>");
    return -1;
  }
  FILE * f = fopen (argv[1],"rb");
  fseek (f,0,SEEK_END);
  int size = ftell (f);
  fseek (f,0,SEEK_SET);
  void * buf = VirtualAlloc (NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
  SetConsoleTextAttribute (GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_BLUE | FOREGROUND_GREEN); // Ustaw kolor tekstu na ~ (0,180,180) (RGB)
  printf ("[-] Code loaded at 0x%.08x (%d bytes)",buf,size);
  SetConsoleTextAttribute (GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); // Ustaw kolor na standardowy
  fread (buf,1,0x1000,f);
  void (*func)(void) = (void(*)(void))buf;
  func();
  return 0;
}

Flaga MEM_RESERVE oznacza że system rezerwuje dany zakres pamięci bez alokacji jej (Można zaalokować nawet 8 TB na x64 ale ta pamięć nie jest „podpięta” do pamięci fizycznej (lub pliku stronicowania)). W użyciu wraz z flagą (poprzez bitowy operator alternatywy, (tak zazwyczaj się łączy flagi)) MEM_COMMIT pozwala na operowanie tą pamięcią. Ciekawostka : wszystkie funkcje alokujące typu „malloc” czy new [] z C++ (który i tak wykonuje malloc 😜 ) ostatecznie prowadzą do wykonania VirtualAlloc.

Następnie czytamy funkcją fread z pliku do naszej zaalokowanej pamięci. Następnie przypisujemy adres zaalokowanej pamięci rzutowanej na wskaźnik na funkcję do wskaźnika na funkcję którą będziemy mogli operować. Ostatecznie wykonujemy tą funkcje. Dla systemów *nix’owych (np. Linux czy Mac OS) kod wyglądałby następująco :

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
int main (int argc, char ** argv)
{
        FILE * f = fopen (argv[1],"rb"); // otwieramy plik binarnie
    if (f == NULL)
    {
      printf ("Nie mozna otworzyc pliku \n");
      return -1;
    }
    fseek (f,0,SEEK_END); // ustawiamy sie na koncu pliku aby sprawdzic jego rozmiar
    int size = ftell(f); // sprawdzamy dlugosc shellcode
    fseek (f,0,SEEK_SET); // wracamy na poczatek pliku
    void * buf = mmap (NULL,0x1000,PROT_EXEC|PROT_READ|PROT_WRITE,MAP_PRIVATE,fileno(f),0); // korzystamy z analogicznej funkcji do VirtualAlloc, nadajemy alokowanej pamieci atrybut wykonywalnosci, oraz sama funkcja mmap „mapuje” zawartosc pliku ktory przeczytalismy do pamieci
    if (buf == MAP_FAILED)
    {
      printf("Nie mozna zaalokowac pamieci\n");
      return -2;
    }

    printf ("\n\033[22;36mCode loaded at 0x%.08x (%d bytes)\033[0m\n\n”,buf,size); //"
        void (*pshellcode)(void) = (void(*)(void))buf;
        pshellcode();
        return 0;
}

Nie opisałem na jakiej zasadzie dodaje niestandardowy kolor w terminalu, ale myślę że to dobry temat na kolejny wpis 😃 shiftleft.bin to krótki kod assembly z mojego innego eksperymentu, który można traktować jako funkcje która przesuwa bitowo o RSI (argument drugi) argument pierwszy (RDI). Z ABI (Application Binary Interface) System V (z którego korzystają systemy *nix’owe) wynika że argumenty funkcji są przekazywane przez rejestry (a potem na stosie) w następującej kolejności : RDI, RSI, RDX, RCX, R8, R9.

[bits 64]
mov rcx,rsi
shl rdi,cl
mov rax,rdi
ret

Miłego haczenia ! 🤪