Únete al equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Contacta con el equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Fundamentos de reversing / 08

Arrays de estructuras y parches

LoberaFormación técnica08 / 33 · 7 min de lectura · Linux · x86 / x86-64
08 / 33

Seguir el acceso a registros consecutivos y comprobar cambios sobre una copia del binario.

Consultar conceptos ↗

Hoy vamos a trabajar con un ejemplo más completo relacionado con structs. El objetivo es entender correctamente cómo funciona el programa internamente y explorar algunas características adicionales de r2.

C / EJEMPLO
#include <stdio.h>

const int MAX = 20;
"td struct stud { char fletter; int age; float mark;};"
struct stud
    {
        char fletter;
        int age;
        float mark;
    };

main(){
    func1();
    getchar();
}


void showstudents(struct stud students[], int n){

    printf("Showing students: \n");

    for(int i = 0; i < n; i++){
        printf("Student num %d: ", i);
        printf("First letter = %c ", students[i].fletter);
        printf("Age = %d ", students[i].age);
        printf("Mark = %f \n", students[i].mark);
    }


}


int addstudents(struct stud students[], int i){
    printf("Round: %d \n", i);

    if(i > MAX){
        printf("Students list is FULL\n");
        return -1;
    }
    else{
        printf("First letter?\n");
        scanf(" %c",&students[i].fletter);

        printf("Age?\n");
        scanf(" %i",&students[i].age);

        printf("Mark?\n");
        scanf(" %f",&students[i].mark);



        return i+1;
    }

}

func1(){

    struct stud students[MAX];

    int i = 0;
    char q =' ';

    while(q != 'q' && i!=-1){
        printf("Action? (q=Quit, a=Add, s=ShowAll)\n");

        scanf(" %c", &q);
        if(q == 'a'){
            i = addstudents(students,i);



        }


    }
}

Localizar las funciones

Veamos qué funciones tenemos:

CÓDIGO / SALIDA
[0x000009c0]> afl
0x000006a0    1 42           entry0
0x000006d0    4 50   -> 40   sym.deregister_tm_clones
0x00000710    4 66   -> 57   sym.register_tm_clones
0x00000760    5 58   -> 51   sym.__do_global_dtors_aux
0x000007a0    1 10           entry.init0
0x00000b90    1 2            sym.__libc_csu_fini
0x000009c0    8 352          sym.func1
0x00000b94    1 9            sym._fini
0x000007c4    4 240          sym.showstudents
0x00000b20    4 101          sym.__libc_csu_init
0x000008b4    4 268          sym.addstudents
0x000007aa    1 26           main
0x00000670    1 6            sym.imp.getchar
0x00000618    3 23           sym._init
0x00000640    1 6            sym.imp.puts
0x00000650    1 6            sym.imp.__stack_chk_fail
0x00000660    1 6            sym.imp.printf
0x00000000    5 97   -> 123  loc.imp._ITM_deregisterTMCloneTable
0x00000680    1 6            sym.imp.__isoc99_scanf

Las funciones más relevantes son showstudents, addstudents y func1; el resto corresponde a la biblioteca stdio y al punto de entrada del programa. Como ya sabemos, el programa «comienza» en sym.func1, así que veamos:

