diff --git a/GetRuntimeAddresses/ebpf/ebpf.go b/GetRuntimeAddresses/ebpf/ebpf.go index 9e480bb..b513de7 100644 --- a/GetRuntimeAddresses/ebpf/ebpf.go +++ b/GetRuntimeAddresses/ebpf/ebpf.go @@ -49,6 +49,8 @@ int bpf_lookup_elem(int fd, const void *key, void *value) return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); } +// TODO: add insert + */ import "C" import ( diff --git a/GetRuntimeAddresses/main.go b/GetRuntimeAddresses/main.go index c5e13a0..ecd0d43 100644 --- a/GetRuntimeAddresses/main.go +++ b/GetRuntimeAddresses/main.go @@ -1,8 +1,13 @@ package main import ( + "bufio" + "debug/elf" "fmt" "os" + "path/filepath" + "strconv" + "strings" "github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/ebpf" ) @@ -20,6 +25,41 @@ func main() { // Solution to your tasks goes here // + proc, err := os.Open("/proc") + if err != nil { + fmt.Printf("Failed to open /proc: %v\n", err) + os.Exit(1) + } + + infos, err := proc.Readdir(-1) + if err != nil { + fmt.Printf("Failed to read /proc: %v\n", err) + os.Exit(1) + } + + proc.Close() + + var ( + ownpid = os.Getpid() + ) + + for _, pinfo := range infos { + var pid_s = pinfo.Name() + + // The entry /proc/NNN/ must be a directory with integer name + if !pinfo.IsDir() { + continue + } else if pid, err := strconv.Atoi(pid_s); err != nil { + continue + } else if pid == ownpid { // skip our own pid + continue + } else if offset, found := searchSymbolIn(pid_s, "*python3*", "_PyRuntime"); !found { + continue + } else { + fmt.Printf("going to set (%d -> 0x%x)\n\n", pid, offset) + } + } + mapContents, err := ebpf.GetMap(mapFD) if err != nil { fmt.Printf("Failed to get the map contents: %s", err) @@ -32,3 +72,150 @@ func main() { } os.Exit(0) } + +type region struct { + start uint64 // could be uintptr + end uint64 +} + +func parseRegion(in string) (r region, e error) { + parts := strings.Split(in, "-") + + if len(parts) != 2 { + e = fmt.Errorf("[parseRegion] unrecognized format for region: %#q", in) + return + } + + r.start, e = strconv.ParseUint(parts[0], 16, 64) + if e != nil { + e = fmt.Errorf("[parseRegion] couldn't parse start-address %#q in %#q: %w", parts[0], in, e) + return + } + + r.end, e = strconv.ParseUint(parts[1], 16, 64) + if e != nil { + e = fmt.Errorf("[parseRegion] couldn't parse end-address %#q in %#q: %w", parts[1], in, e) + return + } + + return +} + +func searchSymbolIn(pid, glob, symbol string) (offset uint64, ok bool) { + // read the maps file for the binary and shared libraries + path := filepath.Join("/proc", pid, "maps") + maps, err := os.Open(path) + if err != nil { + // fmt.Printf("Warning: Failed to read %#q: %v\n", path, err) + return + } + + scanner := bufio.NewScanner(maps) + for scanner.Scan() { + // address perms offset dev inode pathname + // 7fdd8fece000-7fdd8ff74000 rw-p 00423000 fd:01 14156759 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 + + fields := strings.Fields(scanner.Text()) + + // TODO: we assume that the pathname contains no spaces so + // bytes.Fields splits the line excactly into six fields + + if len(fields) != 6 { + continue + } + + pathname := fields[5] + + if !strings.HasPrefix(pathname, "/") { // Not a pathname + continue + } + + filename := filepath.Base(pathname) + ok, err := filepath.Match(glob, filename) + if err != nil || !ok { + continue + } + + if fields[1] != "rw-p" { // symbol needs to be writable + continue + } + + sym, section, err := findSymbol("_PyRuntime", pathname) + if err != nil || section == nil || sym == nil { + // TODO: error handling + // fmt.Printf("Warning: while reading mapped file %q: %w", pathname, err) + continue + } + + arange, err := parseRegion(fields[0]) + if err != nil { + fmt.Printf("%w\n", err) + continue + } + + fileoffset, err := strconv.ParseUint(fields[2], 16, 64) + if err != nil { + fmt.Printf("Error while parsing fileoffset %#q: %w\n", fields[2], err) + continue + } + + memoff := sym.Value - section.Addr + alignedOffset(section) + + fmt.Printf("pid: %s\nsym: %#v\nsection: %#v\nmemoff: 0x%x\narange: %#v\nmap-fileoffset: 0x%x\npathname: %s\n", pid, sym, section, memoff, arange, fileoffset, pathname) + + // stop when only _one_ is found + return arange.start + memoff - fileoffset, true + + } + + return 0, false +} + +func findSymbol(symbol string, pathname string) (*elf.Symbol, *elf.SectionHeader, error) { + // TODO: caching + + var ( + sym *elf.Symbol + section *elf.Section + header *elf.SectionHeader + ) + + file, err := elf.Open(pathname) + if err != nil { + return nil, nil, err + } + + symbols, err := file.DynamicSymbols() + if err != nil { + return nil, nil, err + } + + for _, s := range symbols { + if s.Name == symbol { + sym = &s + break + } + } + + if sym == nil { + return nil, nil, nil + } + + if len(file.Sections) < int(sym.Section) { + return nil, nil, nil + } + + section = file.Sections[sym.Section] + if section == nil { + return nil, nil, nil + } + + header = §ion.SectionHeader + + return sym, header, nil +} + +func alignedOffset(section *elf.SectionHeader) uint64 { + mask := section.Addralign - 1 + return (section.Offset + mask) & (^mask) +} diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..dd787a5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,44 @@ +# TODO + +From map proc(5): + +``` + /proc/[pid]/maps + A file containing the currently mapped memory regions and their access permissions. See mmap(2) for some further information about memory + mappings. + + Permission to access this file is governed by a ptrace access mode PTRACE_MODE_READ_FSCREDS check; see ptrace(2). + + The format of the file is: + + address perms offset dev inode pathname + 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon + 00651000-00652000 r--p 00051000 08:02 173521 /usr/bin/dbus-daemon + 00652000-00655000 rw-p 00052000 08:02 173521 /usr/bin/dbus-daemon + 00e03000-00e24000 rw-p 00000000 00:00 0 [heap] + 00e24000-011f7000 rw-p 00000000 00:00 0 [heap] + ... + 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so + 35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so + 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so + 35b1a21000-35b1a22000 rw-p 00000000 00:00 0 + 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so + 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + 35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so + ... + f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0 [stack:986] + ... + 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [stack] + 7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0 [vdso] + +``` + +1. read file(s) as elf +2. extract offset(s) of symbol(s) +3. find procs with mappings of those files +4. check if symbol-offset falls into any (address-range, offset) of such a mapping +5. if so, populate the ebpf map + +Optional: +6. trigger, if file is read/mapped? diff --git a/scratchpad.txt b/scratchpad.txt new file mode 100644 index 0000000..fabd4e2 --- /dev/null +++ b/scratchpad.txt @@ -0,0 +1,69 @@ +gdb sagt: 0x8de5c0 + +DYNAMIC 0x0000000000437dc0 + + + +1464: 00000000008de5c0 1520 OBJECT GLOBAL DEFAULT 24 _PyRuntime + + +no. 1464 -> 1464*24 = 35136 = 0x 8940 + + + + + +Programm-Header: + Typ Offset VirtAdr PhysAdr + DateiGr SpeiGr Flags Ausr. + PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 + 0x0000000000000268 0x0000000000000268 R 0x8 + INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8 + 0x000000000000001c 0x000000000000001c R 0x1 + LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 + 0x0000000000020ad0 0x0000000000020ad0 R 0x1000 + LOAD 0x0000000000021000 0x0000000000421000 0x0000000000421000 + 0x0000000000258515 0x0000000000258515 R E 0x1000 + LOAD 0x000000000027a000 0x000000000067a000 0x000000000067a000 + 0x00000000001bd120 0x00000000001bd120 R 0x1000 + LOAD 0x0000000000437db0 0x0000000000838db0 0x0000000000838db0 + 0x00000000000a56a0 0x00000000000c8c70 RW 0x1000 + DYNAMIC 0x0000000000437dc0 0x0000000000838dc0 0x0000000000838dc0 + 0x0000000000000230 0x0000000000000230 RW 0x8 + + + + 1464: 00000000008de5c0 1520 OBJECT GLOBAL DEFAULT 24 _PyRuntime + + +00000000008de5c0 - 0x0000000000838dc0 = A5800 + +4DE5C0 + + + + + +018880 + + +00000000004c9b20 + + +8De5C0 + +838DC0 + +A4800 + + +gdb sagt 0x8de5c0 zu python3.7 + +gdb sagt 7FDD8FF73B20 zu /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 + +7FDD8FAAA000-7FDD8FB16000 r--p 00000000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 +7FDD8FB16000-7FDD8FD2B000 r-xp 0006c000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 +7FDD8FD2B000-7FDD8FEC9000 r--p 00281000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 +7FDD8FEC9000-7FDD8FECA000 ---p 0041f000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 +7FDD8FECA000-7FDD8FECE000 r--p 0041f000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 +7FDD8FECE000-7FDD8FF74000 rw-p 00423000 /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0