用户态直接执行仅包含二进制指令的文件

经理的皮鞋湿了,但是却没有变胖,所以经理的皮鞋是人造革的。


刚刚写了一篇文章:
不依赖OS编译器,不依赖库,用汇编/机器码直接编程: https://blog.csdn.net/dog250/article/details/89500153

展示了一个直接执行二进制指令文件的基本方案,基于Linux内核来执行。

值得一提的是,这不是什么特别有技术含量的东西,这只是一个基本功,我在2006年的长春吉林大学边上租住的房子里就玩过此法。那时是用VC 6。

大公司的程序员不会屑于写我这些淫巧,即便他们也不一定会,也不一定懂,他们只是太关注业务,也许是真的没时间,也许真的就是不屑,觉得这太简单。但是,我的意思就是,这些东西真的太简单,如果你不懂,那真的不是一个合格的程序员。


本文写一个 解释器 ,载入一个仅包含二进制指令的文件,然后执行,这非常简单。


有两种方法可以简单的改变程序的执行流:

  1. 通过替换函数调用的返回地址。
    这是call指令提供的功能,call指令执行时,会将下一条指令压栈,我们只需要在栈里找到这个位置,将其替换为自己的指令即可。
  2. 直接通过内联汇编调用jmp指令执行自己的指令。
    这个非常直接,超级简单。

首先看第一个方法,代码如下:

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

// 我们想直接执行这段指令,当然,它保存于某个文件,可以从文件里读出到code内存空间。
// char nop[] = {0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x0f, 0x05};
unsigned char *code;
void exec()
{
    unsigned long a, *p;
    // 替换返回地址,使得exec返回的时候,跳转到我们的代码。
    p = (void*)((long)&a + 24);
    *p = (unsigned long)code;
}

int main(int argc, char **argv)
{
    FILE *fp = NULL;

    fp = fopen(argv[1], "r");
    // 映射地址空间
    code = (unsigned char*)mmap(0, 1024, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, 0, 0);
    //memcpy(code, nop, sizeof(nop));
    // 将指令文件内容读取到code指示的地址空间。
    fread(code, 1024, 1, fp);
    // 执行之
    exec();
    printf("end\n");
}

接下来看看直接jmp的方式,依然是一个很简单的代码:

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

// 我们想直接执行这段指令,当然,它保存于某个文件,可以从文件里读出到code内存空间。
//char nop[] = {0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x0f, 0x05};
unsigned char *code;

int main(int argc, char **argv)
{
    FILE *fp = NULL;

    fp = fopen(argv[1], "r");
    code = (unsigned char*)mmap(0, 1024, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, 0, 0);
    // memcpy(code, nop, sizeof(nop));
    fread(code, 1024, 1, fp);

    asm ( "jmp %0"
          :           
          :"r"(code)
          :);

    printf("end\n");
}

将上述的代码编译成一个比如a.out的程序,将一个二进制指令文件作为参数执行之,就会看到效果了。

我来准备一个二进制指令文件,以fork炸弹为例:

.global _start
_start:
    mov     $57, %rax               # fork 炸弹
    syscall
    jmp _start

照常编译:

[root@localhost ~]# as --64 -o forkbomb.o forkbomb.s
[root@localhost myflat]# ld -melf_x86_64 -o forkbomb forkbomb.o --oformat=binary

然后将这个forkbomb作为参数执行a.out,试试看。


值得注意的是,以上的例子中,我没有为指令文件里的指令分配独立的stack空间,而是借用了a.out主进程的stack空间,所有的pop,push等指令,都会操作a.out主进程的stack。

其实,更优雅的方案是,单独分配一个新的stack空间,切过去,皮鞋就干了。


有人会说这样做没有内核方案直接。确实是的,毕竟我们无法直接在bash的命令提示符里输入forkbomb那般执行,但是想达到这个效果,这也不难啊。

你在命令提示符里输入的任何程序都是通过bash来执行的,我们把上述的a.out整个儿给塞进bash中不就OK了吗?在bash开始fork/exec新进程的时候,自己完成mmap,memcpy的事情,然后要么通过替换函数返回地址后主动return,要么直接内联汇编里jmp,不就OK了么。

非常简单!


请注意,埃里克.雷蒙德有枪!
在这里插入图片描述
皮鞋湿,不会胖!但是经理的皮鞋除外。埃里克.雷蒙德也不懂皮鞋,毕竟,第一次穿。

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

欢迎关注

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

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

    用户态直接执行仅包含二进制指令的文件

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

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

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

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

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

相关推荐