Reconstruir campos, offsets y alineación a partir de la fuente, el ensamblador y la memoria.
Notas de la práctica
En el ejemplo Books comprobado: book_id está en el offset 200, sizeof(struct Books) es 204 y su alineación es 4. La separación de 208 bytes entre las dos variables locales es una observación distinta. Al final se muestra la lectura real con Hermes.
Trabaja sobre el mismo ejemplo
Selecciona este ejemplo en el companion. El número del comando corresponde al identificador original del laboratorio.
./coursectl chat 6 --example booksEstoy siguiendo «Matrices y estructuras» del curso de Lobera. Reconstruir campos, offsets y alineación a partir de la fuente, el ensamblador y la memoria. Dame primero una pista. Después contrasta la explicación con las instrucciones y guarda las evidencias que hayamos comprobado.
El agente y la consola comparten el estado de radare2. Preparación y funcionamiento.
En esta lección continuamos el curso de reversing con radare2.
Seguimos repasando algunas estructuras de datos básicas que aparecen en multitud de proyectos. En mi experiencia, los conceptos clave del reversing son: identificar estructuras de datos, conocer las operaciones básicas y, a partir de ahí, centrarse en el problema concreto. En análisis de malware hay que prestar atención a técnicas habituales como el uso de syscalls específicas o métodos de empaquetado y cifrado; en desarrollo de exploits el foco está en protocolos concretos que se quieren analizar y fuzzear; también hay quien hace análisis de privacidad en aplicaciones y se centra en protocolos de red. Cada área tiene sus particularidades, pero antes de profundizar en cualquiera de ellas se necesita una base sólida en aspectos comunes como las estructuras de datos, ya que todo se construye sobre ellas.
En la entrega anterior recorrimos algunos arrays básicos (int y char); hoy veremos ejemplos más complejos.
Arrays multidimensionales
A pesar del nombre, son estructuras de datos muy sencillas. Un array puede contener elementos de cualquier tipo: ints, floats, chars… y también arrays. Es posible tener arrays de arrays de arrays de [...].
Veamos el siguiente código:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int marks[2][10] =
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
printf("Mark related to third student on first group %d",
marks[0][2]);
printf("Mark related to third student on second group %d",
marks[1][2]);
}
Se declara un array de enteros de 2 dimensiones y se inicializa con algunos valores. Lo interesante es que los dos arrays se inicializan uno a continuación del otro. Puede parecer que tenemos un único array grande de 20 valores en lugar de dos arrays de 10 valores uno «encima» del otro. Tiene todo el sentido: el programa tiene que almacenar esos valores en memoria y forman parte de la misma estructura, así que lo lógico es reservarlos juntos de forma contigua.
Veamos cómo funciona internamente:
[0x55af6f3ae714]> pdf
/ (fcn) sym.func 230
| sym.func ();
| ; var int local_60h @ rbp-0x60
| ; var int local_5ch @ rbp-0x5c
| ; var int local_58h @ rbp-0x58
| ; var int local_54h @ rbp-0x54
| ; var int local_50h @ rbp-0x50
| ; var int local_4ch @ rbp-0x4c
| ; var int local_48h @ rbp-0x48
| ; var int local_44h @ rbp-0x44
| ; var int local_40h @ rbp-0x40
| ; var int local_3ch @ rbp-0x3c
| ; var int local_38h @ rbp-0x38
| ; var int local_34h @ rbp-0x34
| ; var int local_30h @ rbp-0x30
| ; var int local_2ch @ rbp-0x2c
| ; var int local_28h @ rbp-0x28
| ; var int local_24h @ rbp-0x24
| ; var int local_20h @ rbp-0x20
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x55af6f3ae703 (sym.main)
| 0x55af6f3ae714 55 push rbp
| 0x55af6f3ae715 4889e5 mov rbp, rsp
| 0x55af6f3ae718 4883ec60 sub rsp, 0x60 ; '`'
| 0x55af6f3ae71c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55af6f3ae725 488945f8 mov qword [local_8h], rax
| 0x55af6f3ae729 31c0 xor eax, eax
| 0x55af6f3ae72b c745a0010000. mov dword [local_60h], 1
| 0x55af6f3ae732 c745a4020000. mov dword [local_5ch], 2
| 0x55af6f3ae739 c745a8030000. mov dword [local_58h], 3
| 0x55af6f3ae740 c745ac040000. mov dword [local_54h], 4
| 0x55af6f3ae747 c745b0050000. mov dword [local_50h], 5
| 0x55af6f3ae74e c745b4060000. mov dword [local_4ch], 6
| 0x55af6f3ae755 c745b8070000. mov dword [local_48h], 7
| 0x55af6f3ae75c c745bc080000. mov dword [local_44h], 8
| 0x55af6f3ae763 c745c0090000. mov dword [local_40h], 9
| 0x55af6f3ae76a c745c40a0000. mov dword [local_3ch], 0xa
| 0x55af6f3ae771 c745c80b0000. mov dword [local_38h], 0xb ; 11
| 0x55af6f3ae778 c745cc0c0000. mov dword [local_34h], 0xc ; 12
| 0x55af6f3ae77f c745d00d0000. mov dword [local_30h], 0xd ; 13
| 0x55af6f3ae786 c745d40e0000. mov dword [local_2ch], 0xe ; 14
| 0x55af6f3ae78d c745d80f0000. mov dword [local_28h], 0xf ; 15
| 0x55af6f3ae794 c745dc100000. mov dword [local_24h], 0x10 ; 16
| 0x55af6f3ae79b c745e0110000. mov dword [local_20h], 0x11 ; 17
| 0x55af6f3ae7a2 c745e4120000. mov dword [local_1ch], 0x12 ; 18
| 0x55af6f3ae7a9 c745e8130000. mov dword [local_18h], 0x13 ; 19
| 0x55af6f3ae7b0 c745ec140000. mov dword [local_14h], 0x14 ; 20
| 0x55af6f3ae7b7 8b45a8 mov eax, dword [local_58h]
| 0x55af6f3ae7ba 89c6 mov esi, eax
| 0x55af6f3ae7bc 488d3dc50000. lea rdi, qword str.Mark_related_to_third_student_on_first_group__d ; 0x55af6f3ae888 ; "Mark related to third student on first group %d"
| 0x55af6f3ae7c3 b800000000 mov eax, 0
| 0x55af6f3ae7c8 e8f3fdffff call sym.imp.printf ; int printf(const char *format)
| 0x55af6f3ae7cd 8b45d0 mov eax, dword [local_30h]
| 0x55af6f3ae7d0 89c6 mov esi, eax
| 0x55af6f3ae7d2 488d3ddf0000. lea rdi, qword str.Mark_related_to_third_student_on_second_group__d ; 0x55af6f3ae8b8 ; "Mark related to third student on second group %d"
| 0x55af6f3ae7d9 b800000000 mov eax, 0
| 0x55af6f3ae7de e8ddfdffff call sym.imp.printf ; int printf(const char *format)
| 0x55af6f3ae7e3 90 nop
| 0x55af6f3ae7e4 488b55f8 mov rdx, qword [local_8h]
| 0x55af6f3ae7e8 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x55af6f3ae7f1 7405 je 0x55af6f3ae7f8
| | 0x55af6f3ae7f3 e8b8fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x55af6f3ae7f8 c9 leave
\ 0x55af6f3ae7f9 c3 ret
[0x55af6f3ae714]>
A primera vista, radare2 ha identificado cada celda del array multidimensional como una variable independiente. El código es muy sencillo:
| 0x55af6f3ae72b c745a0010000. mov dword [local_60h], 1
| 0x55af6f3ae732 c745a4020000. mov dword [local_5ch], 2
| 0x55af6f3ae739 c745a8030000. mov dword [local_58h], 3
| 0x55af6f3ae740 c745ac040000. mov dword [local_54h], 4
| 0x55af6f3ae747 c745b0050000. mov dword [local_50h], 5
| 0x55af6f3ae74e c745b4060000. mov dword [local_4ch], 6
| 0x55af6f3ae755 c745b8070000. mov dword [local_48h], 7
| 0x55af6f3ae75c c745bc080000. mov dword [local_44h], 8
| 0x55af6f3ae763 c745c0090000. mov dword [local_40h], 9
| 0x55af6f3ae76a c745c40a0000. mov dword [local_3ch], 0xa
| 0x55af6f3ae771 c745c80b0000. mov dword [local_38h], 0xb ; 11
| 0x55af6f3ae778 c745cc0c0000. mov dword [local_34h], 0xc ; 12
| 0x55af6f3ae77f c745d00d0000. mov dword [local_30h], 0xd ; 13
| 0x55af6f3ae786 c745d40e0000. mov dword [local_2ch], 0xe ; 14
| 0x55af6f3ae78d c745d80f0000. mov dword [local_28h], 0xf ; 15
| 0x55af6f3ae794 c745dc100000. mov dword [local_24h], 0x10 ; 16
| 0x55af6f3ae79b c745e0110000. mov dword [local_20h], 0x11 ; 17
| 0x55af6f3ae7a2 c745e4120000. mov dword [local_1ch], 0x12 ; 18
| 0x55af6f3ae7a9 c745e8130000. mov dword [local_18h], 0x13 ; 19
| 0x55af6f3ae7b0 c745ec140000. mov dword [local_14h], 0x14 ; 2
Se asignan los valores y luego se pasan las variables necesarias a printf. Esto es algo que se puede encontrar con cierta frecuencia. Cuando vemos algo así, puede interesar eliminar algunas etiquetas local_whatever innecesarias con avf- y renombrarlas con avfn para que el código quede más legible.
[0x55af6f3ae714]> afv- local_54h
[0x55af6f3ae714]> afv- local_58h
[0x55af6f3ae714]> afv- local_5ch
[0x55af6f3ae714]> afvn local_60h multiarray
[0x55af6f3ae714]> pdf
/ (fcn) sym.func 230
| sym.func ();
| ; var int multiarray @ rbp-0x60
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x55af6f3ae703 (sym.main)
| 0x55af6f3ae714 55 push rbp
| 0x55af6f3ae715 4889e5 mov rbp, rsp
| 0x55af6f3ae718 4883ec60 sub rsp, 0x60 ; '`'
| 0x55af6f3ae71c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55af6f3ae725 488945f8 mov qword [local_8h], rax
| 0x55af6f3ae729 31c0 xor eax, eax
| 0x55af6f3ae72b c745a0010000. mov dword [multiarray], 1
| 0x55af6f3ae732 c745a4020000. mov dword [rbp - 0x5c], 2
| 0x55af6f3ae739 c745a8030000. mov dword [rbp - 0x58], 3
| 0x55af6f3ae740 c745ac040000. mov dword [rbp - 0x54], 4
Al trabajar con un array, en términos generales basta con anotar su dirección base: conociendo la dirección base y el tamaño se puede acceder a cualquier elemento. Hay que etiquetar correctamente esa dirección y tomar notas. Para inspeccionar arrays de forma más eficiente, el comando pf de radare2 resulta muy útil:
[0x555e65e677b7]> afvd
var local_8h = 0x7ffdd66dcfb8 0x7a48b8b948f3ac00 ...H..Hz
var multiarray = 0x7ffdd66dcf60 0x0000000200000001 ........ @rsp
[0x555e65e677b7]> pf 20d val @ 0x7ffdd66dcf60
0x7ffdd66dcf60 [0] {
val : 0x7ffdd66dcf60 = 1
}
0x7ffdd66dcf64 [1] {
val : 0x7ffdd66dcf64 = 2
}
0x7ffdd66dcf68 [2] {
val : 0x7ffdd66dcf68 = 3
}
0x7ffdd66dcf6c [3] {
val : 0x7ffdd66dcf6c = 4
}
0x7ffdd66dcf70 [4] {
val : 0x7ffdd66dcf70 = 5
}
0x7ffdd66dcf74 [5] {
val : 0x7ffdd66dcf74 = 6
}
0x7ffdd66dcf78 [6] {
val : 0x7ffdd66dcf78 = 7
}
0x7ffdd66dcf7c [7] {
val : 0x7ffdd66dcf7c = 8
}
0x7ffdd66dcf80 [8] {
val : 0x7ffdd66dcf80 = 9
}
0x7ffdd66dcf84 [9] {
val : 0x7ffdd66dcf84 = 10
}
0x7ffdd66dcf88 [10] {
val : 0x7ffdd66dcf88 = 11
}
0x7ffdd66dcf8c [11] {
val : 0x7ffdd66dcf8c = 12
}
0x7ffdd66dcf90 [12] {
val : 0x7ffdd66dcf90 = 13
}
0x7ffdd66dcf94 [13] {
val : 0x7ffdd66dcf94 = 14
}
0x7ffdd66dcf98 [14] {
val : 0x7ffdd66dcf98 = 15
}
0x7ffdd66dcf9c [15] {
val : 0x7ffdd66dcf9c = 16
}
0x7ffdd66dcfa0 [16] {
val : 0x7ffdd66dcfa0 = 17
}
0x7ffdd66dcfa4 [17] {
val : 0x7ffdd66dcfa4 = 18
}
0x7ffdd66dcfa8 [18] {
val : 0x7ffdd66dcfa8 = 19
}
0x7ffdd66dcfac [19] {
val : 0x7ffdd66dcfac = 20
}
[0x555e65e677b7]>
Se han recuperado 20 enteros decimales con signo a partir de 0x7ffdd66dcf60. Tras pf se usa 20d para indicar que se quieren recuperar 20 valores con el formato %d desde esa dirección. Se pueden usar otros especificadores: %c para chars, %f para float, %s para cadenas o %x para hexadecimal. También se pueden encadenar especificadores, por ejemplo 20iiis para representar tres enteros y una cadena; lo veremos con más detalle más adelante en el curso.
En la práctica, tener un array de arrays es casi lo mismo que tener un único array grande, ya que todo queda contiguo en memoria. Es más bien un recurso semántico que facilita el trabajo con estructuras de datos durante la programación.
Structs
En proyectos más elaborados es habitual encontrar estructuras de datos más avanzadas asociadas a objetos. En última instancia, esas estructuras están formadas por conjuntos de arrays, etiquetas de direcciones de memoria y variables simples. Es una de las razones por las que trabajar a bajo nivel resulta esclarecedor: todo se vuelve muy concreto.
En lenguajes como C se pueden definir estructuras de datos personalizadas mediante struct. A veces conviene tener una referencia única para un conjunto de datos relacionados con un tema concreto del programa: por ejemplo, almacenar las coordenadas de un punto geográfico, los datos de un alumno con su nombre y sus notas finales, etc. Para eso se usa struct en C, como se puede ver en el siguiente código:
#include <stdio.h>
#include <stdlib.h>
struct students {
int a[20];
char b[20];
};
int main( ) {
time_t t;
int i, n;
srand((unsigned) time(&t));
struct students s1;
for( i = 0 ; i < 20 ; i++ ) {
s1.a[i] = rand() % 11;
}
for( i = 0 ; i < 20 ; i++ ) {
printf("%d ",s1.a[i]);
}
return 0;
}
La estructura students se define con un array de enteros y un array de chars, y se inicializa más adelante en el código. También se usa la biblioteca stdlib y entra en juego rand.
Veámoslo:
[0x7efc2890e090]> ii
[Imports]
1 0x55d00da94000 WEAK NOTYPE _ITM_deregisterTMCloneTable
2 0x55d00da94610 GLOBAL FUNC __stack_chk_fail
3 0x55d00da94620 GLOBAL FUNC printf
4 0x55d00da94000 GLOBAL FUNC __libc_start_main
5 0x55d00da94630 GLOBAL FUNC srand
6 0x55d00da94000 WEAK NOTYPE __gmon_start__
7 0x55d00da94640 GLOBAL FUNC time
8 0x55d00da94000 WEAK NOTYPE _ITM_registerTMCloneTable
9 0x55d00da94000 WEAK FUNC __cxa_finalize
10 0x55d00da94650 GLOBAL FUNC rand
1 0x55d00da94000 WEAK NOTYPE _ITM_deregisterTMCloneTable
4 0x55d00da94000 GLOBAL FUNC __libc_start_main
6 0x55d00da94000 WEAK NOTYPE __gmon_start__
8 0x55d00da94000 WEAK NOTYPE _ITM_registerTMCloneTable
9 0x55d00da94000 WEAK FUNC __cxa_finalize
A primera vista, examinando las importaciones ya sabemos que el programa trabaja con números aleatorios. El bloque de código que nos interesa analizar está en la función main:
[0x55d00da9477a]> pdf
;-- main:
/ (fcn) main 189
| main ();
| ; var int local_7ch @ rbp-0x7c
| ; var int local_78h @ rbp-0x78
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x55d00da9468d (entry0)
| 0x55d00da9477a 55 push rbp
| 0x55d00da9477b 4889e5 mov rbp, rsp
| 0x55d00da9477e 4883c480 add rsp, -0x80
| 0x55d00da94782 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55d00da9478b 488945f8 mov qword [local_8h], rax
| 0x55d00da9478f 31c0 xor eax, eax
| 0x55d00da94791 488d4588 lea rax, qword [local_78h]
| 0x55d00da94795 4889c7 mov rdi, rax
| 0x55d00da94798 b800000000 mov eax, 0
| 0x55d00da9479d e89efeffff call sym.imp.time ; time_t time(time_t *timer)
| 0x55d00da947a2 89c7 mov edi, eax
| 0x55d00da947a4 e887feffff call sym.imp.srand ; void srand(int seed)
| 0x55d00da947a9 c74584000000. mov dword [local_7ch], 0
| ,=< 0x55d00da947b0 eb35 jmp 0x55d00da947e7
| .--> 0x55d00da947b2 e899feffff call sym.imp.rand ; int rand(void)
| :| 0x55d00da947b7 89c1 mov ecx, eax
| :| 0x55d00da947b9 bae9a28b2e mov edx, 0x2e8ba2e9
| :| 0x55d00da947be 89c8 mov eax, ecx
| :| 0x55d00da947c0 f7ea imul edx
| :| 0x55d00da947c2 d1fa sar edx, 1
| :| 0x55d00da947c4 89c8 mov eax, ecx
| :| 0x55d00da947c6 c1f81f sar eax, 0x1f
| :| 0x55d00da947c9 29c2 sub edx, eax
| :| 0x55d00da947cb 89d0 mov eax, edx
| :| 0x55d00da947cd c1e002 shl eax, 2
| :| 0x55d00da947d0 01d0 add eax, edx
| :| 0x55d00da947d2 01c0 add eax, eax
| :| 0x55d00da947d4 01d0 add eax, edx
| :| 0x55d00da947d6 29c1 sub ecx, eax
| :| 0x55d00da947d8 89ca mov edx, ecx
| :| 0x55d00da947da 8b4584 mov eax, dword [local_7ch]
| :| 0x55d00da947dd 4898 cdqe
| :| 0x55d00da947df 89548590 mov dword [rbp + rax*4 - 0x70], edx
| :| 0x55d00da947e3 83458401 add dword [local_7ch], 1
| :| ; JMP XREF from 0x55d00da947b0 (main)
| :`-> 0x55d00da947e7 837d8413 cmp dword [local_7ch], 0x13 ; [0x13:4]=-1 ; 19
| `==< 0x55d00da947eb 7ec5 jle 0x55d00da947b2
| 0x55d00da947ed c74584000000. mov dword [local_7ch], 0
| ,=< 0x55d00da947f4 eb20 jmp 0x55d00da94816
| .--> 0x55d00da947f6 8b4584 mov eax, dword [local_7ch]
| :| 0x55d00da947f9 4898 cdqe
| :| 0x55d00da947fb 8b448590 mov eax, dword [rbp + rax*4 - 0x70]
| :| 0x55d00da947ff 89c6 mov esi, eax
| :| 0x55d00da94801 488d3dbc0000. lea rdi, qword [0x55d00da948c4] ; "%d "
| :| 0x55d00da94808 b800000000 mov eax, 0
| :| 0x55d00da9480d e80efeffff call sym.imp.printf ; int printf(const char *format)
| :| 0x55d00da94812 83458401 add dword [local_7ch], 1
| :| ; JMP XREF from 0x55d00da947f4 (main)
| :`-> 0x55d00da94816 837d8413 cmp dword [local_7ch], 0x13 ; [0x13:4]=-1 ; 19
| `==< 0x55d00da9481a 7eda jle 0x55d00da947f6
| 0x55d00da9481c b800000000 mov eax, 0
| 0x55d00da94821 488b75f8 mov rsi, qword [local_8h]
| 0x55d00da94825 644833342528. xor rsi, qword fs:[0x28]
| ,=< 0x55d00da9482e 7405 je 0x55d00da94835
| | 0x55d00da94830 e8dbfdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x55d00da94835 c9 leave
\ 0x55d00da94836 c3 ret
[0x55d00da9477a]>
El bloque puede parecer grande, pero no hay que dejarse intimidar. A primera vista se detectan un par de bucles: el primero probablemente inicializa el array y el segundo lo recorre imprimiendo sus valores uno a uno, como indica la llamada a printf.
Se puede anotar eso directamente en el desensamblado. Con CCu comment @ addr se añade un comentario junto a una dirección de memoria concreta.
[0x55d00da9477a]> CCu "print array" @ 0x55d00da947f4
| 0x55d00da947ed c74584000000. mov dword [local_7ch], 0
| ,=< 0x55d00da947f4 eb20 jmp 0x55d00da94816 ; "print array"
| .--> 0x55d00da947f6 8b4584 mov eax, dword [local_7ch]
| :| 0x55d00da947f9 4898 cdqe
| :| 0x55d00da947fb 8b448590 mov eax, dword [rbp + rax*4 - 0x70]
| :| 0x55d00da947ff 89c6 mov esi, eax
| :| 0x55d00da94801 488d3dbc0000. lea rdi, qword [0x55d00da948c4] ; "%d "
También se pueden listar todos los comentarios fácilmente con
[0x55d00da9477a]> CC
0x00000000 CCu "[28] ----- section size 254 named .shstrtab"
0x55d00da94000 CCu "[38] m-rw- section size 64 named ehdr"
0x55d00da94040 CCu "[29] m-r-- section size 504 named PHDR"
0x55d00da94238 CCu "[30] m-r-- section size 28 named INTERP"
0x55d00da94254 CCu "[34] m-r-- section size 68 named NOTE"
0x55d00da94274 CCu "[03] --r-- section size 36 named .note.gnu.build_id"
0x55d00da94298 CCu "[04] --r-- section size 28 named .gnu.hash"
0x55d00da942b8 CCu "[05] --r-- section size 264 named .dynsym"
0x55d00da943c0 CCu "[06] --r-- section size 170 named .dynstr"
0x55d00da9446a CCu "[07] --r-- section size 22 named .gnu.version"
0x55d00da94480 CCu "[08] --r-- section size 48 named .gnu.version_r"
0x55d00da944b0 CCu "[09] --r-- section size 192 named .rela.dyn"
0x55d00da94570 CCu "[10] --r-- section size 120 named .rela.plt"
0x55d00da945e8 CCu "[11] --r-x section size 23 named .init"
0x55d00da94600 CCu "[12] --r-x section size 96 named .plt"
0x55d00da94660 CCu "[13] --r-x section size 8 named .plt.got"
0x55d00da94670 CCu "[14] --r-x section size 578 named .text"
0x55d00da947f4 CCu "\"print array\""
0x55d00da948b4 CCu "[15] --r-x section size 9 named .fini"
0x55d00da948c0 CCu "[16] --r-- section size 8 named .rodata"
0x55d00da948c8 CCu "[35] m-r-- section size 60 named GNU_EH_FRAME"
0x55d00da94908 CCu "[18] --r-- section size 264 named .eh_frame"
0x55d00dc94d98 CCu "[37] m-r-- section size 616 named GNU_RELRO"
0x55d00dc94da0 CCu "[20] --rw- section size 8 named .fini_array"
0x55d00dc94da8 CCu "[33] m-rw- section size 496 named DYNAMIC"
0x55d00dc94f98 CCu "[22] --rw- section size 104 named .got"
0x55d00dc95000 CCu "[23] --rw- section size 16 named .data"
0x55d00dc95010 CCu "[24] --rw- section size 0 named .bss"
Pasemos al primer bucle:
[0x55d00da9477a]> pdf
;-- main:
/ (fcn) main 189
| main ();
| ; var int local_7ch @ rbp-0x7c
| ; var int local_78h @ rbp-0x78
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x55d00da9468d (entry0)
El programa establece tres variables: el stack guard y un par de vars; local_7ch se usa para almacenar i (el contador del bucle) y local_78h parece estar relacionado con la llamada a time.
Veámoslo más de cerca:
| 0x55d00da94791 488d4588 lea rax, qword [local_78h]
| 0x55d00da94795 4889c7 mov rdi, rax
| 0x55d00da94798 b800000000 mov eax, 0
| 0x55d00da9479d e89efeffff call sym.imp.time ; time_t time(time_t *timer)
| 0x55d00da947a2 89c7 mov edi, eax
| 0x55d00da947a4 e887feffff call sym.imp.srand ; void srand(int seed)
| 0x55d00da947a9 c74584000000. mov dword [local_7ch], 0
La dirección de memoria de local_78h se pasa a time() como parámetro. El resultado de la llamada (almacenado en eax) se pasa a su vez a srand como semilla; después se inicializa el contador del primer bucle (i=0). La función time funciona así: time_t time( time_t *second ); acepta un único parámetro, second, que se usa para establecer el objeto time_t que almacena el tiempo (http://www.cplusplus.com/reference/ctime/time_t/) y devuelve el tiempo de calendario actual como un objeto de tipo time_t. La función srand establece la semilla del generador de números aleatorios que usará rand(). El generador necesita una semilla porque calcula el número aleatorio mediante una fórmula matemática y, por tanto, requiere una entrada. Usar la misma semilla producirá la misma secuencia de números más adelante en el código.
Como se puede ver, la salida de time se pasa a srand. El tiempo del sistema se usa con frecuencia como semilla aleatoria porque es uno de los valores más impredecibles disponibles en la máquina. También se usan otras fuentes como la posición del ratón o conjuntos de palabras aleatorias; sistemas más complejos, como los empleados en banca, pueden recurrir incluso a sensores de temperatura o presión.
Con srand inicializado, el programa puede generar números aleatorios llamando a rand.
Con rand listo, entramos en el primer bucle:
| 0x55d00da947a2 89c7 mov edi, eax
| 0x55d00da947a4 e887feffff call sym.imp.srand ; void srand(int seed)
| 0x55d00da947a9 c74584000000. mov dword [local_7ch], 0
| ,=< 0x55d00da947b0 eb35 jmp 0x55d00da947e7
| .--> 0x55d00da947b2 e899feffff call sym.imp.rand ; int rand(void)
| :| 0x55d00da947b7 89c1 mov ecx, eax
| :| 0x55d00da947b9 bae9a28b2e mov edx, 0x2e8ba2e9
| :| 0x55d00da947be 89c8 mov eax, ecx
| :| 0x55d00da947c0 f7ea imul edx
| :| 0x55d00da947c2 d1fa sar edx, 1
| :| 0x55d00da947c4 89c8 mov eax, ecx
| :| 0x55d00da947c6 c1f81f sar eax, 0x1f
| :| 0x55d00da947c9 29c2 sub edx, eax
| :| 0x55d00da947cb 89d0 mov eax, edx
| :| 0x55d00da947cd c1e002 shl eax, 2
| :| 0x55d00da947d0 01d0 add eax, edx
| :| 0x55d00da947d2 01c0 add eax, eax
| :| 0x55d00da947d4 01d0 add eax, edx
| :| 0x55d00da947d6 29c1 sub ecx, eax
| :| 0x55d00da947d8 89ca mov edx, ecx
| :| 0x55d00da947da 8b4584 mov eax, dword [local_7ch]
| :| 0x55d00da947dd 4898 cdqe
| :| 0x55d00da947df 89548590 mov dword [rbp + rax*4 - 0x70], edx
| :| 0x55d00da947e3 83458401 add dword [local_7ch], 1
Para analizar este bloque conviene tener muy presente la expresión s1.a[i] = rand() % 11;. Se puede descomponer en tres partes: primero se genera el número aleatorio, luego se aplica % 11 y finalmente se almacena el resultado en s1.a[i].
La primera parte es clara:
| .--> 0x55d00da947b2 e899feffff call sym.imp.rand ; int rand(void)
| :| 0x55d00da947b7 89c1 mov ecx, eax
Con rand se obtiene un número aleatorio (eax) que se almacena en ecx para operar con él. La siguiente parte corresponde al % 10:
| :| 0x55d00da947b9 bae9a28b2e mov edx, 0x2e8ba2e9
| :| 0x55d00da947be 89c8 mov eax, ecx
| :| 0x55d00da947c0 f7ea imul edx
| :| 0x55d00da947c2 d1fa sar edx, 1
| :| 0x55d00da947c4 89c8 mov eax, ecx
| :| 0x55d00da947c6 c1f81f sar eax, 0x1f
| :| 0x55d00da947c9 29c2 sub edx, eax
| :| 0x55d00da947cb 89d0 mov eax, edx
| :| 0x55d00da947cd c1e002 shl eax, 2
| :| 0x55d00da947d0 01d0 add eax, edx
| :| 0x55d00da947d2 01c0 add eax, eax
| :| 0x55d00da947d4 01d0 add eax, edx
| :| 0x55d00da947d6 29c1 sub ecx, eax| :| 0x55d00da947b9 bae9a28b2e mov edx, 0x2e8ba2e9
| :| 0x55d00da947be 89c8 mov eax, ecx
| :| 0x55d00da947c0 f7ea imul edx
| :| 0x55d00da947c2 d1fa sar edx, 1
| :| 0x55d00da947c4 89c8 mov eax, ecx
| :| 0x55d00da947c6 c1f81f sar eax, 0x1f
| :| 0x55d00da947c9 29c2 sub edx, eax
| :| 0x55d00da947cb 89d0 mov eax, edx
| :| 0x55d00da947cd c1e002 shl eax, 2
| :| 0x55d00da947d0 01d0 add eax, edx
| :| 0x55d00da947d2 01c0 add eax, eax
| :| 0x55d00da947d4 01d0 add eax, edx
| :| 0x55d00da947d6 29c1 sub ecx, eax
Ante situaciones como esta que no se comprenden de inmediato, hay al menos dos opciones. La primera es hacerse una idea general de lo que ocurre en el bloque de código y continuar mirando el panorama completo: se pueden poner un par de breakpoints antes y después del bloque, ver cómo entra un valor y observar la salida.
La segunda opción es inspeccionar la función paso a paso, trazando la entrada y poniendo breakpoints donde cambia un valor. Hay que tener cuidado con esta opción, ya que algunas funciones (llamadas a la API de Windows, syscalls, proyectos grandes, etc.) pueden ser muy extensas y consumir mucho tiempo.
| :| 0x55d00da947be b 89c8 mov eax, ecx
| :| 0x55d00da947c0 f7ea imul edx
| :| 0x55d00da947c2 b d1fa sar edx, 1
| :| 0x55d00da947c4 b 89c8 mov eax, ecx
| :| 0x55d00da947c6 c1f81f sar eax, 0x1f
| :| 0x55d00da947c9 b 29c2 sub edx, eax
| :| 0x55d00da947cb 89d0 mov eax, edx
| :| 0x55d00da947cd c1e002 shl eax, 2
| :| 0x55d00da947d0 b 01d0 add eax, edx
| :| 0x55d00da947d2 b 01c0 add eax, eax
| :| 0x55d00da947d4 b 01d0 add eax, edx
| :| 0x55d00da947d6 29c1 sub ecx, eax
| :| 0x55d00da947d8 89ca mov edx, ecx
| .--> 0x555600e8b7b2 e899feffff call sym.imp.rand ; int rand(void)
| :| 0x555600e8b7b7 89c1 mov ecx, eax
| :| 0x555600e8b7b9 bae9a28b2e mov edx, 0x2e8ba2e9 ; rdx
[0x555600e8b7be]> dr eax
0x591fb69a
[0x555600e8b7be]> dr edx
0x2e8ba2e9
| :| 0x555600e8b7be b 89c8 mov eax, ecx
| :| 0x555600e8b7c0 f7ea imul edx
[0x555600e8b7be]> dr edx
0x10344fbf
| :| 0x555600e8b7c4 89c8 mov eax, ecx
| :| 0x555600e8b7c6 c1f81f sar eax, 0x1f
| :| ;-- rip:
[0x555600e8b7be]> dr eax
0x00000000
[0x555600e8b7be]> dr edx
0x081a27df
| :| 0x555600e8b7c9 b 29c2 sub edx, eax
| :| 0x555600e8b7cb 89d0 mov eax, edx
| :| 0x555600e8b7cd c1e002 shl eax, 2
[0x555600e8b7be]> dr eax
0x20689f7c
[0x555600e8b7be]> dr edx
0x081a27df
[0x555600e8b7be]>
[...]
[0x555600e8b7be]> dr
rax = 0x591fb695
rbx = 0x00000000
rcx = 0x00000005
El resultado final es 0x5 → dec 5, es decir, el número aleatorio generado módulo 10 es 5. La función rand() devuelve un número entre 0x0 y 0xFFFFFFFFFFFFFFFF (18446744073709551615) en una máquina x64; el módulo se usa para «recortar» ese valor y fijar un máximo de 50. En este caso rand devolvió 0x591fb69a (1495250586) y a continuación se producen operaciones de desplazamiento, alineación y multiplicaciones para reducirlo al resto respecto a 10.
Para profundizar en ese tema se puede consultar la discusión en: https://stackoverflow.com/questions/8021772/assembly-language-how-to-do-modulo
La operación módulo se puede implementar de muchas formas. Hay maneras que resultarían mucho más legibles que la usada aquí, pero el compilador no busca legibilidad: busca maximizar el rendimiento, y en ciertos contextos un conjunto particular de instrucciones puede producir una ejecución más rápida.
Tras eso, el valor se almacena en el array de la misma forma que se vio antes:
| :| 0x555600e8b7da 8b4584 mov eax, dword [local_7ch]
| :| 0x555600e8b7dd 4898 cdqe
| :| 0x555600e8b7df 89548590 mov dword [rbp + rax*4 - 0x70], edx
| :| 0x555600e8b7e3 83458401 add dword [local_7ch], 1
| :| ; JMP XREF from 0x555600e8b7b0 (main)
| :`-> 0x555600e8b7e7 837d8413 cmp dword [local_7ch], 0x13 ; [0x13:4]=-1 ; 19
| `==< 0x555600e8b7eb 7ec5 jle 0x555600e8b7b2
El bucle se repite 20 veces. Cuando se trabaja con estructuras de datos complejas que contienen o pueden contener información relevante, resulta muy útil llevar un seguimiento de ellas. Se puede tener una idea general de su estructura o disponer de información adicional como un archivo .h. En radare2 es posible «mapear» una definición de estructura a una dirección de memoria concreta usando td y tl.
Conociendo el código, definamos esa struct en radare2 y mapeémosla:
"td struct students {int a[20]; char b[20];}"
tl students = 0x7FFD69D8C94C
Se puede mapear la struct a la dirección que se obtiene al inspeccionar la que parece corresponder al valor inicial del array que se está inicializando, y luego ejecutar dc para ver cómo el programa actualiza (asigna) los valores de ese array:
[0x555600e8b7be]> dc
hit breakpoint at: 555600e8b7be
[0x555600e8b7be]> tl
(students)
a : 0x7ffd69d8c94c = [ 0, 5, 8, 5, 3, 1, 6, 9, 2, 5, 8, 9, 2, 4, 7, 10, 3, 6, 6, 0 ]
b : 0x7ffd69d8c99c =
Dicho esto, hay que tener en cuenta que se puede estar equivocado de muchas formas: se puede errar en la dirección base o deducir una struct incorrecta. Hay que usar la struct que mejor encaje con el análisis.
Terminemos con un último ejemplo:
#include <stdio.h>
#include <string.h>
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( ) {
struct Books Book1; /* Declare Book1 of type Book */
struct Books Book2; /* Declare Book2 of type Book */
/* book 1 specification */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* book 2 specification */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* print Book1 info */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
/* print Book2 info */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);
return 0;
}
El desensamblado tiene este aspecto:
[0x56315b93e6aa]> pdf
;-- main:
/ (fcn) sym.main 529
| sym.main ();
| ; var int local_1b0h @ rbp-0x1b0
| ; var int local_e8h @ rbp-0xe8
| ; var int local_e0h @ rbp-0xe0
| ; var int local_18h @ rbp-0x18
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x56315b93e5bd (entry0)
| 0x56315b93e6aa 55 push rbp
| 0x56315b93e6ab 4889e5 mov rbp, rsp
| 0x56315b93e6ae 4881ecb00100. sub rsp, 0x1b0
| 0x56315b93e6b5 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x56315b93e6be 488945f8 mov qword [local_8h], rax
| 0x56315b93e6c2 31c0 xor eax, eax
| 0x56315b93e6c4 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e6cb 48ba43205072. movabs rdx, 0x6172676f72502043
| 0x56315b93e6d5 488910 mov qword [rax], rdx
| 0x56315b93e6d8 c740086d6d69. mov dword [rax + 8], 0x6e696d6d ; [0x6e696d6d:4]=-1
| 0x56315b93e6df 66c7400c6700 mov word [rax + 0xc], 0x67 ; 'g' ; [0x67:2]=0xffff ; 103
| 0x56315b93e6e5 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e6ec 4883c032 add rax, 0x32 ; '2'
| 0x56315b93e6f0 48b94e756861. movabs rcx, 0x696c41206168754e
| 0x56315b93e6fa 488908 mov qword [rax], rcx
| 0x56315b93e6fd c6400800 mov byte [rax + 8], 0
| 0x56315b93e701 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e708 4883c064 add rax, 0x64 ; 'd'
| 0x56315b93e70c 48ba43205072. movabs rdx, 0x6172676f72502043
| 0x56315b93e716 48b96d6d696e. movabs rcx, 0x755420676e696d6d
| 0x56315b93e720 488910 mov qword [rax], rdx
| 0x56315b93e723 48894808 mov qword [rax + 8], rcx
| 0x56315b93e727 c74010746f72. mov dword [rax + 0x10], 0x69726f74 ; [0x69726f74:4]=-1
| 0x56315b93e72e 66c74014616c mov word [rax + 0x14], 0x6c61 ; [0x6c61:2]=0xffff
| 0x56315b93e734 c6401600 mov byte [rax + 0x16], 0
| 0x56315b93e738 c78518ffffff. mov dword [local_e8h], 0x631caf
| 0x56315b93e742 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e749 48ba54656c65. movabs rdx, 0x206d6f63656c6554
| 0x56315b93e753 48b942696c6c. movabs rcx, 0x676e696c6c6942
| 0x56315b93e75d 488910 mov qword [rax], rdx
| 0x56315b93e760 48894808 mov qword [rax + 8], rcx
| 0x56315b93e764 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e76b 4883c032 add rax, 0x32 ; '2'
| 0x56315b93e76f 48ba5a617261. movabs rdx, 0x696c41206172615a
| 0x56315b93e779 488910 mov qword [rax], rdx
| 0x56315b93e77c c6400800 mov byte [rax + 8], 0
| 0x56315b93e780 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e787 4883c064 add rax, 0x64 ; 'd'
| 0x56315b93e78b 48ba54656c65. movabs rdx, 0x206d6f63656c6554
| 0x56315b93e795 48b942696c6c. movabs rcx, 0x20676e696c6c6942
| 0x56315b93e79f 488910 mov qword [rax], rdx
| 0x56315b93e7a2 48894808 mov qword [rax + 8], rcx
| 0x56315b93e7a6 48b95475746f. movabs rcx, 0x6c6169726f747554
| 0x56315b93e7b0 48894810 mov qword [rax + 0x10], rcx
| 0x56315b93e7b4 c6401800 mov byte [rax + 0x18], 0
| 0x56315b93e7b8 c745e8d41d63. mov dword [local_18h], 0x631dd4
| 0x56315b93e7bf 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e7c6 4889c6 mov rsi, rax
| 0x56315b93e7c9 488d3d740100. lea rdi, qword str.Book_1_title_:__s ; 0x56315b93e944 ; "Book 1 title : %s\n"
| 0x56315b93e7d0 b800000000 mov eax, 0
| 0x56315b93e7d5 e8a6fdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e7da 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e7e1 4883c032 add rax, 0x32 ; '2'
| 0x56315b93e7e5 4889c6 mov rsi, rax
| 0x56315b93e7e8 488d3d680100. lea rdi, qword str.Book_1_author_:__s ; 0x56315b93e957 ; "Book 1 author : %s\n"
| 0x56315b93e7ef b800000000 mov eax, 0
| 0x56315b93e7f4 e887fdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e7f9 488d8550feff. lea rax, qword [local_1b0h]
| 0x56315b93e800 4883c064 add rax, 0x64 ; 'd'
| 0x56315b93e804 4889c6 mov rsi, rax
| 0x56315b93e807 488d3d5d0100. lea rdi, qword str.Book_1_subject_:__s ; 0x56315b93e96b ; "Book 1 subject : %s\n"
| 0x56315b93e80e b800000000 mov eax, 0
| 0x56315b93e813 e868fdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e818 8b8518ffffff mov eax, dword [local_e8h]
| 0x56315b93e81e 89c6 mov esi, eax
| 0x56315b93e820 488d3d590100. lea rdi, qword str.Book_1_book_id_:__d ; 0x56315b93e980 ; "Book 1 book_id : %d\n"
| 0x56315b93e827 b800000000 mov eax, 0
| 0x56315b93e82c e84ffdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e831 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e838 4889c6 mov rsi, rax
| 0x56315b93e83b 488d3d530100. lea rdi, qword str.Book_2_title_:__s ; 0x56315b93e995 ; "Book 2 title : %s\n"
| 0x56315b93e842 b800000000 mov eax, 0
| 0x56315b93e847 e834fdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e84c 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e853 4883c032 add rax, 0x32 ; '2'
| 0x56315b93e857 4889c6 mov rsi, rax
| 0x56315b93e85a 488d3d470100. lea rdi, qword str.Book_2_author_:__s ; 0x56315b93e9a8 ; "Book 2 author : %s\n"
| 0x56315b93e861 b800000000 mov eax, 0
| 0x56315b93e866 e815fdffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e86b 488d8520ffff. lea rax, qword [local_e0h]
| 0x56315b93e872 4883c064 add rax, 0x64 ; 'd'
| 0x56315b93e876 4889c6 mov rsi, rax
| 0x56315b93e879 488d3d3c0100. lea rdi, qword str.Book_2_subject_:__s ; 0x56315b93e9bc ; "Book 2 subject : %s\n"
| 0x56315b93e880 b800000000 mov eax, 0
| 0x56315b93e885 e8f6fcffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e88a 8b45e8 mov eax, dword [local_18h]
| 0x56315b93e88d 89c6 mov esi, eax
| 0x56315b93e88f 488d3d3b0100. lea rdi, qword str.Book_2_book_id_:__d ; 0x56315b93e9d1 ; "Book 2 book_id : %d\n"
| 0x56315b93e896 b800000000 mov eax, 0
| 0x56315b93e89b e8e0fcffff call sym.imp.printf ; int printf(const char *format)
| 0x56315b93e8a0 b800000000 mov eax, 0
| 0x56315b93e8a5 488b75f8 mov rsi, qword [local_8h]
| 0x56315b93e8a9 644833342528. xor rsi, qword fs:[0x28]
| ,=< 0x56315b93e8b2 7405 je 0x56315b93e8b9
| | 0x56315b93e8b4 e8b7fcffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x56315b93e8b9 c9 leave
\ 0x56315b93e8ba c3 ret
[0x56315b93e6aa]>
El código no debe intimidar, ya que es muy repetitivo. Se puede dividir en dos secciones principales: primero se inicializan los datos y luego se imprimen. En la primera sección también se identifica un patrón: dos bloques, cada uno terminando con una asignación:
| 0x56315b93e72e 66c74014616c mov word [rax + 0x14], 0x6c61 ; [0x6c61:2]=0xffff
| 0x56315b93e734 c6401600 mov byte [rax + 0x16], 0
| 0x56315b93e738 c78518ffffff. mov dword [local_e8h], 0x631caf
| 0x56315b93e7b0 48894810 mov qword [rax + 0x10], rcx
| 0x56315b93e7b4 c6401800 mov byte [rax + 0x18], 0
| 0x56315b93e7b8 c745e8d41d63. mov dword [local_18h], 0x631dd4
Analicemos uno de ellos para deducir el conjunto. Leyendo de arriba a abajo se ve:
| 0x56315b93e6cb 48ba43205072. movabs rdx, 0x6172676f72502043
| 0x56315b93e6d5 488910 mov qword [rax], rdx
| 0x56315b93e6d8 c740086d6d69. mov dword [rax + 8], 0x6e696d6d ; [0x6e696d6d:4]=-1
| 0x56315b93e6df 66c7400c6700 mov word [rax + 0xc], 0x67 ; 'g' ; [0x67:2]=0xffff ; 103
| 0x56315b93e6e5 488d8550feff. lea rax, qword [local_1b0h]
Se mueven fragmentos hexadecimales grandes a memoria a partir de local_1b0h; se usa moveabs porque se trata de mover bloques de mayor tamaño. Juntando todo: 432050726f6772616d6d696e67 = C Programming en ASCII (hay que tener en cuenta el endianness). Con eso se puede deducir el resto del código. Cabe señalar que aunque en el fuente se usa strcpy, el programa realiza la copia directamente, sin llamar a ninguna función externa.
[0x56315b93e6aa]> "td struct Books { char title[50]; char author[50]; char subject[100]; int book_id;};"
[0x56315b93e6aa]> tl
[0x56315b93e6aa]> td
Invalid use of td. See td? for help
^Cx56315b93e6aa]>
[0x56315b93e6aa]>
[0x56315b93e6aa]> db 0x56315b93e6cb
[0x56315b93e6aa]> db 0x56315b93e749
[0x56315b93e6aa]> dc
hit breakpoint at: 56315b93e6cb
[0x56315b93e6aa]> afvd
var local_8h = 0x7fff0a95f358 0x4332774ba5ead400 ....Kw2C
var local_1b0h = 0x7fff0a95f1b0 0x00007fff0a95f370 p....... @rsp stack R W 0x1 --> rdi
var local_e8h = 0x7fff0a95f278 0x00007fe2f4b21710 ........ (unk0) R W 0x7fff0a9d2000 --> ([vvar]) map.vdso_._r_x R X 'jg 0x7fff0a9d2047' '[vdso]'
var local_e0h = 0x7fff0a95f280 0x00007fe2f46ba787 ..k..... (__vdso_getcpu)
var local_18h = 0x7fff0a95f348 0x000056315b93e5a0 ...[1V.. (LOAD0) (/home/red/c/chapter6/structs) r12 entry0 program R X 'xor ebp, ebp' 'structs'
[0x56315b93e6aa]> tl Books = 0x7fff0a95f1b0
[0x56315b93e6aa]> tl
(Books)
title : 0x7fff0a95f1b0 = p.....
author : 0x7fff0a95f1e2 = ....
subject : 0x7fff0a95f214 =
book_id : 0x7fff0a95f278 = 4105312016
[0x56315b93e6aa]> dc
hit breakpoint at: 56315b93e749
[0x56315b93e749]> tl
(Books)
title : 0x7fff0a95f1b0 = C Programming
author : 0x7fff0a95f1e2 = Nuha Ali
subject : 0x7fff0a95f214 = C Programming Tutorial
book_id : 0x7fff0a95f278 = 6495407
[0x56315b93e749]>
También se puede «enlazar» una struct a múltiples direcciones de datos y así monitorizar varios segmentos de memoria mientras el programa se ejecuta.
Las siguientes lecciones amplían el análisis de estas estructuras de datos.
Comprobar el layout de una estructura.
Hermes consulta el layout obtenido con el compilador y lo relaciona con las dos escrituras de book_id que aparecen en main.
Leer la explicación y los comandos
La consulta
Comprueba el layout de struct Books con ./coursectl layout 6. Indica el tamaño, la alineación y los offsets de sus cuatro campos según la salida del compilador. Localiza después las dos escrituras de book_id en main; conserva los inmediatos en hexadecimal.
Qué muestra la sesión
La comprobación devuelve sizeof(struct Books) = 204 y una alineación de cuatro bytes. Los offsets de title, author, subject y book_id son 0, 50, 100 y 200.
Las dos asignaciones escriben enteros de 32 bits con los valores 0x631caf y 0x631dd4. Sus posiciones respecto de RBP corresponden a dos variables locales diferentes.
El desplazamiento de un campo se mide desde el comienzo de su objeto. Para comprobar el tamaño del tipo, la sesión utiliza el probe del compilador que incorpora el companion.
Comprobación desde la consola
Desde la consola del companion se revisan estas instrucciones y datos:
pd 1 @ 0x4011e6
pd 1 @ 0x401270
Las direcciones corresponden a la compilación grabada. Localiza las funciones y operaciones de tu propio ejecutable antes de repetir los comandos. Descargar el fuente del vídeo ↙ · Preparar el companion ↗
Vuelve al binario
Reconstruir campos, offsets y alineación a partir de la fuente, el ensamblador y la memoria. 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: books.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.
./coursectl start 6 --example books
./coursectl consoleCierra la sesión anterior con ./coursectl stop antes de cambiar de laboratorio. Ver preparación.
Antes de continuar
Explica por qué book_id empieza en el offset 200 y por qué una separación de 208 bytes entre variables no cambia sizeof(struct Books). Localiza ambos identificadores en el ejemplo.
El progreso se guarda en este navegador. Puedes recorrer las lecciones en cualquier orden.