Ifjoncabb kollégákkal beszélgetve szóba került, hogy még nem láttak assembly kódot, ezért gondoltam mutatok nekik, mégiscsak tudják mi fán terem :)
Az volt az ötletem, hogy nézzünk meg egy HelloWorldöt asm-ben meg javaban, hogy lássák tényleg ilyen ilyen obskúrus izék lesznek abból amit ők írkálnak. Na a Javaról hamar letettem, hogy gdb-vel nézegessek egy System.out.println() -t, próbáljuk meg C-ben, de itt is ráballagtam a banánhéjra.
Az asm progi:
.global _start
.text
_start:
mov $1, %rax
mov $1, %rdi
mov $message, %rsi
mov $13, %rdx
syscall
mov $60, %rax
xor %rdi, %rdi
syscall
.data
message:
.ascii "Hello World!\n"
A C progi:
#include <stdio.h>
int main(void) {
puts("Hello World!\n");
return 0;
}
gcc -Wall -g hello.c -o helloc
gdb helloc
aztán breakpoint a puts-ra és had szaladjon!
...
Reading symbols from helloc...
(gdb) b puts
Breakpoint 1 at 0x1050
(gdb) r
Starting program: /sandbox/asm/helloc
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Enable debuginfod for this session? (y or [n])
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, __GI__IO_puts (str=0x555555556004 "Hello World!\n") at ./libio/ioputs.c:33
33 ./libio/ioputs.c: No such file or directory.
(gdb) disassemble
Dump of assembler code for function __GI__IO_puts:
Address range 0x7ffff7c7af40 to 0x7ffff7c7b166:
=> 0x00007ffff7c7af40 <+0>: endbr64
0x00007ffff7c7af44 <+4>: push %r14
0x00007ffff7c7af46 <+6>: push %r13
0x00007ffff7c7af48 <+8>: push %r12
0x00007ffff7c7af4a <+10>: push %rbp
0x00007ffff7c7af4b <+11>: mov %rdi,%rbp
0x00007ffff7c7af4e <+14>: push %rbx
0x00007ffff7c7af4f <+15>: sub $0x10,%rsp
0x00007ffff7c7af53 <+19>: call 0x7ffff7c224c0 <*ABS*+0xa6ff0@plt>
0x00007ffff7c7af58 <+24>: mov 0x17aec1(%rip),%r13 # 0x7ffff7df5e20
0x00007ffff7c7af5f <+31>: mov %rax,%rbx
0x00007ffff7c7af62 <+34>: mov 0x0(%r13),%r12
0x00007ffff7c7af66 <+38>: testl $0x8000,(%r12)
0x00007ffff7c7af6e <+46>: je 0x7ffff7c7b040 <__GI__IO_puts+256>
0x00007ffff7c7af74 <+52>: mov %r12,%rdi
0x00007ffff7c7af77 <+55>: mov 0xc0(%rdi),%eax
0x00007ffff7c7af7d <+61>: test %eax,%eax
0x00007ffff7c7af7f <+63>: jne 0x7ffff7c7b096 <__GI__IO_puts+342>
0x00007ffff7c7af85 <+69>: movl $0xffffffff,0xc0(%rdi)
0x00007ffff7c7af8f <+79>: mov 0xd8(%rdi),%r14
0x00007ffff7c7af96 <+86>: lea 0x177d63(%rip),%rdx # 0x7ffff7df2d00 <_IO_printf_buffer_as_file_jumps>
...
Nyilván van a glibc-ben egy csomó más fontos sallang is, de azt furcsállom, hogy egy darab kernel hívás (syscall vagy call 0x80) nincs benne. Persze máshol biztos van, csak így, hogy kb. 0 az átfedés a két kód között, nem túl szemléletes :)
Segítsetek kérlek mit, hogy írjak másként, hogy összehasonlítható legyen?
Köszi!
Hozzászólások
(Szerintem a Java byte code-t akartad mutatni esetleg, nem a jvm disassembly-t. https://stackoverflow.com/questions/30928786/how-do-i-check-assembly-ou…)
Azért is vetettem el a Java-t mert ott már első blikkre is nagyon macerásnak tűnt megtalálni az assembly-t de kösz a linket, hasznos!
Nyilván nem fogsz itt syscallt látni, hiszen magát a kernelt a glibc hívja meg, miután platformspecifikus módon implementálja a printf()-et, meg minden más libc függvényt. Ezek az absztrakciós rétegek már csak ilyenek, ez az ára a hordozhatóságnak.
Például a puts belső implementációja is ilyen:
https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/ioputs.c;h=7f719…
Ez ugye láthatólag a belső _IO_sputn-t hívja meg.
Az meg egy makró: https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libioP.h;h=a83a4…
Ami megint csak egy makró: https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libioP.h;h=a83a4…
Nem olyan egyszerű ám a libc, mint az ember gondolná.
Persze hogy nincs, mert a glibc egy bloated foshalmaz, nem véletlenül írták meg a MUSL-t helyette.
Használj "-static" kapcsolót (ekkor kimarad a dinamikus linkelésből eredő plt trampoline és a glibc szimbólumok is bekerülnek a dwarf-ba a "-g" hatására), valamint első körben "objdump", az szépen megmondja, felesleges a gdb.
Egyből látszik is, hogy a fő main függvény:
Tehát az _IO_puts nevű függvényt hívja valójában. Ennek a kódja:
Ez már pont úgy néz ki, mint amit a gdb-ben látsz, és a cimkékből az is látszik, hogy itt bizony virtuális metódusokkal történő gányolás esete forog fenn.
Most, hogy már tudjuk, nem is "puts", hanem "_IO_puts" a függvény neve, a glibc forrásában a C kódra rákeresve ezt kapjuk:
És valóban sehol egy syscall, csak még további bloated hivások függvénycímeket tartalmazó táblákból... (megjegyzem, a file descriptorhoz tartozó átirányítás NEM a glibc dolga, hanem a kernelé, ezért itt erre vtable-t használni szimplán faszság).
Dehogynem, csak a glibc egy fos.
Köszi!
Az a baj, hogy szép, hogy a musl soványabb, és nem olyan elhízott, mint a glibc, de sajnos van bőven szoftver, ami glibc nélkül nem fog menni. Az is igaz, hogy bloat a glibc, de azért attól is függ, hogy mihez képest nézzük. Ha a Javához, meg a hőn utált Rust-odhoz képest nézzük, azokhoz képest akár soványkának is lehetne nevezni.
“The world runs on Excel spreadsheets.” (Dylan Beattie)
Én nem utálom a Rust-ot. Nem a kedvenc nyelvem, látom a hosszútávú buktatóit, de nem utálom. Amit utálok, az a sok nagypofájú balfasz Rustafári, akik azt hiszik, ők szarták a spanyolviaszt és mindenkinek megpróbálnak beugatni, ha kritikát merészel megfogalmazni az imádott nyelvükkel kapcsolatban. Építő jellegű kritika nélkül sose fog beérni, úgyhogy pont a hypevonatos Rustafárik miatt fog megbukni ez a nyelv.
Inkább a többi libc-hez kell mérni, pl. MUSL, vagy uClibc vagy newlib.
Probalj meg a puts() helyett egy write()-t. Pl write(1,"xyz\n",4); Az a write() valojaban egy makro a __libc_write-ra, de ha static-cal forditod es ugy csinalsz egy `objdump -d`-t, akkor ott a <__libc_write>: sorra rakeresve mar latni fogod azonnal a syscall-okat! Sot, meg az is viszonylag gyorsan latszik hogy a syscall-t koveto-megelozo feltetelek a tobbszalusag eseten fellepo esetek meg az errno-beallitasok kapcsan vannak, de ha azokkal minden klappol, akkor pont ugyanaz a rax=1-es syscall lesz mint a nyitoban fent a peldad:
Simán kicserélve nem lett jó.
üdv.
Az unistd.h-t be kell fűzni a forráskódba.
Szerk: és az stdio.h nem kell
/* bocs az esetleges helyesirasi hidakert */
Viszont az unistd.h behúzása magával vonhatja impliciten az stdio.h-t a szabvány szerint:
Inclusion of the <unistd.h> header may make visible all symbols from the headers <fcntl.h>, <stddef.h>, <stdint.h>, and <stdio.h>.
(May = megteheti, de nem köteles, implementációfüggő)
Pörfikt! Köszi!
A többiek majd javítanak ha szerintük rosszat mondok, de szerintem ott rontottad el, hogy túl magas szintű eljárásal akartál valamit bemutatni. Ha puts, vagy printf vagy hasonló, akkor az már a "nagy" C-függvénykönyvtár része (man, 3. fejezet) - ilyen feladatra egyértelműen sokkal jobb a rendszerhívásokhoz sokkal közelebbi példákat használó eszköz - ezért is használhatóbb a fenti write-os példa (elvben ugye a write az rendszerhívás - system call - "rendes" rendszerben a man 2. fejezet.) A man-t amúgy se hátrány ilyen esetben lapozgatni, elvben legalábbis ildomos lenne ilyeneket leírni, ami itt is kiderült, hogy stdio-t vagy unistd-t kell-e inkludálni.
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?
Igazad van!
Nem mozgok otthonosan c-ben meg asm-ben ezért ezért a kisebb buktatókban is könnyen pofára tudok esni. :)
Ajánlom a https://godbolt.org/ oldalt. A bal oldali panelbe beírod a C kódot, jobb oldalon meg kb. valós időben látod az assembly-t, az egymásnak megfelelő részeket azonos színnel kiemelve. Választhatsz egy csomó féle nyelvet és fordítóprogramot.
Off: Az biztos, hogy a glibc tele van felesleges bonyolításokkal, de azért talán árnyalja a kérdést, ha megnézzük ezt: https://man7.org/linux/man-pages/man3/fopencookie.3.html
Vagyis saját rutinokat használhatunk FILE-ként. (BSD-ben funopen)
Egy számmal közelebb vagyunk a kernelhez a `write` használatával: https://man7.org/linux/man-pages/man2/write.2.html
graal vm-mel lehet binárisra fordítani Java programot. Indulás teljesítmény okokból szokták használni például. Hogy a generált ASM milyen, meg hogy hogy kell megnézni arról fogalmam sincsen sajnos. (Subscribe)
Húbakker, tényleg! Hogy ez nem jutott eszembe, pedig még meséltem is a graalvm-ről a kollégáknak, hogy az nem bytekódra hanem gépi kódra fordít.
Ha már assembly tanulás, és Linux a célrendszer, akkor én is ajánlok weboldalt:
https://github.com/0xAX/asm
"Share what you know. Learn what you don't."
banánhélyra?
akkor ezen most duplán elcsúsztam :) Javítva.