
libtalloc is a python script for use with GDB that can be used to analyse the "trivial allocator" (talloc)
libtalloc est un script Python destiné à GDB, qui peut être utilisé pour analyser l'« allocateur trivial » (talloc). Une introduction à talloc est disponible ici :
https://talloc.samba.org/talloc/doc/html/index.html
libtalloc s'inspire d'autres scripts Python pour gdb analysant les tas, comme unmask_jemalloc et libheap. Certaines fonctionnalités de base sont identiques à celles de ces projets.
https://github.com/cloudburst/libheap
https://github.com/argp/unmask_jemalloc
Veuillez noter que je ne suis pas un gourou de Python et que la qualité du code en témoigne. Si vous voyez quelque chose qui vous déplaît, n'hésitez pas à envoyer un correctif ou à me faire des suggestions. Tous les retours sont les bienvenus.
libtalloc a été testé sur plusieurs versions 2.x de talloc et prend en charge la détection dynamique de version afin de tenter de pallier les différentes différences structurelles entre les versions. Il a été testé en 32 bits et en 64 bits, mais pas de manière exhaustive, alors ne soyez pas surpris s'il casse de temps en temps.
Il a été testé dans une certaine mesure sur x86 et x64 :
Si vous le testez avec une autre version, merci de me dire si cela a fonctionné, ou ce qui a cassé, et j'essaierai de le mettre à jour et/ou de mettre à jour la documentation en conséquence.
Le script nécessite simplement une version relativement récente de GDB avec le support de Python.
Certaines distributions LTS, comme Ubuntu 12.04, utilisent encore GDB avec Python 2.7, tandis que les versions plus récentes comme 14.04 utilisent Python 3.0. J'ai essayé de faire fonctionner ce script avec les deux, vous devriez donc simplement avoir besoin de :
(gdb) source libtalloc.py
La plupart des fonctionnalités sont calquées sur l'approche d'unmask_jemalloc, où une commande GDB distincte est fournie plutôt qu'un ensemble complexe d'options.
Un certain nombre de méthodes conçues spécifiquement pour imiter les fonctions C de la bibliothèque talloc sont disponibles, afin d'aider ceux qui cherchent à étendre libtalloc s'ils sont déjà familiers avec la bibliothèque.
Pour voir la liste complète des commandes, vous pouvez lancer la commande 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
L'une des commandes les plus importantes est tcprobe. Elle doit être exécutée afin de déterminer quelle version de talloc est réellement installée. La disposition des structures peut varier considérablement selon les versions ; par conséquent, pour que la plupart des fonctions fonctionnent, la version doit être connue.
Si la commande fonctionne, elle devrait vous indiquer la version détectée :
(gdb) tcprobe
Version: 2.1.1
File: /usr/lib/libtalloc.so.2.1.1
La commande tcinfo est destinée à afficher autant d'informations que possible collectées sur le tas, comme les informations de tcprobe, la structure null_context si elle a été trouvée, et plus encore. Pour l'instant, elle n'affiche que la version et si null_context est défini. null_context est requis pour la plupart des fonctions qui parcourent réellement la hiérarchie, et pour le trouver, la plupart des autres fonctionnalités, comme tchunk, etc., tenteront de le localiser automatiquement.
Après avoir exécuté tcprobe mais avant d'utiliser réellement tchunk :
(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
Puis après avoir analysé un chunk, comme ceci :
(gdb) tcchunk 0xb94a52b0 WARNING: 0xb94a52b0 not a talloc_chunk. Assuming ptr to chunk data 0xb94a5280 sz:0x0000003c, flags:...., name:struct tevent_context
Vous pouvez confirmer qu'il a été trouvé après coup en utilisant tcinfo.
(gdb) tcinfo [libtalloc] null_context: 0xb94a5028 [libtalloc] Version: 2.0.7 [libtalloc] File: /usr/lib/i386-linux-gnu/libtalloc.so.2.0.7
Maintenant que null_context est défini, vous pouvez exécuter d'autres commandes qui se plaindraient normalement qu'il n'est pas défini, comme la commande tcsearch.
tcchunk peut vous fournir un résumé du chunk, une sortie plus verbeuse de chaque champ, ou des informations extrêmement verbeuses sur chaque chunk environnant.
NOTE : Un point important à noter à propos de tcchunk est qu'il utilise en interne la méthode tc_chunk(), qui tente de corriger les erreurs commises lors de la transmission de l'adresse du chunk. Plus précisément, si vous transmettez l'adresse des données du chunk lui-même et qu'il ne trouve pas la valeur magique talloc attendue, il recherchera un en-tête de chunk légitime un peu plus tôt en mémoire. Cela peut vous jouer des tours dans les scénarios de corruption, alors assurez-vous toujours de transmettre l'adresse explicite, sauf si vous effectuez une analyse superficielle.
Sortie du résumé :
(gdb) tcchunk 0x80a13c88
0x80a13c88 sz:0x00000020, flags:..p., name:struct netr_ServerPasswordSet
Voici une légende pour les chunks dans la sortie du résumé :
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)
Sortie verbeuse :
(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
Les chunks talloc contiennent des valeurs magiques qui peuvent être utilisées pour valider leur intégrité. La commande tcvalidate analyse un chunk pour s'assurer que la valeur magique du chunk est celle attendue. De plus, elle analyse tous les autres pointeurs membres pour s'assurer qu'ils se trouvent bien dans des plages mémoire (telles que connues par gdb), que la taille est valide, etc.
(gdb) tcvalidate 0x80a13c88
Chunk header is valid
Nous allons utiliser une méthode intégrée pour modifier une valeur afin de montrer comment cela pourrait échouer :
(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 peut être utilisé pour afficher tous les parents du chunk fourni :
(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 peut être utilisé pour afficher tous les enfants (et petits-enfants, etc.) du chunk fourni :
(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 a le concept de chunks de pool (pool chunks). Ce sont essentiellement des chunks talloc classiques, mais qui sont utilisés pour allouer de nouveaux chunks plutôt que de recourir à l'implémentation malloc() sous-jacente du système. Un chunk de pool a des en-têtes légèrement différents selon la version utilisée, utilisant parfois du padding, et parfois un en-tête de préfixe/suffixe.
tcpool peut être utilisé pour analyser l'en-tête d'un chunk de pool, de la même manière que 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
Dans le cas ci-dessus, le pool avait un talloc_pool_hdr préfixé, comme indiqué. L'option -l peut être passée à tcpool pour lister tous les chunks alloués dans 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
Notez le drapeau P dans la sortie ci-dessus, le chunk du haut étant le chunk de pool qui contient tous les chunks en dessous.
tcdump peut être utilisé pour vider tous les chunks de l'arbre entier. Par défaut, ils sont affichés dans un ordre hiérarchique, mais l'option -s peut être utilisée pour trier la sortie par adresse.
(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 est une commande similaire à tcdump mais elle embellit quelque peu la sortie et est destinée à imiter la fonction de débogage talloc_report_full() fournie par la bibliothèque talloc elle-même.
(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]
Il existe deux commandes pour la recherche : tcsearch et tcfindaddr.
tcsearch peut être utilisé pour trouver les chunks qui contiennent la valeur hexadécimale fournie. Elle fonctionne en parcourant toute la hiérarchie de l'arbre à partir de null_context (s'il est connu), ou à partir d'un chunk de départ fourni.
(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 peut être utilisé pour déterminer si une adresse se trouve dans les limites d'un chunk de l'arbre talloc. Supposons que vous sachiez que 0x80a1d3280 contient des données que vous contrôlez et que vous souhaitiez voir si cela se trouve dans un chunk. Notez que la seconde adresse est le null_context, mais peut être n'importe quel chunk qui permet de trouver le sommet du tas.
(gdb) tcfindaddr 0x80a1d328 0x809f8300
[libtalloc] address 0x80a1d328 falls within chunk @ 0x80a1d218 (size 0x80050)
Une partie de la recherche dans l'arbre est effectuée à l'aide d'une fonction récursive que j'ai exposée via la commande tcwalk. C'est une fonction d'aide qui vous permet de spécifier une méthode Python qui sera appelée sur chaque chunk découvert dans l'arbre.
Dans l'exemple ci-dessous, nous allons appeler la méthode de validation du tas sur chaque chunk du tas pour voir si quelque chose est corrompu.
(gdb) tcwalk validate_chunk
0x80a13c88: Chunk has bad destructor pointer 0x41414141
Notez que si null_context n'a pas été défini, vous devez passer une adresse de chunk comme second argument.
Écrit par Aaron Adams
Email : aaron (dot) adams (at) nccgroup (dot) trust
Twitter : @fidgetingbits