Identificación de offsets - Explotación de binarios
Antes de continuar
El contenido que se muestra en este post es meramente informativo y su misión es servir como apoyo para poder aprender a realizar la identificación de offsets cuando se trata de explotar las fallas de seguridad de binarios. Esta información se distribuye con la intención de servir de aprendizaje para entusiastas de la ciberseguridad, nunca para llevar a cabo prácticas maliciosas.
- Arquitectura: x86_64
Bienvenidos mis hackers a un nuevo post donde aprenderemos el fundamento de la explotación de binarios, la "identificación de offsets". Los offsets son una cifra esencial cuando hablamos de llevar a cabo lo que se conoce como "Buffer Overflow". Estas técnicas que he mencionado se llevan usando desde hace décadas para hackear aplicaciones y programas de todo tipo que manejaban recursos en memoria, y dependiendo de las protecciones y los recursos del hacker, podían y pueden llegar a ocasionar la toma de control del dispositivo sobre el que se ejecuta dicha aplicación.
Pero no nos apresuremos. Antes de poder llegar a tomar el control de un sistema, debemos primero de saber cómo se realizan estos ataques, y el primer paso para hacer uno nosotros mismos es entender las bases. Por ello vengo a exponer y explicar a todos, cómo identificar un offset.
¿Qué es un offset?
Un offset en el contexto de la explotación de binarios singifica el número de bytes que debe de superarse dentro de un buffer para poder llegar a sobreescribir el puntero de instrucción (rip) de la aplicación.
¿Por qué queremos llegar al puntero de instrucción?
Por que el rip es el encargado de ejecutar las instrucciones en lenguaje ensamblador que prosiguen en la memoria. Si pudieramos sobreescribir el rip de un binario, eso significaría que podríamos tomar control del flujo de ejecución del programa, llegando a poder realizar acciones como "Ejecución de comandos" o "ROP Chains", de la cual he realizado un ejemplo en el siguiente post.

