Tento článek vysvětluje nástroje a příkazy, které lze použít ke zpětnému inženýrství spustitelného souboru v prostředí Linuxu.
Reverzní inženýrství je akt zjišťování, co dělá software, ke kterému není k dispozici žádný zdrojový kód. Zpětné inženýrství vám nemusí poskytnout přesné podrobnosti o softwaru. Ale můžete docela dobře pochopit, jak byl software implementován.
Reverzní inženýrství zahrnuje následující tři základní kroky:
- Shromažďování informací
- Určení chování programu
- Zachycování hovorů knihovny
I. Shromažďování informací
Prvním krokem je shromáždit informace o cílovém programu a o tom, co dělá. Pro náš příklad si vezmeme příkaz „kdo“. Příkaz ‘kdo’ vytiskne seznam aktuálně přihlášených uživatelů.
1. Příkaz Strings
Strings je příkaz, který tiskne řetězce tisknutelných znaků v souborech. Nyní to tedy použijeme proti našemu cílovému (who) příkazu.
# strings /usr/bin/who
Některé z důležitých řetězců jsou,
users=%lu EXIT COMMENT IDLE TIME LINE NAME /dev/ /var/log/wtmp /var/run/utmp /usr/share/locale Michael Stone David MacKenzie Joseph Arceneaux
Z výstupu about můžeme vědět, že „kdo“ používá nějaké 3 soubory (/var/log/wtmp, /var/log/utmp, /usr/share/locale).
Přečtěte si více:Příklady příkazů Linux Strings (Vyhledat text v binárních souborech UNIX)
2. nm Příkaz
nm, se používá k výpisu symbolů z cílového programu. Pomocí nm můžeme poznat lokální a knihovní funkce a také použité globální proměnné. nm nemůže pracovat na programu, který je prokládaný pomocí příkazu „strip“.
Poznámka:Ve výchozím nastavení je příkaz „who“ odstraněn. Pro tento příklad jsem znovu zkompiloval příkaz „who“.
# nm /usr/bin/who
Zobrazí se následující seznam:
08049110 t print_line 08049320 t time_string 08049390 t print_user 08049820 t make_id_equals_comment 080498b0 t who 0804a170 T usage 0804a4e0 T main 0804a900 T set_program_name 08051ddc b need_runlevel 08051ddd b need_users 08051dde b my_line_only 08051de0 b time_format 08051de4 b time_format_width 08051de8 B program_name 08051d24 D Version 08051d28 D exit_failure
Ve výše uvedeném výstupu:
- t|T – Symbol je přítomen v sekci .text code
- b|B – Symbol je v sekci .data inicializované OSN
- D|d – Symbol je v sekci Initialized .data.
Velké nebo malé písmeno určuje, zda je symbol místní nebo globální.
Z výstupu about můžeme vědět následující,
- Má globální funkci (main,set_program_name,usage,atd..)
- Má některé místní funkce (print_user, time_string atd..)
- Má globální inicializované proměnné (Version,exit_failure)
- Má proměnné inicializované OSN (time_format, time_format_width atd..)
Někdy můžeme pomocí názvů funkcí odhadnout, co funkce udělají.
Přečtěte si více:10 praktických příkladů příkazů Linux nm
Další příkazy, které lze použít k získání informací, jsou
- příkaz ldd
- příkaz fixační jednotky
- příkaz lsof
- /proc souborový systém
II. Určení chování programu
3. Příkaz ltrace
Sleduje volání funkce knihovny. V tomto procesu spustí program.
# ltrace /usr/bin/who
Výstup je uveden níže.
utmpxname(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0 setutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 1 getutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(NULL, 384) = 0x09ed59e8 getutxent(0, 384, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 768) = 0x09ed59e8 getutxent(0x9ed59e8, 768, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1152) = 0x09ed59e8 getutxent(0x9ed59e8, 1152, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1920) = 0x09ed59e8 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 3072) = 0x09ed59e8 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)
Můžete pozorovat, že existuje sada volání getutxentu a jeho rodiny funkcí knihovny. Můžete si také všimnout, že ltrace poskytuje výsledky v pořadí, v jakém jsou funkce v programu volány.
Nyní víme, že příkaz „who“ funguje voláním getutxentu a jeho rodiny funkcí, aby získal přihlášené uživatele.
4. Příkaz strace
Příkaz strace se používá ke sledování systémových volání prováděných programem. Pokud program nepoužívá žádnou knihovní funkci a používá pouze systémová volání, pak pomocí plain ltrace nemůžeme sledovat provádění programu.
# strace /usr/bin/who
[b76e7424] brk(0x887d000) = 0x887d000 [b76e7424] access("/var/run/utmpx", F_OK) = -1 ENOENT (No such file or directory) [b76e7424] open("/var/run/utmp", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 . . . [b76e7424] fcntl64(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=0, len=0}) = 0 [b76e7424] read(3, "\10\325"..., 384) = 384 [b76e7424] fcntl64(3, F_SETLKW, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
Můžete pozorovat, že kdykoli je volána funkce malloc, volá systémové volání brk(). Funkce knihovny getutxent ve skutečnosti volá systémové volání ‚open‘ k otevření ‚/var/run/utmp‘ a vloží zámek pro čtení a přečte obsah a poté zámky uvolní.
Nyní jsme potvrdili, že příkaz who přečetl soubor utmp, aby zobrazil výstup.
„Strace“ i „ltrace“ má řadu dobrých možností, které lze použít.
- -p pid – Připojí se k zadanému pid. Užitečné, pokud je program již spuštěn a chcete znát jeho chování.
- -n 2 – Odsadit každé vnořené volání o 2 mezery.
- -f – Sledovat rozvětvení
Přečtěte si více:7 vzorových příkladů pro ladění provádění programu v Linuxu
III. Zachycování hovorů knihovny
5. LD_PRELOAD &LD_LIBRARY_PATH
LD_PRELOAD nám umožňuje přidat knihovnu ke konkrétnímu provádění programu. Funkce v této knihovně přepíše skutečnou funkci knihovny.
Poznámka:Toto nemůžeme použít s programy nastavenými s bitem „suid“.
Vezměme si následující program.
#include <stdio.h> int main() { char str1[]="TGS"; char str2[]="tgs"; if(strcmp(str1,str2)) { printf("String are not matched\n"); } else { printf("Strings are matched\n"); } }
Zkompilujte a spusťte program.
# cc -o my_prg my_prg.c # ./my_prg
Vypíše „Řetězce se neshodují“.
Nyní napíšeme vlastní knihovnu a uvidíme, jak dokážeme zachytit funkci knihovny.
#include <stdio.h> int strcmp(const char *s1, const char *s2) { // Always return 0. return 0; }
Zkompilujte a nastavte proměnnou LD_LIBRARY_PATH na aktuální adresář.
# cc -o mylibrary.so -shared library.c -ldl # LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
Nyní bude vytvořen soubor s názvem ‚library.so‘.
Nastavte proměnnou LD_PRELOAD na tento soubor a spusťte program pro porovnání řetězců.
# LD_PRELOAD=mylibrary.so ./my_prg
Nyní vypíše „Strings are matched“, protože používá naši verzi funkce strcmp.
Poznámka:Pokud chcete zachytit jakoukoli knihovní funkci, pak by vaše vlastní knihovní funkce měla mít stejný prototyp jako původní knihovní funkce.
Právě jsme pokryli úplně základní věci potřebné k zpětnému inženýrství programu.
Pro ty, kteří by chtěli udělat další krok v reverzním inženýrství, pomůže ve větší míře pochopení formátu souboru ELF a programu Assembly Language.