【问题标题】:Trouble with getting syscalls with own python debugger使用自己的 python 调试器获取系统调用的问题
【发布时间】:2020-06-11 19:34:58
【问题描述】:

我试图在 32 位测试 Linux 系统 (Lubuntu) 上用 python3 编写一个简单的调试器,它应该能够捕获任意程序的所有系统调用(在这种情况下:/bin/ls)。为此,我使用 ptrace 系统调用来单步执行该过程。在每一步之后,我都会读取寄存器以找到指令指针 eip 以从下一条指令中读取 2 个字节。如果这 2 个字节是 0xcd0x80 这表示 int 80 是系统调用。我知道还有用于此目的的 PTRACE_SYSCALL,但我想在不使用它的情况下执行此操作。

在下面我向您展示代码,它似乎可以工作,但是有一些奇怪的行为:

为了确定这是否有效,我使用 strace 将其输出与我自己的系统调用进行比较。而且似乎我的程序只显示了系统调用的第一部分,第二部分只是丢失了。为了向您展示我在下面发布了我的程序和 strace 的输出。有人知道这里可能出了什么问题吗?

import os               # os interaction
from struct import pack # dealing with bytes (ptrace)
import ctypes           # support c data structures

""" ========================================================== """

# 32 bit reg process structrue
class UserRegsStruct(ctypes.Structure):
    _fields_ = [
        ("ebx", ctypes.c_ulong),
        ("ecx", ctypes.c_ulong),
        ("edx", ctypes.c_ulong),
        ("esi", ctypes.c_ulong),
        ("edi", ctypes.c_ulong),
        ("ebp", ctypes.c_ulong),
        ("eax", ctypes.c_ulong),
        ("xds", ctypes.c_ulong),
        ("xes", ctypes.c_ulong),
        ("xfs", ctypes.c_ulong),
        ("xgs", ctypes.c_ulong),
        ("orig_eax", ctypes.c_ulong),
        ("eip", ctypes.c_ulong),
        ("xcs", ctypes.c_ulong),
        ("eflags", ctypes.c_ulong),
        ("esp", ctypes.c_ulong),
        ("xss", ctypes.c_ulong),
    ]

# ptrace constants
PTRACE_TRACEME = 0
PTRACE_PEEKDATA = 2
PTRACE_SINGLESTEP = 9
PTRACE_GETREGS = 12

CPU_WORD_SIZE = 4   # size of cpu word size (32 bit = 4 bytes)

# for syscalls
libc = ctypes.CDLL('libc.so.6')

# check if child (tracee) is still running
def WIFSTOPPED(status):
    return (status & 0xff) == 0x7f

# read from process memory by PTRACE_PEEKDATA
def ReadProcessMemory(pid, address, size):

    # address must be aligned!!
    offset = address % CPU_WORD_SIZE
    if offset:
        address -= offset
        word = libc.ptrace(PTRACE_PEEKDATA, pid, address, 0)
        wordbytes = pack("i", word)
        subsize = min(CPU_WORD_SIZE - offset, size)
        data = wordbytes[offset:offset + subsize]
        size -= subsize
        address += CPU_WORD_SIZE
    else:
        data = bytes(0)

    while size:
        word = libc.ptrace(PTRACE_PEEKDATA, pid, address, 0)
        wordbytes = pack("i", word)
        if size < CPU_WORD_SIZE:
            data += wordbytes[:size]
            break
        data += wordbytes
        size -= CPU_WORD_SIZE
        address += CPU_WORD_SIZE

    return data

""" ========================================================== """

# extract syscall names
fp = open("/usr/include/i386-linux-gnu/asm/unistd_32.h", "r")
syscalls = [0] * 400

for line in fp:
    if "__NR_" in line:
        a = line.rstrip().split(" ")
        name = a[1].split("NR_")[1]
        number = int(a[2])
        syscalls[number] = name

# "int 80" asm instruction = (0xCD 0x80)
a0 = 0xcd
a1 = 0x80

# create child tracee
pid = os.fork()

if pid == 0:    # in tracee
    libc.ptrace(PTRACE_TRACEME, 0, 0, 0)    # make child traceable
    os.execv("/bin/ls", [":-P"])            # run test programm
else:           # in tracer
    pid, status = os.waitpid(pid, 0)
    regs = UserRegsStruct()

# catch all syscalls
while True:

    libc.ptrace(PTRACE_SINGLESTEP, pid, 0, 0)               # execute next instruction
    pid, status = os.waitpid(pid, 0)                        # wait for tracee
    libc.ptrace(PTRACE_GETREGS, pid, 0, ctypes.byref(regs)) # get register values
    data = ReadProcessMemory(pid, regs.eip, 2)              # read 2 bytes from instruction pointer address

    # now check if this is a syscall
    if data[0] == a0 and data[1] == a1:
        print("HEUREKA! SYSCALL at " + hex(regs.eip) + ": " + syscalls[regs.eax])

    if WIFSTOPPED(status) == False: break # exit loop when tracee stopped