ENSAMBLADOR / REFERENCIA
[0x000006a0]> s sym.func1
[0x000009c0]> pdf
            ; CALL XREF from main @ 0x7b3
 352: sym.func1 ();
           ; var int64_t var_2dh @ rbp-0x2d
           ; var uint32_t var_2ch @ rbp-0x2c
           ; var int64_t var_28h @ rbp-0x28
           ; var int64_t var_20h @ rbp-0x20
           ; var int64_t canary @ rbp-0x18
           ; var int64_t var_10h @ rbp-0x10
           0x000009c0      55             push rbp
           0x000009c1      4889e5         mov rbp, rsp
           0x000009c4      4154           push r12
           0x000009c6      53             push rbx
           0x000009c7      4883ec20       sub rsp, 0x20
           0x000009cb      64488b3c2528.  mov rdi, qword fs:[0x28]
           0x000009d4      48897de8       mov qword [canary], rdi
           0x000009d8      31ff           xor edi, edi
           0x000009da      4889e6         mov rsi, rsp
           0x000009dd      4989f4         mov r12, rsi
           0x000009e0      be14000000     mov esi, 0x14
           0x000009e5      4863f6         movsxd rsi, esi
           0x000009e8      4883ee01       sub rsi, 1
           0x000009ec      488975d8       mov qword [var_28h], rsi
           0x000009f0      be14000000     mov esi, 0x14
           0x000009f5      4863f6         movsxd rsi, esi
           0x000009f8      4889f0         mov rax, rsi
           0x000009fb      ba00000000     mov edx, 0
           0x00000a00      486bfa60       imul rdi, rdx, 0x60
           0x00000a04      486bf000       imul rsi, rax, 0
           0x00000a08      4801fe         add rsi, rdi
           0x00000a0b      bf60000000     mov edi, 0x60               ; '`'
           0x00000a10      48f7e7         mul rdi
           0x00000a13      4801d6         add rsi, rdx
           0x00000a16      4889f2         mov rdx, rsi
           0x00000a19      b814000000     mov eax, 0x14
           0x00000a1e      4863d0         movsxd rdx, eax
           0x00000a21      4889d0         mov rax, rdx
           0x00000a24      4801c0         add rax, rax
           0x00000a27      4801d0         add rax, rdx
           0x00000a2a      48c1e002       shl rax, 2
           0x00000a2e      b814000000     mov eax, 0x14
           0x00000a33      4898           cdqe
           0x00000a35      4889c1         mov rcx, rax
           0x00000a38      bb00000000     mov ebx, 0
           0x00000a3d      486bd360       imul rdx, rbx, 0x60
           0x00000a41      486bc100       imul rax, rcx, 0
           0x00000a45      488d3402       lea rsi, [rdx + rax]
           0x00000a49      b860000000     mov eax, 0x60               ; '`'
           0x00000a4e      48f7e1         mul rcx
           0x00000a51      488d0c16       lea rcx, [rsi + rdx]
           0x00000a55      4889ca         mov rdx, rcx
           0x00000a58      b814000000     mov eax, 0x14
           0x00000a5d      4863d0         movsxd rdx, eax
           0x00000a60      4889d0         mov rax, rdx
           0x00000a63      4801c0         add rax, rax
           0x00000a66      4801d0         add rax, rdx
           0x00000a69      48c1e002       shl rax, 2
           0x00000a6d      488d5003       lea rdx, [rax + 3]
           0x00000a71      b810000000     mov eax, 0x10
           0x00000a76      4883e801       sub rax, 1
           0x00000a7a      4801d0         add rax, rdx
           0x00000a7d      bb10000000     mov ebx, 0x10
           0x00000a82      ba00000000     mov edx, 0
           0x00000a87      48f7f3         div rbx
           0x00000a8a      486bc010       imul rax, rax, 0x10
           0x00000a8e      4829c4         sub rsp, rax
           0x00000a91      4889e0         mov rax, rsp
           0x00000a94      4883c003       add rax, 3
           0x00000a98      48c1e802       shr rax, 2
           0x00000a9c      48c1e002       shl rax, 2
           0x00000aa0      488945e0       mov qword [var_20h], rax
           0x00000aa4      c745d4000000.  mov dword [var_2ch], 0
           0x00000aab      c645d320       mov byte [var_2dh], 0x20    ; "@"
       ┌─< 0x00000aaf      eb40           jmp 0xaf1
          ; CODE XREF from sym.func1 @ 0xafd
      ┌──> 0x00000ab1      488d3d880100.  lea rdi, str.Action___q_Quit__a_Add__s_ShowAll ; 0xc40 ; "Action? (q=Quit, a=Add, s=ShowAll)" ; const char *s
      ╎│   0x00000ab8      e883fbffff     call sym.imp.puts           ; int puts(const char *s)
      ╎│   0x00000abd      488d45d3       lea rax, [var_2dh]
      ╎│   0x00000ac1      4889c6         mov rsi, rax
      ╎│   0x00000ac4      488d3d5e0100.  lea rdi, [0x00000c29]       ; " %c" ; const char *format
      ╎│   0x00000acb      b800000000     mov eax, 0
      ╎│   0x00000ad0      e8abfbffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
      ╎│   0x00000ad5      0fb645d3       movzx eax, byte [var_2dh]
      ╎│   0x00000ad9      3c61           cmp al, 0x61
     ┌───< 0x00000adb      7514           jne 0xaf1
     │╎│   0x00000add      488b45e0       mov rax, qword [var_20h]
     │╎│   0x00000ae1      8b55d4         mov edx, dword [var_2ch]
     │╎│   0x00000ae4      89d6           mov esi, edx                ; signed int64_t arg2
     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8c6fdffff     call sym.addstudents
     │╎│   0x00000aee      8945d4         mov dword [var_2ch], eax
     │╎│   ; CODE XREFS from sym.func1 @ 0xaaf, 0xadb
     └─└─> 0x00000af1      0fb645d3       movzx eax, byte [var_2dh]
          0x00000af5      3c71           cmp al, 0x71
      ╎┌─< 0x00000af7      7406           je 0xaff
      ╎│   0x00000af9      837dd4ff       cmp dword [var_2ch], 0xffffffff
      └──< 0x00000afd      75b2           jne 0xab1
          ; CODE XREF from sym.func1 @ 0xaf7
       └─> 0x00000aff      4c89e4         mov rsp, r12
           0x00000b02      90             nop
           0x00000b03      488b5de8       mov rbx, qword [canary]
           0x00000b07      6448331c2528.  xor rbx, qword fs:[0x28]
       ┌─< 0x00000b10      7405           je 0xb17
          0x00000b12      e839fbffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
          ; CODE XREF from sym.func1 @ 0xb10
       └─> 0x00000b17      488d65f0       lea rsp, [var_10h]
           0x00000b1b      5b             pop rbx
           0x00000b1c      415c           pop r12
           0x00000b1e      5d             pop rbp
           0x00000b1f      c3             ret

Esto empieza a tener cierta complejidad; el bloque es grande. En situaciones así puede interesarte examinar el programa con un estilo más cercano a C para obtener una visión general. Los decompiladores ayudan con eso. Radare2 incluye un decompilador llamado r2dec, que se puede instalar mediante el gestor de paquetes de radare2, r2pm.

Podemos instalar un paquete —r2dec en este caso— con lo siguiente:

CÓDIGO / SALIDA
r2pm init
r2pm install r2dec

Y después, con pdd, ejecutamos el decompilador.

C / EJEMPLO
0x000009c0]> pdd
/* r2dec pseudo code output */
/* struct1 @ 0x9c0 */
#include <stdint.h>

