Ubuntu 18.04 Bionic (and other OSes) have switched to randomizing the address space layout, which breaks simple approaches for symbol resolution. From https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes#Security_Improvements:
In Ubuntu 18.04 LTS, gcc is now set to default to compile applications as position independent executables (PIE) as well as with immediate binding, to make more effective use of Address Space Layout Randomization (ASLR).
The bpftrace uaddr() call needs to work on both normal executables, as well as PIE executables (gcc -pie -fpie). Here's how to tell the difference:
# file uaddr-old uaddr-pie
uaddr-old: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1babcdea1d0220ae6982428da4e7e4c665c587d7, not stripped
uaddr-pie: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=01419c4c8cddc834734c552097af169b83b7d77e, not stripped
From the above output, uaddr-old is an "executable", whereas uaddr-pie is a "shared object".
You can also see this in the address space of a running process:
# pmap -x `pgrep -n uaddr-old` | head
30157: ./uaddr-old
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- uaddr-old
0000000000400000 0 0 0 r-x-- uaddr-old
0000000000600000 4 4 4 r---- uaddr-old
0000000000600000 0 0 0 r---- uaddr-old
0000000000601000 4 4 4 rw--- uaddr-old
0000000000601000 0 0 0 rw--- uaddr-old
00007ff992637000 1792 804 0 r-x-- libc-2.23.so
00007ff992637000 0 0 0 r-x-- libc-2.23.so
# pmap -x `pgrep -n uaddr-pie` | head
30158: ./uaddr-pie
Address Kbytes RSS Dirty Mode Mapping
0000561202e7b000 4 4 0 r-x-- uaddr-pie
0000561202e7b000 0 0 0 r-x-- uaddr-pie
000056120307b000 4 4 4 r---- uaddr-pie
000056120307b000 0 0 0 r---- uaddr-pie
000056120307c000 4 4 4 rw--- uaddr-pie
000056120307c000 0 0 0 rw--- uaddr-pie
00007feec176e000 1792 776 0 r-x-- libc-2.23.so
00007feec176e000 0 0 0 r-x-- libc-2.23.so
Which means techniques like objdump no longer work:
# objdump -tT uaddr-old | grep my
0000000000400500 l F .text 0000000000000000 frame_dummy
0000000000600e10 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000400526 g F .text 0000000000000011 mysleep
0000000000601038 g O .data 0000000000000008 mystring
# objdump -tT uaddr-pie | grep my
0000000000000740 l F .text 0000000000000000 frame_dummy
0000000000200de0 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000000770 g F .text 0000000000000011 mysleep
0000000000201038 g O .data 0000000000000008 mystring
However:
# bpftrace -e 'uprobe:/root/uaddr-old:mysleep { printf("hit at %llx\n", reg("ip")); }'
Attaching 1 probe...
hit at 400526
hit at 400526
^C
# bpftrace -e 'uprobe:/root/uaddr-pie:mysleep { printf("hit at %llx\n", reg("ip")); }'
Attaching 1 probe...
hit at 561202e7b770
hit at 561202e7b770
^C
uprobe already works for both!
uprobe uses bcc_resolve_symname() to get the offset. Maybe we can do the same here, since it seems to already deal with PIE.
Ubuntu 18.04 Bionic (and other OSes) have switched to randomizing the address space layout, which breaks simple approaches for symbol resolution. From https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes#Security_Improvements:
The bpftrace uaddr() call needs to work on both normal executables, as well as PIE executables (gcc -pie -fpie). Here's how to tell the difference:
From the above output, uaddr-old is an "executable", whereas uaddr-pie is a "shared object".
You can also see this in the address space of a running process:
Which means techniques like
objdumpno longer work:However:
uprobe already works for both!
uprobe uses bcc_resolve_symname() to get the offset. Maybe we can do the same here, since it seems to already deal with PIE.