Por mirarlo desde un enfoque mucho más metafórico, el offset es el camino hacia la inyección de memoria que contiene el código en lenguaje ensamblador malicioso que haremos ejecutar al programa de forma forzada, evitando que prosiga su ejecución como estaba planeada y pudiendo tomar control del binario.
¿Qué es un buffer?
Un buffer es el espacio en memoria reservado para cierta información (variables). Podríamos decir que el buffer es el espacio que se le asigna a un dato con el que el binario operará. El buffer suele ser de tamaño fijo y si se supera ese límite hasta llegar al rip se produce lo conocido como "Violación de segmento" (SIGSEGV), que significa el cese de ejecución del binario debido a que ha accedido a una zona de memoria sobre la ue no tenía autorización (esto es debido a que se superan los límites del buffer, ha entrado en el rip, el rip ha intentado ejecutar los datos que ha invadido la zona de memoria sobre la que se encontraba debido al desbordamiento de información en el buffer, y al intentar ejecutar la información, vio que no tenía sentido y detuvo la ejecución del programa).
En el ejemplo de abajo, se puede observar de forma má gráfica la representación de un buffer.
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char message[32];
strcpy(message, argv[1]);
printf("%s\n", message);
return 0;
}Declaramos una variable de tipo array de caracteres (char []) y el nombre que le ponemos es message. El buffer, que es el tamaño del array (el tamaño que reservamos en memoria) es de 32 bytes, y teniendo en cuenta que un caracter solo ocupa un byte, podemos almacenar en el buffer un total de 32 caracteres. Veamos lo que sucede si ejecutamos el programa y le doy al programa una cadena de caracteres de 13 bytes.
❱❱ ./vuln "Hello World!"
Hello World!Como era de esperar, el programa realiza su función, que es la siguiente:
Reserva 32 bytes en memoria (Buffer)
↓
Copia la cadena de caracteres del primer argumento del binario al buffer
↓
Accede a la dirección de memoria donde se encuentra la cadena (el buffer)
↓
Usa el contenido del buffer para imprimirlo en pantalla ("Hello World!")Ahora, veamos que ocurre si me salgo del buffer unos cuantos bytes más.
❱❱ ./vuln "Hellooooooooooooo Wooooooooooooooooorld!"
Hellooooooooooooo Wooooooooooooooooorld!
Violación de segmentoAnalizando el binario
Lo que ha sucedido es que he rellenado un buffer de 32 bytes con una cadena de caracteres de 41 bytes, llevando al programa a producir un SIGSEGV. Ahora, echemos un vistazo desde más profundo. Desde la memoria del propio binario usando "PwnDBG".
pwndbg> disas main
Dump of assembler code for function main:
0x0000000000001149 <+0>: push rbp
0x000000000000114a <+1>: mov rbp,rsp
0x000000000000114d <+4>: sub rsp,0x30
0x0000000000001151 <+8>: mov DWORD PTR [rbp-0x24],edi
0x0000000000001154 <+11>: mov QWORD PTR [rbp-0x30],rsi
0x0000000000001158 <+15>: mov rax,QWORD PTR [rbp-0x30]
0x000000000000115c <+19>: add rax,0x8
0x0000000000001160 <+23>: mov rdx,QWORD PTR [rax]
0x0000000000001163 <+26>: lea rax,[rbp-0x20]
0x0000000000001167 <+30>: mov rsi,rdx
0x000000000000116a <+33>: mov rdi,rax
0x000000000000116d <+36>: call 0x1030 <strcpy@plt>
0x0000000000001172 <+41>: lea rax,[rbp-0x20]
0x0000000000001176 <+45>: mov rdi,rax
0x0000000000001179 <+48>: call 0x1040 <puts@plt>
0x000000000000117e <+53>: mov eax,0x0
0x0000000000001183 <+58>: leave
0x0000000000001184 <+59>: ret
End of assembler dump.
pwndbg> El output anterior nos muestra las instrucciones en ensamblador que ejecuta el binario. Las instrucciones que van desde +0 hasta +4 son lo denominado como el prólogo de procedimiento (encargado de construir el Stack). Las instrucciones desde +8 hasta +11 son los argumentos que recoge el binario, además de las variables de entorno, y las líneas +15 hasta +59 se tratan de las instrucciones que realiza el binario en ensamblador. Para ver de forma más clara cómo se almacena la información en memoria, veamos lo siguiente. Primero, ejecutaremos el programa en GDB usando el comando "run" de la siguiente manera:
pwndbg> run "Hello World!"
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln "Hello World!"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Hello World!
[Inferior 1 (process 564284) exited normally]Como podemos ver, el binario se ejcuta como se esperaba y se guarda la cadena en memoria, y luego, se imprime por pantalla. Ahora, veamos en qué parte de la memoria se guarda, y para ello, estableceremos "breakpoints" que detendrán el programa mientras se está ejecutando. El primero que estableceré será en la línea main+19, que realiza la siguiente instrucción:
0x000000000000115c <+19>: add rax,0x8Añade 8 bytes a rax, pero no sabemos el por qué. Para ello, deberemos averiguar qué hacen las instrucciones anteriores. Ahora sí, estableceré el breakpoint.
pwndbg> break *main+19
Breakpoint 1 at 0x115c: file vuln.c, line 6.
pwndbg> info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000000115c in main at vuln.c:6
pwndbg>A continuación volveré a ejecutar el binario con la cadena "Hello World!":
pwndbg> run "Hello world!"
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln "Hello world!"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x000055555555515c in main (argc=0x2, argv=0x7fffffffdf78) at vuln.c:6
6 strcpy(message, argv[1]);Como podemos ver en el depurador, nos hemos detenido antes de ejecutar la instrucción add rax,0x8.
pwndbg> disas main
Dump of assembler code for function main:
0x0000555555555149 <+0>: push rbp
0x000055555555514a <+1>: mov rbp,rsp
0x000055555555514d <+4>: sub rsp,0x30
0x0000555555555151 <+8>: mov DWORD PTR [rbp-0x24],edi
0x0000555555555154 <+11>: mov QWORD PTR [rbp-0x30],rsi
0x0000555555555158 <+15>: mov rax,QWORD PTR [rbp-0x30]
=> 0x000055555555515c <+19>: add rax,0x8
0x0000555555555160 <+23>: mov rdx,QWORD PTR [rax]
0x0000555555555163 <+26>: lea rax,[rbp-0x20]
0x0000555555555167 <+30>: mov rsi,rdx
0x000055555555516a <+33>: mov rdi,rax
0x000055555555516d <+36>: call 0x555555555030 <strcpy@plt>
0x0000555555555172 <+41>: lea rax,[rbp-0x20]
0x0000555555555176 <+45>: mov rdi,rax
0x0000555555555179 <+48>: call 0x555555555040 <puts@plt>
0x000055555555517e <+53>: mov eax,0x0
0x0000555555555183 <+58>: leave
0x0000555555555184 <+59>: ret
End of assembler dump.
pwndbg> Si comprobamos las instrucciones anteriores, podemos ver que hay una instrucción que dice: "Guarda la dirección de memoria que esté 30 bytes antes del registro rbp en el registro rax".
mov rax,QWORD PTR [rbp-0x30]Pero nosotros no sabemos qué hay dentro de rax, por lo que deberemos de echar un vistazo. Para ello, usaremos el comando x/ en PwnDBG (aunque x/ es originalmente un comando de GDB).
pwndbg> x/xg $rbp-0x30
0x7fffffffde40: 0x00007fffffffdf88Aparentemente está guardando en rax la dirección de memoria 0x00007fffffffdf88. Continuemos observando las instrucciones adyacentes.
add rax,0x8Ahora vemos que a rax se le añaden 8 bytes. Veamos por qué sucede.
pwndbg> info reg rax
rax 0x7fffffffdf88 0x7fffffffdf88Como podemos ver en el output anterior, aparece que almacena una dirección de memoria. Veamos qué hay dentro de la dirección de memoria.
pwndbg> x/xg 0x7fffffffdf88
0x7fffffffdf88: 0x00007fffffffe2c3Aparece una nueva dirección de memoria, en este caso, 0x00007fffffffe2c3. Si observamos qué reside dentro de esta nueva dirección de memoria, el programa que se está ejecutando, es decir, el argumento número 0.
pwndbg> x/s 0x00007fffffffe2c3
0x7fffffffe2c3: "/home/usuario/daemon/testing/bufferoverflow/vuln"Y si añadimos ocho bytes de memoria a la dirección 0x7fffffffdf88, acabamos en obteniendo como resultado una segunda región de memoria, que es la que almacena el argumento número 1, es decir, la cadena de caracteres.
pwndbg> x/xg 0x7fffffffdf88+8
0x7fffffffdf90: 0x00007fffffffe2f4
pwndbg> x/s 0x00007fffffffe2f4
0x7fffffffe2f4: "Hello World!"
pwndbg> Entendiendo el buffer
Si observamos los bytes que ocupa la cadena con el comando x/32xb, podemos observar que la cadena (Hello World!) ocupa un total de 12 bytes. Sin embargo, a la cadena se le suma el byte 0x00, que es conocido como "Null Terminator", que es el encargado de separar información en la memoria. Sin el Null Terminator, la cadena de caracteres continuaría expandiéndose por la memoria y la cadena no tendría fin, por lo que la longitud total de la cadena es de 13 bytes.
pwndbg> x/32xb 0x00007fffffffe2f4
0x7fffffffe2f4: 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x57 0x6f
0x7fffffffe2fc: 0x72 0x6c 0x64 0x21 0x00 0x53 0x48 0x45
0x7fffffffe304: 0x4c 0x4c 0x3d 0x2f 0x62 0x69 0x6e 0x2f
0x7fffffffe30c: 0x62 0x61 0x73 0x68 0x00 0x57 0x49 0x4e
pwndbg>
Pero, ¿cómo puedo saber que esos 13 bytes pertenecen a las 12 letras de Hello World! mas el Null Terminator? Pues por que esos bytes son representaciones de letras en hexadecimal, tal y como se muestra en esta imagen.

