02 - Un poco de arquitectura para hacer algo de daño en RE

Los ejemplos de estas notas se pueden descargar en: http://ccom.uprrp.edu/~rarce/ccom4702c22-clase01.tgz

Estos son los registros de uso general que tienen las arquitecturas x86 (32 bits) y x86-64 (64 bits)

Para x86 (32 bits)

Regs x86

Nota que podemos referirnos a algunos registros por:

Para x86-64 (64 bits)

Regs x86-64

En ambas arquitecturas

El stack

Hay dos registros que se utilizan para llevar cuenta de las direccciones top y el bottom del stack.

Ejemplo de un stack frame

RSP = 0x7fffffffe090
RBP = 0x7fffffffe0b0

                    ^
                    |  stack crece hacia
                    |  direcciones menores
                    |
               +------------+
0x7fffffffe090 |            |  TOP
               +------------+  \
0x7fffffffe098 |            |  |
               +------------+  |
0x7fffffffe0a0 |            |  | : StackFrame
               +------------+  |
0x7fffffffe0a8 |            |  |
               +------------+  /
0x7fffffffe0b0 |            |  BOTTOM
               +------------+

El stack crece hacia direcciones más bajas, i.e. el contenido del RSP es menor que el RBP.

Instrucciones básicas

Los operandos de las instrucciones en Assembly pueden ser de diversos tipos y en las referencias se utilizan las siguientes abreviaturas para describirlos:

Una referencia de instrucciones x86-64: https://www.felixcloutier.com/x86/index.html

Instrucciones de transferencia de datos

