前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个elf程序实现代码注入的实例

一个elf程序实现代码注入的实例

作者头像
望月从良
发布2021-05-08 15:22:27
1.1K0
发布2021-05-08 15:22:27
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

本节我们看看如何针对ELF可执行文件实现代码注入,这是一个简单的实例,但却可以有效的揭开冰山的一角。

我想很多人都用过破解软件,破解的一种机制就是通过修改程序的二进制代码,越过软件的授权认证机制,例如下面这个例子:

代码语言:javascript
复制
   auth = autoVerifyCode(code); //认证输入验证码是否正确
   if (auth == False) {
        exit(1); //验证错误直接退出
    }
    //下面代码是软件功能的正常运行

面对上面验证码的验证模式,一种破解办法就是将auth的值直接修改成真,从而让代码运行时直接跳过if(auth == False) 这条语句,我们本节看看如何实现这样的功能。首先我们先编写一段代码,然后将其编译成可执行的ELF文件:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdarg.h>

void error_exit(char const *err) {
    printf("error msg:%s", err);
    exit(1);
}

int main(int argc, char *argv[]) {
    FILE *f;
    char *infile, *outfile;
    unsigned char *key, *buf;
    size_t  char *key, *buf;
    if (argc != 4) {
        error_exit("usage: input_file, output_file, key");
    }
    infile = argv[1];
    outfile = argv[2];
    f = fopen(unsigned char*)argv[3];
    if (!f) {
        error_exit("failed to open file");
    }
    fseek(f, 0, SEEK_END);
    n = ftell(f);
    fseek(f, 0, SEEK_SET);
    buf = malloc(n);
    if (!buf) {
        error_exit("out of memory");
    }
    if (fread(buf, 1, n, f) != n) {
        error_exit("fail to read file");
    }

    fclose(f);
    j = 0;
    for (i = 0; i < n - 1; i++) {  //这里是问题点,应该是i<n,这种情况叫做off-one-byte-error
        buf[i] ^= key[j];
        j = (j+1) % strlen(key);
    }
    f = fopen(outfile, "wb");
    if (!f) {
         error_exit("fail to open file");
    }
    if (fwrite(buf, 1, n, f) != n) {
        error_exit("Failed to write file");
    }
    fclose(f);
    return 0;
}

上面代码的基本逻辑是,程序接收一个输入文件用于加密,然后指定加密后的输出文件,最后指定加密用的密钥。加密算法非常简单,就是用密钥中的字符与输入文件中每个字符做xor操作。但是在代码中存在一个典型的one-byte-off 错误,也就是for(i = 0; i < n-1; i++)其中的i<n-1应该是i<n,这个错误会使得被加密文件的最后一个字符没有被加密,我们看一个例子。

在本地创建一个文件: vim encrypted.txt,然后在里面输入字符串“hello world”,接着查看其十六进制内容:

代码语言:javascript
复制
xxd encrypted.txt

上面代码执行后所得结果如下:

我们看到文件的最后一个字节是0x0a,然后我们将前面的代码编译,将编译结果用于加密上面的文件,用于编译的Makefile文件内容如下:

代码语言:javascript
复制
CC=gc
AS=nasm
OBJ=xor_encrypt
.PHONY: all clean
all: $(OBJ)
xor_enrypt: xor_encrypt.c
    $(CC) -o xor_encrypt -O2 xor_encrypt.c
clean:
    rm -rf $(OBJ)

注意上面脚本中前面有”空格“的行,对应”空格“其实是TAB键。完成上面脚本后,使用make命令就能编译出ELF可执行文件,然后使用如下命令对文本进行加密:

代码语言:javascript
复制
./xor_encrypt encrypted.txt encrypted foobar

其中encrypted 是要输出的加密文件, foobar就是用于加密的密钥,执行后我们可以看到encrypted文件在本地目录,接下来我们继续使用xxd查看加密文件的16进制内容:

代码语言:javascript
复制
xxd encrypted

上面命令执行后所得结果如下:

注意看上图,加密后文本内所有字符都变了,唯独最后一个字符没变,这是因为前面提到的one-byte-off错误引起。假设我们现在没有源码,要想改正这个错误,我们唯一能做的就是直接修改编译出来的二进制文件。

由于bug的原因是语句i < n - 1,如果它能改成i <= n-1,那么问题就可以纠正。假设在没有源码的情况下,我们必须直接修改编译好的二进制文件,我们先反汇编以便找到需要修改的地方:

代码语言:javascript
复制
objdum -M intel -d xor_encrypted

上面命令运行后会得到一大堆反汇编指令,从头往下拉你会看到如下内容:

上图中选中的那段就对应for循环的内容。cmp r12, rbx 对应i < n-1,也就是n-1的数值存放在寄存器r12, i变量的值存放在寄存器rbx。其中指令jne表示不相等就跳转,也就是先将r12的值与寄存器rbx的值比较,如果两者不相等就跳转进入for对应的循环体。

由于代码的错误是循环执行少了一次,正确的逻辑是如果i的值与n-1相等时也要跳转,因此我们需要修改jne,让代码的逻辑变成不相等时跳转,转换为大于等于时都跳转,也就是把语句i < n-1变成i <= n-1,对应的就是当n-1 “大于等于”i的值时就要跳转到循环体。由于n-1对应寄存器r12,因此我们需要将指令jne转换成jae,这条语句的意思是”大于等于时跳转“,我们注意看,jne指令对应的字节码是75 ,d9对应当前所在位置的偏移,因此我们需要在编译出来的二进制文件中找到数值75 d9,这里我们使用文件hexedit:

代码语言:javascript
复制
hexedit xor_encrypted

在打开的内容后按下”/“按钮,也就是有斜杆对应那个按钮,然后输入要查找的数值字符串:

按下回车我们就跳转到对应指令出,由于75对应指令jne, 73对应指令jae因此,我们把跳转到的数值75直接修改成73,然后按ctrl+x,在弹出的Save changes中输入Y,这样修改就完成了,我们再次使用反汇编看看修改后的结果:

可以看到jne指令成功的被修改成jae指令,最后我们再次运行该程序看看结果:

代码语言:javascript
复制
./xor_encrypt encryptd.txt encrypted foobar
xxd encrypted

两个命令输入后可以得到如下结果:

可以看到,最后一个字符被成功加密了,这就是一个简单的代码注入ELF可执行文件的例子。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档