暑期自学知识复习
0x00 栈帧
1.销毁栈帧的两种写法:
1 | leave <=> mov esp, ebp |
2.压参的两种写法:
1 | push 1 sub esp,0xC |
3.两个很棒的例题:
以下是一段C语言程序片段,假设栈可执行,
“jmp esp”
的地址为0x8048570
,s的位置在ebp - 0x30
处。现在已知一段能获得系统控制台权限的shellcode
:“\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80”
请问如何构造输入,使得程序能获得系统控制台权限?1
2
3
4
5
6int main(){
truechar s[16]; // ebp - 0x30
truegets(s);
trueputs(s);
truereturn 0;
}1
2
3
4
5Answer:
输入:
‘a’ * 0x34 + ‘\x70\x85\x04\x08’/*!!!小端序,低位储存低位,高位储存高位,逆序存储方式!!!*/ + ‘\x31\xc9\xf7\xe1\x51\x68\x2f\x2f
\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80’
(注:前0x34字节作为填充可为除换行符以外的所有字符) 【以上为常规思路,若利用Stack Pivoting技术抬高shellcode地址 的方法也正确,故答案不唯一】在函数返回的时候,ESP 恰好指向栈帧中返回地址的后 一个位置!
一般情况下,ESP 寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP 所指的位置恰好是我们所淹没的返回地址的下一个位置。
由于 ESP 寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们可以进行动态定位。
(1)用内存中任意一个 jmp esp 指令的地址覆盖函数返回地址,而不是原来用手工查出的 shellcode 起始地址直接覆盖。
(2)函数返回后被重定向去执行内存中的这条jmp esp指令,而不是直接开始执行shellcode。
(3)由于 esp 在函数返回时仍指向栈区(函数返回地址之后),jmp esp 指令被执行后,处 理器会到栈区函数返回地址之后的地方取指令执行。
(4)重新布置 shellcode。在淹没函数返回地址后,继续淹没一片栈空间。将缓冲区前边一 段地方用任意数据填充,把 shellcode 恰好摆放在函数返回地址之后。这样,jmp esp 指令执行 过后会恰好跳进shellcode。
这种定位 shellcode 的方法使用进程空间里一条 jmp esp 指令作为“跳板”,不论栈帧怎么 “移位”,都能够精确地跳回栈区,从而适应程序运行中 shellcode 内存地址的动态变化。
——<0day安全软件漏洞分析技术>
阅读以下C语言程序,请问如何布置输入可以使得程序执行
system(“/bin/sh”)
这个语句?并使得四字节整型变量a值改变为100(十进制)?其中输入变量s地址位于ebp - 0x20
,变量a的地址为ebp - 0x10
,system
函数地址为0xfff53090
处,“/bin/sh”
字符串的地址为0xfff5602d
处。1
2
3
4
5
6int main() {
char s[8]; // ebp - 0x20
int a = 0xdeadbeef; // ebp - 0x10
gets(s);
return 0;
}1
'a' * 0x10 + '\x64\x00\x00\x00' + 'a' * 0x10 + '\x90\x30\xf5\xff' + 'aaaa' /*用于代替函数返回地址,前面进入system函数,为了符合栈帧格式,ebp下要有函数返回地址。注此时ebp还没有push进去*/ + '\x2d\x60\xf5\xff'
一定注意讲函数操作的栈和实际地址分开!!!
0x01 汇编基础
1.16位、32位、64位汇编区别
寄存器
函数调用方式不同,32位、64位不再使用栈帧
16位操作系统的中段地址和偏移地址在32位中消失了,在32位操作系统中统一采用平坦的内存地址模式寻址。
16位操作系统中的程序运行在RING0级,也就是说普通程序和操作系统程序运行在同一个级别并且拥有最高权限,而32位操作系统中的程序一般只拥有RING3级别运行权限,程序的所有操作都受到操作系统控制,若程序要获得RING0操作特权只能通过驱动程序实现。
386保护模式:分系统态与用户态,相当于起到隔离作用。
16位操作系统的可执行文件格式和32位操作系统的可执行文件格式不同,在32位的Windows操作系统中,可执行文件的格式加PE格式,32位的Windows操作系统运行在CPU的保护之上,而16位的系统则运行在CPU的实模式上。
2.lea
lea
指令可以将有效地址传送到指定的的寄存器。对标志位无影响。
有时候可以用mov
代替,也有特定起作用的时候。
3.test
test
命令将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。test
命令的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。
test eax,eax
用于判断eax
是否为0。
3.例题
阅读下面汇编代码,回答下面三个问题:
1 | truemov ecx, 0 |
附加说明:从esp + 0x14
这个内存地址开始,由低地址到高地址存放着一段二进制串,分别为:
E6 EC E1 E7 BA F4 E5 F3 F4 F4 E5 F3 F4 00
另外,在本程序中,[esp]
存放着接下来所调用函数的第一个参数,[esp + 0x4]
存放着接下来所调用函数的第 二个参数。在函数调用后面紧接着的eax
存放着函数的返回值。
①红色加粗的那段指令可不可以改写成“mov edx, esp + 0x28”
?请说明理由。
②从esp + 0x28
这个内存地址开始,由低地址到高地址要存放着一段什么样的二进制串,XXX函数才会被调用?
③若第②题的二进制串的意义是一段ASCII码,请把它翻译成字符串。
Answer:
① 不能,因为语法上不允许
【解析】其实这条指令的意义是将
esp+0x28
这个结果赋给edx
,但是若直接mov edx, esp+0x28
,这个在语法上不允许,但是mov edx, [esp+0x28]
是允许的,不过这样含义就变了,变成了将esp+0x28
这个位置存放的值给edx
了,所以变通一下,只要把它改成lea edx, [esp+0x28]
就行了,这有点像C语言中*(&a)=1
一样,实际上就是a赋值1,但是因为某些其他原因需要这么绕一下
!!!注意理解,该程序中需要引用esp+0x28
做地址,但无法直接用mov
赋,于是用了lea
。
② 66 6C 61 67 3A 74 65 73 74 74 65 73 74 00
【解析】我相信看完程序和题干内容后,你应该能反应过来,在
esp+0x28
和esp+0x14
这两个位置开始分别存放着两个二进制字串,我们姑且把它命名为a和b。现在是已知esp+0x14
这个字串让你推出esp+0x28
这个字串。先仔细分析L1那一小节,它先把esp+0x28
这个值赋给edx
作为字串的首地址,然后再把edx
加上ecx
,然后观察一下,这个ecx
是从0开始,每次循环都inc
加1,那么edx
加上ecx
相当于就是遍历这个二进制字串的每一个元素,用C语言风格写就是a[ecx]
。这时候mov al, [edx]
很关键,它说明取出edx
所指向的元素,而且只取一个字节(因为左值是al
,字长1字节)。这时候test al, al
和jz L2
说明如果遇到0就不做了,直接跳到L2去,否则就将al
与0x80
做“或”运算,并且把做完的结果又放回去。所以L1段的功能就是把二进制串a整体与0x80
做“或”运算,以00
作为结束。这时候来看L2段,给
[esp+0x4]
和[esp]
赋值的意义提示里面已经说了,是为了给strcmp
函数传参,这个具体是为什么会在“栈帧”那一讲细说,这里只要知道是这个意思就行了,所以L2段写成C语言:
eax = strcmp
(二进制串b的首地址,二进制串a的首地址);
1
2
3
4
5
6
7
8
9 > if (eax == 0) //比较成功
> {
> XXX();
> }
> else {
> L3:
> ...
> }
>
所以这个程序的意思就很简单了,先将a字串与
0x80
做“或”运算再跟b比对,如果比对成功就可以执行XXX函数,否则不能执行。现在题目是已知b来反推a,所以我们来分析一下逆算法。当m与
0x80
做“或”运算等于n时,由于0x80写成二进制就是10000000,也就是说m的后七位实际上是和n的后七位一样,关键是m的最高位有可能是0,也有可能是1,其实这道题我应该在第②题就告诉你们最终结果的含义是ASCII码,这样你们就知道最高位是0了,但是就算不提示,你们也要明白最终结果肯定是越接近可读字符越好,这也是CTF比赛中的潜规则,所以说如果二进制串中出现0x3X
,0x4X
,0x6X
和部分0x7X
(X表示一个任意的十六进制位)时一定要高度留心,因为它们作为ASCII
码时是可读字符。所以结果只要把b串的后七位不变,最高位变为0就行。
b串:
E6 EC E1 E7 BA F4 E5 F3 F4 F4 E5 F3 F4 00
a串:
66 6C 61 67 3A 74 65 73 74 74 65 73 74 00
`第4题的第③小问也就自然了,把a串随便放到一个
ASCII
码转字符串的工具里面去,很容易发现其明文为:
flag:testtest
0x02 函数调用约定
__cdecl
: C/C++默认方式,参数从右向左入栈,主调函数销参
__stdcall
: windows API默认方式,参数从右向左入栈,被调函数销参;返回时命令为ret 0Ch,需要销毁三个参数。并且可以看到三个参数都是通过栈帧传递的。
__fastcall
: 快速调用,参数优先从寄存器(ECX、EDX)传入 ;返回时命令为ret 4,表示一个需要销毁的参数。另外两个参数通过寄存器传递。
0x03 PE头
为装载时大小一致,补充部分区域用0补齐。
DOS头为了兼容DOS环境。
PE文件分析器了解一下。
0x04 IDA常用操作(先码为敬orz)
1.IDA常用快捷键
动态调试相关
ESC
退到上一个操作地址F4
运行到光标处F7
单步执行F8
过程执行G
搜索地址或者符号CTRL+F7
运行到函数返回地址CTRL+ALT+B
打开断点列表C伪代码相关
空格
反汇编窗口的图形模式与文本模式的互换分号键
注释Num + *
修改数组信息N
重命名X
交叉引用Y
明确设置函数信息F5
C伪代码ALT+A
修改字符串风格汇编代码相关
空格键
反汇编窗口切换文本跟图形C
(code) 光标地址出内容解析成代码P
在函数开始处使用P,从当前地址处解析成函数D
(data) 解析成数据A
(ASCII) 解析成ASCIIU
(unDefined) 解析成未定义的内容ALT+A
修改字符串风格CTRL +S
二进制段的开始地址结束地址其他
ALT+B
搜索16进制 搜索opcode 如ELF文件头ALT+T
搜索文本ALT+M
添加标签CTRL+M
列出所有标签CTRL +S
二进制段的开始地址结束地址
2.IDA插件
-
超级好用的打补丁插件
IDApython
自行编写脚本
无需安装
-
找加密方式
-
调试安卓中so文件
-
CPU模拟器
-
符号执行
0x04 小结
- 伪C代码具有欺骗性,汇编代码阅读及转换能力需提高
- 做题太少,看书太少,理解不足,练习不足,熟练度不足