int64_t func1 (void) {
    int64_t var_2dh;
    uint32_t var_2ch;
    int64_t var_28h;
    int64_t var_20h;
    int64_t canary;
    int64_t var_10h;
    rdi = *(fs:0x28);
    canary = *(fs:0x28);
    edi = 0;
    rsi = rsp;
    r12 = rsp;
    esi = 0x14;
    rsi = (int64_t) esi;
    rsi--;
    var_28h = rsi;
    esi = 0x14;
    rsi = (int64_t) esi;
    rax = rsi;
    edx = 0;
    rdi = rdx * 0x60;
    rsi = rax * 0;
    rsi += rdi;
    edi = 0x60;
    rdx:rax = rax * rdi;
    rsi += rdx;
    rdx = rsi;
    eax = 0x14;
    rdx = (int64_t) eax;
    rax = rdx;
    rax += rax;
    rax += rdx;
    rax <<= 2;
    eax = 0x14;
    rax = (int64_t) eax;
    rcx = rax;
    ebx = 0;
    rdx = rbx * 0x60;
    rax = rcx * 0;
    rsi = rdx + rax;
    eax = 0x60;
    rdx:rax = rax * rcx;
    rcx = rsi + rdx;
    rdx = rcx;
    eax = 0x14;
    rdx = (int64_t) eax;
    rax = rdx;
    rax += rax;
    rax += rdx;
    rax <<= 2;
    rdx = rax + 3;
    eax = 0x10;
    rax--;
    rax += rdx;
    ebx = 0x10;
    edx = 0;
    rax = rdx:rax / rbx;
    rdx = rdx:rax % rbx;
    rax *= 0x10;
    rax = rsp;
    rax += 3;
    rax >>= 2;
    rax <<= 2;
    var_20h = rax;
    var_2ch = 0;
    var_2dh = 0x20;
    while (var_2ch != 0xffffffff) {
        puts ("Action? (q=Quit, a=Add, s=ShowAll)");
        rax = &var_2dh;
        rsi = rax;
        rdi = 0x00000c29;
        eax = 0;
        isoc99_scanf ();
        eax = (int32_t) var_2dh;
        if (al == 0x61) {
            rax = var_20h;
            edx = var_2ch;
            esi = var_2ch;
            rdi = rax;
            showstudents ();
            var_2ch++;
        }
        eax = (int32_t) var_2dh;
        if (al == 0x71) {
            goto label_0;
        }
    }
label_0:
    rbx = canary;
    rbx ^= *(fs:0x28);
    if (var_2ch != 0xffffffff) {
        stack_chk_fail ();
    }
    rsp = &var_10h;
    return rax;
}

En este ejemplo nos encontramos con el primer proyecto en el que omitiremos algunas partes; es decir, obtendremos la visión general y luego nos centraremos solo en las áreas o aspectos estratégicos del programa. A partir de lo que vimos al ejecutar el decompilador podemos ver que la función establece el stack canary, ejecuta un bloque grande de cálculos, almacena el resultado en var_20h e inicializa un par de variables: var_2ch a cero y var_2dh con 0x20 (espacio ASCII ' '). Como ya tenemos el código fuente original, podemos deducir rápidamente que corresponden a la inicialización de las variables de la función. Lo almacenado en var_20h debería ser la dirección base del array de structs; sabiendo eso, podemos entender mejor todo el bloque de operaciones que aparece justo antes. Si lo examinamos con más atención, podemos detectar fácilmente que:

ENSAMBLADOR / REFERENCIA
           0x000009e0      be14000000     mov esi, 0x14
           0x000009e5      4863f6         movsxd rsi, esi
           0x000009e8      4883ee01       sub rsi, 1
           0x000009ec      488975d8       mov qword [var_28h], rsi
           0x000009f0      be14000000     mov esi, 0x14
           0x000009f5      4863f6         movsxd rsi, esi
           0x000009f8      4889f0         mov rax, rsi
           0x000009fb      ba00000000     mov edx, 0
           0x00000a00      486bfa60       imul rdi, rdx, 0x60
           0x00000a04      486bf000       imul rsi, rax, 0
           0x00000a08      4801fe         add rsi, rdi
           0x00000a0b      bf60000000     mov edi, 0x60               ; '`'
           0x00000a10      48f7e7         mul rdi
           0x00000a13      4801d6         add rsi, rdx
           0x00000a16      4889f2         mov rdx, rsi
           0x00000a19      b814000000     mov eax, 0x14
           0x00000a1e      4863d0         movsxd rdx, eax
           0x00000a21      4889d0         mov rax, rdx
           0x00000a24      4801c0         add rax, rax
           0x00000a27      4801d0         add rax, rdx
           0x00000a2a      48c1e002       shl rax, 2
           0x00000a2e      b814000000     mov eax, 0x14

Reservar y recorrer el array

0x14 (decimal 20) se repite varias veces, por lo que el programa básicamente «reserva» espacio suficiente para almacenar el array completo en memoria; dicho de otro modo, calcula una dirección de inicio para la estructura, ya que no puede referenciar simplemente una variable con ebp-20 o similar, porque nuestra estructura contiene varios valores de distintos tamaños. Otro detalle a tener en cuenta: como se puede ver, no se llama a ninguna función en ese bloque de código, lo que indica que no ocurre nada especialmente relevante para la lógica del algoritmo. A veces, cuando ves bloques así, suelen corresponder a cálculos de direcciones de memoria u operaciones matemáticas sobre variables. Presta siempre atención a valores estáticos como esos 0x14 (o el espacio ASCII, etc.); pueden indicar el tamaño de estructuras de datos, índices, inicializaciones de variables, valores de comparación (límites), etc. En la mayoría de los casos no es necesario entender el 100 % del código instrucción por instrucción; con experiencia y una visión general puedes centrarte rápidamente en las partes estratégicas.

