Functions

RAM is divided in 4 segments, 1 of them is the stack.

The Stack

The stack is a segment of memory allocated to store data, usually to story data and retrieve them back temporarily. Top of the stack is referenced as rsp and the bottom as rbp.

Instruction
Description
Example

push

Copy address to top of stack

push rax

pop

Move item to top of the stack

pop rax

We can push rax a value into the stack and it would come on top of the value we pused before, would we use pop rax a value it would remove it from top of the stack.

Functions/Syscalls

Functions and syscall use the registers for processing, if values stored in registers will get changed after a function call or syscall we will lose them.

When calling a syscall to print Hello world and save the value in rax, we would push rax into stack. We can then execute the syscall and afterwards pop value back to rax. Now is saves the value of rax.

Lets say we have "42" stored in rax.

; Push rax
push rax    ; Put 42 on top of stack

; Use rax for syscall
mov rax 1   ; 1 is syscall number for write
syscall     ; syscall "Hello word
; rax now contains value from syscall

; Get original value back
pop rax     ; Get 42 back from stack

Syscalls

A syscall is like a global function written in C, provided by the OS kernel. Where the kernel is like a bridge between the programs and hardware. A syscall takes the arguments in registers and executes it. To write something to screen we can use write syscall, input the string to be printed and then call the syscall to print.

A list of syscalls provided by Linux Kernel

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1

#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5

Write syscall

# Finding the arguments
$ man -s 2 write
       ssize_t write(int fd, const void *buf, size_t count);

Its asking for 3 arguments:

  1. File Descriptor fd to be printed to (usually 1 for stdout)

  2. The address pointer to the string to be printed

  3. The length we want to print

Calling syscall

To call a syscall, 3 things are required:

  1. Save registers to stack

  2. Set its syscall number in rax

  3. Sets arguments in registers

  4. Use syscall instruction to call it.

Syscall Number

Start by moving syscall number to rax register. In the manual we find #define __NR_write 1 . The kernel now knows which syscall to use.

mov rax, 1

Syscall Arguments

Put each function's argument in het corresponding register. The first argument should be place in rdi.

Syscall Number/Return value

rax

al

Callee Saved

rbx

bl

1st arg

rdi

dil

2nd arg

rsi

sil

3rd arg

rdx

dl

4th arg

rcx

cl

5th arg

r8

r8b

6th arg

r9

r9b

We know have to pass fd, pointer and length but our string we want to "Fibonacci Sequence:" is longer than 16 chars so it doesnt fit. Lets make a variable

global  _start

section .data
    message db "Fibonacci Sequence:", 0x0a ; 0x0a is new line

Message is the pointer to where our string will be stored in memory. We can wc use to count bytes echo -n "Fibonacci Sequence:" | wc -c. But 1 byte because of the new line.

mov rax, 1       ; rax: syscall number 1
mov rdi, 1       ; rdi: fd 1 for stdout
mov rsi,message  ; rsi: pointer to message
mov rdx, 20      ; rdx: print length of 20 bytes
Run and debug
# Running show the string
$ ./assembler.sh fib.s

Fibonacci Sequence:
[1]    107348 segmentation fault  ./fib


# Finding the syscall address we and stepping to it we find the string
➜  ~ gdb -q ./fib
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 16.3 in 0.00ms using Python engine 3.13
Reading symbols from ./fib...
(No debugging symbols found in ./fib)
gef➤  disas _start
Dump of assembler code for function _start:
   0x0000000000401000 <+0>:     mov    eax,0x1
   0x0000000000401005 <+5>:     mov    edi,0x1
   0x000000000040100a <+10>:    movabs rsi,0x402000
   0x0000000000401014 <+20>:    mov    edx,0x14
   0x0000000000401019 <+25>:    syscall
   0x000000000040101b <+27>:    xor    rax,rax
   0x000000000040101e <+30>:    xor    rbx,rbx
   0x0000000000401021 <+33>:    inc    rbx
End of assembler dump.
gef➤  b *_start+25
Breakpoint 1 at 0x401019
gef➤  r
Starting program: /home/kali/fib

