Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda
wget http://elf.cs.pub.ro/so/res/cursuri/exec-process-demo.zip
și apoi decomprimăm arhiva
unzip exec-process-demo.zip
și accesăm directorul rezultat în urma decomprimării
cd exec-process-demo/
Acum putem parcurge secțiunile cu demo-uri de mai jos.
Dorim să urmărim adresele secțiunilor și simbolurilor în cadrul unui fișier executabil de tip ELF (Executable and Linking Format). Pentru aceasta accesăm subdirectorul exec-addr/
; urmărim conținutul fișierului exec-addr.c
. În acest fișier definim variabile globale și afișăm adresele acestor variabile și a funcțiilor din modul. Vom observa că adresele variabilelor globale și a funcțiior sunt cunoscute de la link-time, în momentul link-editării și a obținerii executabilului.
Compilăm programul folosind make
.
Pentru început investigăm simbolurile din executabil. Ne interesează variabilele globale și funcțiile așa că vom rula comanda de afișare a simbolurilor din care vom extrage liniile de interes:
user@host:$ objdump --syms exec-addr | grep '\(exec_\| main\|simple_func\)' 0000000000600da0 l O .data 0000000000000004 exec_static_int_global 000000000040076b l F .text 000000000000001a simple_func 0000000000600dc4 g O .bss 0000000000000004 exec_int_global_noinit 0000000000600da4 g O .data 0000000000000004 exec_int_global 00000000004008b8 g O .rodata 0000000000000006 exec_array_ro 0000000000400785 g F .text 000000000000009c main
Prin rularea comenzii objdump de mai sus afișăm informații despre simboluri, în format pe coloane, astfel:
l
(local) pentru că vor fi locale modulului. Celelalte sunt marcate cu g
(global) și vor putea fi exportate în alte module..data
: variabile globale inițializate.bss
: variabile globale neinițializate.rodata
: variabile globale de tip read-only.text
: zonă de cod/instrucțiuni (pentru funcții)sizeof(int) = 4
octeți, șirul de caractere ocupă 6 octeți (incluzând NUL-terminatorul) iar funcțiile ocupă spațiul dat de codul acestora.
Observăm că zona de date read-only (.rodata
) este apropiată de zona de cod (.text
) ambele fiind zone care nu pot fi scrise.
Ca să verificăm faptul că adresele precizate în executabil se vor regăsi și în momentul rulării procesului, la run-time, rulăm executabilul:
$ ./exec-addr Inside simple_func Run-time addresses are: &exec_static_int_global: 0x600da0 &exec_int_global: 0x600da4 &exec_int_global_noinit: 0x600dc4 &exec_array_ro: 0x4008b8 &simple_func: 0x40076b &main: 0x400785 Run `pmap -p $(pidof exec-addr)' to show process map. Press ENTER to continue ...
Observăm din rezultatul rulării că adresele de la run-time sunt aceleași cu cele din executabil.
După cum ni se indică la rulare, vom rula pmap
pentru a consulta spațiul virtual de adresă al procesului:
$ pmap -p $(pidof exec-addr) 13545: ./exec-addr 0000000000400000 4K r-x-- /home/razvan/school/2011-2012/so/git-repos/cursuri.git/curs-07-demo/exec-addr/exec-addr 0000000000600000 4K rw--- /home/razvan/school/2011-2012/so/git-repos/cursuri.git/curs-07-demo/exec-addr/exec-addr 00007fd884842000 1664K r-x-- /lib/x86_64-linux-gnu/libc-2.18.so 00007fd8849e2000 2044K ----- /lib/x86_64-linux-gnu/libc-2.18.so 00007fd884be1000 16K r---- /lib/x86_64-linux-gnu/libc-2.18.so 00007fd884be5000 8K rw--- /lib/x86_64-linux-gnu/libc-2.18.so 00007fd884be7000 16K rw--- [ anon ] 00007fd884beb000 128K r-x-- /lib/x86_64-linux-gnu/ld-2.18.so 00007fd884dcf000 12K rw--- [ anon ] 00007fd884e06000 16K rw--- [ anon ] 00007fd884e0a000 4K r---- /lib/x86_64-linux-gnu/ld-2.18.so 00007fd884e0b000 4K rw--- /lib/x86_64-linux-gnu/ld-2.18.so 00007fd884e0c000 4K rw--- [ anon ] 00007fffb51d1000 132K rw--- [ stack ] 00007fffb51fe000 8K r-x-- [ anon ] ffffffffff600000 4K r-x-- [ anon ] total 4068K
Legat de partea de executabil, observăm că avem două pagini (4K
) mapate din executabilul exec-addr
. Una este readable/executable (r-x
) și începe de la adresa 0x400000
, iar alta este readable/writable (rw-
) și începe de la adresa 0x600000
. În prima pagină sunt mapate secțiunile .text
și .rodata
, iar în cealaltă sunt mapate secțiunile .data
și .bss
; observăm acest lucru pe baza adreselor.
Pentru a obține un executabil, folosim linker-ul pentru a lega unul sau mai multe module obiect cu biblioteci. Legarea (linking) presupune:
.text
) sunt comasate într-o singură secțiuneÎn modul simplu, legarea este statică (static linking) iar rezultatul este un executabil static. Un executabil static realizează complet toți pașii de mai sus și are în cadrul său toate sețiunile de care are nevoie și toate referințele rezolvate.
O alternativă la legarea statică este legarea dinamică (dynamic linking) care are ca rezultat un executabil dinamic. Un executabil dinamic nu include toate secțiunile și menține referințe nerezolvate; rezolvarea acestora este amânată la momentul lansării în execuție a executabilului, numit și încărcare (loading, load time), când se creează procesul corespunzător.
Un executabil dinamic nu include în secțiunile sale părțile corespunzătoare din biblioteci. Aceste biblioteci sunt biblioteci dinamice și vor fi adăugate în spațiul virtual de adrese al procesului după încărcare (loading), fără a încărca executabilul.
Astfel, un executabil dinamic va fi semnificativ mai mic decât un executabil static. Pe lângă dimensiunea redusă a executabilului, un alt avantaj este partajarea zonelor de cod din cadrul bibliotecilor dinamice cu alte procese. Întrucât aceste zone nu sunt modificate, sunt mapate în spațiul virtual al tuturor proceselor care le foloesc. De exemplu, în cazul bibliotecii standard C, folosită de toate procesele sistemului, zona sa de cod este prezentă o singură dată în memoria RAM și mapată în spațiul virtual de adrese al tuturor proceselor provenite din executabile dinamice. Un proces obținut dintr-un executabil static nu poate partaja nici o parte din zona sa cu alte procese obținute din executabile static, însemnând un consum mai mare de memorie. Bibliotecile dinamice, partajabile, poartă în Linux denumirea de shared objects
; de aici și extensia fișierelor de tip bibliotecă dinamică .so
.
Executabilele dinamice au dezavantajul unui timp mai mare de încărcare. La orice lansare în execuție a executabilului trebuie realizat procesul de rezolvare a referințelor, numit dynamic binding. Suplimentar, dacă un executabil dinamic este transferat pe un sistem unde nu este prezentă o bibliotecă dinamică de care are nevoie, sau este prezentă o versiune incompatibilă, acesta nu va rula.
Sumarizând, executabilele statice și dinamice au următoarele avantaje:
.text
, .rodata
) din bibilioteci cu alte procese obținute din executabile dinamice care folosesc aceleași biblioteci.
În subdirectorul static-dynamic/
se găsește fișierul sursă hello.c
și un fișier Makefile
pe care îl folosim pentru a compila un executabil static (hello-static
) și unul dinamic (hello-dynamic
):
$ ls hello.c Makefile $ make cc -Wall -fno-PIC -c -o hello.o hello.c cc -no-pie -static -o hello-static hello.o cc -no-pie -o hello-dynamic hello.o $ ls -F hello.c hello-dynamic* hello.o hello-static* Makefile
Putem observa că executabilul static are dimensiunea mai mare decât cel dinamic (de circa 100 de ori mai mare) și că are mai multe simboluri:
$ ls -lh hello-* -rwxr-xr-x 1 student student 8.4K Mar 29 21:57 hello-dynamic -rwxr-xr-x 1 student student 826K Mar 29 21:57 hello-static $ nm hello-static | wc -l 1664 $ nm hello-dynamic | wc -l 38
Așa cum am discutat, un executabil dinamic are referințe nerezolvate către biblioteci (în cazul de față biblioteca standard C), referințe ce vor fi rezolvate la load time. Executabilul static nu are referințe nerezolvate:
$ nm hello-dynamic | grep ' U ' U fflush@@GLIBC_2.2.5 U fgets@@GLIBC_2.2.5 U __libc_start_main@@GLIBC_2.2.5 U printf@@GLIBC_2.2.5 U puts@@GLIBC_2.2.5 U __stack_chk_fail@@GLIBC_2.4 $ nm hello-static | grep ' U ' $
Utilitarul ldd
afișează bibliotecile dinamice necesare pentru executabilele dinamice, iar pentru un executabil static nu afișează nimic:
$ ldd hello-dynamic linux-vdso.so.1 (0x00007ffcda18d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6dbc088000) /lib64/ld-linux-x86-64.so.2 (0x00007f6dbc479000) $ ldd hello-static not a dynamic executable
La rularea oricărui executabil (loading), acesta așteaptă să apăsăm Enter
ca să continue. Acest lucru ne permite să investigăm spațiul de adresă pentru procesul proaspăt pornit folosind utilitarul pmap
ca mai jos:
$ pmap $(pidof hello-dynamic) 24227: ./hello-dynamic 0000000000400000 4K r-x-- hello-dynamic 0000000000600000 4K r---- hello-dynamic 0000000000601000 4K rw--- hello-dynamic 0000000001986000 132K rw--- [ anon ] 00007f556ba27000 1948K r-x-- libc-2.27.so 00007f556bc0e000 2048K ----- libc-2.27.so 00007f556be0e000 16K r---- libc-2.27.so 00007f556be12000 8K rw--- libc-2.27.so 00007f556be14000 16K rw--- [ anon ] 00007f556be18000 156K r-x-- ld-2.27.so 00007f556c01d000 8K rw--- [ anon ] 00007f556c03f000 4K r---- ld-2.27.so 00007f556c040000 4K rw--- ld-2.27.so 00007f556c041000 4K rw--- [ anon ] 00007ffeceb5d000 132K rw--- [ stack ] 00007ffeceba4000 12K r---- [ anon ] 00007ffeceba7000 4K r-x-- [ anon ] ffffffffff600000 4K --x-- [ anon ] total 4508K $ pmap $(pidof hello-static) 24245: ./hello-static 0000000000400000 728K r-x-- hello-static 00000000006b6000 24K rw--- hello-static 00000000006bc000 4K rw--- [ anon ] 000000000091c000 140K rw--- [ anon ] 00007ffdb1a33000 132K rw--- [ stack ] 00007ffdb1b80000 12K r---- [ anon ] 00007ffdb1b83000 4K r-x-- [ anon ] ffffffffff600000 4K --x-- [ anon ] total 1048K
Observăm că în cazul executabilului dinamic, spațiul virtual de adrese cuprinde bibliotecile dinamice partajate (biblioteca standard C libc-2.27.so
și dynamic linker/loader-ul ld-2.27.so
). Spațiul virtual de adrese al executabilului static este mai redus, cuprinzând doar zonele specifice executabilului și zonele dinamice ale procesului (stivă, heap, mapări anonime), fără biblioteci.
sudo apt install nasm
În mod obișnuit un executabil este obținut prin legarea unui sau a mai multor module obiect la biblioteca standard C (libc
) și, la nevoie, alte biblioteci. Așa cum am prezentat mai sus, în cazul legării statice (static linking) linker-ul adaugă în executabil toate componentele necesare (cod, date) din cadrul bibliotecii statice; în cazul legării dinamice (dynamic linking) linker-ul adaugă referințe la simbolurile din bibliotecile dinamice, iar rezolvarea acestor simboluri și adăugarea componentelor se întămplă la încărcare (load time).
Putem crea programe și folosi linker-ul să nu folosească deloc biblioteca standard C. Acest lucru este util pentru medii de lucru specializate (numite free standing environments) în care nu avem acces la o bibliotecă standard C sau nu o putem folosi. Astfel de programe vor implementa singure funcționalități similare celor oferite de biblioteca standard C sau vor realiza direct apeluri de sistem.
În directorul no-libc/
avem un exemplu de astfel de program, care folosește direct apeluri de sistem pentru a afișa un mesaj la ieșirea standard. Programul este scris în limbaj de asamblare pentru arhictectura x86_64 în sintaxa AT&T folosind asamblorul GNU (gas
parte din GCC) în fișierul hello.s
și în sintaxa Intel folosind asamblorul NASM în fișierul hello.asm
.
În ambele fișiere (hello.s
și hello.asm
) invocăm apelul de sistem write(1, “Hello, world!\n”, 14);
. Pentru invocarea acestui apel de sistem, urmărim convenția de apel de sistem specifică arhitecturii x86_64 pe Linux:
rax
reține numărul apelului de sistem (1
pentru apelul de sistem sys_write
)rdi
reține primul argument (1
pentru descriptorul de fișier corespunzător ieșirii standard - standard output)rsi
reține al doilea argument (adresa mesajului de afișat, adică adresa șirului Hello, world!\n
)rdx
reține al treilea argument (14
pentru lungimea mesajului de afișat)
Pentru încheierea execuției, invocăm apelul de sistem exit(0)
.
Pentru a obține executabilele este nevoie de un pas de asamblare, care obține fișierul obiect corespunzător și un pas de legare care obține fișierul executabil. Realizăm acești pași prin intermediul comenzii make
:
$ ls hello.asm hello.s Makefile $ make nasm -f elf64 -o hello-nasm.o hello.asm cc -nostdlib -no-pie -Wl,--entry=main -Wl,--build-id=none hello-nasm.o -o hello-nasm gcc -c -o hello-gas.o hello.s cc -nostdlib -no-pie -Wl,--entry=main -Wl,--build-id=none hello-gas.o -o hello-gas $ ls hello.asm hello-gas hello-gas.o hello-nasm hello-nasm.o hello.s Makefile
În urma rulării comenzii am obținut fișierele obiect hello-nasm.o
și hello-gas.o
și fișierele executabile hello-nasm
și hello-gas
.
Rulăm cele două executabile care vor afișa mesajul Hello, world!
la ieșirea standard:
$ ./hello-nasm Hello, world! $ ./hello-gas Hello, world!
Putem observa diferențe între aceste executabile (obținute fără suport de bibliotecă standard C) și cele de la demo-ul anterior, obținute cu suport de bibliotecă standard C.
Fișierele sunt mai mici, nu conțin elemente de setup și cleanup din biblioteca standard C, au doar miminul necesar pentru a funcționa: au minimul de simboluri, minimul de secțiuni, minimul de cod, așa cum putem observa rulând comenzile de mai jos:
$ ls -l hello-gas hello-nasm $ nm hello-gas $ nm hello-nasm $ objdump -d hello-gas $ objdump -d -M intel hello-nasm $ readelf -S hello-gas $ readelf -S hello-nasm
Putem obține aceste executabile fără să apelăm comanda gcc
cu un număr complicat de parametri. Putem invoca direct linker-ul folosind comenzile:
ld -o hello-gas --entry=main hello-gas.o ld -o hello-nasm --entry=main hello-nasm.o
Nu în ultimul rând, putem reduce dimensiunea executabilelor eliminând tabela de simboluri cu ajutorul comenzii strip
:
$ strip hello-nasm $ strip hello-gas
În mod obișnuit, un program execută fluxul de apel dat de codul său și de intrarea venită de la utilizator sau alte procese sau alte forme de I/O. În anumite situații putem dori să declanșăm mai repede un apel al unei funcționalități prezente în executabil, dar care nu se găsește pe fluxul de apel curent.
La nivel de provocare (challenge) în lumea competițiilor de tip Capture the Flag (CTF), sunt situații în care un program conține funcționalități ascunse; adică sunt funcții și variabile prezente în program dar care nu sunt apelate sau folosite explicit. Un astfel de exemplu este în subdirectorul hidden/
.
În subdirectorul hidden/
se găsește executabilul hidden
obținut din compilarea și legarea fișierelor hidden.c
și reader.c
. Obiectivul nostru este să afișăm mesajul success
cu ajutorul acestui program, în orice mod posibil.
Pentru început, observăm că acest program se apelează funcția hidden_function()
. Numai că apelul se realizează astfel încât depinde de valori aleatoare. În plus valorile variabilelor test
și success_message
nu ajută la obiectivul nostru: afișarea mesajului success
. Identificăm următoarele limitări ale programului, limitări peste care trebuie să trecem:
test
are o valoare predefinită care nu este modificată în programsuccess_message
are o valoare predefinită care nu este modificată în programhidden_function()
depinde de două valori aleatoare, generate la rulare (run time)
Pentru început ne propunem să realizăm primul apel al funcției hidden_function()
, adică apelul hidden_function(100)
apel ce nu depinde de valori aleatoare generate la rulare. Putem să realizăm acest apel folosind doar analiză statică (pe executabil) și modificând executabilul. Adică urmărim:
test
cu valoarea 100
, argumentul apelului hidden_function()
. În felul acesta vom trece de condiția if
din cadrul funcției hidden_function()
(linia 11
din fișierul hidden.c
).success_message
cu valoarea success\0
. în felul acesta apelul funcției puts()
din cadrul funcției hidden_function()
(linia 12
din fișierul hidden.c
).Pentru a realiza aceste suprascrieri trebuie să știm la ce offset în cadrul fișierului executabil se găsesc cele două valori. Pentru a determina acest offset, vom face pașii:
test
și success_message
). Fie aceste adrese test_address
și success_message_address
..data
în care se găsesc cele două variabile. Fie această adresă data_address
..data
. Fie acest offset data_offset
.test_offset = test_address - data_address + data_offset
, success_message_offset = success_message_address - data_address + data_offset
.
Pasul 1
îl putem realiza folosind utilitarul nm
sau utiltarul readelf
:
$ nm hidden | grep ' test$' | awk -F '[ \t]+' '{print $1;}' 0000000000601080 $ readelf -s hidden | grep ' test$' | awk -F '[ \t]+' '{print $3;}' 0000000000601080
Pasul 2
și pasul 3
îi realizăm folosind utilitarul readelf
:
$ readelf -S hidden | grep ' .data ' | awk -F '[ \t]+' '{print $5;}' 0000000000601060 $ readelf -S hidden | grep ' .data ' | awk -F '[ \t]+' '{print $6;}' 00001060
Pasul 4
îl realizăm prin operații aritmetice simple.
O dată aflat offset-ul putem folosi utilitarul dd
pentru a suprascrie părți din fișier, cu template-ul:
$ echo -en "<value>" | dd seek=<offset> of=hidden bs=1 conv=notrunc
Acești pași sunt realizați în scriptul rewrite_exec.sh
. În urma rulării acestui script obținem fișierul hidden_copy
cu modificările realizate pentru a afișa mesajul success
:
$ ./rewrite_exec.sh test_offset: 0x1080 success_message_offset: 0x10a0 Generated updated executable in hidden_copy. $ ./hidden_copy success Give number: ^C
În acest moment avem realizat cu succes primul apel al funcției hidden_function()
în urmă căruia este apelat mesajul success
.
Folosind doar analiză statică (lucru pe executabil) am realizat primul apel reușit al funcției hidden_function()
. Urmărim să realizăm și al doilea apel. Pentru al doilea apel depindem de informații aleatoare, a căror valoarea este știută doar la rulare (run time). De aceea avem nevoie de analiză dinamică, adică investigația folosind un debugger, în cazul nostru GDB.
Pentru a apela corespunzător a doua oară funcția hidden_function()
, trebuie ca cele două condiții if
(liniile 11
și 26
din fișierul hello.c
) să fie satisfăcute. Pentru aceasta trebuie:
r1
și r2
generate aleatorin
valoarea variabilei r1
ca să satisfacem condiția if
de la linia 26
test
cu valorea variabilei r2
ca să satisfacem condiția if
de la linia 11
Ne vom folosi de facilitățile GDB pentru acest lucru:
Adică vom urma pașii:
r1
și r2
.test
în memorie.test
cu valoarea variabilei r2
ca să satisfacem condiția if
de la linia 11
.read_uint()
) vom transmite valoarea variabilei r1
ca să satisfacem condiția if
de la linia 26
.hidden_function()
care va apela a doua oară mesajul success
.Pașii sunt urmați în GDB în secvența de mai jos:
$ gdb -q ./hidden_copy Reading symbols from ./hidden_copy...done. (gdb) start Temporary breakpoint 1 at 0x400702: file hidden.c, line 20. Starting program: /home/student/so/demo/exec-process/hidden/hidden_copy Temporary breakpoint 1, main () at hidden.c:20 20 hidden_function(100); (gdb) set disassembly-flavor intel (gdb) disass Dump of assembler code for function main: 0x00000000004006fa <+0>: push rbp 0x00000000004006fb <+1>: mov rbp,rsp 0x00000000004006fe <+4>: sub rsp,0x10 => 0x0000000000400702 <+8>: mov edi,0x64 0x0000000000400707 <+13>: call 0x4006d7 <hidden_function> 0x000000000040070c <+18>: mov edi,0x0 0x0000000000400711 <+23>: call 0x4005d0 <time@plt> 0x0000000000400716 <+28>: mov edi,eax 0x0000000000400718 <+30>: call 0x4005a0 <srand@plt> 0x000000000040071d <+35>: call 0x4005e0 <rand@plt> 0x0000000000400722 <+40>: mov DWORD PTR [rbp-0x4],eax 0x0000000000400725 <+43>: call 0x4005e0 <rand@plt> 0x000000000040072a <+48>: mov DWORD PTR [rbp-0x8],eax 0x000000000040072d <+51>: mov edi,0x400834 0x0000000000400732 <+56>: call 0x400753 <read_uint> 0x0000000000400737 <+61>: mov DWORD PTR [rbp-0xc],eax 0x000000000040073a <+64>: mov eax,DWORD PTR [rbp-0xc] 0x000000000040073d <+67>: cmp eax,DWORD PTR [rbp-0x4] 0x0000000000400740 <+70>: jne 0x40074c <main+82> 0x0000000000400742 <+72>: mov eax,DWORD PTR [rbp-0x8] 0x0000000000400745 <+75>: mov edi,eax 0x0000000000400747 <+77>: call 0x4006d7 <hidden_function> 0x000000000040074c <+82>: mov eax,0x0 0x0000000000400751 <+87>: leave 0x0000000000400752 <+88>: ret End of assembler dump. (gdb) b *0x0000000000400732 Breakpoint 2 at 0x400732: file hidden.c, line 25. (gdb) c Continuing. success Breakpoint 2, 0x0000000000400732 in main () at hidden.c:25 25 in = read_uint("Give number: "); (gdb) x/2wd $rbp-8 0x7fffffffdc08: 1586380876 1175232931 (gdb) p test $1 = 100 (gdb) p &test $2 = (unsigned int *) 0x601080 <test> (gdb) set *0x601080=1586380876 (gdb) p test $1 = 1586380876 (gdb) c Continuing. Give number: 1175232931 success [Inferior 1 (process 28011) exited normally] (gdb) q
Comenzile GDB folosite în secvența de mai sus sunt:
start
: pentru a porni procesul și a crea un breakpoint la începutul funcției main
hidden_function
, se afișează success
set disassembly-flavor intel
: pentru a dezasambla în sintaxă Inteldisass
: pentru a dezasambla codul, să urmărim unde putem plasa un breakpointb *0x0000000000400732
: pentru a crea un breakpoint înainte de apelul funcției read_uint
; adică *după* ce au fost generate în variabilele r1
și r2
r1
și r2
sunt reținute pe stivă respectiv la adresele rbp-4
și rbp-8
; observăm în secvența dezasamblată între adresele 0x40071d
și 0x40072a
c
: pentru continuarea execuției până la breakpoint-ul de mai susr1
și r2
x/2wd $rbp-8
: pentru citirea a două (2
) numere pe 32 de biți (w
) și afișarea lor în format zecimal (d
) de la adresa rbp-8
, adică valorile de la rbp-8
(r2
) și rbp-4
(r1
). Rezultă de aici că r1
este 1175232931
și r2
este 1586380876
.p test
: pentru afișarea valorii variabilei test
, inițial 100
p &test
: pentru afișarea adresei variabilei test
, adică 0x601080
set *0x601080=1586380876
: pentru modificarea valorii de la adresa 0x601080
(adică a variabilei test
) în valoarea variabilei r2
(1586380876
)p test
: pentru verificarea noii valori a variabilei test
; se confirmă că am modificat valoarea la 1586380876c
: pentru continuarea execuției programuluiread_uint
care solicită furnizarea unui număr ce va fi comparat cu r1
; furnizăm valoarea lui r1
adică 1175232931
hidden_function
, afișează a doua oară success
q
: pentru încheierea sesiunii curente și închiderea GDB
Pașii de mai sus pot fi simplificați prin folosirea în GDB a comenzii call
care permite apelul direct al unei funcții. Astfel, la începutul sesiunii GDB a programului, putem declanșa apelul direct al funcției hidden_function
ca mai jos:
(gdb) start Temporary breakpoint 1 at 0x400702: file hidden.c, line 20. Starting program: /home/student/so/demo/exec-process/hidden/hidden_copy Temporary breakpoint 1, main () at hidden.c:20 20 hidden_function(100); (gdb) call hidden_function(100) success
În felul acesta putem apela orice funcție din executabil fără a fi nevoie să facem operații mai complicate de investigare și modificare a memoriei.
Ba mai mult, putem apela direct funcția puts
:
(gdb) call puts("success") success $1 = 8
PEDA este o versiune mai puțin menținută curentă, dar cu o istorie mai lungă în comunitatea de securitatea. pwndbg și GEF sunt proiecte mai recente, cu funcționalități mai multe și comunități active de dezvoltare.
Cele trei proiecte pot fi configurate să fie prezente simultan (dar folosite alternativ) în GDB, așa cum este descris aici (și configurat automat cu acest repository).