这产生了以下输出:

HEUREKA! SYSCALL at 0xb7fae2c5: brk
HEUREKA! SYSCALL at 0xb7fa3944: access
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf689: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faa758: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf57e: read
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faa758: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf57e: read
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faa758: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf57e: read
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faa758: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf57e: read
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faa758: access
HEUREKA! SYSCALL at 0xb7faf4b5: openat
HEUREKA! SYSCALL at 0xb7faf57e: read
HEUREKA! SYSCALL at 0xb7faf419: fstat64
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7faf755: close
HEUREKA! SYSCALL at 0xb7faf7ae: mmap2
HEUREKA! SYSCALL at 0xb7f95bd9: set_thread_area
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf822: mprotect
HEUREKA! SYSCALL at 0xb7faf7ff: munmap
test.py

这是 strace 的输出:

execve("/bin/ls", ["/bin/ls"], 0xbfef5e40 /* 45 vars */) = 0
brk(NULL)                               = 0x220c000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f00000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=89915, ...}) = 0
mmap2(NULL, 89915, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7eea000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0L\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=169960, ...}) = 0
mmap2(NULL, 179612, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7ebe000
mmap2(0xb7ee7000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0xb7ee7000
mmap2(0xb7ee9000, 3484, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7ee9000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\220\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1942840, ...}) = 0
mmap2(NULL, 1948188, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7ce2000
mprotect(0xb7eb7000, 4096, PROT_NONE)   = 0
mmap2(0xb7eb8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d5000) = 0xb7eb8000
mmap2(0xb7ebb000, 10780, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7ebb000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\16\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=480564, ...}) = 0
mmap2(NULL, 483512, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7c6b000
mmap2(0xb7ce0000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x74000) = 0xb7ce0000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\n\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=13796, ...}) = 0
mmap2(NULL, 16500, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7c66000
mmap2(0xb7c69000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0xb7c69000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300P\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=142820, ...}) = 0
mmap2(NULL, 123544, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7c47000
mmap2(0xb7c62000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a000) = 0xb7c62000
mmap2(0xb7c64000, 4760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7c64000
close(3)                                = 0
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7c45000
set_thread_area({entry_number=-1, base_addr=0xb7c45780, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=6)
mprotect(0xb7eb8000, 8192, PROT_READ)   = 0
mprotect(0xb7c62000, 4096, PROT_READ)   = 0
mprotect(0xb7c69000, 4096, PROT_READ)   = 0
mprotect(0xb7ce0000, 4096, PROT_READ)   = 0
mprotect(0xb7ee7000, 4096, PROT_READ)   = 0
mprotect(0x469000, 4096, PROT_READ)     = 0
mprotect(0xb7f2d000, 4096, PROT_READ)   = 0
munmap(0xb7eea000, 89915)               = 0

直到这里完全符合我自己的输出,但剩余的系统调用从未出现在我的程序中。这就是问题所在。我希望有人知道答案:P 如有任何问题,请追问!

set_tid_address(0xb7c457e8)             = 9767
set_robust_list(0xb7c457f0, 12)         = 0
rt_sigaction(SIGRTMIN, {sa_handler=0xb7c4baf0, sa_mask=[], sa_flags=SA_SIGINFO}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0xb7c4bb80, sa_mask=[], sa_flags=SA_RESTART|SA_SIGINFO}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
ugetrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM_INFINITY}) = 0
uname({sysname="Linux", nodename="p200300D053D7310F22107AFFFE01D58C", ...}) = 0
statfs("/sys/fs/selinux", 0xbffeddb4)   = -1 ENOENT (No such file or directory)
statfs("/selinux", 0xbffeddb4)          = -1 ENOENT (No such file or directory)
brk(NULL)                               = 0x220c000
brk(0x222d000)                          = 0x222d000
brk(0x222e000)                          = 0x222e000
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "nodev\tsysfs\nnodev\trootfs\nnodev\tr"..., 1024) = 401
read(3, "", 1024)                       = 0
close(3)                                = 0
brk(0x222d000)                          = 0x222d000
access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=3365136, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7a45000
close(3)                                = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=48, ws_col=198, ws_xpixel=0, ws_ypixel=0}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
fstat64(3, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
getdents64(3, /* 3 entries */, 32768)   = 80
getdents64(3, /* 0 entries */, 32768)   = 0
close(3)                                = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
write(1, "test.py\n", 8test.py
)                = 8
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++


【问题讨论】:

    标签: python linux x86 system-calls ptrace


    【解决方案1】:

    如果您在寻找int 0x80,您将错过使用sysenter 指令进行的正常32 位系统调用(通常通过glibc 调用VDSO 页面)。 https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/。 (同样在旧的 AMD CPU 上,也可以使用 32 位 syscall,如果它们太旧而无法支持 sysenter,则可能会默认使用。)

    我猜早期的 ld.so 代码使用旧的 int 0x80 机制而不是调用 VDSO。 (这是有道理的;VDSO 将自己呈现为一个映射到内存中的 ELF 共享对象;在动态链接器将函数指针设置到其中之前,它不能使用它。)

    64 位模式更简单:64 位 ABI 使用 syscall


    请注意,在指令执行之前或之后检查机器代码可能会被试图隐藏跟踪的代码所欺骗。第二个线程可能会在您查看机器代码字节后交叉修改它,在它执行之前。 (也许有一个线程存储一个标志,这将导致另一个线程在注意到时立即存储。如果时机合适,这可能会在您的 ptrace 获取和执行下一个单步之间潜入。)

    在 64 位模式下,strace 使用的 PTRACE_SYSCALL(或可能试图欺骗它的代码上的沙盒/系统调用日志记录或过滤工具)在现实生活中存在类似的竞争条件,试图计算确定是否调用了 32 位或 64 位 ABI(因为索书号不同)。 Can ptrace tell if an x86 system call used the 64-bit or 32-bit ABI?(或者是,直到 Linux 内核 5.3 添加了 PTRACE_GET_SYSCALL_INFO)。

    可以在 64 位代码中调用 int 0x80,尽管这基本上不是一个好主意:What is the explanation of this x86 Hello World using 32-bit int 0x80 Linux system calls from _start? 有一些关于系统调用的内核端发生的事情的详细信息.


    同样,如果您关心程序试图从您的跟踪器中混淆其活动,这只是一个问题,例如作为一种反调试措施。让另一个线程覆盖正在执行的代码不会偶然发生。但在设计调试/跟踪工具时需要注意这一点。

    如果将此代码用作库,有人可能会尝试从中构建沙盒系统调用过滤器,那么真正的危险就来了。例如检查所有文件访问系统调用中的路径,或拒绝未以只读方式打开的open 调用。然后逃避追踪成为一个真正的安全问题。 (当然,通常有更好的沙盒方法。)

    【讨论】:

      【解决方案2】:

      非常感谢!!你是对的!

      我现在稍微更新了代码。我使用 /proc/(pid)/auxv 文件通过键 AT_SYSINFO 获取 VDSO 中 sysenter 例程的地址。现在我可以通过将此地址与 eip 进行比较来检测非传统系统调用。其实很简单,又学了点东西;P

      这是我更新的代码:

      import os                       # os interaction
      from struct import pack, unpack # dealing with bytes (ptrace)
      import ctypes                   # support c data structures
      
      """ ========================================================== """
      
      # 32 bit reg process structrue
      class UserRegsStruct(ctypes.Structure):
          _fields_ = [
              ("ebx", ctypes.c_ulong),
              ("ecx", ctypes.c_ulong),
              ("edx", ctypes.c_ulong),
              ("esi", ctypes.c_ulong),
              ("edi", ctypes.c_ulong),
              ("ebp", ctypes.c_ulong),
              ("eax", ctypes.c_ulong),
              ("xds", ctypes.c_ulong),
              ("xes", ctypes.c_ulong),
              ("xfs", ctypes.c_ulong),
              ("xgs", ctypes.c_ulong),
              ("orig_eax", ctypes.c_ulong),
              ("eip", ctypes.c_ulong),
              ("xcs", ctypes.c_ulong),
              ("eflags", ctypes.c_ulong),
              ("esp", ctypes.c_ulong),
              ("xss", ctypes.c_ulong),
          ]
      
      # ptrace constants
      PTRACE_TRACEME = 0
      PTRACE_PEEKDATA = 2
      PTRACE_SINGLESTEP = 9
      PTRACE_GETREGS = 12
      
      
      AT_SYSINFO = 32     # for getting the syscall entry address by the auxv (/proc/(pid)/auxv
      
      CPU_WORD_SIZE = 4   # size of cpu word size (32 bit = 4 bytes)
      
      # for syscalls
      libc = ctypes.CDLL('libc.so.6')
      
      # check if child (tracee) is still running
      def WIFSTOPPED(status):
          return (status & 0xff) == 0x7f
      
      # read from process memory by PTRACE_PEEKDATA
      def ReadProcessMemory(pid, address, size):
      
          # address must be aligned!!
          offset = address % CPU_WORD_SIZE
          if offset:
              address -= offset
              word = libc.ptrace(PTRACE_PEEKDATA, pid, address, 0)
              wordbytes = pack("i", word)
              subsize = min(CPU_WORD_SIZE - offset, size)
              data = wordbytes[offset:offset + subsize]
              size -= subsize
              address += CPU_WORD_SIZE
          else:
              data = bytes(0)
      
          while size:
              word = libc.ptrace(PTRACE_PEEKDATA, pid, address, 0)
              wordbytes = pack("i", word)
              if size < CPU_WORD_SIZE:
                  data += wordbytes[:size]
                  break
              data += wordbytes
              size -= CPU_WORD_SIZE
              address += CPU_WORD_SIZE
      
          return data
      
      def GetSyscallEntry(pid):
          # find the syscall entry in vdso
          # read the auxv of the child process
          fd = open("/proc/" + str(pid) + "/auxv", "rb")
          while True:
              k = fd.read(4)
              v = fd.read(4)
              if not k or not v: break
              k = unpack('i', k)[0]
              v = unpack('i', v)[0]
              #print(str(k) + ":" + str(v))
              if k == AT_SYSINFO:
                  sc_entry = ctypes.c_ulong(v).value
                  #print("found syscall entry: " + hex(sc_entry))
                  return sc_entry
      
      """ ========================================================== """
      
      # extract syscall names
      fp = open("/usr/include/i386-linux-gnu/asm/unistd_32.h", "r")
      syscalls = [0] * 400
      
      for line in fp:
          if "__NR_" in line:
              a = line.rstrip().split(" ")
              name = a[1].split("NR_")[1]
              number = int(a[2])
              syscalls[number] = name
      
      # "int 80" asm instruction = (0xCD 0x80)
      a0 = 0xcd
      a1 = 0x80
      
      # create child tracee
      pid = os.fork()
      
      if pid == 0:    # in tracee
          libc.ptrace(PTRACE_TRACEME, 0, 0, 0)    # make child traceable
          os.execv("/bin/ls", [":-P"])            # run test programm
      else:           # in tracer
          pid, status = os.waitpid(pid, 0)
          regs = UserRegsStruct()
          sc_entry = GetSyscallEntry(pid)         # get the syscall entry address in child vdso space
          print("child pid: " + str(pid))
      
      # catch all syscalls
      while True:
      
          libc.ptrace(PTRACE_SINGLESTEP, pid, 0, 0)               # execute next instruction
          pid, status = os.waitpid(pid, 0)                        # wait for tracee
          libc.ptrace(PTRACE_GETREGS, pid, 0, ctypes.byref(regs)) # get register values
          data = ReadProcessMemory(pid, regs.eip, 2)              # read 2 bytes from instruction pointer address
      
          # now check if this is a syscall
          if data[0] == a0 and data[1] == a1:
              print("HEUREKA! SYSCALL (legacy) at " + hex(regs.eip) + ": " + syscalls[regs.eax])
      
          if regs.eip == sc_entry:
              print("HEUREKA! SYSCALL (sysenter) at " + hex(regs.eip) + ": " + syscalls[regs.eax])
      
          if WIFSTOPPED(status) == False: break # exit loop when tracee stopped
      

      【讨论】:

      • 理论上二进制文件可以手动使用sysenter,而不是调用VDSO。如果您不关心检测到这一点,请不要担心。
      • 所以我假设我可以通过再次检查指令代码来考虑这一点,就像已经使用 int 80 一样。sysenter 系统调用的代码是 0F 34。或者你知道另一种方式吗?跨度>
      • 我刚刚添加了这个功能,但现在我捕获了两次非传统系统调用。分析 eip 地址告诉我,在跳转到 vdso-space 之后,调用了 sysenter。所以在我看来,直接检测 sysenter 是更好的选择。
      • 是的,如果您已经有代码来检查机器代码,您可以使用它来检查 int 0x80、sysenter 或 syscall。 (AMD CPU 支持 32 位模式下的 syscall;如果可用,VDSO 将在 64 位内核下使用它,但 sysenter 不可用。)您通常可以假设没有前缀,尽管 rep int 0x80 例如仍将作为 @987654325 执行@.
      • 但是如果一个前卫想要躲避你的追踪器,那么如果没有PTRACE_SYSCALL,你就完蛋了。程序可以从另一个线程交叉修改其机器代码,因此int 0x80 可以在一个线程中执行,但来自另一个线程的存储可以在您的 ptrace 有机会读取它之前修改这些字节。 (如果另一个线程在执行系统调用之前正在旋转等待存储的标志,则非常合理)。 strace 也存在问题,区分 32 位和 64 位 ABI:参见 Can ptrace tell if an x86 system call used the 64-bit or 32-bit ABI?
      猜你喜欢
      • 1970-01-01
      • 2017-07-27
      • 2011-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-22
      • 1970-01-01
      相关资源
      最近更新 更多