分析完节头之后,我最大的收获就是,这么多的01,并不是所有的都是用来执行我写的那段输出helloworld的程序的,而且代码段中有很大一段空闲空间,这就给我们一个向可执行文件中注入自己代码,然后通过修改程序逻辑达到让它去执行我们自己写的的部分的代码的逻辑的机会。
这里我们的源代码是这样的:
1 #include <stdio.h>
2 int main()
3 {
4 printf("hello world");
5 return 0;
6 }
也就是我们一直分析的源码,把它编译生成可执行文件:
gcc hello.c -o hello.out
然后运行看看:
这里我想完成两件事:
- 通过对其只读数据节的修改,让它输出加个换行
- 向代码中静态注入自己的一个函数,然后让它输出两次hello world
这里我们用hexedit来协助实验,它的用法很简单,0-F直接输入就可以替换原来的值,Ctrl+x保存退出,Ctrl+c不保存退出,知道这些就足够了,没有的话可以考虑安装一个。
sudo apt-get install hexedit
1.修改只读数据段:
这个相对容易一些,之前通过对节的分析,我们知道程序的只读数据节位于以下位置:
[16] .rodata PROGBITS 00000000004005c0 000005c0
0000000000000010 0000000000000000 A 0 0 4
即偏移为0x05c0的位置,我们查看一下这一位置:
可以看到"helloworld"这个字符串并没有顶着0x05c0的头写,接下来我们再看一下objdump -d hello.out中main方法的指令:
可以看到,读取这个数据的时候也考虑了偏移量。
那么现在,为了达到修改数据段并让函数正确输出,我们需要做两件事情:
- 修改rodata节中的内容。
- 修改main函数中推入寄存器的地址。
1.修改rodata节的内容
换行符的ASCII码为10,即0x0A,把rodata之前的所有字节往前推动一位,然后末位添加0x0A即可:
2.修改main函数中的内容
我们只需要把之前的偏移减一,变成0x05c3即可:
好,一切就绪,Ctrl+x保存退出。
运行一波看看:
可见我们已经用后期帮粗心的程序员加上了换行符。
2. 静态注入函数
注入这个词听起来很高端,不过鉴于这只是一个hello world性质的实验并且本人目前的水平还很一般,这里其实实现的方式非常简单,就是在空白的代码段的部分增添上自己的代码,然后修改程序逻辑让源控制流指向自己的函数块而已。这整个过程也是全手动实现,先不用而且也不会太高深的工具。
我们的目的是让函数输出两次hello world,为了达到这个目的,我们需要做两件事:
- 增加自己的函数块
- 修改main函数部分
1.增加自己的函数块
我们自己的函数逻辑与当前的main函数基本相同,所以我们可以先参考一下当前的main函数的指令
前后两行是调用函数固定的格式,要做的其实就是将字符串移入edi寄存器,清空eax寄存器,调用printf函数,再清空eax寄存器即可 (到这里我还没有去学太多汇编相关的知识,所以有些步骤不一定是必需的,这里只是为了保证正确性)。
接下来我们只要把这些十六进制格式的指令copy到自己的函数块所在的地址处就行了,但其实这里有一个关键的问题,我在做这个实验的时候也被这个问题缠了一会:
观察调用的函数的地址,可以发现,这个值并不直接是0x400400,而是0xfffffec7,这里其实是有一个地址转换过程:
-
P-这个值所在的位置:0x400535
-
F-要调用的函数所在的位置 0x400400
那么调用地址A=F-P-4=-0x139=FFFFEFC7,(这个4也是有讲究的,等看到那部分而且看懂了之后再解释)。
所以我们在写我们自己的函数块的时候也要这样计算一下,接下来的计算过程我就省略了。
首先我们找到有效代码段之后的一块空白区域,比如这里:
然后种上我们自己的代码,完成后是这个样子:
2.修改main函数部分
我们已经知道我们的函数的入口位于0x4006F0,值所在位置为0x400535:
则调用地址为:0x4006F0-0x400535-4=0x1B7,所以这样修改:
修改完成,Ctrl+x保存退出
运行一波看看:
可见我们自己的函数逻辑得以执行而且输出了想要的结果。
附加实验
昨晚前两个之后我突发奇想,能不能输出除了hello world之外的内容,也就是如果把非只读数据段位置的数据送入edi寄存器,它能不能被成功输出?于是这里我又加了一个实验,首先我在偏移0x730处增加了一个字符串数据:“emmm”
然后我修改我的函数,增加一个指令: mov $0x400730,%edi,修改后的代码如下:
然后运行一波可以看到:
出现了我们想要的结果。