Skip to content

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
r8r15 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:

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: