https://chacker.pl/
Teraz, gdy zobaczyłeś wnętrze pliku obiektowego, czas rozmontować kompletny plik binarny. Zacznijmy od przykładowego pliku binarnego z symbolami, a następnie przejdźmy do jego pozbawionego odpowiednika, aby zobaczyć różnicę w wynikach dezasemblacji. Istnieje duża różnica między dezasemblacją pliku obiektowego a plikiem wykonywalnym binarnym, jak widać w wynikach objdump w Listingu 1-10
Listing 1-10: Disassembling an executable with objdump
$ objdump -M intel -d a.out
a.out: file format elf64-x86-64
Disassembly of section (1).init:
00000000004003c8 <_init>:
4003c8: 48 83 ec 08 sub rsp,0x8
4003cc: 48 8b 05 25 0c 20 00 mov rax,QWORD PTR [rip+0x200c25]
4003d3: 48 85 c0 test rax,rax
4003d6: 74 05 je 4003dd <_init+0x15>
4003d8: e8 43 00 00 00 call 400420 <__libc_start_main@plt+0x10>
4003dd: 48 83 c4 08 add rsp,0x8
4003e1: c3 ret
Disassembly of section (2).plt:
00000000004003f0 <puts@plt-0x10>:
4003f0: ff 35 12 0c 20 00 push QWORD PTR [rip+0x200c12]
4003f6: ff 25 14 0c 20 00 jmp QWORD PTR [rip+0x200c14]
4003fc: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
0000000000400400 <puts@plt>:
400400: ff 25 12 0c 20 00 jmp QWORD PTR [rip+0x200c12]
400406: 68 00 00 00 00 push 0x0
40040b: e9 e0 ff ff ff jmp 4003f0 <_init+0x28>
…
Disassembly of section (3).text:
0000000000400430 <_start>:
400430: 31 ed xor ebp,ebp
400432: 49 89 d1 mov r9,rdx
400435: 5e pop rsi
400436: 48 89 e2 mov rdx,rsp
400439: 48 83 e4 f0 and rsp,0xfffffffffffffff0
40043d: 50 push rax
40043e: 54 push rsp
40043f: 49 c7 c0 c0 05 40 00 mov r8,0x4005c0
400446: 48 c7 c1 50 05 40 00 mov rcx,0x400550
40044d: 48 c7 c7 26 05 40 00 mov rdi,0x400526
400454: e8 b7 ff ff ff call 400410 <__libc_start_main@plt>
400459: f4 hlt
40045a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
0000000000400460 <deregister_tm_clones>:
…
0000000000400526 (4)<main>:
400526: 55 push rbp
400527: 48 89 e5 mov rbp,rsp
40052a: 48 83 ec 10 sub rsp,0x10
40052e: 89 7d fc mov DWORD PTR [rbp-0x4],edi
400531: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
400535: bf d4 05 40 00 mov edi,0x4005d4
40053a: e8 c1 fe ff ff call 400400 (5)<puts@plt>
40053f: b8 00 00 00 00 mov eax,0x0
400544: c9 leave
400545: c3 ret
400546: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40054d: 00 00 00
0000000000400550 <__libc_csu_init>:
…
Disassembly of section .fini:
00000000004005c4 <_fini>:
4005c4: 48 83 ec 08 sub rsp,0x8
4005c8: 48 83 c4 08 add rsp,0x8
4005cc: c3 ret
Możesz zobaczyć, że plik binarny ma o wiele więcej kodu niż plik obiektowy. Nie jest to już tylko funkcja główna ani nawet pojedyncza sekcja kodu. Teraz jest wiele sekcji o nazwach takich jak .init (1), .plt (2) i .text (3). Wszystkie te sekcje zawierają kod obsługujący różne funkcje, takie jak inicjalizacja programu lub stuby do wywoływania bibliotek współdzielonych. Sekcja .text jest główną sekcją kodu i zawiera funkcję główną (4). Zawiera również szereg innych funkcji, takich jak _start, które są odpowiedzialne za zadania takie jak konfigurowanie argumentów wiersza poleceń i środowiska wykonawczego dla main i czyszczenie po main. Te dodatkowe funkcje są standardowymi funkcjami, obecnymi w każdym pliku binarnym ELF wygenerowanym przez gcc. Możesz również zobaczyć, że wcześniej niekompletny kod i odwołania do danych zostały teraz rozwiązane przez linker. Na przykład wywołanie puts (5) wskazuje teraz na właściwy stub (w sekcji .plt) dla biblioteki współdzielonej, która zawiera puts. Tak więc pełny plik wykonywalny binarny zawiera znacznie więcej kodu (i danych, choć tego nie pokazałem) niż odpowiadający mu plik obiektowy. Ale jak dotąd wynik nie jest o wiele trudniejszy do zinterpretowania. To się zmienia, gdy plik binarny jest rozbierany, jak pokazano w Liście 1-11, gdzie używa się objdump do rozbierania rozbieranej wersji przykładowego pliku binarnego.
Listing 1-11: Disassembling a stripped executable with objdump
$ objdump -M intel -d ./a.out.stripped
./a.out.stripped: file format elf64-x86-64
Disassembly of section (1).init:
00000000004003c8 <.init>:
4003c8: 48 83 ec 08 sub rsp,0x8
4003cc: 48 8b 05 25 0c 20 00 mov rax,QWORD PTR [rip+0x200c25]
4003d3: 48 85 c0 test rax,rax
4003d6: 74 05 je 4003dd <puts@plt-0x23>
4003d8: e8 43 00 00 00 call 400420 <__libc_start_main@plt+0x10>
4003dd: 48 83 c4 08 add rsp,0x8
4003e1: c3 ret
Disassembly of section (2).plt:
…
Disassembly of section (3).text:
0000000000400430 <.text>:
(4) 400430: 31 ed xor ebp,ebp
400432: 49 89 d1 mov r9,rdx
400435: 5e pop rsi
400436: 48 89 e2 mov rdx,rsp
400439: 48 83 e4 f0 and rsp,0xfffffffffffffff0
40043d: 50 push rax
40043e: 54 push rsp
40043f: 49 c7 c0 c0 05 40 00 mov r8,0x4005c0
400446: 48 c7 c1 50 05 40 00 mov rcx,0x400550
40044d: 48 c7 c7 26 05 40 00 mov rdi,0x400526
(5) 400454: e8 b7 ff ff ff call 400410 <__libc_start_main@plt>
400459: f4 hlt
40045a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
(6) 400460: b8 3f 10 60 00 mov eax,0x60103f
…
400520: 5d pop rbp
400521: e9 7a ff ff ff jmp 4004a0 <__libc_start_main@plt+0x90>
(7) 400526: 55 push rbp
400527: 48 89 e5 mov rbp,rsp
40052a: 48 83 ec 10 sub rsp,0x10
40052e: 89 7d fc mov DWORD PTR [rbp-0x4],edi
400531: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
400535: bf d4 05 40 00 mov edi,0x4005d4
40053a: e8 c1 fe ff ff call 400400 <puts@plt>
40053f: b8 00 00 00 00 mov eax,0x0
400544: c9 leave
(8) 400545: c3 ret
400546: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40054d: 00 00 00
400550: 41 57 push r15
400552: 41 56 push r14
…
Disassembly of section .fini:
00000000004005c4 <.fini>:
4005c4: 48 83 ec 08 sub rsp,0x8
4005c8: 48 83 c4 08 add rsp,0x8
4005cc: c3 ret
Najważniejszym wnioskiem z Listingu 1-11 jest to, że podczas gdy różne sekcje są nadal wyraźnie rozróżnialne (oznaczone (1), (2) i (3)), funkcje nie. Zamiast tego wszystkie funkcje zostały połączone w jeden duży fragment kodu. Funkcja _start zaczyna się w (4), a deregister_tm_clones zaczyna się w (6). Funkcja main zaczyna się w (7) i kończy w (8), ale we wszystkich tych przypadkach nie ma nic szczególnego, co wskazywałoby, że instrukcje przy tych znacznikach reprezentują uruchomienia funkcji. Jedynymi wyjątkami są funkcje w sekcji .plt, które nadal mają swoje nazwy jak poprzednio (jak widać w wywołaniu __libc_start_main w (5)). Poza tym musisz sam spróbować zrozumieć wynik demontażu. Nawet w tym prostym przykładzie wszystko jest już mylące; wyobraź sobie próbę zrozumienia większego pliku binarnego zawierającego setki różnych funkcji połączonych ze sobą! Właśnie dlatego dokładne automatyczne wykrywanie funkcji jest tak ważne w wielu obszarach analizy binarnej.