之前的方法太坑了,我更新了一下phase2 _(:з」∠)_ 55555
直接复制过来排版太烂,我也懒得改了_(:з」∠)_,如果看的不舒服,移步:
update:关于0xffffffa6为什么反汇编之后会变?其实是我call指令用错啦,举个例子:
所以实际应该在getcode.s里面填0xffffffaa才对,不过意思到了就行,下面就不改了
第3章 各阶段的原理与方法
每阶段40分,phasex.o 20分,分析20分,总分不超过80分
(请先移步阅读)
3.1 阶段1的分析
程序运行结果截图:
分析与设计的过程:
首先
直接将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的一个字节。
3.2 阶段2的分析
程序运行结果截图:
分析与设计的过程:
(不想看我多哔哔,可以直接到这里)
分析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地址是变化的,我们是无法直接获得压栈的地址值的。
解决问题
- 如何进行相对位置调用:在汇编指令中call指令就是根据PC与跳转目标指令的相对差来进行修改PC值从而完成跳转的,注意,这里的PC值指的是call这一条完整指令的下一条指令的地址值。
- 如何获得目标字符串MYID的地址:通过观察do_phase函数,我们发现一个名为x86.get_PC_thunk.ax的call调用,跟进发现其中的执行逻辑是:
询问搜索引擎后得知,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
得到:
(反编译之后 call后面的值变化的话,自己修改回来)
剩下的就是用HexEdit更改二进制了,二进制在上图左边。
完事儿,(´థ౪థ)σ。
获得16进制代码的方法:
- 编写汇编代码入getcode.s
- gcc –m32 –c getcode.s获得getcode.o
- objdump –d getcode.o > getcode.txt 将反汇编代码放入getcode.txt中,就可以得到上图的反汇编代码了,左侧就是相对应的16进制代码。
使用HexEdit修改phase2.o,修改之后为:
找到插入位置的方法(其实只要看见一片90无脑覆盖就行了):
- readelf -a phase2.o > phase2.elf 得到.text节相对于elf文件头的偏移地址0x44:
- objdump -s -d phase2.o > phase2.txt得到第一个nop相对于.text节的偏移量0x4e:
- 计算第一个nop在HexEdit中的地址0x44+0x4e=0x92
- 在HexEdit中寻找其他位置的操作类似。
3.3 阶段3的分析
程序运行结果截图:
获得数组名称
使用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
3.4 阶段4的分析
程序运行结果截图:
(注:因为这个题个人感觉PPT上叙述的做法有点冲突,所以我是直接修改的linkbomb4,所以在测试的时候直接运行linkbomb4即可)
分析与设计的过程:
了解框架:
分析反汇编代码:
-
- 通过edb获得cookie的值,可以得到:
-
- 定位switch主体代码:
-
-
- 分析
- 可以看出这里代码块的主要功能就是计算地址值到eax,然后跳转到eax所指向的地址。Eax的计算公式为((%eax-0x41)<<2+%ebx-0x176c),外面的一个括号代表寻址,这里是将cookie值作为索引映射到switch跳转表,需要注意的是switch跳转表保存在.roddata中,同时,当只有phase4.o的时候switch的跳转表是不能确定的,只有当将main.o和phase4.o链接之后才能确定跳转表的值。当程序运行时,程序先拿到跳转表的值,跳转表中存放着相对偏移位置,通过 %eax=%ebx+偏移量 获得存储在.text段中共的case代码段地址,之后跳转执行。
- 根据.rodata段的性质我们可以确定我们的破解策略:将main.o和phase4.o进行链接成为linkbomb4,通过HexEdit程序更改linkbomb4程序的rodata段中switch的跳转表为满足cookie映射先后顺序。(注意,每次连接过程可能会产生不同的跳转表)。PS:注意到PPT中是让修改phase4.o的rodata节,但是这里的rodata是在链接之后才能确定的,如果修改phase4.o的rodata链接之后会被覆盖,因此只能修改linkbomb4。
- 分析
-
构造答案:
-
- 首先通过edb查看没有修改过的linkbomb4,发现第一个cookie值“X”对应的跳转表中的偏移量为0xffffe738,对应到case代码块执行输出0x33,查看phase4的反汇编代码:
b)通过phase4反汇编代码我们可以确定case代码块之间相对位置,通过输出0x33的代码块的跳转表偏移量,我们可以得到所有的case类在跳转表里面对应的偏移量,我们选择输出指定字母串为学号的case块的跳转表偏移量 按cookie映射顺序与位置 填入到.rodata跳转表之中。说起来有点儿绕,我们不妨看一下截图:
截图说明:前面的6只是为了标识开始而已;其中填入0的都是cookie映射不到的;以0xffffe738为例,偏移量对应cookie-“X”(填入到了跳转表中‘x’映射到的位置),而0xffffe738是我们填入跳转表中的值,凭借跳转表程序跳转到switch的对应case语句,通过反汇编代码我们知道case之间的相对位置,在0xffffe738基础上进行加减就可以获得其他case块对应的跳转表值。
3.5 阶段5的分析
程序运行结果截图:
分析与设计的过程:
-
- EDB操作基础知识:首先点击运行,这时程序会运行前面的初始化函数到main,此时可以开始单步调试。
- step into:执行代码,如果是函数则进入。
- step over:执行代码,如果是函数会执行然后跳过
- step out:如果没有断点会直接跳到函数的ret指令处。
- F2调用右键的Toggle BreakPoint设置断点, 当然右键也有Conditionnal BreakPoint 条件断点的选择,对应Shift+F2。
- Rigisters和Stack窗口可以分别查看运行处寄存器和栈的值。Data Dump可以查看内存区域的值,这一块内存区域应该是伴随程序分配的内存区域。三个窗口呈现的形式都是左边是二进制,右边是字符串,如果真的存储的是字符串类型,那么我们可以通过右边直接看到,比较方便。
- 如果想要查看程序不同段的内存,可以通过View->Memory Regions查看。
- 无论是Registers还是Stack都可以右键直接设定二进制的值,这个特性可以用来进行程序的调试工作。
- Ctrl+F调用Plugin中的BinarySearcher可以直接搜索内存区域中的字符串。
- 选定一行,Ctrl+*调用邮件的set EIP to this instruction,跳过前面的代码直接运行到当前行。
- EDB操作基础知识:首先点击运行,这时程序会运行前面的初始化函数到main,此时可以开始单步调试。
了解以上知识之后,简单使用edb运行程序应该不成问题。
-
- Readelf:将所有的elf信息输出重定向到phase5.elf文件中,操作命令为readelf –a phase5.o > phase5.elf。在phase5.elf文件中,我们可以观察到Section Headers,rel.text,.rel.rodata和.symtab等各个Section信息,.rel.重定向节包括
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文件的重定位信息部分为:
我是迷人的小尾巴
以下外链,利益相关: