Smalamál
Þrep í þýðingarferlinu
Þegar þú þýðir C forrit gerist eftirfarandi:
C kóði → Assembly → Machine code (binary)
↑ ↑ ↑
Lesanlegt Ghidra CPU keyrir
Við erum að vinna á assembly-þrepi, mun nær vélbúnaðinum en C.
Assembly sem við sjáum er x86-64 (einnig kallað AMD64), staðallinn á 64-bit Linux og Windows. x86-64 er CISC-arkitektúr (Complex Instruction Set Computing), sem þýðir að skipanirnar eru breytilegrar að lengd (1–16 bæti) — ólíkt einfaldari arkitektúrum þar sem allar skipanir eru jafn langar.
Sjá assembly í rauntíma
godbolt.org leyfir þér að skrifa C í vafranum og sjá assembly-úttakið samstundis. Frábær leið til að tengja saman C og assembly.
Gistar
Gistar eru mjög skilvirkir geymslustaðir beint í örgjörva. Helstu gistar í x86-64:
| Gisti | Notkun |
|---|---|
rax |
Gisti fyrir skilagildi, niðurstaða falls |
rbx |
Almennur gisti |
rcx |
4. frumbreyta í föllum |
rdx |
3. frumbreyta í föllum |
rsi |
2. frumbreyta í föllum |
rdi |
1. frumbreyta í föllum |
r8–r15 |
Almennir gistar |
rsp |
Stack pointer: bendir efst á stafla |
rbp |
Base pointer: bendir á botn núverandi ramma |
rip |
Instruction pointer: bendir á næstu skipun sem á að keyra |
Mismunandi stærðir
Sami gistinn hefur mismunandi nöfn eftir stærð:
64-bit: rax (8 bæti)
32-bit: eax (4 bæti) ← neðri helmingur rax
16-bit: ax (2 bæti)
8-bit: al (1 byte)
Gott að muna
Í 64-bit föllum berast fyrstu 6 frumbreyturnar í: rdi, rsi, rdx, rcx, r8, r9, og skilagildið kemur til baka í rax.
Calling Conventions
Calling convention skilgreinir hvernig frumbreytur eru send í föll og hvernig skilagildi koma til baka.
64-bit Linux: System V AMD64 ABI
Þetta er staðallinn á 64-bit Linux:
| Röð frumbreyta | Gisti |
|---|---|
| 1. | rdi |
| 2. | rsi |
| 3. | rdx |
| 4. | rcx |
| 5. | r8 |
| 6. | r9 |
| 7+. | Á stafla |
Skilagildi: rax (og rdx ef 128-bit)
; C: int result = add(1, 2, 3);
mov rdi, 1 ; 1. frumbreyta
mov rsi, 2 ; 2. frumbreyta
mov rdx, 3 ; 3. frumbreyta
call add
; Niðurstaðan er nú í rax
32-bit Linux: cdecl
Í 32-bit Linux eru öll viðföng sett á stafla (í öfugri röð):
; C: int result = add(1, 2, 3);
push 3 ; Síðasta frumbreytan fyrst
push 2
push 1 ; Fyrsta frumbreytan efst á stafla
call add
add esp, 12 ; Hreinsa stafla eftir kall (3 × 4 bæti)
; Niðurstaðan er í eax
32-bit vs 64-bit í CTF
checksec og file segja þér hvort tvíundaskráin sé 32-bit eða 64-bit. Þetta skiptir máli í pwn vegna þess að ROP (return oriented programming) virkar öðruvísi: í 32-bit fara viðföng á staflann, en í 64-bit þarf pop rdi; ret gadget.
Stafli
Staflinn er hluti af minni forritsins sem notaður er til að geyma:
- Staðværar breytur
- Skilavistfang þegar fall er kallað
- Vistuð gistigildi
Staflinn vex niður á við, þ.e. þegar gögn eru bætt við, minnkar rsp.
Hærra [vistfang](../glossary.md#vistfang)
┌──────────────┐
│ ... │
├──────────────┤ ← Gamla rsp (áður en kallað er)
│ Return addr │ ← Þetta er lykilatriði í pwn!
├──────────────┤
│ saved rbp │
├──────────────┤
│ local var 1 │
├──────────────┤
│ local var 2 │
├──────────────┤ ← rsp (nýjasta gildi)
│ ... │
Lægra vistfang
push og pop
push rax ; rsp -= 8, [rsp] = rax
pop rbx ; rbx = [rsp], rsp += 8
Algengar skipanir
Gagnaflutningsskipanir
mov rax, 5 ; rax = 5
mov rax, rbx ; rax = rbx
mov rax, [rbx] ; rax = gildið á vistfanginu sem rbx bendir á
mov [rax], rbx ; Skrifar rbx á vistfangið sem rax bendir á
lea rax, [rbx+8] ; rax = rbx + 8 (reiknar vistfang, les ekki minni)
Reikningsskipanir
add rax, rbx ; rax = rax + rbx
sub rax, 10 ; rax = rax - 10
imul rax, rbx ; rax = rax * rbx
xor rax, rax ; rax = 0 (XOR við sjálft sig = 0, algeng leið til að núllstilla)
inc rax ; rax = rax + 1
dec rax ; rax = rax - 1
Samanburður og stökk
cmp rax, rbx ; Ber saman rax og rbx (reiknar rax - rbx en geymir ekki niðurstöðu)
test rax, rax ; Athugar hvort rax sé 0 (and við sjálft sig)
jmp label ; Hoppar alltaf
je label ; Hoppar ef jafnt
jne label ; Hoppar ef ójafnt
jl label ; Hoppar ef minna
jg label ; Hoppar ef meira
jle label ; Hoppar ef minna eða jafnt
jge label ; Hoppar ef meira eða jafnt
Fallakall
call 0x401234 ; Vistar return address á stafla, hoppar í fallið
ret ; Sækir return address af stafla, hoppar þangað
Dæmi: if/else í smalakóða
C kóði:
if (x == 42) {
puts("Rétt!");
} else {
puts("Rangt.");
}
Smalakóði:
cmp dword ptr [rbp-0x4], 0x2a ; ber saman x við 42 (0x2a hex = 42 decimal)
jne else_branch ; ef ójafnt, hoppa í else
lea rdi, [rip + str_right] ; 1. frumgildi: "Rétt!"
call puts
jmp end
else_branch:
lea rdi, [rip + str_wrong] ; 1. frumgildi: "Rangt."
call puts
end:
Dæmi: for lykkja í assembly
C kóði:
for (int i = 0; i < 10; i++) {
// ...
}
Assembly:
mov dword ptr [rbp-0x4], 0 ; i = 0
loop_start:
cmp dword ptr [rbp-0x4], 9 ; i < 10?
jg loop_end ; ef i > 9, hætta
; ... líkami lykkjunnar ...
add dword ptr [rbp-0x4], 1 ; i++
jmp loop_start
loop_end: