Pytanie o wykonanie shellcodu w C

0

Mam taki shellcode, który uruchamia powłoke na Linuksie:

// shellcode.c
char shellcode[] =
    “\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46”
    “\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1”
    “\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68”;
int main()
{
    int *ret;
    ret = (int *)&ret + 4;
    (*ret) = (int)shellcode;
}

Wnioskuję, że shellcode jest załadowany do pamięci 4 bajty zaraz za wskaźnikiem, bo taki jest rozmiar wskaźnika.
Czyli tak:

  1. deklaruje wskaźnik
  2. przypisuje mu adres samego siebie + 4
  3. pod adres, na który wskazuje wskaźnik (czyli adres samego wskaźnika + 4) umieszczam shellcode

Czy ten shellcode zostanie automatycznie uruchomiony czy trzeba wywołać jeszcze jakąś funkcje?
Tak sobie myślę, że dlaczego miałby się sam uruchomić, skoro jest tylko w pamięci. I jeśli tak jest to jak go wywołać?

2

Stos "rosnie w dol" czyli ret = (int *)&ret + 4; pod targetowana architektura wskazuje na adres powrotu a nastepnie podmieniasz adres powrotu na adres tablicy shellcode.

https://stackoverflow.com/que[...]plain-this-code-to-me/2705871

1

Wnioskuję, że shellcode jest załadowany do pamięci 4 bajty zaraz za wskaźnikiem (...)

Nie polegałbym na tym zbytnio. Zerknij w debugger, a następnie w pamięć binarki po jej skompilowaniu to będziesz znał dokładną lokalizację shellcode'u i wskaźnika. Z tym, że w Twoim przypadku shellcode jest w buforze globalnym. Pod Windowsem na przykład ten shellcode znalazłby się w sekcji .data, w każdym razie na pewno nie trafiłby na stos. Pod Linuxem tak samo.

Czy ten shellcode zostanie automatycznie uruchomiony czy trzeba wywołać jeszcze jakąś funkcje?

Skąd pomysł, że shellcode się sam magicznie uruchomi? Jak exploitujesz jakąś binarkę to nadpisujesz jej pamięć shellcodem (jest to jeden z wariantów ataków) i dodatkowo nadpisujesz też adres powrotu, którejś z funkcji programu, żeby zmienić jego execution flow i przekierować wykonanie na ten shellcode właśnie. Jeśli nie nadpisujesz adresu powrotu to musisz utworzyć wskaźnik na funkcję, który będzie wskazywał na shellcode.
Jak wywołasz tę funkcję to nastąpi skok do Twojego shellcode'u i się wykona. Czyli mówiąc prościej - funkcją jest wtedy shellcode. Jednakże jak się będziesz bawił w takie rzeczy to musisz uważać na DEP.(https://docs.microsoft.com/en[...]ory/data-execution-prevention) Nie pamiętam czy DEP pod Windowsem jest włączone domyślnie, ale jeśli by było to shellcode wrzucony na stos się nie wykona choćbyś nawet wywołał go jako funkcję.

1

Kontynujac troche za @Shizzer

Wnioskuję, że shellcode jest załadowany do pamięci 4 bajty zaraz za wskaźnikiem

Nie beda to 4 bajty a 4 "inty" (16 bajtow). I nie za wskaznikiem na stosie tylko PRZED wskaznikiem co jest kluczowe bo exploit spodziewa sie tam spotkac adres powrotu:)

No i potem nie ladujesz shellcode'u tylko jego adres.

Skąd pomysł, że shellcode się sam magicznie uruchomi?

Czy zmanipulowanie adresu powrotu to nie jest troche takie magiczne uruchomienie wlasnie?

0

@stivens: racja, ładnie wszystko opisane pod tym linkiem, tylko errata do polskiej wersji mnie zmyliła, bo w erracie jest +4, a w książce +2, muszę doczytać o co biega

2

Ten kod to jest Undefined Behavior, więc kompilator może zmasakrować ten niecny plan (zależnie od ustawień kompilatora).
Tak jak ci pisze stivens to jest próba nadpisania adresu powrotu. Dzięki temu kod maszynowy ukryty pod shellcode ma być uruchomiony po powrocie z funkcji main.

4
  1. Musisz wyłączyć stack protection podczas kompilacji, inaczej nici z całego shellcode'u (https://stackoverflow.com/a/40867982) Minimum to -fno-stack-protector
  2. Kompilator może dołożyć jakieś swoje ukryte zmienne. Polecam Ghidra i popatrzenie w wygenerowany assembler (z Ghidrą bardzo łatwe jak się skompiluje z dbg info)
  3. Zgodnie ze standardową konwencją cdecl, zaraz po zmiennej na stacku jest stary EBP (rejestr przechowujący początek stosu) trzeba by więc na 32-bitowej maszynie się o 8 przesunąć (https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl). Tutaj przesuwamy się o 4*sizeof(int) = 16 być może po to zeby ominąć stack protector'a.

PS. Zawsze deassembluj znaleziony shellcode przed wykonaniem na kompie (lub rób to w VM). Inaczej ktoś może wydojić wszystkie twoje bitcoiny...

3

a nie prościej spróbować tak:

char shellcode[] =
    "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46"
    "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1"
    "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";

int main() {
    int (*f)() = (int(*)())shellcode;
    f();
    return 0;
}

https://godbolt.org/z/MPqY5r

Co do oryginalnego kodu, jak widać tu kompilator wykrywa UB i usuwa kod z tym hackiem dla -O3 (a nawet dla -O1).
Na dodatek kompilator ostrzega, że rozmiar adresu i int się nie zgadza (domyślnie buduje dla 64 bitów). Ok przegapiłem flagę -m32.

1

@0xmarcin: Ale to nie jest prawda, stack protector nie chroni przed nadpisaniem adresu powrotu, jeśli pozostawimy kanarek (jedna wartość obok) nieruszony.

A co do tego dlaczego leci SEGFAULT, ja bym tylko zgadywał, że strona na której znalazł się bufor nie dostała uprawnień do wykonywania. Możesz spróbować najpierw zrobić

#include <sys/mman.h>

// ... int main() { itd.

mprotect(data & (~0xfff), sizeof(data), PROT_READ | PROT_WRITE |PROT_EXEC);
0

@0xmarcin: flaga -fno-stack-protector niewiele dała
@enedil: nie do końca rozumiem działania funkcji mprotect, ale w miejsce data dałem adres &shellcode i teraz gdy dodaje tyle samo bajtów to nie wywala błędu naruszenia ochrony pamięci, o to chodziło? Pomijam już fakt, że powłoka i tak się nie uruchamia.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0