Linux系统调试之return probe原理和示例

前面谈了kprobe的原理,其实uprobe也差不多:
https://blog.csdn.net/dog250/article/details/106520658

那么return probe如何实现呢?

我们知道,hook一个函数的起始位置非常容易,拿函数名当指针,直接修改成0xcc或者别的什么call/jmp即可,而hook一个函数的结束就没有这么简单了:

  • 函数大小不容易计算。
  • 函数可以在任意位置调用return。

怎么办呢?

很简单,只要执行流到了函数里面,直接取RSP寄存器指示的地址即可,它就是函数返回的地址,hook这个地址,就OK了。

于是,方法也就有了:

  • 在函数开头打int3断点(也可以ftrace,但这里仅谈int3)。
  • 在函数调用时的int3处理函数中获取stack上的return address。
  • 将return adress替换成int3的address(也可以用单独的函数)。
  • 在return address的int3处理函数中调用return probe函数。
  • 恢复正常流程。

如下图所示:

在这里插入图片描述

下面是一个示例程序:

#include <stdio.h>
#include <sys/mman.h>
#include <signal.h>

// sigframe的RIP偏移
#define PC_OFFSET       192
// sigframe的RSP偏移
#define SP_OFFSET       184
// sigframe的RAX偏移,用于获取返回值
#define AX_OFFSET       168

#define I_BRK   0xcc

unsigned long *orig;

void trap(int unused);
void fbrk()
{
    asm ("nop;");
}
unsigned char *pbrk;
void breakpoint(unsigned long addr)
{
    unsigned char *page;

    signal(SIGTRAP, trap);
    page = (unsigned char *)((unsigned long)addr & 0xffffffffffff1000);
    mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);
    page = (unsigned char *)((unsigned long)fbrk & 0xffffffffffff1000);
    mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);
    // 配置int3 buffer,用于替换return address。
    pbrk = page;
    *pbrk = I_BRK;
    // 保存函数头的原始指令,用于restore。
    orig = (unsigned long *)*(unsigned long *)addr;
    // 函数开头打断点。
    *(unsigned char *)(addr) = I_BRK;
}

void trap(int unused)
{
    unsigned long *p;
    static int ret = 0;

    if (ret == 0) { // 函数开头的int3处理
        p = (unsigned long *)((unsigned char *)&p + PC_OFFSET);
        // 恢复原始指令。
        *p = *p - 1;
        *(unsigned long *)*p = (unsigned long)orig;
        // 保存原始的返回地址。
        orig = (unsigned long *)*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET);
        // 替换返回地址为int3 buffer。
        *(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET) = (unsigned long)pbrk;
        ret = 1;
    } else if (ret == 1) { // 函数返回时的int3处理
        p = (unsigned long*)((unsigned char *)&p + PC_OFFSET);
        printf("浙江温州皮鞋湿,下雨进水不会胖。[%d]\n", *(int *)((unsigned char *)&p + AX_OFFSET));
        // 更改函数的返回值,仅做测试...
        *(int *)((unsigned char *)&p + AX_OFFSET) = 1222;
        // 恢复原始流程。
        *p = (unsigned long)orig;
        ret = 0;
    }
}

// 测试函数,返回值为参数。
int test_function(int ret)
{
    printf("[test function]\n");
    return ret;
}

int main(int argc, char **argv)
{
    int ret = 0;

    ret = atoi(argv[1]);
    breakpoint((unsigned long)&test_function);

    printf("before call: %d\n", ret);
    ret = test_function(ret);
    printf("after call: %d\n", ret);
}

OK,编译,运行,看效果:

[root@localhost probe]# gcc retdebug.c -O0 -o retdebug
[root@localhost probe]# ./retdebug 12345
before call: 12345
[test function]
浙江温州皮鞋湿,下雨进水不会胖。[12345]
after call: 1222

成功打印了一句话并修改了返回值。

其实,内核中的kretprobe差不多也就是这个意思。

哦,不,你看我把return handler实现在trap信号处理函数里了,这并不好。不过在我的例子里,仅仅是打印一句话,所以也就无所谓了,真正正确的做法是,单独写一个stub,来call return handler,而不是用int3来中转:

; 汇编实现的stub
asm_stub:
    SAVE_ALL;
    call ret_handler;
    RESTORE_ALL;
    push _orig_;
    ret;

// 更加简洁的trap函数
void trap(int unused)
{
    unsigned long *p;
    p = (unsigned long *)((unsigned char *)&p + PC_OFFSET);
    // 恢复原始指令。
    *p = *p - 1;
    *(unsigned long *)*p = (unsigned long)orig;
    // 保存原始的返回地址。
    _orig_ = (unsigned long *)*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET);
    // 替换返回地址为int3 buffer。
    *(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET) = (unsigned long)asm_stub;
}

嗯,这才是正确的方法:
在这里插入图片描述

后记
虽然我一直都在顽强得抗争着,但我感觉我的精神已经达到了顶点,很难再次突破,所以,我决定开始学习编程,顺便考个中级职称!基础差,底子薄并不可怕,过不了几个月,我应该就不会再说自己不会编程了,也算一件幸事!


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/106547963

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Linux系统调试之return probe原理和示例

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/405954

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:31
下一篇 2023年4月26日 上午9:32

相关推荐