La H sería equivalente a 0x48, la e sería equivalente a 0x65, y demás. De forma que 0x6f57206f6c6c6548 sería Hello Wo y el siguiente octeto de bytes, 0x0021646c72, sería rld!.

A continuación esa cadena se transladará a un espacio de memoria reservado de 32 bytes. He insertado otro breakpoint en la línea en la que define el buffer para que se pueda ver de forma clara.
pwndbg> disas main
Dump of assembler code for function main:
0x0000555555555149 <+0>: push rbp
0x000055555555514a <+1>: mov rbp,rsp
0x000055555555514d <+4>: sub rsp,0x30
0x0000555555555151 <+8>: mov DWORD PTR [rbp-0x24],edi
0x0000555555555154 <+11>: mov QWORD PTR [rbp-0x30],rsi
0x0000555555555158 <+15>: mov rax,QWORD PTR [rbp-0x30]
0x000055555555515c <+19>: add rax,0x8
0x0000555555555160 <+23>: mov rdx,QWORD PTR [rax]
0x0000555555555163 <+26>: lea rax,[rbp-0x20]
=> 0x0000555555555167 <+30>: mov rsi,rdx
0x000055555555516a <+33>: mov rdi,rax
0x000055555555516d <+36>: call 0x555555555030 <strcpy@plt>
0x0000555555555172 <+41>: lea rax,[rbp-0x20]
0x0000555555555176 <+45>: mov rdi,rax
0x0000555555555179 <+48>: call 0x555555555040 <puts@plt>
0x000055555555517e <+53>: mov eax,0x0
0x0000555555555183 <+58>: leave
0x0000555555555184 <+59>: ret
End of assembler dump.
pwndbg> x/32xb $rax
0x7fffffffde50: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde58: 0x00 0x56 0xfe 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffde60: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde68: 0x00 0xdf 0xff 0xff 0xff 0x7f 0x00 0x00
pwndbg> El registro rax ahora contiene la ubicación del buffer, y lo que se ejecutará a continuación pegará el contenido de rdx (que ahora contiene la cadena Hello World!) en la ubicación del buffer. Si continuamos la ejecución y volvemos a revisar el buffer, nos encontramos con que, efectivamente, la cadena de caracteres sí ha sido pegada dentro del buffer.
pwndbg> continue
pwndbg> x/32xb $rax
0x7fffffffde50: 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x57 0x6f
0x7fffffffde58: 0x72 0x6c 0x64 0x21 0x00 0x7f 0x00 0x00
0x7fffffffde60: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde68: 0x00 0xdf 0xff 0xff 0xff 0x7f 0x00 0x00
pwndbg> x/s $rax
0x7fffffffde50: "Hello World!"
pwndbg> Podemos ver cómo el buffer se rellena si en lugar de especificar una cadena de 13 caracteres ponemos una de 32 caracteres (32 bytes, el mismo tamaño que el buffer).
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
pwndbg> continue
pwndbg> continue
pwndbg> x/s $rax
0x7fffffffde30: 'A' <repeats 32 times>
pwndbg> x/32xb $rax
0x7fffffffde30: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffde38: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffde40: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffde48: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
pwndbg> Identificación del offset
Ahora que hemos observado cómo se rellena el buffer, hay que explicar qué es el Buffer Overflow y cómo realizar el primer paso para conseguirlo: Identificar el offset.
Identificar el offset es posible gracias a muchos factores, y existen varias maneras de identificar el offset de un binario.
- Primera opción: Usar las funciones
cyclicycyclic_finden pwntools (ocyclicycyclic -len PwnDBG). - Segunda opción: Identificar el offset manualmente observando los registros a la hora de desbordar el stack.
Primera opción
Primero generaremos una cadena larga de caracteres que nos asegure desbordar el buffer. Podemos escoger el tamaño que queramos, pero en este caso yo escogeré una cadena de un tamaño de 100 bytes. Para ello, usaré el siguiente comando que genere una cadena con un patrón específico de caracteres que me permita identificar en qué punto se desborda el buffer.
❱❱ pwn cyclic -n 8 100
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaAhora esta cadena la pondré en PwnDBG y ejecutaré el binario.
pwndbg> run aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Program received signal SIGSEGV, Segmentation fault.Observando más de cerca los detalles sobre los registros y las instrucciones desensambladas, nos encontramos con lo siguiente.
───────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────
► 0x555555555184 <main+59> ret <0x6161616161616166>
↓NOTA: Este valor también se puede obtener de los registros en caso de no encontrarlo en el detalle del crash del programa. Para poder observarlo, simplemente se ha de poner el siguiente comando en PwnDBG y fijarse en el valor que se encuentra guardado dentro de la dirección de memoria que almacena el registro rsp:
pwndbg> info reg
rax 0x0 0x0
rbx 0x7fffffffdf28 0x7fffffffdf28
rcx 0x0 0x0
rdx 0x0 0x0
rsi 0x5555555592a0 0x5555555592a0
rdi 0x7ffff7f887b0 0x7ffff7f887b0
rbp 0x6161616161616165 0x6161616161616165
rsp 0x7fffffffde18 0x7fffffffde18
r8 0x0 0x0
r9 0x0 0x0
r10 0x0 0x0
r11 0x202 0x202
r12 0x0 0x0
r13 0x7fffffffdf40 0x7fffffffdf40
r14 0x7ffff7ffd000 0x7ffff7ffd000
r15 0x555555557dd8 0x555555557dd8
rip 0x555555555184 0x555555555184 <main+59>
eflags 0x10202 [ IF RF ]
cs 0x33 0x33
ss 0x2b 0x2b
ds 0x0 0x0
es 0x0 0x0
fs 0x0 0x0
gs 0x0 0x0
fs_base 0x7ffff7d9e740 0x7ffff7d9e740
gs_base 0x0 0x0pwndbg> x/xg 0x7fffffffde18
0x7fffffffde18: 0x6161616161616166
pwndbg>Como se puede ver, es exactamente el patrón que buscamos.
El patrón de bytes 0x6161616161616166 pertenece al los caracteres faaaaaaa. Debido a cómo está diseñado Pwntools, con la f de esa cadena de caracteres es capaz de detectar el byte donde desborda el buffer y con una simple operación matemática, devuelve el offset. Esto se puede lograr con el comando cyclic -n8 -l <bytes>.
pwndbg> cyclic -n8 -l 0x6161616161616166
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40
pwndbg>En este caso, el offset es el 40.
Segunda opción
La segunda opción es manejar manualmente el valor de bytes que se le da al binario hasta conseguir desbordarlo, y afinar el valor de bytes hasta dar con el número exacto que lleva al offset. A continuación una demostración.
Con el mismo binario, yo quiero lograr desbordarlo, por lo que le inserto una cantidad de bytes que sé que va a provocar el desbordamiento. En este caso, 100 bytes al igual que en el ejemplo anterior (los 100 bytes serán un total de 92 bytes que simbolizan ser la letra "A" y 8 bytes que simbolizan ser la letra "B").
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Program received signal SIGSEGV, Segmentation fault.Como he visto que se ha producido el error, puedo observar el detalle del crash para observar qué valor se ha quedado en el registro rsp.
───────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────
► 0x555555555184 <main+59> ret <0x4141414141414141>
↓Una buena forma de comprobar el offset es comprobar si los últimos 8 bytes son los asignados a la letra "B". Si es así, la cantidad de bytes de la letra "A" indica el offset. Ahora, viendo que se ha producido una violación de segmento (un crash), probaré a reducir la cantidad de bytes a la mitad para observar si ahora finalmente consigo dar con el offset.
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Program received signal SIGSEGV, Segmentation fault.Observando el detalle que aparece en la parte inferior, puedo ver que me estoy acercando a dar con el offset exacto.
───────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────
► 0x555555555184 <main+59> ret <0x4242424242424141>
↓Esa cadena que ha quedado en el registro rsp coincide con la cadena de caracteres AABBBBBB, lo que significa que me han sobrado 2 bytes correspondientes a la letra "A". Si en el ejemplo anterior generé la cadena de 50 bytes componiéndola con 42 bytes de letras "A" y 8 bytes de letras "B", y para obtener el offset debo eliminar dos letras "A", eso indicaría que el offset es el número 40 (por que recordemos que el offset es igual a la cantidad de letras "A" que hemos especificado en la cadena).
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Program received signal SIGSEGV, Segmentation fault.───────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────
► 0x555555555184 <main+59> ret <0x4242424242424242>
↓En el ejemplo anterior, habríamos obtenido el offset. Y finalmente, para comprobar que hemos dado con el offset exacto (por si queda algún atisbo de duda), haremos lo siguiente. Probaremos a ver si se produce una violación de segmento usando 40 bytes, y si se produce, probaremos usando 39. Como dijimos que el offset es la cantidad de bytes que debemos alcanzar hasta poder controlar el rip, y la violación de segmento se produce cuando el rip no encuentra instrucciones que ejecutar, nada mas obtener el offset debería de ocurrir una violación de segmento. Probaremos la teoría con el siguiente comando:
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.Y como vemos, con 40 bytes correspondientes a la letra "A" se produce la violación de segmento (SIGSEGV). Probando con 39 bytes, por otro lado, no provoca una violación de segmento.
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/usuario/daemon/testing/bufferoverflow/vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[Inferior 1 (process 487511) exited normally]
pwndbg>Por lo que efectivamente, el offset es 40. Y de estas dos maneras, hemos podido obtener el offset de un binario y avanzar un paso más en la explotación del mismo hasta lograr obtener una shell o realizar una cadena ROP.
