Saltar a contenido

Libc Functions

Hasta ahora, solo hemos estado imprimiendo números de Fibonacci que son menores que 10. Pero de esta manera, nuestro programa es estático e imprimirá el mismo resultado cada vez. Para hacerlo más dinámico, podemos pedirle al usuario el número máximo de Fibonacci que desea imprimir y luego usarlo con cmp. Antes de comenzar, recordemos la convención de llamadas a funciones:

  1. Save Registers en el Stack (Caller Saved)
  2. Pasar Function Arguments (como syscalls)
  3. Ajustar la Stack Alignment
  4. Obtener el Return Value de las funciones (en rax)

Así que, importemos nuestra función y comencemos con los pasos de la convención de llamadas.


Importing libc Functions

Para hacerlo, podemos usar la función scanf de libc para tomar la entrada del usuario y convertirla correctamente en un entero, que luego usaremos con cmp. Primero, debemos importar scanf, de la siguiente manera:

global  _start
extern  printf, scanf

Ahora podemos comenzar a escribir un nuevo procedimiento, getInput, para llamarlo cuando lo necesitemos:

getInput:
    ; call scanf

Saving Registers

Como estamos al comienzo de nuestro programa y aún no hemos usado ningún registro, no tenemos que preocuparnos por guardar registros en el Stack. Así que, podemos proceder con el segundo punto, y pasar los argumentos requeridos a scanf.


Function Arguments

A continuación, necesitamos saber qué argumentos acepta scanf, como se muestra a continuación:

man -s 3 scanf

...SNIP...
int scanf(const char *format, ...);

Vemos que, de manera similar a printf, scanf acepta un formato de entrada y el buffer donde queremos guardar la entrada del usuario. Así que, primero agreguemos la variable inFormat:

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

También cambiamos nuestro mensaje de introducción de Fibonacci Sequence: a Please input max Fn, para decirle al usuario qué entrada se espera de ellos.

Luego, debemos establecer un espacio de buffer para el almacenamiento de entrada. Como mencionamos en la sección de Processor Architecture, el espacio de buffer no inicializado debe almacenarse en el segmento de memoria .bss. Así que, al comienzo de nuestro código en ensamblador, debemos agregarlo bajo la etiqueta .bss, y usar resb 1 para indicarle a nasm que reserve 1 byte de espacio de buffer, de la siguiente manera:

section .bss
    userInput resb 1

Ahora podemos establecer nuestros argumentos de función en nuestro procedimiento getInput:

getInput:
    mov rdi, inFormat   ; set 1st parameter (inFormat)
    mov rsi, userInput  ; set 2nd parameter (userInput)

Stack Alignment

A continuación, tenemos que asegurarnos de que nuestro Stack esté alineado en un límite de 16 bytes. Actualmente estamos dentro del procedimiento getInput, por lo que tenemos 1 instrucción call y ninguna instrucción push, por lo que tenemos un límite de 8 bytes. Así que, podemos usar sub para arreglar rsp, como se muestra a continuación:

getInput:
    sub rsp, 8
    ; call scanf
    add rsp, 8

Podemos usar push rax en su lugar, y esto alineará correctamente el Stack también. De esta manera, nuestro Stack debería estar perfectamente alineado con un límite de 16 bytes.


Function Call

Ahora, configuramos los argumentos de la función y llamamos a scanf, como se muestra a continuación:

getInput:
    sub rsp, 8          ; align stack to 16-bytes
    mov rdi, inFormat   ; set 1st parameter (inFormat)
    mov rsi, userInput  ; set 2nd parameter (userInput)
    call scanf          ; scanf(inFormat, userInput)
    add rsp, 8          ; restore stack alignment
    ret

También agregaremos call getInput en _start, para que vayamos a este procedimiento justo después de imprimir el mensaje de introducción, como se muestra a continuación:

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

Finalmente, tenemos que hacer uso de la entrada del usuario. Para hacerlo, en lugar de usar un 10 estático al comparar en cmp rbx, 10, lo cambiaremos a cmp rbx, [userInput], como se muestra a continuación:

loopFib:
    ...SNIP...
    cmp rbx,[userInput] ; do rbx - userInput
    js loopFib          ; jump if result is <0
    ret

Nota: Usamos [userInput] en lugar de userInput, ya que queríamos comparar con el valor final, y no con la dirección del puntero.

Con todo esto hecho, nuestro código completo final debería verse de la siguiente manera:

global  _start
extern  printf, scanf

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

section .bss
    userInput resb 1

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

printMessage:
    ...SNIP...

getInput:
    sub rsp, 8          ; align stack to 16-bytes
    mov rdi, inFormat   ; set 1st parameter (inFormat)
    mov rsi, userInput  ; set 2nd parameter (userInput)
    call scanf          ; scanf(inFormat, userInput)
    add rsp, 8          ; restore stack alignment
    ret

initFib:
    ...SNIP...

printFib:
    ...SNIP...

loopFib:
    ...SNIP...
    cmp rbx,[userInput] ; do rbx - userInput
    js loopFib          ; jump if result is <0
    ret

Exit:
    ...SNIP...

Dynamic Linker

Ensamblemos nuestro código, enlacémoslo e intentemos imprimir números de Fibonacci hasta 100:

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

Please input max Fn:
100
1
1
2
3
5
8
13
21
34
55
89

Vemos que nuestro código funcionó como se esperaba e imprimió números de Fibonacci menores al número que especificamos. Con esto, podemos completar nuestro proyecto de módulo y crear un programa que calcule e imprima números de Fibonacci basados en una entrada que proporcionemos, usando únicamente ensamblador.

Además, necesitamos aprender cómo convertir el código en ensamblador en shellcode de máquina, que luego podemos usar directamente en nuestros payloads en Binary Exploitation.