Gehaxelts Blog

IT-Security & Hacking

Programme mit strings, ltrace & strace untersuchen

Ich möchte einen kurzen Blogpost darüber loswerden, wie man unter Linux Programme untersucht. Dazu möchte ich die Tools "strings", "ltrace", "strace" und "ldd" vorstellen.

strings

Das Tool "strings" gibt es im Paket "binutils", welches soweit vorinstalliert sein sollte. Mit diesem Tool kann man sich alle Zeichenketten einer Datei ausgeben lassen. Das kann nützlich sein, wenn man in einer Binary nach Zeichenketten sucht, z.B. C&C-Server eines Trojaners.

strings /usr/bin/ls

Möchte man sich noch die Offsets der Strings anzeigen lassen, so kann man die Option "-t" nutzen. Diese unterscheidet zwischen drei Ausgabeformaten: o für octal, x für hexadecimal, d für decimal.

strings -t x /etc/passwd

strace & ltrace

Möchte ein Programm nicht das machen was es soll oder hängt es sich, ohne Ausgabe auf dem stderr Kanal, auf, dann kann man strace bzw. ltrace nutzen, um ggf. die Ursache herauszufinden. Dabei führen diese Tools das übergeben Programm aus und protokollieren die entsprechenden Aufrufe.

Die beiden Tools kann man in den gleichnamigen Paketen nachinstallieren:

sudo apt-get install ltrace strace

strace

Strace protokolliert alle Systemaufrufe des ausgeführten Programms. Man erkennt dann z.B. welche Dateien ausgeführt, geöffnet, geschlossen usw. werden und welche Rückgabewerte erzeugt werden.

Sieht man z.B. bei einem "open" einen negativen Rückgabewert (-1), und das Programm bricht daraufhin ab, kann das darauf hindeuten, dass die im Aufruf angegeben Datei nicht gefunden werden konnte.

