建立:
应用程序
我正在尝试编写即使启用了ASLR也应该能够在内存中找到结构的代码。可悲的是,我找不到对这些区域的任何静态引用,因此我猜测我必须使用蛮力方式并扫描进程内存。我试图做的是扫描应用程序的整个地址空间,但由于某些内存区域未分配,因此无法正常工作,因此在访问时会产生
SIGSEGV
。现在,我认为getpid()
是一个好主意,然后使用pid访问/proc/$PID/maps
并尝试从中解析数据。但是我想知道,是否有更好的方法来识别分配的区域?甚至甚至不需要我访问libc(=
getpid, open, close
)或弄乱字符串的方式吗?
最佳答案
我认为没有任何标准的POSIX API。
解析/proc/self/maps
是您最好的选择。 (可能有一个库可以帮助您解决这个问题,但IDK)。
但是,您标记了此ASLR。如果您只想知道text/data/bss段的位置,则可以在它们的开头/结尾放置标签,以便这些地址在C中可用。 extern const char bss_end[];
是引用您使用链接器脚本和某些手写的asm放在BSS末尾的标签的好方法。编译器生成的asm将使用相对于RIP的LEA指令来获取相对于当前指令地址的寄存器中的地址(CPU知道该地址,因为它正在执行映射到那里的代码)。
或者可能只是一个链接描述文件,并在自定义部分中声明伪C变量。
我不确定是否可以为堆栈映射做到这一点。在大型环境和/或argv中,进入main()
甚至_start
时的初始堆栈可能与堆栈映射中的最高地址不在同一页中。
要进行扫描,您需要捕获SIGSEGV
或使用系统调用而不是用户空间加载或存储进行扫描。 mmap
和mprotect
无法查询旧设置,因此它们对非破坏性内容不是很有用。带有提示但没有mmap
的MAP_FIXED
可以映射页面,然后可以对其进行munmap
。如果实际选择的地址是==提示,则可以假定该地址已被使用。
也许更好的选择是使用madvise(MADV_NORMAL)
扫描并检查EFAULT
,但一次仅一页。
您甚至可以使用 errno=0; posix_madvise(page, 4096, POSIX_MADV_NORMAL)
进行此操作。然后检查errno
:ENOMEM
:指定范围内的地址部分或完全不在调用者的地址空间之内。
在具有 madvise(2)
的Linux上,您可以使用MADV_DOFORK
或在每个页面上使用非默认设置的可能性更低。
但是在Linux上,用于只读查询进程内存映射的一个更好的选择是 mincore(2)
:对于查询范围内的无效地址,它也使用错误代码ENOMEM
。 “addr
到addr + length
包含未映射的内存”。 (EFAULT
是指向未映射内存的结果 vector ,而不是addr)。
仅errno
结果有用; vec
结果显示页面在RAM中是否很热。 (我不确定是否显示您已将哪些页面链接到硬件页表中,或者是否会计算驻留在页面高速缓存中的页面是否是内存映射文件但未链接的页面,所以访问会触发软链接(soft link)页面错误)。
您可以通过调用长度更大的mincore
来对大型映射的结尾进行二进制搜索。
但是不幸的是,在未映射的页面之后,我没有找到用于查找下一个映射的等效项,这将更加有用,因为大多数地址空间都将被映射。尤其是在具有64位地址的x86-64中!
对于稀疏文件,有lseek(SEEK_DATA)
。我想知道这是否适用于Linux的/proc/self/mem
吗?可能不会。
因此,也许很大(例如256MB)的(tmp=mmap(page, blah blah)) == page
调用是浏览未映射区域以查找映射页面的好方法。 ,无论哪种方式,您都可以简单地使用munmap(tmp)
,无论mmap
是否使用您的提示地址。
解析/proc/self/maps
几乎可以肯定是更有效率的。
,但是最有效的方法是将标签放置在您希望它们用于静态地址的位置,并跟踪动态分配,以便您已经知道内存的位置。如果没有内存泄漏,则此方法有效。 (glibc malloc
可能具有用于遍历映射的API,但我不确定。)
请注意,如果您向该系统调用传递了未指向参数的地址(应指向某个参数),则该系统调用都会生成errno=EFAULT
。
access(2)
是一个可能的候选者,它采用文件名并返回整数。它对其他任何状态(成功或失败)的影响都为零,但是如果指向的内存是有效的路径字符串,则不利的是文件系统访问。而且它正在寻找一个隐式长度的C字符串,因此如果很快将指针传递给没有0
字节的内存的指针,则可能也会很慢。我猜想ENAMETOOLONG
会启动,但它肯定仍会读取您使用它的每个可访问页面,即使页面被调出也会出错。
如果在/dev/null
上打开文件描述符,则可以使用它进行write()
系统调用。 甚至使用 writev(2)
:writev(devnull_fd, io_vec, count)
在一个系统调用中将内核的指针 vector 传递给内核,如果其中任何一个不好,则得到EFAULT。 (每个长度为1个字节)。 但是(除非/dev/null
驱动程序足够早地跳过读取),这实际上是从有效页面读取的,从而使它们出现错误,而不是mincore()
。取决于内部实现的方式,/dev/null
驱动程序可能会尽早看到请求以使其“返回true”,而无需执行任何操作,以避免在检查EFAULT后实际触摸页面。检查会很有趣。
关于c - 从进程内部查找映射的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53022573/