PC平台逆向破解实验报告(待补充)
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
实践内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
1 预备知识
linux下的破解自然需要熟悉linux的命令与操作,这里给出一些linux下有关二进制的一些知识。
1.1 汇编语言基础
一些同学已经汇编语言程序设计课程学习过汇编语言了,我们使用的语法是Intel的。而在linux下用gdb和objdump得到的汇编代码的语法是AT&T的(严格来说有些区别),这两者之间存在一些区别。
我们学的还是8086的实模式下的16位汇编语言,还需要了解32位寄存器。这个转换还是比较容易的。
1.1.1 Intel语法与AT&T语法的区别
区别 | Intel语法 | AT&T语法 | 备注 |
---|---|---|---|
源操作数和目的操作数的位置 | MOV EAC, ECX | movl %ecx, %eax | 将ECX的值存入EAX |
常量和寄存器的表示 | MOV EAX, 12h | movl $0x12, %eax | 将0x12存入EAX |
寻址方式的表示 | MOV EAX, [EBX+20h] | movl 0x20(%ebx), %eax | Intel语法中[base+index * scale+disp]相当于AT&T中的disp(base, index, scale) |
MOV EAX, [EBX+ECX * 4h -20h] | movl -0x20h(%ebx, %ecx, 0x04),%eax | ||
指令后缀 | MOV EAX, dword ptr [EBX] | movl (%ebx), %eax |
AT&T语法中,指令后缀l, w, b分别对应long, word, byte, 在Intel语法里就是dword ptr, word ptr, byte ptr。
1.1.2 x86寄存器与常用指令
x86处理器有一下的一些通用寄存器:
- %eax (许多函数的返回值默认存在这个寄存器里)
- %ebx
- %ecx
- %edx
- %esi
- %edi
还有几个专用寄存器:
- %ebp
- %esp (指向栈顶)
- %eip (保存下一条指令的地址)
- %eflag (各种标志位都在这里)
常用汇编指令
指令 | 机器码 | 说明 | 示例 |
---|---|---|---|
NOP | 0x90 | 空指令,啥也不做 | |
JNE | 0x75 | 条件转移指令,标志位ZF==0 则跳转 | |
JE | 0x74 | 条件转移指令,标志位ZF==1 则跳转 | |
JMP | 0xe9 后面是四个字节的偏移量 | 无条件转移指令 | |
0xeb 后面是两个字节的偏移量 | |||
0xff25 后面是四个字节的地址 | |||
CMP | 比较指令,相当于减法,可以改变标志位 | ||
call | 0xe8 后面是四个字节的偏移量 | 函数调用,可以认为是先push下一条指令的地址,再jmp到对应的地址 |
1.1.3 其他内容
一般来说,我们看到的汇编语言函数的结构是这个样子的:
<function_name>:
pushl %ebp
movl %esp, %ebp
subl 4*N, %esp # N为局部变量个数
# 函数主体
leave
ret
leave
指令相当于
movl %ebp, %esp
popl %ebp
于是调用函数的时候,看到的汇编语言指令可能是这个样子的
# 函数: <function_name>
# 函数参数: <p1> <p2> <p3>...<pn>
pushl <pn>
...
...
pushl <p2>
pushl <p1>
call <function_name> # 执行call命令时,会将<next_code>的地址压栈
<next_code>:
此时,栈看起来就是这个样子的:
低地址
+--------------+
| 局部变量2 <--- -8(%ebp)
+--------------+
| 局部变量1 <--- -4(%ebp)
+--------------+
| 旧%ebp <--- (%ebp)
+--------------+
| RET地址 <--- 4(%ebp)
+--------------+
| 参数1 <--- 8(%ebp)
+--------------+
| 参数2 <--- 12(%ebp)
+--------------+
| ... ...
+--------------+
| 参数N <--- N*4+4(%ebp)
+--------------+
高地址
1.2 gdb的常用命令
下面给出常用的几个gdb命令,并不全面。在以后的实验中慢慢学习gdb就可以了,不用强求。
gdb命令 | 参数 | 含义 | 示例 |
---|---|---|---|
break | 地址 | 设置断点,简写为b,地址类型包括:函数名,行号,*内存地址 | break main; break 12; break *0x08048373 |
run | 命令行参数 | 运行程序,可简写为r | |
clear | 地址 | 清除断点,与break相反 | |
info | break | 显示断点信息 | |
continue | 继续执行程序 | ||
attach | 进程号 | 调试已经运行的程序 | |
quit | 退出 |
1.3 objdump的常见用法
objdump可以快速方便地反编译简单的、未被篡改的二进制文件,它可以读取所有常用的ELF类型的文件。下面给出objdump常见的用法。
- 查看ELF文件中所有节的数据或代码
objdump -D <elf_object>
- 只查看ELF文件中的程序代码
objdump -d <elf_object>
- 查看所有符号
objdump -tT <elf_object>
2 实验过程
2.1 直接修改程序机器指令,改变程序执行流程
2.1.1 思路
第一个实验的思路非常简单,原本的getShell函数是程序中的“死代码”,正常情况下永远也不会执行。
我们通过修改main函数中的call指令,使得原本执行foo函数的程序转而去执行getShell函数。总结下来就是下面两步:
- 找到getShell函数的位置
- 修改main函数中,call指令的参数,使得程序调用getShell函数
2.1.2 过程
先用objdump反汇编目标程序,输入指令objdump -d 20155110pwn1 | less
用less分页显示比more更方便,可以像使用vim那样用“/”查找字符串。
然后再看看main函数
我们发现,在main函数中,按照正常流程,call指令会调用foo函数,e8是call指令的机器码,后面跟着四字节的偏移量ff ff ff d7(小端序,补码)。这里的偏移量是怎么求的呢?
call指令在执行时,会EIP当前的值,也就是下一条指令的地址——0x080484ba压栈,然后修改寄存器EIP,EIP+偏移量= 0x080484b + 0xffffffd7 = 0x08048491(32位的有符号数运算),将EIP指向foo函数的起始地址。
我们需要修改call指令的偏移量,根据“目的地址=EIP(call的下一条指令的地址)+偏移量”,新的偏移量 = 0x0804847d(getShell函数的起始地址) - 0x080484ba = 0xffffffc3(补码运算)
接着用十六进制编辑器,或者vim来修改目标程序即可。这里使用vim,输入:%!xxd
进入十六进制模式,修改偏移量。
修改后,得到
运行修改后的程序,得到shell
未完待续
原文地址:https://www.cnblogs.com/wyf12138/p/8541895.html