Jump to content
Zamolxis666

[TUTORIAL] Shellcode

Recommended Posts

Cu ceva in urma am intrebat pe forum ce inseamna shellcode, ce face el samd. Intrucat nu am fost suficient lamurit, m-am gandit sa ma documentez mai mult si asta a rezultat in urmatorul tutorial:

1. Introduction

1.1 Ce este shellcode-ul?

In domeniul securitatii calculatoarelor, luat ad-literam, shellcode-ul inseamna scrierea de cod care returneaza un remote shell cand este executat. Intelesul cuvantaului, insa, a evoluat, in zilele noastre reprezentand orice cod care urmeaza a fi inserat intr-o vulnerabilitate cu scopul ca aplicatia exploatata sa indeplineasca anumite sarcini, altele decat si le-a dorit programatorul.

1.2 Prerechizite

Ne vom concentra pe scriere de cod pt sistem linux cu arhitectura x86. Aceleasi metode si principii pot fi aplicate si pe diferite variante de UNIX sau Win32, dar trebuie avute in vedere diferentele intre conventiile pentru apelul functiilor de sistem. Pentru a scrie cod in limbaj de asamblare vom folosi nasm. Vom avea nevoie si de un debugger sau de un tool pentru citirea object code-ului cum ar fi objdump.

1.3 Care sunt diferentele intre shellcode-ul de pe Windows si cel de pe Linux?

Spre deosebire de Windows, Linux-ul ofera o interfata directa cu kernel-ul prin intermediul intreruperii (sau instructiunii, depinde de interpretare) int 0x80. De cealalta parte, Windows-ul nu dispune de o interfata directa cu kernel-ul. Sistemul lucreaza prin incarcarea adresei functiei care trebuie executata dintr-un DLL (Diynamic Link Library). Principala diferenta dintre cele doua sisteme este faptul ca adresele functiilor din Windows variaza de la o versiune la alta a sistemului de operare, in timp ce int 0x80 ramane aceeasi.

1.4 Cum gasim adresele functiilor din DLL-uri?

Exista o multime de cai de a gasi aceste adrese. Cele mai raspandite doua metode sunt: gasirea adresei functiei la runtime sau folosirea memoriei hard-codata. Singurul DLL care se mapeaza in mod sigur pe spatiul de adrese al shellcode-ului este kernel32.dll. Acest DLL contine LoadLibrary si GetProcAddress, functii necesare pentru obrinerea oricaror adrese de functii.

1.5 NULL bytes.

Problema NULL bytes consta in faptul ca shellcode-ul nu trebuie sa contina bytes nuli si asta deoarece codul inserat (exploit-ul) este un string. Dupa cum se stie, stringurile se termina cu NULL byte (in limbaje precum C sau C++). Daca avem NULL bytes in shellcode, acesta nu va functiona.

2. Informatii de background

? EAX, EBX, ECX si EDX sunt registri generali pe 32 de biti pe arhitecturi 80x86.

? eax mai poarta numele de acumulator, fiind folosit pentru a stoca rezultate aritmetice, ebx – registru baza, folosit pentru adresarea bazata si indexata (baza si offset), ecx – registru numarator utilizat pentru cicluri, edx – registru data.

? ah, bh, ch, dh – registre pe 16 biti (registrele superioare)

? al, bl, cl, dl – registre pe 16 biti (registrele inferioare)

? esi, edi – registre pe 32 de biti folosite de obicei ca indecsi si pentru apelul functiilor de sistem pe Linux

3. Tool-uri necesare

? gcc - compilator de C

? ld – linker

? nasm – asamblor

? objdump – dezasamblor

4. Linux Shellcoding

Cand se testeaza shellcode-ul, este frumos sa fie bagat intr-un program si lasat sa mearga. Programul C de mai jos este (si va fi) folosit pentru a testa shellcode-ul


char shellcode[] = "bytecode will go here!";
int main(int argc, char **argv)
{
int (*shell)();
shell = code;
shell();
}

Exemplu 1 – Exit Program

Cea mai facila cale de a incepe este folosirea functiei de sistem exit, pentru simplitatea ei. Codul asm:


;exit.asm
[SECTION .text]
global _start
_start:
xor eax, eax ;curata registrul eax (eax = 0x000000
mov al, 1 ;codul pentru functia de sistem exit este 1
xor ebx,ebx ;curata ebx
int 0x80 ;si…activeaza!

Compilarea si extragerea byte code-ului se face in trei pasi simpli:


~$ nasm -felf exit.asm
~$ ld -o exit exit.o
~$ objdump -d exit

Si obtinem:


exiter: file format elf32-i386

Disassembly of section .text:

08048080 <_start>:
8048080: b0 01 mov $0x1,%al
8048082: 31 db xor %ebx,%ebx
8048084: cd 80 int $0x80

Octetii care ne trebuie sunt: b0 01 31 db cd 80.

Avemc codul in C:


const char shellcode[] = “\xb0\x01\x31\xdb\xcd\x80“;
int main(int argc, char **argv)
{
int (*shell)();
shell = code;
shell();
}

Exemplu 2 - Hello

Cand sunt accesate valori statice (in special stringuri) din shellcode, trebuie ca acesta sa fie scris astfel incat sa acceseze valorile statice fara a folosi in vreun fel maparea adresei acestora, si asta pentru ca programatorul nu stie in ce zona de memorie este incarcat codul. Acelasi lucru se intampla si cand se incearca executarea instructiunilor care au ca operand memoria (jmp, call, etc). Din fericire, se poate folosi adresarea relativa cu ajutorul familiei de instructiuni jmp/call.

Pentru inceput, cosul in asmblare, neoptimizat:


SEGMENT .text
mov eax, 4 ; pune in eax valoare 4 (codul functiei de sistem write)
mov ebx, 1 ; pune 1 in ebx (codul iesirii stdout)
mov ecx, message ; pune pointerul la string in eax
mov edx, 29 ; pune 11 in edx (lungimea stringului)
int 80h ; si…activeaza!
mov eax, 1 ; pune 1 in eax (codul functiei de sistem exit)
mov ebx, 0 ; pune 0 in ebx (int status)
int 80h ; si…activeaza!

message db ‘hello world', 7, 10

Se observa ca exista in cod NULL bytes si de asemenea nu este rezolvata problema independentei pozitiei. Pentru asta se recurge la urmatorul truc:


SEGMENT .text
xor ebx, ebx ; curata registrii
mul ebx ;
mov al, 4 ; pune 4 in eax codul (functiei de sistem write)
inc ebx ; incrementeaza ebx => ebx = 1 (stdout, int fd)
jmp short get_string ; Daca nu specificam sa faca un salt de 8 biti, nasm va face
; jmp near, ceea ce inseamna 16bit offset la eip, pe cand noi
continue: ; avem nevoie de 1 octet
pop ecx ; extrage pointerul de deasupra stivei la stringul ;nostru, in eax
mov dl, 29 ; pune 11 edx (size_t len)
int 80h ; si…activeaza!
mov al, 1 ; pune 1 in eax (syscall number of exit)
dec ebx ; decrementeaza ebx => ebx = 0 (int status)
int 80h ; intra in modul kernel

get_string:
call continue ; salt la eticheta continue si incarca stringul ca ret
db ‘hello world', 7, 10


~$ nasm -felf hello.asm
~$ ld -o hello hello.o
~$ objdump -d hello


write: file format elf32-i386

Disassembly of section .text:

08048100 <continue-0x9>:
8048100: 31 db xor %ebx,%ebx
8048102: f7 e3 mul %ebx,%eax
8048104: b0 04 mov $0x4,%al
8048106: 43 inc %ebx
8048107: eb 0a jmp 8048113 <string>

08048109 <continue>:
8048109: 59 pop %ecx
804810a: b2 1d mov $0x1d,%dl
804810c: cd 80 int $0x80
804810e: b0 01 mov $0x1,%al
8048110: 4b dec %ebx
8048111: cd 80 int $0x80
08048113 <get_string>:
8048113: e8 f1 ff ff ff call 8048109 <continue>
8048118: 6b 65 72 6e imul $0x6e,0x72(%ebp),%esp
804811c: 65 gs
804811d: 6c insb (%dx),%es:(%edi)
804811e: 65 64 3a 20 cmp %fs:%gs:(%eax),%ah
8048122: 61 popa
8048123: 72 65 jb 804818a <_etext+0x55>
8048125: 20 79 6f and %bh,0x6f(%ecx)
8048128: 75 20 jne 804814a <_etext+0x15>

Shellcode-ul nostru este:


"\x31\xdb\xf7\xe3\xb0\x04\x43\xeb\x0a\x59\xb2\x1d\xcd\x80\xb0\x01"
"\x4b\xcd\x80\xe8\xf1\xff\xff\xffhello world\a\n";

Tutorialul l-am tradus si adaptat pe cat am putut de bine din engleza. Sursele originale le gasiti aici:

http://infosecwriters.com/hhworld/shellcode.txt

Writing shellcode

Shellcoding for Linux and Windows Tutorial

  • Upvote 3
  • Downvote 1
Link to comment
Share on other sites

Mersi frumos

1. L-am citit acuma mai cu atentie.

Nu sunt eu mare priceput in asm/shellcode, dar parca shellcod-ul se scria de la coada la cap si nu asa cum citesti dintr-o carte, asa cum l-ai scris tu conform codului in asm. Nu ?

2. Tare as vrea sa faca cineva (tu sau oricine altcineva) un exemplu practic de scriere a unui shellcode atat ptr Windows ca vreau sa vad cum e cu adresele alea din dll-uri, cat si ptr. linux. Un video ceva sau macar un tutorial cu imagini despre cum se face asta.

Multumesc !

Link to comment
Share on other sites

1. L-am citit acuma mai cu atentie.

Nu sunt eu mare priceput in asm/shellcode' date=' dar parca shellcod-ul se scria de la coada la cap si nu asa cum citesti dintr-o carte, asa cum l-ai scris tu conform codului in asm. Nu ?

[/quote']

Probabil te referi la arhitectura. Exista doua tipuri de arhitecturi: little-endian si big-endian. De exemplu: avem numarul 0x5566 pe care dorim sa il punem in stiva. Singurul mod in care punem in stiva este deasupra ei. Sa presupunem ca adresa stivei de unde incepem puerea este 0xAABBCC. La aceasta adresa, conform arhitecturii little-endian se va aseza octetul 66, iar imediat la adresa urmatoare, 0xAABBCD se va aseza octetul 55. La arhiectura big-endian va fi pe dos, mai intai octetul 55 apoi octetul 66. In ceea ce priveste opcode-ul instructiunilor, daca o sa dezasamblezi un executabil cu TurboDebugger (td) pe Windows, o sa observi ca instructiunile pe 2 octeti sunt afisate conform arhitecturii little-endian, adica instructiunea mov bx, ax care are opcode-ul D88B va fi afisata: 8BD8 (intai octetul inferior si apoi cel superior)

O sa incerc sa fac un filmulet, cand imi permite timpul, cu shellcode pe windows si shellcode pe linux.

Link to comment
Share on other sites

Probabil te referi la arhitectura. Exista doua tipuri de arhitecturi: little-endian si big-endian. De exemplu: avem numarul 0x5566 pe care dorim sa il punem in stiva. Singurul mod in care punem in stiva este deasupra ei. Sa presupunem ca adresa stivei de unde incepem puerea este 0xAABBCC. La aceasta adresa, conform arhitecturii little-endian se va aseza octetul 66, iar imediat la adresa urmatoare, 0xAABBCD se va aseza octetul 55. La arhiectura big-endian va fi pe dos, mai intai octetul 55 apoi octetul 66. In ceea ce priveste opcode-ul instructiunilor, daca o sa dezasamblezi un executabil cu TurboDebugger (td) pe Windows, o sa observi ca instructiunile pe 2 octeti sunt afisate conform arhitecturii little-endian, adica instructiunea mov bx, ax care are opcode-ul D88B va fi afisata: 8BD8 (intai octetul inferior si apoi cel superior)

O sa incerc sa fac un filmulet, cand imi permite timpul, cu shellcode pe windows si shellcode pe linux.

Nu prea inteleg ceea ce spui asa ca nu am de unde sa stiu daca ai dreptate sau nu.

Ca sa iti dau un exemplu la ceea ce ma refer:

Ai zis asa:

Si obtinem:


exiter: file format elf32-i386

Disassembly of section .text:

08048080 <_start>:
8048080: b0 01 mov $0x1,%al
8048082: 31 db xor %ebx,%ebx
8048084: cd 80 int $0x80

Octetii care ne trebuie sunt: b0 01 31 db cd 80.

Avemc codul in C:


const char shellcode[] = “\xb0\x01\x31\xdb\xcd\x80“;
int main(int argc, char **argv)
{
int (*shell)();
shell = code;
shell();
}

Ei bine eu ce zic si te intreb daca e gresit sau nu este urmatorul lucru:

ai pus "const char shellcode[] = “\xb0\x01\x31\xdb\xcd\x80“;"

cand de fapt eu stiu ca se pune exact de la coada la cap, adica asa:

"const char shellcode[] = “x\80\xcd\xdb\x31\x01\xb0“;"

Observi diferenta ?

Dupa cum am zis eu nu sunt expert, doar asa am vazut ca se face.

....

Am facut o pauza, am tras aer in piept, si acuma vad ca tu de fapt tot mie imi dai dreptate. Ceea ce zic eu este little indian dar am vazut si eu la unele tutoriale cum ca tu chiar daca vezi in OllyDbg opcod-urile ca fiind in ordine (adica de la cap la coada, adica Big indian), aia tot pe dos il puneau, adica de la coada la cap, adica little indian. Si, culmea, la dezasamblarea programului facut asa, opcod-urile erau tot in ordine , adica big indian, desi el le scria exact pe dos.

=> Rezulta de aici, probabil (nu am incercat) ca daca le scria exact asa cum le vedea (adica de la cap la coada), iar fi iesit altceva la dezasamblare. Nu ?

---------------------------------------------------

Asteptam filmuletul ! >:D<

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...