Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用自己的python调试器获取系统调用时遇到问题_Python_Linux_X86_System Calls_Ptrace - Fatal编程技术网

使用自己的python调试器获取系统调用时遇到问题

使用自己的python调试器获取系统调用时遇到问题,python,linux,x86,system-calls,ptrace,Python,Linux,X86,System Calls,Ptrace,我试图在32位测试linux系统Lubuntu上用python3编写一个简单的调试器,在这种情况下,它应该能够捕获abitrary程序的所有系统调用:/bin/ls。为此,我使用ptrace系统调用来单步完成这个过程。在每一步之后,我读取寄存器以找到指令指针eip,从而从下一条指令中读取2个字节。如果这两个字节是0xcd和0x80,则表示int 80是系统调用。我知道也有用于此目的的PTRACE_系统调用,但我想在不使用它的情况下执行此操作 在下面的代码中,我向您展示了代码,它似乎可以工作,但有

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

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

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

导入操作系统交互 从结构导入包处理字节ptrace 导入ctypes支持c数据结构 ========================================================== 32位reg进程结构 类UserRegsStructTypes。结构: _字段=[ ebx,ctypes.c_ulong, ecx,ctypes.c_ulong, edx,ctypes.c_ulong, esi,ctypes.c_ulong, 电子数据交换,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, 原版,ctypes.c_ulong, eip,ctypes.c_ulong, xcs,ctypes.c_ulong, eflags,ctypes.c_ulong, esp,ctypes.c_ulong, xss,ctypes.c_ulong, ] ptrace常数 PTRACE_TRACEME=0 PTRACE_PEEKDATA=2 PTRACE_SINGLESTEP=9 PTRACE_GETREGS=12 CPU\u字大小=4 CPU字大小的大小32位=4字节 用于系统调用 libc=ctypes.CDLL'libc.so.6' 检查子tracee是否仍在运行 def WIFSTOPPEDstatus: 返回状态&0xff==0x7f 通过PTRACE_数据从进程内存读取 def ReadProcessMemoryId、地址、大小: 地址必须对齐!! 偏移量=地址%CPU\u字大小 如果偏移: 地址-=偏移量 word=libc.ptracePTRACE_peek数据,pid,地址,0 wordbytes=packi,word SUFFISZE=最小CPU\u字\u大小-偏移量,大小 数据=字字节[偏移量:偏移量+子空间] 大小-=补贴 地址+=CPU\u字大小 其他: 数据=字节0 而尺寸: word=libc.ptracePTRACE_peek数据,pid,地址,0 wordbytes=packi,word 如果大小
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 +++


如果只查找int 0x80,则将错过通常通过glibc调用VDSO页面使用sysenter指令进行的正常32位系统调用。同样在旧的AMD CPU上,32位系统调用也是可能的,如果它们太旧而无法支持syscenter,则默认情况下可能会使用32位系统调用

我猜早期的ld.so代码使用传统的int0x80机制,而不是调用VDSO。这是有道理的;VDSO呈现为映射到内存中的ELF共享对象;直到动态链接器设置函数指针 如果你喜欢它,它就不能使用它

64位模式更简单:所有操作都对64位ABI使用syscall

请注意,在指令执行之前或之后检查机器代码可能会被试图隐藏跟踪的代码欺骗。第二个线程可以在查看机器代码字节后,在执行之前交叉修改机器代码字节。也许让一个线程存储一个标志,这将导致另一个线程在它发现时立即存储。如果时机正确,这可能会在ptrace获取和执行下一个步骤之间发生

在现实生活中,strace使用的PTRACE_SYSCALL或sandbox/SYSCALL日志记录或筛选工具在64位模式下试图欺骗代码,试图找出是调用了32位还是64位ABI,因为调用号不同,这也是一个类似的争用条件。或者是,直到Linux内核5.3添加了PTRACE\u GET\u SYSCALL\u INFO

在64位代码中调用int 0x80是可能的,尽管这基本上不是一个好主意:有一些关于系统调用内核端发生什么的详细信息

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

如果将此代码用作一个库,有人可能试图从中构建沙箱系统调用过滤器,那么真正的危险就来了。e、 g.检查所有文件访问系统调用中的路径,或拒绝非只读打开的打开调用。那么,逃避追踪就成了一个真正的安全问题。当然,通常有更好的方法来进行沙箱处理。