Breakpoint 1, 0x0000000000401019 in _start ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x1
$rbx   : 0x0
$rcx   : 0x0
$rdx   : 0x14
$rsp   : 0x00007fffffffdf30  →  0x0000000000000001
$rbp   : 0x0
$rsi   : 0x0000000000402000  →  "Fibonacci Sequence:\n"
$rdi   : 0x1
$rip   : 0x0000000000401019  →  <_start+0019> syscall
$r8    : 0x0
$r9    : 0x0
$r10   : 0x0
$r11   : 0x0
$r12   : 0x0
$r13   : 0x0
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdf30│+0x0000: 0x0000000000000001   ← $rsp
0x00007fffffffdf38│+0x0008: 0x00007fffffffe2af  →  "/home/kali/fib"
0x00007fffffffdf40│+0x0010: 0x0000000000000000
0x00007fffffffdf48│+0x0018: 0x00007fffffffe2be  →  "ALACRITTY_LOG=/tmp/Alacritty-1343.log"
0x00007fffffffdf50│+0x0020: 0x00007fffffffe2e4  →  "ALACRITTY_SOCKET=/run/user/1000/Alacritty-:0-1343.[...]"
0x00007fffffffdf58│+0x0028: 0x00007fffffffe31b  →  "COLORTERM=truecolor"
0x00007fffffffdf60│+0x0030: 0x00007fffffffe32f  →  "COMMAND_NOT_FOUND_INSTALL_PROMPT=1"
0x00007fffffffdf68│+0x0038: 0x00007fffffffe352  →  "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401005 <_start+0005>    mov    edi, 0x1
     0x40100a <_start+000a>    movabs rsi, 0x402000
     0x401014 <_start+0014>    mov    edx, 0x14
●→   0x401019 <_start+0019>    syscall
     0x40101b <_start+001b>    xor    rax, rax
     0x40101e <_start+001e>    xor    rbx, rbx
     0x401021 <_start+0021>    inc    rbx
     0x401024 <loopFib+0000>   add    rax, rbx
     0x401027 <loopFib+0003>   xchg   rbx, rax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fib", stopped 0x401019 in _start (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401019 → _start()

Exit Syscall

By not properly ending the program we get the error segmentation fault. In the manual we can find the exit syscall number and arguments.

# find number
$ grep exit /usr/include/x86_64-linux-gnu/asm/unistd_64.h

#define __NR_exit 60
#define __NR_exit_group 231

# find arguments
$ man -s 2 exit
void _exit(int status);

It needs 1 integer argument, in this we use the exit code 0. And this is our exit syscall code.

    mov rax, 60
    mov rdi, 0
    syscall

Final code

global  _start

section .data
    message db "Fibonacci Sequence:", 0x0a

section .text
_start:
    mov rax, 1       ; rax: syscall number 1
    mov rdi, 1      ; rdi: fd 1 for stdout
    mov rsi,message ; rsi: pointer to message
    mov rdx, 20      ; rdx: print length of 20 bytes
    syscall         ; call write syscall to the intro message
    xor rax, rax    ; initialize rax to 0
    xor rbx, rbx    ; initialize rbx to 0
    inc rbx         ; increment rbx to 1
loopFib:
    add rax, rbx    ; get the next number
    xchg rax, rbx   ; swap values
    cmp rbx, 10		; do rbx - 10
    js loopFib		; jump if result is <0
    mov rax, 60
    mov rdi, 0
    syscall

Now if we run it we dont get a segmentation fault

➜  ~ ./assembler.sh fib.s
Fibonacci Sequence:

Procedures

To make code more more efficient we can use functions and procedures. Procedures are most used for code refactoring. Its a set of instructions to execute a point in the program. We define it under a procedure label.

We can create procedures: printMessage, initFib, loopFib and Exit. To make it more efficent we use call to call a procedure. Call saves next instruction pointer rip to the stack and then just to the procedure. If procedure has ran we end it with ret to return to the point where we were before jumping to procedure.

ret instruction is crucial in Return-Oriented Programming (ROP)

Instruction
Description
Example

call

push the next instruction pointer rip to the stack, then jumps to the specified procedure

call printMessage

ret

