
libtalloc is a python script for use with GDB that can be used to analyse the "trivial allocator" (talloc)
libtalloc es un script de python para usar con GDB que se puede utilizar para analizar el "asignador trivial" (talloc). Puedes encontrar una introducción a talloc aquí:
https://talloc.samba.org/talloc/doc/html/index.html
libtalloc se inspiró en otros scripts de python para gdb que analizan heaps, como unmask_jemalloc y libheap. Parte de la funcionalidad básica es idéntica a la de estos proyectos.
https://github.com/cloudburst/libheap
https://github.com/argp/unmask_jemalloc
Ten en cuenta que no soy un gurú de python y la calidad del código lo refleja. Si ves algo que te disgusta, no dudes en enviar un parche o darme algunas sugerencias. Todos los comentarios son bienvenidos.
libtalloc ha sido probado con varias versiones 2.x de talloc y soporta detección dinámica de versión para intentar superar las distintas diferencias estructurales entre versiones. Se ha probado en 32 bits y 64 bits, aunque no de forma exhaustiva, así que no te sorprendas si se rompe de vez en cuando.
Se ha probado en cierta medida en x86 y x64:
Si lo pruebas con otra versión, por favor házmelo saber si funcionó, o qué se rompió, e intentaré actualizarlo y/o la documentación en consecuencia.
El script solo requiere una versión relativamente moderna de GDB con soporte para python.
Algunas distros LTS, como Ubuntu 12.04, todavía usan GDB con python 2.7, mientras que versiones más nuevas como 14.04 usan python 3.0. Intenté hacer que este script funcione con ambas, por lo que solo deberías necesitar:
(gdb) source libtalloc.py
La mayor parte de la funcionalidad está modelada siguiendo el enfoque de unmask_jemalloc, donde se proporciona un comando GDB separado en lugar de un complejo conjunto de opciones.
Hay disponibles varios métodos diseñados específicamente para imitar las funciones C de la biblioteca talloc, para ayudar a quienes intentan ampliar libtalloc si ya están familiarizados con la biblioteca.
Para ver la lista completa de comandos puedes ejecutar el comando tchelp:
(gdb) tchelp
[libtalloc] talloc commands for gdb
[libtalloc] tcchunk -v -x <addr> : show chunk contents (-v for verbose, -x for data dump)
[libtalloc] tcvalidate -a <addr> : validate chunk (-a for whole heap)
[libtalloc] tcsearch <addr> : search heap for hex value or address
[libtalloc] tcwalk <func> : walk whole heap calling func on every chunk
[libtalloc] tcreport <addr> : give talloc_report_full() info on memory context
[libtalloc] tcdump -s <addr> : dump chunks linked to memory context (-s for sorted by addr)
[libtalloc] tcparents <addr> : show all parents of chunk
[libtalloc] tcchildren <addr> : show all children of chunk
[libtalloc] tcinfo : show information known about heap
[libtalloc] tcprobe : try to collect information about talloc version
[libtalloc] tchelp : this help message
Uno de los comandos más importantes es tcprobe. Debe ejecutarse para averiguar qué versión de talloc está realmente instalada. Los diseños de estructuras para diferentes versiones pueden variar significativamente, por lo que para que la mayoría de las funciones funcionen, se debe conocer la versión.
Si el comando funciona, debería indicarte la versión detectada:
(gdb) tcprobe
Version: 2.1.1
File: /usr/lib/libtalloc.so.2.1.1
El comando tcinfo pretende mostrar tanta información recopilada sobre el heap como sea posible, como la información de tcprobe, la estructura null_context si se encontró, y más. Por ahora solo muestra la versión y si null_context está establecido. El null_context es necesario para la mayoría de las funciones que recorren la jerarquía real, y para encontrarlo, la mayoría de las demás funcionalidades, como tchunk, etc., intentarán auto-localizarlo.
Después de ejecutar tcprobe pero antes de usar tchunk realmente:
(gdb) tcinfo [libtalloc] null_context not yet found yet [libtalloc] Version: 2.0.7 [libtalloc] File: /usr/lib/i386-linux-gnu/libtalloc.so.2.0.7
Luego, después de analizar un chunk, así:
(gdb) tcchunk 0xb94a52b0 WARNING: 0xb94a52b0 not a talloc_chunk. Assuming ptr to chunk data 0xb94a5280 sz:0x0000003c, flags:...., name:struct tevent_context
Puedes confirmar que se encontró después con tcinfo.
(gdb) tcinfo [libtalloc] null_context: 0xb94a5028 [libtalloc] Version: 2.0.7 [libtalloc] File: /usr/lib/i386-linux-gnu/libtalloc.so.2.0.7
Ahora que null_context está establecido, podrías ejecutar otros comandos que normalmente se quejarían de que no estaba establecido, como el comando tcsearch.
tcchunk puede proporcionarte un resumen del chunk, una salida más detallada de cada campo, o información extremadamente detallada sobre cada chunk circundante.
NOTA: Una cosa importante a tener en cuenta sobre tcchunk es que internamente usa el método tc_chunk(), que intenta corregir errores cometidos al pasar la dirección del chunk. Específicamente, si pasas la dirección de los datos del chunk en sí, y no encuentra la magia esperada de talloc, buscará una cabecera de chunk legítima un poco antes en la memoria. Esto puede confundirte en escenarios corruptos, así que asegúrate siempre de pasar la dirección explícita a menos que estés haciendo un análisis superficial.
Salida resumida:
(gdb) tcchunk 0x80a13c88
0x80a13c88 sz:0x00000020, flags:..p., name:struct netr_ServerPasswordSet
Lo siguiente es una leyenda para los chunks dentro de la salida resumida:
p - Member of a pool (POOLMEM flag)
P - Chunk is a pool (POOL flag)
F - Chunk is free (FREE flag)
L - Chunk is looped (LOOP flag)
Salida detallada:
(gdb) tcchunk -v 0x80a13c88
struct talloc_chunk @ 0x80a13c88 {
next = 0x0
prev = 0x80a140c8
parent = 0x0
child = 0x80a14088
refs = 0x0
destructor = 0x0
name = 0x807d9f2f (struct netr_ServerPasswordSet)
size = 0x20
flags = 0xe8150c78 (POOLMEM)
limit = 0x0
pool = 0x80a13248
Los chunks de talloc contienen algunos valores mágicos que se pueden usar para validar si son válidos. El comando tcvalidate analizará un chunk para asegurarse de que la magia del chunk sea la esperada. Además, analiza todos los demás miembros puntero para asegurarse de que realmente caen dentro de rangos de memoria (según los conoce gdb), si el tamaño es válido, etc.
(gdb) tcvalidate 0x80a13c88
Chunk header is valid
Usaremos un método incorporado para modificar un valor y mostrar cómo podría fallar:
(gdb) python set_destructor(tc_chunk(0x80a13c88), 0x41414141)
(gdb) tcchunk -v 0x80a13c88
struct talloc_chunk @ 0x80a13c88 {
next = 0x0
prev = 0x80a140c8
parent = 0x0
child = 0x80a14088
refs = 0x0
destructor = 0x41414141
name = 0x807d9f2f (struct netr_ServerPasswordSet)
size = 0x20
flags = 0xe8150c78 (POOLMEM)
limit = 0x0
pool = 0x80a13248
(gdb) tcvalidate 0x80a13c88
Chunk header is invalid:
0x80a13c88: Chunk has bad destructor pointer 0x41414141
tcparents se puede usar para ver todos los padres del chunk proporcionado:
(gdb) tcparents 0x80a13c88
0x809f8300: null_context
0x80a08660: TALLOC_CTX *
0x809f8370: talloc_new: ../lib/util/talloc_stack.c:147
0x809fb680: talloc_new: ../lib/util/talloc_stack.c:147
0x80a13258: UNNAMED
0x80a13c58: talloc_new: ../lib/util/talloc_stack.c:147
0x80a13c88: struct netr_ServerPasswordSet
tcchildren se puede usar para ver todos los hijos (y nietos, etc.) del chunk proporcionado:
(gdb) tcchildren 0x80a13c88
0x80a14088: struct netr_Authenticator
0x80a14048: librpc/gen_ndr/ndr_netlogon.c:10964
0x80a14008: librpc/gen_ndr/ndr_netlogon.c:10958
0x80a13fc8: librpc/gen_ndr/ndr_netlogon.c:10951
0x80a13f88: lib/charcnv.c:506
0x80a13ec8: lib/charcnv.c:506
0x80a13d48: librpc/gen_ndr/ndr_netlogon.c:10913
0x80a13e08:
0x80a13cd8: struct ndr_pull
0x80a13f48: struct ndr_token_list
0x80a13f08: struct ndr_token_list
0x80a13e88: struct ndr_token_list
0x80a13e48: struct ndr_token_list
0x80a13dc8: struct ndr_token_list
0x80a13d88: struct ndr_token_list
talloc tiene el concepto de chunks de pool. Estos son básicamente chunks normales de talloc, pero se utilizan para asignar nuevos chunks en lugar de recurrir a la implementación subyacente de malloc() del sistema. Un chunk de pool tiene cabeceras ligeramente diferentes según la versión utilizada, a veces usando relleno (padding), y a veces usando una cabecera de prefijo/sufijo.
tcpool se puede usar para analizar una cabecera de chunk de pool, de manera similar a tcchunk:
# First we find a pool to analyze
(gdb) tcchunk -v 0x80a13c88
struct talloc_chunk @ 0x80a13c88 {
next = 0x0
prev = 0x80a140c8
parent = 0x0
child = 0x80a14088
refs = 0x0
destructor = 0x0
name = 0x807d9f2f (struct netr_ServerPasswordSet)
size = 0x20
flags = 0xe8150c78 (POOLMEM)
limit = 0x0
pool = 0x80a13248
(gdb) tcpool -v 0x80a13248
struct talloc_pool_hdr @ 0x80a13248 {
end = 0x80a14108
object_count = 0x19
poolsize = 0x2000
struct talloc_chunk @ 0x80a13258 {
next = 0x0
prev = 0x0
parent = 0x809fb680
child = 0x80a13c58
refs = 0x0
destructor = 0x80429aa0
name = 0x0 (UNNAMED)
size = 0x0
flags = 0xe8150c74 (POOL)
limit = 0x0
pool = 0x0
En el caso anterior, el pool tenía un talloc_pool_hdr prefijado, que se muestra. La opción -l se puede pasar a tcpool para listar todos los chunks asignados dentro de un pool:
(gdb) tcpool -l 0x80a13248
Pool summary -- objects: 0x19, total size: 0x2000, space left: 0x1180, next free: 0x80a14108
0x80a13258 sz:0x00000000, flags:.P.., name:UNNAMED
0x80a13288 sz:0x000007a3, flags:..p., name:char
0x80a13a68 sz:0x0000005c, flags:..p., name:struct smb_request
0x80a13af8 sz:0x00000008, flags:..p., name:struct pipe_write_andx_state
0x80a13b38 sz:0x00000038, flags:..p., name:struct tevent_req
0x80a13ba8 sz:0x00000028, flags:..p., name:struct tevent_immediate
0x80a13c08 sz:0x00000014, flags:..p., name:struct np_write_state
0x80a13c58 sz:0x00000000, flags:..p., name:talloc_new: ../lib/util/talloc_stack.c:147
0x80a13c88 sz:0x00000020, flags:..p., name:struct netr_ServerPasswordSet
0x80a13cd8 sz:0x00000038, flags:..p., name:struct ndr_pull
0x80a13d48 sz:0x00000001, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10913
0x80a13d88 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13dc8 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13e08 sz:0x00000001, flags:..p., name:
0x80a13e48 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13e88 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13ec8 sz:0x00000008, flags:..p., name:lib/charcnv.c:506
0x80a13f08 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13f48 sz:0x00000010, flags:..p., name:struct ndr_token_list
0x80a13f88 sz:0x00000008, flags:..p., name:lib/charcnv.c:506
0x80a13fc8 sz:0x0000000c, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10951
0x80a14008 sz:0x00000010, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10958
0x80a14048 sz:0x0000000c, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10964
0x80a14088 sz:0x0000000c, flags:..p., name:struct netr_Authenticator
0x80a140c8 sz:0x0000000b, flags:..p., name:/etc/samba
Nota el flag P en la salida anterior, siendo el chunk superior el chunk de pool que contiene todos los chunks de abajo.
tcdump se puede usar para volcar todos los chunks de todo el árbol. Por defecto se muestran en orden jerárquico; sin embargo, la opción -s se puede usar para ordenar la salida por dirección.
(gdb) tcdump -a 0x809f8300
0x809f8300 sz:0x00000000, flags:...., name:null_context
0x80a0b3f8 sz:0x0000000c, flags:...., name:struct handle_list
0x809ff178 sz:0x00000014, flags:...., name:struct security_token
0x80a089e8 sz:0x00000198, flags:...., name:lib/util_nttoken.c:50
0x80a07268 sz:0x00000188, flags:...., name:connection_struct
0x80a08bc8 sz:0x00000020, flags:...., name:struct fd_handle
0x80a00c30 sz:0x000000f0, flags:...., name:struct files_struct
0x80a083b8 sz:0x00000008, flags:...., name:struct fake_file_handle
0x80a08c20 sz:0x0000009c, flags:...., name:struct pipes_struct
0x80a07900 sz:0x00000760, flags:...., name:uint8_t
0x80a07428 sz:0x000000c0, flags:...., name:struct auth_serversupplied_info
0x80a06390 sz:0x00000001, flags:...., name:
0x80a06350 sz:0x00000007, flags:...., name:nobody
0x80a06158 sz:0x000000cc, flags:...., name:struct netr_SamInfo3
0x80a062d8 sz:0x00000044, flags:...., name:struct dom_sid
[SNIP]
tcreport es un comando similar a tcdump, pero embellece un poco la salida y está pensado para imitar la función de depuración talloc_report_full() proporcionada por la propia biblioteca talloc.
(gdb) tcreport 0x80a0b3f8 -a
Full talloc report on 'null_context' (total 558651 bytes in 446 blocks)
struct handle_list contains 12 bytes in 1 blocks (ref 67) 0x80a0b3f8
struct security_token contains 428 bytes in 2 blocks (ref 66) 0x809ff178
lib/util_nttoken.c:50 contains 408 bytes in 1 blocks (ref 0) 0x80a089e8
connection_struct contains 531071 bytes in 36 blocks (ref 65) 0x80a07268
struct fd_handle contains 32 bytes in 1 blocks (ref 4) 0x80a08bc8
struct files_struct contains 529681 bytes in 22 blocks (ref 3) 0x80a00c30
struct fake_file_handle contains 529308 bytes in 19 blocks (ref 1) 0x80a083b8
struct pipes_struct contains 529300 bytes in 18 blocks (ref 0) 0x80a08c20
uint8_t contains 1888 bytes in 1 blocks (ref 2) 0x80a07900
struct auth_serversupplied_info contains 934 bytes in 10 blocks (ref 1) 0x80a07428
contains 1 bytes in 1 blocks (ref 4) 0x80a06390
nobody contains 7 bytes in 1 blocks (ref 3) 0x80a06350
struct netr_SamInfo3 contains 290 bytes in 4 blocks (ref 2) 0x80a06158
[SNIP]
Hay dos comandos para buscar: tcsearch y tcfindaddr.
tcsearch se puede usar para encontrar chunks que contengan el valor hexadecimal proporcionado. Funciona recorriendo toda la jerarquía del árbol comenzando desde null_context (si se conoce), o desde un chunk inicial proporcionado.
(gdb) python set_destructor(tc_chunk(0x80a13c88), 0x41414141)
(gdb) tcsearch 0x41414141 0x809f8300
[libtalloc] 0x41414141 found in chunk at 0x80a1d218
[libtalloc] 0x41414141 found in chunk at 0x80a13c88
(gdb) tcchunk -v 0x80a1d218
struct talloc_chunk @ 0x80a1d218 {
next = 0x80a00158
prev = 0x809fb0c8
parent = 0x0
child = 0x0
refs = 0x0
destructor = 0x0
name = 0x8071fcbd (uint8_t)
size = 0x80050
flags = 0xe8150c70 ()
limit = 0x0
pool = 0x0
(gdb) tcchunk -x 0x80a1d218
0x80a1d218 sz:0x00080050, flags:...., name:uint8_t
Chunk data (524368 bytes):
0x80a1d248: 0x41414141 0x00000000 0x00000000 0x00000000
0x80a1d258: 0x00000001 0x00000000 0x00000001 0x00020000
0x80a1d268: 0x00000001 0x00000000 0x00000001 0xaaaa0000
[SNIP]
(gdb) tcchunk -v 0x80a13c88
struct talloc_chunk @ 0x80a13c88 {
next = 0x0
prev = 0x80a140c8
parent = 0x0
child = 0x80a14088
refs = 0x0
destructor = 0x41414141
name = 0x807d9f2f (struct netr_ServerPasswordSet)
size = 0x20
flags = 0xe8150c78 (POOLMEM)
limit = 0x0
pool = 0x80a13248
tcfindaddr se puede usar para determinar si una dirección cae dentro de los límites de un chunk dentro del árbol de talloc. Supón que sabes que 0x80a1d3280 contiene algunos datos que controlas, por lo que quieres ver si caen dentro de un chunk. Ten en cuenta que la segunda dirección es null_context, pero puede ser cualquier chunk que nos permita encontrar la parte superior del heap.
(gdb) tcfindaddr 0x80a1d328 0x809f8300
[libtalloc] address 0x80a1d328 falls within chunk @ 0x80a1d218 (size 0x80050)
Parte de la búsqueda en el árbol se realiza mediante una función recursiva que expuse a través del comando tcwalk. Es una función auxiliar que te permite especificar un método de python que se llamará en cada chunk descubierto en el árbol.
En el siguiente ejemplo llamaremos al método de validación del heap en cada chunk del heap para ver si algo está corrupto.
(gdb) tcwalk validate_chunk
0x80a13c88: Chunk has bad destructor pointer 0x41414141
Ten en cuenta que si null_context no se ha establecido, tienes que pasar una dirección de chunk como segundo argumento.
Escrito por Aaron Adams
Email: aaron (dot) adams (at) nccgroup (dot) trust
Twitter: @fidgetingbits