什么是最“陷阱,能”段错误?调用空函数指针?一双免费? ...?
我可以在信号处理程序中做什么?似乎存在一些苛刻的条件(可重入,异步信号安全功能等)。或比“只读POSIX圣经”有用的解释。我们将不胜感激。
我的代码不需要可移植。如果它可以在中等标准的Linux(Debian,Redhat,Ubuntu,CentOS)上运行,就可以了。
#1 楼
我的问题给出了一个在Windows中工作的解决方案-这样,您可以在收到SIGSEGV
后使程序继续运行。已弃用(请参见链接)。也就是说,为该信号注册信号处理程序并以您喜欢的任何方式导致它-执行将传递到该处理程序,您可以在其中继续。 #include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void handler(int sig)
{
write(1, "success", strlen("success")); // printf is not recommended here, but should work as well
exit(0);
}
int main()
{
struct sigaction sa;
memset (&sa, 'q4312078q', sizeof(sa));
sa.sa_sigaction = &handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL); // register handler for SIGSEGV
int *a; // a will contain some garbage value
int b = *a; // trigger segmentation fault; transfer control to handler
}
当然,您可以在Windows上使用与我的示例相同的技巧,然后在
sigaction
之外继续执行。#2 楼
可以针对此类问题进行参考的出色参考(也许是最好的参考)是Linux编程接口,其中包括有关信号主题的3个完整章节:概念信号:信号处理程序
信号:高级功能
这些章节包括示例代码以及图表和清晰的说明。
(本书的免费pdf文件可以可以很容易地在网上找到。)
另请参阅:使用信号进行二进制混淆
对您的问题:
最多的是“陷阱式” segfault?调用null函数指针?一双免? ...?信号处理程序(如果存在的话)将由内核根据信号(特别是其在
<signal.h>
中定义的编号)而不是触发信号的特定事件。这意味着您可以自由决定要创建哪种无效的内存引用,以触发分段错误。取消引用空指针可能是最可靠的方法,因为它可以确保程序行为具有确定性,因为无论堆栈内容或进程在内存中的位置如何,都会始终发生分段错误。我可以在信号处理程序中做什么?似乎存在一些苛刻的条件(可重入,异步信号安全功能...)。
您有几种选择。但是首先,关于非可重入库函数:
如果函数将静态数据结构用于其函数,它们也可以是不可重入的
内部簿记。此类函数最明显的示例是
stdio
库的成员(printf()
,scanf()
等),这些成员为缓冲的I / O更新内部数据结构。因此,在信号处理程序中使用printf()
时,如果处理程序在执行对printf()
或另一个stdio
函数的调用的过程中中断了主程序,则有时可能会看到奇怪的输出,甚至程序崩溃或数据损坏。 /> 重要的部分是最后一句话。如果在异常处理程序中调用了不可重入的库函数,则为
SIGSEGV
实现异常处理程序可能会导致意外的信号处理程序行为,因为可能会在您预期的特定条件之外触发分段错误。例如,当通过scanf
将用户输入读入缓冲区时,除非正确实施了边界检查,否则由于输入1000'A而导致的缓冲区溢出会导致SIGSEGV
被发送到进程,然后在进程执行期间触发异常处理程序。执行stdio
函数。如果信号处理程序还调用stdio
函数,则可能会发生未定义的行为。对于您的情况,最有趣的方法可能是更改信号处理程序的
uc_mcontext.gregs[REG_RIP]
结构中的context
(此处的RIP是x86-64指令指针)值,以指向程序中其他位置的函数。信号处理程序完成后,程序将在该函数处恢复执行。或者,可以增加uc_mcontext.gregs[REG_RIP]
的值以跳过/跳过导致信号处理器首先执行的指令。这意味着可以将信号处理程序设计为在接收到SIGSEGV
时简单地跳转到程序中的其他位置。这种方法(或执行非本地goto方法)消除了信号处理程序执行任何I / O的需要(不需要printf()
等)。缺点是该方法依赖于体系结构。在下面的文章中可以找到说明此技术的示例:Linux-编写故障处理程序。还可以在此处找到一些相关的示例代码:在信号处理程序中,如何知道程序在何处中断?请注意,由于进程可以向自身发送信号,因此不必选择
SIGSEGV
作为已处理信号; getpid()
和kill()
可用于向进程发送其他信号,以触发信号处理程序。 此外,还可以通过
sigsetjmp()
和siglongjmp()
函数从异常处理程序自身内部执行非本地goto。与使用uc_mcontext.gregs[XXX]
修改指令指针不同,此方法似乎是可移植的:通常,最好编写简单的信号处理程序。这样做的重要原因之一是要减少产生比赛条件的风险。信号处理程序的两种常见设计如下:信号处理程序设置全局标志并退出。主程序定期
检查此标志,如果已设置,则采取适当的措施。 (如果主程序
无法执行此类定期检查,因为它需要监视一个或多个文件描述符以查看是否可以进行I / O,因此信号处理程序还可以将单个字节写入专用管道,该管道的读取端包括在文件中
描述符由主程序监视。我们在第63.5.2节中展示了此技术的一个示例。)
信号处理程序执行某种类型的清除,然后终止
进程或使用非本地goto(第21.2.1节)来展开该处理。堆栈并
将控制返回到主程序中的预定位置。
[执行非本地goto]提供了一种在传递由硬件异常(例如内存访问错误)引起的信号之后进行恢复的方法,并且还允许捕获信号并将控制权返回到程序中的特定点。例如,在收到SIGINT信号(通常通过键入Control-C生成)后,外壳程序执行非本地goto来将控制权返回到其主输入循环(并因此读取新命令)。
补充信息:
信号是对流程的通知,事件已发生。信号有时被描述为软件中断。信号类似于硬件中断,因为它们会中断程序的正常执行流程。在大多数情况下,无法准确预测信号何时到达。
一个进程可以(如果具有适当的权限)将信号发送到另一个进程。
在这种情况下,可以使用信号作为一种同步技术,或者甚至作为进程间通信(IPC)的原始形式。进程也可能向自身发送信号。但是,发送给进程的许多信号的通常来源是内核。
参考:Linux编程接口,第20和21章,
评论
谢谢<3我刚刚完成了我最新的crackme(行会大厅冒险),并将在第4章中实现此技巧! :)
– ker2x
19年7月5日在20:11
@ ker2x,欢迎您,我期待着尝试解决您的新Crackme程序:)
– julian♦
19年7月6日,1:15
评论
谢谢 !我为您链接的帖子添加了书签,并且也会阅读。我没有足够的声誉来支持您,但您的答复深表感谢。
– ker2x
19年7月1日在18:10
如果您认为它回答了您的问题,则可以接受。如果没有,请让我知道任何疑问,以便我改善。
–bart1e
19年7月1日在18:11