pop the address at rsp into rip, then jump to it

ret

Our new script

global  _start

section .data
    message db "Fibonacci Sequence:", 0x0a

section .text
_start:
    call printMessage   ; print intro message
    call initFib        ; set initial Fib values
    call loopFib        ; calculate Fib numbers
    call Exit           ; Exit the program

printMessage:
    mov rax, 1      ; rax: syscall number 1
    mov rdi, 1      ; rdi: fd 1 for stdout
    mov rsi,message ; rsi: pointer to message
    mov rdx, 20     ; rdx: print length of 20 bytes
    syscall         ; call write syscall to the intro message
    ret

initFib:
    xor rax, rax    ; initialize rax to 0
    xor rbx, rbx    ; initialize rbx to 0
    inc rbx         ; increment rbx to 1
    ret

loopFib:
    add rax, rbx    ; get the next number
    xchg rax, rbx   ; swap values
    cmp rbx, 10		; do rbx - 10
    js loopFib		; jump if result is <0
    ret

Exit:
    mov rax, 60
    mov rdi, 0
    syscall

Functions

Calling Functions

Functions are used to use the stack and registers fully. Functions have a Calling Convention. There are four main things we need to consider before calling a function:

  1. Save Registers on the stack (Caller Saved)

  2. Pass Function Arguments (like syscalls)

  3. Fix Stack Alignment

  4. Get Function's Return Value (in rax)

Writing Functions

When it comes to writing a function, there are different things to consider:

  1. Saving Callee Saved registers (rbx and rbp)

  2. Get arguments from registers

  3. Align the Stack

  4. Return value in rax

Using External Functions

We can use libc to utilize function without have to rewrite or write that code. Like the printf function in libc which accepts printing format. We can pass it numbers and print it as integers, conversion is done automatically. If we want to use a function from libc we have to import it and specify the libc library for dynamic linking when linking with ld.

Importing libc Functions

We can use the extern instuction to import the libc function. In this case we use the printf function.

global  _start
extern  printf

section .data
    message db "Fibonacci Sequence:", 0x0a
    outFormat db  "%d", 0x0a, 0x00

section .text
_start:
    call printMessage   ; print intro message
    call initFib        ; set initial Fib values
    call loopFib        ; calculate Fib numbers
    call Exit           ; Exit the program

printMessage:
    mov rax, 1           ; rax: syscall number 1
    mov rdi, 1          ; rdi: fd 1 for stdout
    mov rsi, message    ; rsi: pointer to message
    mov rdx, 20          ; rdx: print length of 20 bytes
    syscall             ; call write syscall to the intro message
    ret

initFib:
    xor rax, rax        ; initialize rax to 0
    xor rbx, rbx        ; initialize rbx to 0
    inc rbx             ; increment rbx to 1
    ret

printFib:
    push rax            ; push registers to stack
    push rbx
    mov rdi, outFormat  ; set 1st argument (Print Format)
    mov rsi, rbx        ; set 2nd argument (Fib Number)
    call printf         ; printf(outFormat, rbx)
    pop rbx             ; restore registers from stack
    pop rax
    ret

loopFib:
    call printFib       ; print current Fib number
    add rax, rbx        ; get the next number
    xchg rax, rbx       ; swap values
    cmp rbx, 10		    ; do rbx - 10
    js loopFib		    ; jump if result is <0
    ret

Exit:
    mov rax, 60
    mov rdi, 0
    syscall

To compile we have to link the libc library

// Some code$ nasm -f elf64 fib.s &&  ld fib.o -o fib -lc --dynamic-linker /lib64/ld-linux-x86-64.so.2 && ./fib

Importing libc Functions

We can use scanf to take user input and have it converted to an integer which we can use with cmp.

Import with

global  _start
extern  printf, scanf

getInput:
    ; call scanf

The set the variables of scanf.

section .data
    message db "Please input max Fn", 0x0a
    outFormat db  "%d", 0x0a, 0x00
    inFormat db  "%d", 0x00

We need to set a buffer space for input storage in the .bss memory segment. We reserve 1 byte.

section .bss
    userInput resb 1

Last updated

Was this helpful?