Ahora sabemos que la función comienza con un cálculo de direcciones de memoria y también inicializa algunas variables; después parece saltar a un bucle while:

ENSAMBLADOR / REFERENCIA
           0x00000aab      c645d320       mov byte [var_2dh], 0x20    ; "@"
       ┌─< 0x00000aaf      eb40           jmp 0xaf1
          ; CODE XREF from sym.func1 @ 0xafd
      ┌──> 0x00000ab1      488d3d880100.  lea rdi, str.Action___q_Quit__a_Add__s_ShowAll ; 0xc40 ; "Action? (q=Quit, a=Add, s=ShowAll)" ; const char *s
      ╎│   0x00000ab8      e883fbffff     call sym.imp.puts           ; int puts(const char *s)
      ╎│   0x00000abd      488d45d3       lea rax, [var_2dh]
      ╎│   0x00000ac1      4889c6         mov rsi, rax
      ╎│   0x00000ac4      488d3d5e0100.  lea rdi, [0x00000c29]       ; " %c" ; const char *format
      ╎│   0x00000acb      b800000000     mov eax, 0
      ╎│   0x00000ad0      e8abfbffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
      ╎│   0x00000ad5      0fb645d3       movzx eax, byte [var_2dh]
      ╎│   0x00000ad9      3c61           cmp al, 0x61
     ┌───< 0x00000adb      7514           jne 0xaf1
     │╎│   0x00000add      488b45e0       mov rax, qword [var_20h]
     │╎│   0x00000ae1      8b55d4         mov edx, dword [var_2ch]
     │╎│   0x00000ae4      89d6           mov esi, edx                ; signed int64_t arg2
     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8c6fdffff     call sym.addstudents
     │╎│   0x00000aee      8945d4         mov dword [var_2ch], eax
     │╎│   ; CODE XREFS from sym.func1 @ 0xaaf, 0xadb
     └─└─> 0x00000af1      0fb645d3       movzx eax, byte [var_2dh]
          0x00000af5      3c71           cmp al, 0x71
      ╎┌─< 0x00000af7      7406           je 0xaff
      ╎│   0x00000af9      837dd4ff       cmp dword [var_2ch], 0xffffffff
      └──< 0x00000afd      75b2           jne 0xab1
          ; CODE XREF from sym.func1 @ 0xaf7
       └─> 0x00000aff      4c89e4         mov rsp, r12

A continuación, lo que sigue ya debería resultarnos familiar.

ENSAMBLADOR / REFERENCIA
      ┌──> 0x00000ab1      488d3d880100.  lea rdi, str.Action___q_Quit__a_Add__s_ShowAll ; 0xc40 ; "Action? (q=Quit, a=Add, s=ShowAll)" ; const char *s
      ╎│   0x00000ab8      e883fbffff     call sym.imp.puts           ; int puts(const char *s)
      ╎│   0x00000abd      488d45d3       lea rax, [var_2dh]
      ╎│   0x00000ac1      4889c6         mov rsi, rax
      ╎│   0x00000ac4      488d3d5e0100.  lea rdi, [0x00000c29]       ; " %c" ; const char *format
      ╎│   0x00000acb      b800000000     mov eax, 0
      ╎│   0x00000ad0      e8abfbffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
      ╎│   0x00000ad5      0fb645d3       movzx eax, byte [var_2dh]
      ╎│   0x00000ad9      3c61           cmp al, 0x61
     ┌───< 0x00000adb      7514           jne 0xaf1
     │╎│   0x00000add      488b45e0       mov rax, qword [var_20h]

La cadena que se imprime ya nos indica qué ocurre en ese bloque de código: lee la entrada del usuario (opción: quit, add, show) y, como compara la entrada con 0x61 (ASCII 'a'), comprueba la entrada y ejecutará una acción según ella.

Veamos qué ocurre cuando el usuario introduce 'a' (add):

ENSAMBLADOR / REFERENCIA
     │╎│   0x00000add      488b45e0       mov rax, qword [var_20h]
     │╎│   0x00000ae1      8b55d4         mov edx, dword [var_2ch]
     │╎│   0x00000ae4      89d6           mov esi, edx                ; signed int64_t arg2
     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8c6fdffff     call sym.addstudents
     │╎│   0x00000aee      8945d4         mov dword [var_2ch], eax

El contenido de var_20h y var_2ch se pasan como parámetros. Como ya sabemos, var_20h representa (o eso creemos) el puntero al array de structs; var_2ch, como vimos, se inicializa a cero y se actualiza con el valor de eax, por lo que podemos asumir que es el contador. Se pasa a la función addstudents para indicar la posición actual en la que se está trabajando.

Después de eso, el bucle continúa y el programa realiza un par de cmps:

ENSAMBLADOR / REFERENCIA
          0x00000af5      3c71           cmp al, 0x71
      ╎┌─< 0x00000af7      7406           je 0xaff
      ╎│   0x00000af9      837dd4ff       cmp dword [var_2ch], 0xffffffff
      └──< 0x00000afd      75b2           jne 0xab1

Que representan la condición q != 'q' && i!=-1. Al ser un &&, la condición será falsa si q=='q' y no necesitará comprobar i!=-1, de ahí que tengamos esos dos cmp encadenados de esa forma.

Reconstruir los campos de Student

Dicho esto, podemos saltar a la función addstudents para inspeccionar cómo funciona.

