之前的方法太坑了,我更新了一下phase2 _(:з」∠)_ 55555
直接复制过来排版太烂,我也懒得改了_(:з」∠)_,如果看的不舒服,移步:
hahalidaxin's Github
update:关于0xffffffa6为什么反汇编之后会变?其实是我call指令用错啦,举个例子:
所以实际应该在getcode.s里面填0xffffffaa才对,不过意思到了就行,下面就不改了
每阶段40分,phasex.o 20分,分析20分,总分不超过80分
(请先移步阅读)
* edb操作基础知识
* readelf使用
* 在EditHex中定位节Section
程序运行结果截图:
分析与设计的过程:
首先
直接将main.o编译,得到linkbomb,运行之后屏幕显示:
通过查看main的反汇编可以得出main函数的主要逻辑:判断phase是否为空,如果为空则打印上述输出字串,如果不为空则调用phase函数。
尝试将main.o与phase1.o进行链接,运行linkbomb后屏幕输出为:
可以看到这是一串没有什么特殊意义字符串。我们的目标就是将该字符串的前部替换为我们的学号,最终使屏幕输出我们的学号。
分析一下
printf(“%s\n”,s)输出函数最终会被优化为puts(s),s为字符串常数因此被保存在.data节数据段中。因此我们只需要在phase1中查找到相应的字符串更改为学号就行了
更改代码
利用HexEdit打开phase1(HexEdit可以直接看到字符串的内容,简化了定位的操作),将学号“1170300825\0”填入到指定位置,如下截图:
\0是全0的一个字节。
程序运行结果截图:
分析与设计的过程:
(不想看我多哔哔,可以直接到这里)
分析ELF文件结构
如下:
ELF头 |
段头部表 |
.init |
.text |
.rodata |
.data |
.bss |
.symtab |
.debug |
.line |
.strtab |
节头部表 |
do_phase函数结构如下:
代码分析
对ELF文件的Section部分进行解析
objdump –s –d phase2.o > phase2.txt
在.text节中找到指定的输出函数位置(注意,puts,strcmp等函数是要在链接重定向完成之后才能确定地址,在反汇编代码中显示出来。这一步对应命令:gcc –m32 –o linkbomb2 main.o phase2.o , objdump –s –d linkbomb2 > linkbomb2.txt):
通过edb在代码中定位strcmp函数的位置,发现此时eax指向学号的字符串,在执行strcmp之前向栈中压入了两个参数,显然一个是MYID一个则是函数传入的参数,因此我们目标就是在do_phase的nop中写入执行压栈和相对位置跳转(call)到输出函数SBavgzZu的逻辑,我们需要压栈的值是MYID的地址。同时因为这里的MYID地址是变化的,我们是无法直接获得压栈的地址值的。
解决问题
询问搜索引擎后得知,from StackOverFlow caf:
也就是说执行完这个函数之后我们就可以通过被写的寄存器来通过偏移量访问global类型,这也恰好解决了我们的问题。这是如何实现的呢?call调用之后此时PC指向下一条指令,同时将这条指令的地址压入栈中,进入x86.get_PC_thunk.ax之后,将栈顶的值赋值给指定的寄存器(后缀ax代表是%eax),这时候指定寄存器中就放着我们可以用来相对寻址的下一条指令的位置了。
其实,
call linkbomb2!__x86.get_pc_thunk.ax
addl $0x19d6, %eax
实现了将%eax指向_GLOBAL_OFFSET_TABLE_的功能,_GLOBAL_OFFSET_TABLE_用来定位global变量的真实(运行时)地址,对于上图的
b3 ff ff ff 和 d6 19 00 00
都是在链接过程中经过重定位确定了值,没有链接之前是这样滴
得到_GLOBAL_OFFSET_TABLE_地址之后,加上指定偏移量就可以得到特定的global变量。
我们先来观察do_phase函数和输出函数SbavgzZu的反汇编代码,将没有修改的phase2.o链接,对linkbomb2反汇编,如下:
Do_phase:
SbavgzZu函数:
在SbavgzZu函数中,经过:
Calll linkbomb2!__x86.get_pc_thunk.bx
addl $0x1a13,%ebx
leal -0x18b0(%ebx),%eax
前两步将%ebx指向_GLOBAL_OFFSET_TABLE_,后一步也是一个重定向之后确定的值,没有重定向之前是这样滴:
重定向之后%eax指向了.rodata,就是MYID。
所以我们只需要将%eax也指向.rodata就可以了。在do_phase的nop之前eax也已经指向了_GLOBAL_OFFSET_TABLE_,所以只需要
leal -0x18b0(%eax),%eax
就在do_phase中也使%eax指向了.rodata,将之作为参数压栈,然后call指令执行相对跳转,最后不要忘记使eax出栈“恢复现场”。
修改后汇编代码如下:
操作总结
输出函数SBavgzZu的反汇编:
看一下自己输出函数中leal [数字x](%ebx),%eax是什么样的,(在我的里面数字x是-0x18b0,看看上面那张图标绿的那句),然后用数字x来替换
的第一句中的-0x18b0(正常人应该都能看得懂吧。。。),得到自己应该插入的汇编代码,保存在getcode.s。
gcc -m32 -c getcode.s
objdump -d getcode .o > getcode.txt
得到:
剩下的就是用HexEdit更改二进制了,二进制在上图左边。
完事儿,(´థ౪థ)σ。
获得16进制代码的方法:
使用HexEdit修改phase2.o,修改之后为:
找到插入位置的方法(其实只要看见一片90无脑覆盖就行了):
程序运行结果截图:
获得数组名称
使用readelf确定PPT中所谓的PHASE3_CODEBOOK数组对应的名称。
readelf -a phase3.o > phase3.txt
得到:
可见,PHASE3_CODEBOOK的实际名称是QDwnxQFyLh。
这是因为QDwnxQFyLh已经在phase3.o中有了全局弱定义,所以必然存在于符号表.symtab中。
分析do_phase结构
可见最终输出的字符存储在QDwnxQFyLh数组中,最终输出是使用cookie这个字符数组来进行寻址,cookie是已知不变的,所以我们的工作是:得到cookie数组,根据cookie数组构造QDwnxQFyLh数组,要求按照cookie的索引顺序在QDwnxQFyLh中依次填入自己的学号。
获得cookie数组
gcc -o linkbomb3 main.o phase3.o
得到linkbomb3,使用edb运行linkbomb3,点击运行,运行到main函数,然后单步运行到do_phase中的循环位置,如下:
得到我的cookie数组是lxkhpsimqn。
构造字符串
因此我们只需要在字符数组cookie的字符所指向的OdwnxQFyLh数组的指定位置处 按顺序 填上自己的学号即可。
解释一下:比如我的cookie数组是lxkhpsimqn,正好10个对应我的10位学号,比如我要填第一个,‘l’对应ASCII码的108,所以我要使OdwnxQFyLh[108]=’1’。剩下的类似。其他地方随便填,我就用x填上了。
字符串构造如下:
char OdwnxQFyLh[256]=xxx......xxx
剩下的工作:在phase3_patch.c中定义OdwnxQFyLh数组,
(如果不爽 换种字符数组初始化方法就是了)
然后:
gcc -m32 -c phase3_patch.c
gcc -m32 -o linkbomb3 main.o phase3.o phase3_patch.o
程序运行结果截图:
(注:因为这个题个人感觉PPT上叙述的做法有点冲突,所以我是直接修改的linkbomb4,所以在测试的时候直接运行linkbomb4即可)
分析与设计的过程:
了解框架:
分析反汇编代码:
构造答案:
b)通过phase4反汇编代码我们可以确定case代码块之间相对位置,通过输出0x33的代码块的跳转表偏移量,我们可以得到所有的case类在跳转表里面对应的偏移量,我们选择输出指定字母串为学号的case块的跳转表偏移量 按cookie映射顺序与位置 填入到.rodata跳转表之中。说起来有点儿绕,我们不妨看一下截图:
截图说明:前面的6只是为了标识开始而已;其中填入0的都是cookie映射不到的;以0xffffe738为例,偏移量对应cookie-“X”(填入到了跳转表中‘x’映射到的位置),而0xffffe738是我们填入跳转表中的值,凭借跳转表程序跳转到switch的对应case语句,通过反汇编代码我们知道case之间的相对位置,在0xffffe738基础上进行加减就可以获得其他case块对应的跳转表值。
程序运行结果截图:
分析与设计的过程:
操作姿势
了解以上知识之后,简单使用edb运行程序应该不成问题。
offset |
需要进行重定向的代码在.text或.data节中的偏移位置,4个字节。 |
Info |
包括symbol和type两部分,其中symbol占前3个字节,type占后1个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型 |
Type |
重定位到的目标的类型 |
Name |
重定向到的目标的名称 |
其中我们需要补充的就是每个重定位目标的offset和info,一共8个字节,这里注意因为小端序的原因在hexedit中作为数的两者都是反的。
.symtab节包括:
Num |
symbol十进制的偏移量 |
Name |
Symbol 的名字 |
Section Headers中我们可以看到所有节偏移量off和大小size,这个偏移量是相对于整个elf文件而言的,通过这个偏移量和hexedit我们可以找到对应的节在elf二进制文件中的位置,进而进行观察和修改。
c)HexEdit:hexedit可以用来修改.o文件或elf可执行文件的二进制信息。我们可以查看elf文件已经有的重定位二进制信息。
查看已有的重定位信息
readelf –a phase5.o > phase5.elf获得phase5.elf文件,截图如下:
通过hexedit查看重定位部分对应的二进制信息(通过phase5.elf中Section Headers节给出的信息定位各个节的位置):
.rel.text
.rel.rodata
仔细查看两种显示下重定位信息的对应关系,我们发现一个重定位信息占8个字节,前4个字节代表offset,后四个字节代表info,其中info的高3个字节代表symbol是该symbol在.symtab中的Num,info的低1个字节代表type对应上面phase5.elf截图的Type,不同的值代表不同的type。
将.rel.text的已有重定位信息整理如下,
info.type |
含义 |
已知重定位目标 |
02 |
R_386_PC32 |
__X86.get_pc_thunk.dx ,encode |
0a |
R_386_GOTPC |
_GLOBAL_OFFSET_TABLE_ |
09 |
R_386_GOTOFF |
qJuHGm , .rodata , CODE,BUF |
04 |
R_386_PLT32 |
puts |
其中.rel.rodata没有的重定位信息是.L3。
我们需要加上的就是没有的重定位信息。
补充重定位信息
代码框架:
通过phase5.elf我们可以得到已经重定位的代码offset(相对于.text节),就可以推断出重定位代码的大致位置。通过给出的代码框架和phase5.o的反汇编代码,我们可以推出在哪里插入,以及插入什么重定位信息。这里不再详述,将我的反汇编代码中作出的重定位信息补充列在下面(绿的是已有的,红的是补充的):
通过hexedit修改之后的elf文件的重定位信息部分为:
我是迷人的小尾巴
以下外链,利益相关:
济南江鹏工贸有限公司(山东济南机械加工),济南彤昌机械科技有限公司(山东济南机械加工)