Hier ein Strace von "mkdir testing":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
gehaxelt@LagTop [09:50:09] [~/temp] [master *]
-> % strace mkdir testing
execve("/usr/bin/mkdir", ["mkdir", "testing"], [/* 51 vars */]) = 0
brk(0)                                  = 0x1e68000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=195863, ...}) = 0
mmap(NULL, 195863, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7311aa6000
close(3)                                = 0
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \33\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2035539, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7311aa5000
mmap(NULL, 3853456, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7311508000
mprotect(0x7f73116ac000, 2093056, PROT_NONE) = 0
mmap(0x7f73118ab000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a3000) = 0x7f73118ab000
mmap(0x7f73118b1000, 15504, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f73118b1000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7311aa4000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7311aa3000
arch_prctl(ARCH_SET_FS, 0x7f7311aa4700) = 0
mprotect(0x7f73118ab000, 16384, PROT_READ) = 0
mprotect(0x60b000, 4096, PROT_READ)     = 0
mprotect(0x7f7311ad6000, 4096, PROT_READ) = 0
munmap(0x7f7311aa6000, 195863)          = 0
brk(0)                                  = 0x1e68000
brk(0x1e89000)                          = 0x1e89000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2098672, ...}) = 0
mmap(NULL, 2098672, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7311307000
close(3)                                = 0
mkdir("testing", 0777)                  = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Man kann sich auch eine kurze Statistik zu den ausgeführten Systemaufrufen anzeigen lassen. Dazu ruft man strace mit der Option "-c" auf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
gehaxelt@LagTop [09:58:04] [~/temp] [master *]
-> % strace -c mkdir testing
mkdir: das Verzeichnis „testing“ kann nicht angelegt werden: Die Datei existiert bereits
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  -nan    0.000000           0        18           read
  -nan    0.000000           0         4           write
  -nan    0.000000           0        18        11 open
  -nan    0.000000           0         9           close
  -nan    0.000000           0         7           fstat
  -nan    0.000000           0        12           mmap
  -nan    0.000000           0         4           mprotect
  -nan    0.000000           0         3           munmap
  -nan    0.000000           0         4           brk
  -nan    0.000000           0         1         1 access
  -nan    0.000000           0         1           execve
  -nan    0.000000           0         1         1 mkdir
  -nan    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    83        13 total

ltrace

Im Gegensatz zu strace protokolliert ltrace nur die Aufrufe von externen Bibliotheken (librarys).

Beispiel mit rm -r testing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
gehaxelt@LagTop [10:04:36] [~/temp] [master *]
-> % ltrace rm -r testing
__libc_start_main(0x401c10, 3, 0x7fff4010fa18, 0x409b70 <unfinished ...>
strrchr("rm", '/')                                                                                = nil
setlocale(LC_ALL, "")                                                                             = "LC_CTYPE=de_DE.UTF-8;LC_NUMERIC="...
bindtextdomain("coreutils", "/usr/share/locale")                                                  = "/usr/share/locale"
textdomain("coreutils")                                                                           = "coreutils"
__cxa_atexit(0x4035f0, 0, 0, 0x736c6974756572)                                                    = 0
isatty(0)                                                                                         = 1
getopt_long(3, 0x7fff4010fa18, "dfirvIR", 0x40a6a0, nil)                                          = 114
getopt_long(3, 0x7fff4010fa18, "dfirvIR", 0x40a6a0, nil)                                          = -1
__lxstat(1, "/", 0x7fff4010f7a0)                                                                  = 0
malloc(128)                                                                                       = 0xa74110
strlen("testing")                                                                                 = 7
realloc(0, 4352)                                                                                  = 0xa741a0
malloc(272)                                                                                       = 0xa752b0
memcpy(0xa753b8, "", 0)                                                                           = 0xa753b8
strlen("testing")                                                                                 = 7
malloc(279)                                                                                       = 0xa753d0
memcpy(0xa754d8, "testing", 7)                                                                    = 0xa754d8
__fxstatat(1, 0xffffff9c, 0xa754d8, 0xa75448)                                                     = 0
malloc(272)                                                                                       = 0xa754f0
memcpy(0xa755f8, "", 0)                                                                           = 0xa755f8
malloc(32)                                                                                        = 0xa75610
free(0xa754f0)                                                                                    = <void>
free(0xa75610)                                                                                    = <void>
memmove(0xa741a0, "testing\0", 8)                                                                 = 0xa741a0
strrchr("testing", '/')                                                                           = nil
malloc(32)                                                                                        = 0xa75610
openat(0xffffff9c, 0xa741a0, 0x30900, 0x7fff4010f860)                                             = 3
fdopendir(3, 0xa741a0, 0x30900, -1)                                                               = 0xa75640
__errno_location()                                                                                = 0x7f74035886a8
readdir(0xa75640)                                                                                 = 0xa75668
readdir(0xa75640)                                                                                 = 0xa75680
readdir(0xa75640)                                                                                 = 0
closedir(0xa75640)                                                                                = 0
geteuid()                                                                                         = 1000
__fxstatat(1, 0xffffff9c, 0xa741a0, 0x7fff4010f700)                                               = 0
faccessat(0xffffff9c, 0xa741a0, 2, 512)                                                           = 0
unlinkat(0xffffff9c, 0xa741a0, 512, 1)                                                            = 0
free(0xa753d0)                                                                                    = <void>
free(0xa752b0)                                                                                    = <void>
__errno_location()                                                                                = 0x7f74035886a8
__errno_location()                                                                                = 0x7f74035886a8
free(0)                                                                                           = <void>
free(0xa741a0)                                                                                    = <void>
free(0xa75610)                                                                                    = <void>
free(0xa74110)                                                                                    = <void>
exit(0 <unfinished ...>
__fpending(0x7f7403394240, 0, 0, 4)                                                               = 0
fileno(0x7f7403394240)                                                                            = 0
__freading(0x7f7403394240, 0, 0, 4)                                                               = 1
fileno(0x7f7403394240)                                                                            = 0
lseek(0, 0, 1)                                                                                    = -1
fclose(0x7f7403394240)                                                                            = 0
__fpending(0x7f7403394160, 0, 0x7f7403395800, 0xfbad000c)                                         = 0
fileno(0x7f7403394160)                                                                            = 1
__freading(0x7f7403394160, 0, 0x7f7403395800, 0xfbad000c)                                         = 0
__freading(0x7f7403394160, 0, 4, 0xfbad000c)                                                      = 0
fflush(0x7f7403394160)                                                                            = 0
fclose(0x7f7403394160)                                                                            = 0
__fpending(0x7f7403394080, 0, 0x7f7403395800, 0xfbad000c)                                         = 0
fileno(0x7f7403394080)                                                                            = 2
__freading(0x7f7403394080, 0, 0x7f7403395800, 0xfbad000c)                                         = 0
__freading(0x7f7403394080, 0, 4, 0xfbad000c)                                                      = 0
fflush(0x7f7403394080)                                                                            = 0
fclose(0x7f7403394080)                                                                            = 0
+++ exited (status 0) +++

In der Ausgabe sieht man die Librarycalls und deren Rückgabewerte. Das kann ebenfalls für Debugging-/Analysezwecke hilfreich sein.

Nützlich kann auch die Fähigkeit, an laufende Prozesse anzudocken, sein. Dazu muss man die Option "-p" gefolgt von einer ProzessID aufrufen:

ltrace -p $(pgrep vlc)

Mit "pgrep" kann man sich die ProzessID eines Prozesses mit dem als Parameter übergebenen Namen zurückgeben lassen.

Ldd

Auf den Hinweis von @robertmarquardt möchte ich den Artikel um das Programm "ldd" erweitern.

Mit dem Programm kann man sich die die Abhängigkeit eines Programms von externen Bibliotheken anzeigen.

Beispiel:

1
2
3
4
5
6
7
8
9
10
gehaxelt@LagTop [10:32:11] [~] 
-> % ldd /usr/bin/ls
  linux-vdso.so.1 (0x00007fffc4bff000)
  librt.so.1 => /usr/lib/librt.so.1 (0x00007f237c63b000)
  libcap.so.2 => /usr/lib/libcap.so.2 (0x00007f237c437000)
  libacl.so.1 => /usr/lib/libacl.so.1 (0x00007f237c22e000)
  libc.so.6 => /usr/lib/libc.so.6 (0x00007f237be81000)
  libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f237bc65000)
  libattr.so.1 => /usr/lib/libattr.so.1 (0x00007f237ba60000)
  /lib/ld-linux-x86-64.so.2 (0x00007f237c843000)

Falls eine benötigte Bibliothek fehlt, dann kann man diese so ausfindig machen.

Danke hier nochmal für den Hinweis.

Fazit

Falls mal ein Programm nicht so will, wie es soll, bieten strace & ltrace erste Anlaufstellen, um nach der Ursache des Problems zu suchen.

Gruß

gehaxelt