ENSAMBLADOR / REFERENCIA
[0x000008b4]> pdf
            ; CALL XREF from sym.func1 @ 0xae9
 268: sym.addstudents (int64_t arg1, signed int64_t arg2);
           ; var signed int64_t var_ch @ rbp-0xc
           ; var int64_t var_8h @ rbp-0x8
           ; arg int64_t arg1 @ rdi
           ; arg signed int64_t arg2 @ rsi
           0x000008b4      55             push rbp
           0x000008b5      4889e5         mov rbp, rsp
           0x000008b8      4883ec10       sub rsp, 0x10
           0x000008bc      48897df8       mov qword [var_8h], rdi     ; arg1
           0x000008c0      8975f4         mov dword [var_ch], esi     ; arg2
           0x000008c3      8b45f4         mov eax, dword [var_ch]
           0x000008c6      89c6           mov esi, eax
           0x000008c8      488d3d2a0300.  lea rdi, str.Round:__d      ; 0xbf9 ; "Round: %d \n" ; const char *format
           0x000008cf      b800000000     mov eax, 0
           0x000008d4      e887fdffff     call sym.imp.printf         ; int printf(const char *format)
           0x000008d9      b814000000     mov eax, 0x14
           0x000008de      3945f4         cmp dword [var_ch], eax
       ┌─< 0x000008e1      7e16           jle 0x8f9
          0x000008e3      488d3d1b0300.  lea rdi, str.Students_list_is_FULL ; 0xc05 ; "Students list is FULL" ; const char *s
          0x000008ea      e851fdffff     call sym.imp.puts           ; int puts(const char *s)
          0x000008ef      b8ffffffff     mov eax, 0xffffffff         ; -1
      ┌──< 0x000008f4      e9c5000000     jmp 0x9be
      ││   ; CODE XREF from sym.addstudents @ 0x8e1
      │└─> 0x000008f9      488d3d1b0300.  lea rdi, str.First_letter   ; 0xc1b ; "First letter?" ; const char *s
          0x00000900      e83bfdffff     call sym.imp.puts           ; int puts(const char *s)
          0x00000905      8b45f4         mov eax, dword [var_ch]
          0x00000908      4863d0         movsxd rdx, eax
          0x0000090b      4889d0         mov rax, rdx
          0x0000090e      4801c0         add rax, rax
          0x00000911      4801d0         add rax, rdx
          0x00000914      48c1e002       shl rax, 2
          0x00000918      4889c2         mov rdx, rax
          0x0000091b      488b45f8       mov rax, qword [var_8h]
          0x0000091f      4801d0         add rax, rdx
          0x00000922      4889c6         mov rsi, rax
          0x00000925      488d3dfd0200.  lea rdi, [0x00000c29]       ; " %c" ; const char *format
          0x0000092c      b800000000     mov eax, 0
          0x00000931      e84afdffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
          0x00000936      488d3df00200.  lea rdi, str.Age            ; 0xc2d ; "Age?" ; const char *s
          0x0000093d      e8fefcffff     call sym.imp.puts           ; int puts(const char *s)
          0x00000942      8b45f4         mov eax, dword [var_ch]
          0x00000945      4863d0         movsxd rdx, eax
          0x00000948      4889d0         mov rax, rdx
          0x0000094b      4801c0         add rax, rax
          0x0000094e      4801d0         add rax, rdx
          0x00000951      48c1e002       shl rax, 2
          0x00000955      4889c2         mov rdx, rax
          0x00000958      488b45f8       mov rax, qword [var_8h]
          0x0000095c      4801d0         add rax, rdx
          0x0000095f      4883c004       add rax, 4
          0x00000963      4889c6         mov rsi, rax
          0x00000966      488d3dc50200.  lea rdi, [0x00000c32]       ; " %i" ; const char *format
          0x0000096d      b800000000     mov eax, 0
          0x00000972      e809fdffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
          0x00000977      488d3db80200.  lea rdi, str.Mark           ; 0xc36 ; "Mark?" ; const char *s
          0x0000097e      e8bdfcffff     call sym.imp.puts           ; int puts(const char *s)
          0x00000983      8b45f4         mov eax, dword [var_ch]
          0x00000986      4863d0         movsxd rdx, eax
          0x00000989      4889d0         mov rax, rdx
          0x0000098c      4801c0         add rax, rax
          0x0000098f      4801d0         add rax, rdx
          0x00000992      48c1e002       shl rax, 2
          0x00000996      4889c2         mov rdx, rax
          0x00000999      488b45f8       mov rax, qword [var_8h]
          0x0000099d      4801d0         add rax, rdx
          0x000009a0      4883c008       add rax, 8
          0x000009a4      4889c6         mov rsi, rax
          0x000009a7      488d3d8e0200.  lea rdi, [0x00000c3c]       ; " %f" ; const char *format
          0x000009ae      b800000000     mov eax, 0
          0x000009b3      e8c8fcffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
          0x000009b8      8b45f4         mov eax, dword [var_ch]
          0x000009bb      83c001         add eax, 1
          ; CODE XREF from sym.addstudents @ 0x8f4
      └──> 0x000009be      c9             leave
           0x000009bf      c3             ret
[0x000008b4]> 

Esa función parece algo más extensa que las de los tutoriales anteriores, pero se puede ver fácilmente que sigue un patrón:

ENSAMBLADOR / REFERENCIA
      │└─> 0x000008f9      488d3d1b0300.  lea rdi, str.First_letter   ; 0xc1b ; "First letter?" ; const char *s
          0x00000900      e83bfdffff     call sym.imp.puts           ; int puts(const char *s)
          0x00000905      8b45f4         mov eax, dword [var_ch]
          0x00000908      4863d0         movsxd rdx, eax
          0x0000090b      4889d0         mov rax, rdx
          0x0000090e      4801c0         add rax, rax
          0x00000911      4801d0         add rax, rdx
          0x00000914      48c1e002       shl rax, 2
          0x00000918      4889c2         mov rdx, rax
          0x0000091b      488b45f8       mov rax, qword [var_8h]
          0x0000091f      4801d0         add rax, rdx
          0x00000922      4889c6         mov rsi, rax
          0x00000925      488d3dfd0200.  lea rdi, [0x00000c29]       ; " %c" ; const char *format
          0x0000092c      b800000000     mov eax, 0
          0x00000931      e84afdffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
          0x00000936      488d3df00200.  lea rdi, str.Age            ; 0xc2d ; "Age?" ; const char *s

El patrón se repite tres veces para los tres valores que la función solicita: letter, age y mark, con una única diferencia. En el bloque de age vemos un add 0x4 adicional, y 0x8 en el bloque del float. Podemos relacionar fácilmente esos valores con el tamaño de int y float en C.

Inspeccionemos primero el bloque relacionado con la lectura del char. Carga el valor de local_ch, ejecuta algunas operaciones sobre él y luego lo suma al valor de local_8h. Como podemos ver examinando el inicio de esta función y el orden de los parámetros que se le pasan en sym.func1, local_ch corresponde al int relacionado con la posición en el array y local_8h a la dirección base del array. ¿Qué hacen esas operaciones? En este caso son relevantes porque nos ayudan a entender mejor cómo están estructurados nuestros datos.

Esas operaciones se traducen en esto:

CÓDIGO / SALIDA
val = local_ch
val = val + val
val = val + local_ch

val = val + 2^2 (the shl 2 translates to that)

addr_char_towrite = local_8h + val

Como podemos calcular, para un índice de 0x0 el resultado será 0xC (decimal 12). En el siguiente bloque se repite la misma operación y se añade un 0x4 adicional; en el bloque final, un 0x8. Hay por tanto una distancia de 0xC (decimal 12) entre el primero y el último, lo que tiene todo el sentido. Con eso puedes deducir la estructura completa. Intenta depurar el programa, añadir algunos valores y volcar el struct.

Una vez que hemos «deducido» la estructura de los datos, podemos incluso ejecutar r2 en modo depuración y volcar el contenido así:

CÓDIGO / SALIDA
[0x5645465e8aee]> pf 5c...df @ 0x7ffe3530d310
0x7ffe3530d310 [0] {
  0x7ffe3530d310 = 'A'
        0x7ffe3530d314 = 23
  0x7ffe3530d318 = 3.29999995
}
0x7ffe3530d31c [1] {
  0x7ffe3530d31c = 'L'
        0x7ffe3530d320 = 2
  0x7ffe3530d324 = 3.4000001
}
0x7ffe3530d328 [2] {
  0x7ffe3530d328 = 'B'
        0x7ffe3530d32c = 19
  0x7ffe3530d330 = 6.4000001
}
0x7ffe3530d334 [3] {
  0x7ffe3530d334 = 'C'
        0x7ffe3530d338 = 32
  0x7ffe3530d33c = 7.69999981
}

Relacionar lectura y escritura

Si recuerdas, había otra función interesante en el programa: sym.showstudents, pero no la hemos visto en ningún punto del código. De momento podemos buscarla con s e inspeccionarla.