如果您只查找int 0x80,您将错过通常通过glibc调用VDSO页面使用sysenter指令进行的正常32位syscall。同样在旧的AMD CPU上,32位系统调用也是可能的,如果它们太旧而无法支持syscenter,则默认情况下可能会使用32位系统调用

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

64位模式更简单:所有操作都对64位ABI使用syscall

请注意,在指令执行之前或之后检查机器代码可能会被试图隐藏跟踪的代码欺骗。第二个线程可以在查看机器代码字节后,在执行之前交叉修改机器代码字节。也许让一个线程存储一个标志,这将导致另一个线程在它发现时立即存储。如果时机正确,这可能会在ptrace获取和执行下一个步骤之间发生

在现实生活中,strace使用的PTRACE_SYSCALL或sandbox/SYSCALL日志记录或筛选工具在64位模式下试图欺骗代码,试图找出是调用了32位还是64位ABI,因为调用号不同,这也是一个类似的争用条件。或者是,直到Linux内核5.3添加了PTRACE\u GET\u SYSCALL\u INFO

在64位代码中调用int 0x80是可能的,尽管这基本上不是一个好主意:有一些关于系统调用内核端发生什么的详细信息

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


如果将此代码用作一个库,有人可能试图从中构建沙箱系统调用过滤器,那么真正的危险就来了。e、 g.检查所有文件访问系统调用中的路径,或拒绝非只读打开的打开调用。那么,逃避追踪就成了一个真正的安全问题。当然,通常有更好的方法来进行沙箱处理。

非常感谢!!你是对的

我现在稍微更新了代码。我使用/proc/pid/auxv文件通过_SYSINFO处的键获取VDSO中syscenter例程的地址。现在我可以通过比较这个地址和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

非常感谢你!!你是对的

我现在稍微更新了代码。我使用/proc/pid/auxv文件通过_SYSINFO处的键获取VDSO中syscenter例程的地址。现在我可以通过比较这个地址和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

理论上,二进制文件可以手动使用syscenter,而不是调用VDSO。如果你不关心检测,那就不用担心了。所以我想我可以通过检查指令代码来考虑这一点
同样,对于int 80也是如此。sysenter syscall的代码为0F 34。或者你知道另一种方法吗?我刚刚添加了这个特性,但是现在我捕获了两次非遗留系统调用。通过分析eip地址,我发现在跳入vdso空间后,会调用syscenter。所以在我看来,直接检测syscenter是更好的选择。是的,如果你已经有代码来检查机器代码,你可以用它来检查int 0x80、syscenter或syscall。AMD CPU支持32位模式下的系统调用;如果它可用,VDSO将在64位内核下使用它,但sysenter不可用。您通常可以假定没有前缀,尽管例如rep int 0x80仍将作为int 0x80执行。但是,如果一个prog想要隐藏跟踪程序,那么在没有PTRACE_SYSCALL的情况下,您无论如何都会失败。程序可以从另一个线程交叉修改其机器代码,以便int 0x80可以在一个线程中执行,但另一个线程的存储可以在ptrace有机会读取之前修改这些字节。如果在执行系统调用之前,另一个线程正在等待存储的标志,这是非常合理的。对于strace来说,区分32位和64位ABI也是一个问题:从理论上看,二进制文件可以手动使用syscenter,而不是调用VDSO。如果你不关心检测,那就不用担心了。所以我想我可以考虑一下,只需再次检查指令代码,就像检查int 80一样。sysenter syscall的代码为0F 34。或者你知道另一种方法吗?我刚刚添加了这个特性,但是现在我捕获了两次非遗留系统调用。通过分析eip地址,我发现在跳入vdso空间后,会调用syscenter。所以在我看来,直接检测syscenter是更好的选择。是的,如果你已经有代码来检查机器代码,你可以用它来检查int 0x80、syscenter或syscall。AMD CPU支持32位模式下的系统调用;如果它可用,VDSO将在64位内核下使用它,但sysenter不可用。您通常可以假定没有前缀,尽管例如rep int 0x80仍将作为int 0x80执行。但是,如果一个prog想要隐藏跟踪程序,那么在没有PTRACE_SYSCALL的情况下,您无论如何都会失败。程序可以从另一个线程交叉修改其机器代码,以便int 0x80可以在一个线程中执行,但另一个线程的存储可以在ptrace有机会读取之前修改这些字节。如果在执行系统调用之前,另一个线程正在等待存储的标志,这是非常合理的。这也是strace的一个问题,区分32位和64位ABI:参见