MOV (https://www.felixcloutier.com/x86/mov) y sus primos

MOV ofrece muchas variaciones sobre desde dónde y hacia donde mover un dato. Veamos un ejemplo.

  mov rax, 0789ADEADBEEDh  ; de inmediato a registro
  mov rbx, rax             ; de registro a registro
  push rbx
  xor ebx, ebx
  mov rax, rsp             ; de registro a registro
  mov rax, [rax]           ; de MEM a registro
  mov rbx, [rsp+010h]      ; de MEM a registro

El operador [ ] se utiliza para desreferenciar los operandos. Por ejemplo, si rcx = 0x7ffff8080 y el contenido de la dirección 0x7ffff8080 es 0xbabababa

Por default la transferencia se asume que es del tamaño del registro. Si deseamos transferir una cantidad menor, usamos otras variantes de MOV, como MOVZX (https://www.felixcloutier.com/x86/movzx)

  mov rax, 0789ADEADBEEDh
  movzx rbx, al

No se permite transferencias de memoria a memoria. O sea nunca verás instrucciones como esta: `mov [eax], [ebp+0x10]

A las variaciones sobre desde dónde y hacia donde mover un dato se les llama addressing modes https://www.geeksforgeeks.org/addressing-modes/

gdb con gdb-peda

Para correr gdb sobre un programa sin argumentos basta con: gdb ./programa

Para correr gdb sobre un programa con argumentos : gdb --args ./programa uno dos tres

Cuando entras a gdb eres recibida con el prompt gdb-peda$. La gran mayoría de las veces los primeros comandos que darás serán:

gdb va a detener la ejecución al principio de main (tal y como le pediste)

gdb-peda facilita la visualización de varias cosas interesantes durante la ejecución de un programa: registros, disassembly, stack.

En la parte de los registros, el valor al lado del registro indica su contenido. Por ejemplo, cuando RAX contiene 0x789adeadbeed.

RAX: 0x789adeadbeed

Si el valor del registro tiene un --> al lado, significa que gdb-peda te esta mostrando la desreferencia del valor del registro, i.e. asumiendo que el registro contiene una dirección, y mostrándote el contenido de esa dirección de memoria.

En este ejemplo, RBX contiene 0x7fffffffe1a8. gdb-peda te muestra que la dirección de memoria 0x7fffffffe1a8 contiene el valor 0x7fffffffe4a4. Tambien te dice que en la dirección 0x7fffffffe4a4 está el comienzo del string "calabaza".

RBX: 0x7fffffffe1a8 --> 0x7fffffffe4a4 ("calabaza")

Aquí está diciendo que RCX contiene 0x7fffffffd4a4, que es la dirección donde comienza el string "zanahoria"

RCX: 0x7fffffffd4a4 ("zanahoria")
Aritmética

Las operaciones aritméticas implican el destino y el segundo operando y tienden a modificar los status flags. Ejemplos...

  mov rax, 0FFFFFFFFFFFFFEEDh
  add rax, 01h
  add rax, 01987h
  xor rax, 0FFFFh

El adjust flag es un carry de la mitad del word a la otra mitad.

Calling conventions

http://6.s081.scripts.mit.edu/sp18/x86-64-architecture-guide.html#:~:text=Calling%20Convention&text=The%20caller%20uses%20registers%20to,off%20the%20stack%20in%20order

We will use the standard Linux function calling convention. The calling convention is defined in detail in System V Application Binary Interface—AMD64 Architecture Processor Supplement.

The caller uses registers to pass the first 6 arguments to the callee. Given the arguments in left-to-right order, the order of registers used is: %rdi, %rsi, %rdx, %rcx, %r8, and %r9. Any remaining arguments are passed on the stack in reverse order so that they can be popped off the stack in order.

The callee (la función invocada) is responsible for perserving the value of registers %rbp %rbx, and %r12-r15, as these registers are owned by the caller. The remaining registers are owned by the callee. The callee places its return value in %rax and is responsible for cleaning up its local variables as well as for removing the return address from the stack.

Un ejemplo,

int foo(int a, int b) {
    int c = a - b;
    int d = a + b;
    return c * d;
}

int main() {
  unsigned int r = rand();
  int g = foo(r, r + 10);
  return g;
}
0000000000001149 <foo>:
1149:   endbr64
114d:   push   rbp        ; storing rbp and
114e:   mov    rbp,rsp    ; moving foo's rbp to where main's rsp was

1151:   mov    DWORD PTR [rbp-0x14],edi  ; reading argument a
1154:   mov    DWORD PTR [rbp-0x18],esi  ; reading argument b
1157:   mov    eax,DWORD PTR [rbp-0x14]
115a:   sub    eax,DWORD PTR [rbp-0x18]
115d:   mov    DWORD PTR [rbp-0x8],eax
1160:   mov    edx,DWORD PTR [rbp-0x14]
1163:   mov    eax,DWORD PTR [rbp-0x18]
1166:   add    eax,edx
1168:   mov    DWORD PTR [rbp-0x4],eax
116b:   mov    eax,DWORD PTR [rbp-0x8]
116e:   imul   eax,DWORD PTR [rbp-0x4]
1172:   pop    rbp        ; returning the rbp to where it was
1173:   ret

0000000000001174 <main>:
1174:   endbr64
1178:   push   rbp       ; storing rbp and was
1179:   mov    rbp,rsp   ; moving main's rbp to where its caller's rsp was
117c:   sub    rsp,0x10  ; oppening up the stack frame
1180:   call   1050 <rand@plt>
1185:   mov    DWORD PTR [rbp-0x8],eax
1188:   mov    eax,DWORD PTR [rbp-0x8]
118b:   add    eax,0xa
118e:   mov    edx,eax
1190:   mov    eax,DWORD PTR [rbp-0x8]
1193:   mov    esi,edx   ; cargando los valores de los params
1195:   mov    edi,eax   ; a esi y edi
1197:   call   1149 <foo>
119c:   mov    DWORD PTR [rbp-0x4],eax
119f:   mov    eax,DWORD PTR [rbp-0x4]
11a2:   leave
11a3:   ret

Push y pop:

Ejemplo 05

Ret vs leave? https://stackoverflow.com/questions/29790175/assembly-x86-leave-instruction

Find, using gdb peda

Puedes ver el memory map del programa usando info proc map

gdb-peda$ info proc map
process 3812290
Mapped address spaces:

    Start Addr           End Addr       Size     Offset objfile
      0x400000           0x401000     0x1000        0x0 /home/rarce/ccom4702/code/lab01/ejemplo05
      0x401000           0x402000     0x1000     0x1000 /home/rarce/ccom4702/code/lab01/ejemplo05
      0x402000           0x403000     0x1000     0x2000 /home/rarce/ccom4702/code/lab01/ejemplo05
      0x403000           0x404000     0x1000     0x3000 /home/rarce/ccom4702/code/lab01/ejemplo05
0x7ffff7dba000     0x7ffff7ddc000    0x22000        0x0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7ddc000     0x7ffff7f54000   0x178000    0x22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f54000     0x7ffff7fa2000    0x4e000   0x19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa2000     0x7ffff7fa6000     0x4000   0x1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa6000     0x7ffff7fa8000     0x2000   0x1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa8000     0x7ffff7fae000     0x6000        0x0
0x7ffff7fc9000     0x7ffff7fcd000     0x4000        0x0 [vvar]
0x7ffff7fcd000     0x7ffff7fcf000     0x2000        0x0 [vdso]
0x7ffff7fcf000     0x7ffff7fd0000     0x1000        0x0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000     0x7ffff7ff3000    0x23000     0x1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000     0x7ffff7ffb000     0x8000    0x24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]

Si deseas buscar un string en el espación de memoría correspondiente al stack puedes usar searchmem:

searchmem "elstring" stack