ENSAMBLADOR / REFERENCIA
[0x000007c4]> pdf
 240: sym.showstudents (int64_t arg1, signed int64_t arg2);
           ; var signed int64_t var_1ch @ rbp-0x1c
           ; var int64_t var_18h @ rbp-0x18
           ; var int64_t var_4h @ rbp-0x4
           ; arg int64_t arg1 @ rdi
           ; arg signed int64_t arg2 @ rsi
           0x000007c4      55             push rbp
           0x000007c5      4889e5         mov rbp, rsp
           0x000007c8      4883ec20       sub rsp, 0x20
           0x000007cc      48897de8       mov qword [var_18h], rdi    ; arg1
           0x000007d0      8975e4         mov dword [var_1ch], esi    ; arg2
           0x000007d3      488d3dd20300.  lea rdi, str.Showing_students: ; 0xbac ; "Showing students: " ; const char *s
           0x000007da      e861feffff     call sym.imp.puts           ; int puts(const char *s)
           0x000007df      c745fc000000.  mov dword [var_4h], 0
       ┌─< 0x000007e6      e9ba000000     jmp 0x8a5
          ; CODE XREF from sym.showstudents @ 0x8ab
      ┌──> 0x000007eb      8b45fc         mov eax, dword [var_4h]
      ╎│   0x000007ee      89c6           mov esi, eax
      ╎│   0x000007f0      488d3dc80300.  lea rdi, str.Student_num__d: ; 0xbbf ; "Student num %d: " ; const char *format
      ╎│   0x000007f7      b800000000     mov eax, 0
      ╎│   0x000007fc      e85ffeffff     call sym.imp.printf         ; int printf(const char *format)
      ╎│   0x00000801      8b45fc         mov eax, dword [var_4h]
      ╎│   0x00000804      4863d0         movsxd rdx, eax
      ╎│   0x00000807      4889d0         mov rax, rdx
      ╎│   0x0000080a      4801c0         add rax, rax
      ╎│   0x0000080d      4801d0         add rax, rdx
      ╎│   0x00000810      48c1e002       shl rax, 2
      ╎│   0x00000814      4889c2         mov rdx, rax
      ╎│   0x00000817      488b45e8       mov rax, qword [var_18h]
      ╎│   0x0000081b      4801d0         add rax, rdx
      ╎│   0x0000081e      0fb600         movzx eax, byte [rax]
      ╎│   0x00000821      0fbec0         movsx eax, al
      ╎│   0x00000824      89c6           mov esi, eax
      ╎│   0x00000826      488d3da30300.  lea rdi, str.First_letter____c ; 0xbd0 ; "First letter = %c " ; const char *format
      ╎│   0x0000082d      b800000000     mov eax, 0
      ╎│   0x00000832      e829feffff     call sym.imp.printf         ; int printf(const char *format)
      ╎│   0x00000837      8b45fc         mov eax, dword [var_4h]
      ╎│   0x0000083a      4863d0         movsxd rdx, eax
      ╎│   0x0000083d      4889d0         mov rax, rdx
      ╎│   0x00000840      4801c0         add rax, rax
      ╎│   0x00000843      4801d0         add rax, rdx
      ╎│   0x00000846      48c1e002       shl rax, 2
      ╎│   0x0000084a      4889c2         mov rdx, rax
      ╎│   0x0000084d      488b45e8       mov rax, qword [var_18h]
      ╎│   0x00000851      4801d0         add rax, rdx
      ╎│   0x00000854      8b4004         mov eax, dword [rax + 4]
      ╎│   0x00000857      89c6           mov esi, eax
      ╎│   0x00000859      488d3d830300.  lea rdi, str.Age____d       ; 0xbe3 ; "Age = %d " ; const char *format
      ╎│   0x00000860      b800000000     mov eax, 0
      ╎│   0x00000865      e8f6fdffff     call sym.imp.printf         ; int printf(const char *format)
      ╎│   0x0000086a      8b45fc         mov eax, dword [var_4h]
      ╎│   0x0000086d      4863d0         movsxd rdx, eax
      ╎│   0x00000870      4889d0         mov rax, rdx
      ╎│   0x00000873      4801c0         add rax, rax
      ╎│   0x00000876      4801d0         add rax, rdx
      ╎│   0x00000879      48c1e002       shl rax, 2
      ╎│   0x0000087d      4889c2         mov rdx, rax
      ╎│   0x00000880      488b45e8       mov rax, qword [var_18h]
      ╎│   0x00000884      4801d0         add rax, rdx
      ╎│   0x00000887      f30f104008     movss xmm0, dword [rax + 8]
      ╎│   0x0000088c      f30f5ac0       cvtss2sd xmm0, xmm0
      ╎│   0x00000890      488d3d560300.  lea rdi, str.Mark____f      ; 0xbed ; "Mark = %f \n" ; const char *format
      ╎│   0x00000897      b801000000     mov eax, 1
      ╎│   0x0000089c      e8bffdffff     call sym.imp.printf         ; int printf(const char *format)
      ╎│   0x000008a1      8345fc01       add dword [var_4h], 1
      ╎│   ; CODE XREF from sym.showstudents @ 0x7e6
      ╎└─> 0x000008a5      8b45fc         mov eax, dword [var_4h]
          0x000008a8      3b45e4         cmp eax, dword [var_1ch]
      └──< 0x000008ab      0f8c3affffff   jl 0x7eb
           0x000008b1      90             nop
           0x000008b2      c9             leave
           0x000008b3      c3             ret
[0x000007c4]> 

Como vemos, aparece exactamente el mismo patrón.

ENSAMBLADOR / REFERENCIA
      ╎│   0x00000801      8b45fc         mov eax, dword [var_4h]
      ╎│   0x00000804      4863d0         movsxd rdx, eax
      ╎│   0x00000807      4889d0         mov rax, rdx
      ╎│   0x0000080a      4801c0         add rax, rax
      ╎│   0x0000080d      4801d0         add rax, rdx
      ╎│   0x00000810      48c1e002       shl rax, 2
      ╎│   0x00000814      4889c2         mov rdx, rax
      ╎│   0x00000817      488b45e8       mov rax, qword [var_18h]
      ╎│   0x0000081b      4801d0         add rax, rdx
      ╎│   0x0000081e      0fb600         movzx eax, byte [rax]
      ╎│   0x00000821      0fbec0         movsx eax, al
      ╎│   0x00000824      89c6           mov esi, eax
      ╎│   0x00000826      488d3da30300.  lea rdi, str.First_letter____c ; 0xbd0 ; "First letter = %c " ; const char *format
      ╎│   0x0000082d      b800000000     mov eax, 0
      ╎│   0x00000832      e829feffff     call sym.imp.printf         ; int printf(const char *format)

Tiene mucho sentido, ya que la función es casi idéntica a addstudents. La diferencia principal es que addstudents escribe en memoria desde un puntero y esta lee desde un puntero; el cálculo del puntero es el mismo, al igual que los parámetros de entrada. La función sigue el mismo patrón para imprimir los valores de cada struct.

Un parche sobre el programa de práctica

¿Qué ocurre entonces con la función showstudents? No aparece en el código; puede ser un error del desarrollador o una funcionalidad oculta. Podemos intentar corregirlo parcheando el programa.

Parchear un binario ELF (o cualquier programa) no es trivial; puede ser una operación muy compleja, ya que desplazar o añadir un solo byte puede romper todo. Explicar el formato ELF, los alineamientos de memoria y conceptos similares queda fuera del alcance de este artículo; lo trataremos más adelante. Hay que tener en cuenta que el programa tiene un tamaño fijo en disco y una estructura determinada. Si queremos añadir más código —más instrucciones—, como una llamada a sym.showstudents, tenemos dos opciones: a) sobreescribir parte del espacio existente (probablemente sobreescribiendo instrucciones) en, por ejemplo, la sección .text (marcada como ejecutable), o b) aumentar el tamaño del fichero, añadir el código y realinear todo. Optaremos por la primera opción ahora y profundizaremos en la segunda más adelante (si quieres más información sobre esa segunda opción, consulta: http://phrack.org/issues/66/14.html).

Seguiremos con la primera opción: sobreescribir espacio existente (instrucciones) en el programa para llamar a la función showstudents.

Tras examinar el código, vemos que la función sym.addstudents se llama aquí:

ENSAMBLADOR / REFERENCIA
     │╎│   0x00000ae4      89d6           mov esi, edx                ; signed int64_t arg2
     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8c6fdffff     call sym.addstudents
     │╎│   0x00000aee      8945d4         mov dword [var_2ch], eax
     │╎│   ; CODE XREFS from sym.func1 @ 0xaaf, 0xadb

El contexto alrededor de esta llamada es ideal: como tanto la dirección base como el contador se cargan como parámetros, sería perfecto añadir una llamada a showstudents justo antes de la llamada a addstudents, pero no podemos hacerlo sin «romper» el programa, ya que no podemos sobreescribir ninguna instrucción de ese bloque porque todas son relevantes.

Pero... volvamos a mirar dentro de showstudents:

ENSAMBLADOR / REFERENCIA
           0x000007cc      48897de8       mov qword [var_18h], rdi    ; arg1
           0x000007d0      8975e4         mov dword [var_1ch], esi    ; arg2
           0x000007d3      488d3dd20300.  lea rdi, str.Showing_students: ; 0xbac ; "Showing students: " ; const char *s
           0x000007da      e861feffff     call sym.imp.puts           ; int puts(const char *s)

Hay un printf que no es necesario, así que podemos sobreescribirlo con una llamada a addstudents; de ese modo, antes de mostrar los estudiantes, el programa nos permitirá añadir uno nuevo.

Tras sobreescribir ahí, también podemos sobreescribir la llamada en sym.func1 con una llamada a showstudents. Si hacemos eso, también tendremos que actualizar el mov dword var_2ch, eax; como showstudents no devuelve nada, podemos sustituir ese mov por un inc.

En r2, el parcheo se realiza con wa. Con s nos desplazamos a la dirección que queremos actualizar y con wa ensamblamos una nueva instrucción. Recuerda que hay que abrir el programa con la opción de escritura habilitada (r2 -wA program) para editarlo.

ENSAMBLADOR / REFERENCIA
$ r2 -wA struct1
[0x0000138b]> s 0x000014b8
[0x000014b8]> wa inc [ebp-0x2c]

     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8c6fdffff     call sym.addstudents
     │╎│   0x00000aee      ff45d4         inc dword [var_2ch]
     │╎│   ; CODE XREFS from sym.func1 @ 0xaaf, 0xadb
     └─└─> 0x00000af1      0fb645d3       movzx eax, byte [var_2dh]


[0x0000118f]> s 0x000011a5
[0x000011a5]> wa call sym.addstudents
Written 5 byte(s) (call sym.addstudents) = wx e8d5000000


[0x000011a5]> s 0x0000119e
[0x0000119e]> wa nop
Written 1 byte(s) (nop) = wx 90

[0x0000118f]> s 0x0000119f
[0x0000119f]> wa nop

           0x000007d3      90             nop
           0x000007d4      90             nop
           0x000007d5      3dd2030000     cmp eax, 0x3d2              ; "9_scanf"
           0x000007da      e8d5000000     call sym.addstudents
           0x000007df      c745fc000000.  mov dword [var_4h], 0


[0x0000138b]> s 0x000014b3
[0x000014b3]> wa call sym.showstudents
Written 5 byte(s) (call sym.showstudents) = wx e8d7fcffff


     │╎│   0x00000ae6      4889c7         mov rdi, rax                ; int64_t arg1
     │╎│   0x00000ae9      e8d6fcffff     call sym.showstudents
     │╎│   0x00000aee      ff45d4         inc dword [var_2ch]
     │╎│   ; CODE XREFS from sym.func1 @ 0xaaf, 0xadb
CÓDIGO / SALIDA
First letter?
X
Age?
34
Mark?
4.6
Student num 0: First letter = P Age = 23 Mark = 3.400000 
Student num 1: First letter = k Age = 3 Mark = 4.000000 
Action? (q=Quit, a=Add, s=ShowAll)

En las siguientes lecciones profundizaremos en la modificación y el parcheo de binarios.

Práctica manual

Vuelve al binario

Seguir el acceso a registros consecutivos y comprobar cambios sobre una copia del binario. Identifica al menos una instrucción y el dato que utiliza; conserva la compilación con la que lo has observado.

Fuente de esta práctica: student-array.c ↓.

Descargar los 13 ejemplos de fundamentos ↙

Puedes utilizar la consola del companion sin conversar con el agente, o abrir el ejecutable en tu desensamblador habitual.

TERMINAL / COMPANION
./coursectl start 7 --example student-array
./coursectl console

Cierra la sesión anterior con ./coursectl stop antes de cambiar de laboratorio. Ver preparación.

Comprobación personal

Antes de continuar

Explica con tus palabras la operación estudiada y señala las instrucciones o capturas que la justifican. Distingue los datos observados de los nombres y tipos que has reconstruido.

El progreso se guarda en este navegador. Puedes recorrer las lecciones en cualquier orden.

Código original: AGPLv3. Textos y diagramas originales: CC BY-SA 4.0. Reutilización y alcance ↗.

Lobera

Conceptos de reversing

Consulta una definición y continúa donde estabas.