csapp_bomblab&attacklab
第三章
书
- 程序计数器(%rip):将要执行的下一条命令在内存中的地址
- (%rsp)栈顶指针
gcc -Og -S m.c生成汇编objdump -d m.o- 汇编代码中的.开头的通常可以忽略

- 地址只能用64位所以(%eax)是错的
- mov后缀表示的是操作的是这么长的数据
- imulq:操作数有两个的时候正常截断,仅一个操作数:固定寄存器中的值乘这个,然后在两个地方表示
- 反汇编后的硬编码寻址

- call Q:将该指令紧接的下一条(A)的地址压入栈中,程序计数器设置为Q的起始地址,ret返回从栈中弹出A,并将程序计数器设置为A
- 调用者保存和被调用者保存(惯例是%rbp,%rbx以及%r12~15为被调用)
- 当过程 P调用过程Q时,Q必须保存这些寄存器的值,保证它们的值在Q返回到 P时与Q被调用时是一样的。过程Q保存一个寄存器的值不变,要么就是根本不去改变它,要么就是把原始值压人栈中,改变寄存器的值,然后在返回前从栈中弹出旧值。压人寄存器的值会在栈帧中创建标号为“保存的寄存器”的一部分
- 保存在栈上
movq %rdi (%rsp)
- 好的编程实践
typedef int fix_matrix[N][N] - 有时会有变长栈帧:使用%rbp保存
程序的机器级表示
linux> gcc -Og -o p p1.c p2.c
-Og生成最符合原始代码结构的机器代码,实际中采用-O1等优化选项会使代码变得难以理解

- -s生成汇编代码得到的结果忽略.开头得到上述结果
- 函数调用修改某寄存器(%rbx)的值,在调用结束后应该恢复(两种方法,调用函数前后保存和恢复,调用的函数内部保存和恢复(push + pop))


- 反汇编:objdump,机器代码翻译为汇编代码,其实结果有细微差别
寄存器与数据传送指令


Instruciton:指令
大部分满足操作码+(0到多个)操作数(operation code+operand)
操作数:立即数(immediate,以$开头的数)、寄存器(register,%rax)、内存引用(memory reference,(%rax))

- move指令:原操作数移动到目标操作数
x86-64不允许两个都是内存引用,所以内存数据的复制需要两个mov指令- movq的原操作数如果是立即数,只能是32位补码进行符号位扩展之后移动
- movabsq:支持64位,但是目标操作数只能是寄存器
- 任何为寄存器生成32位值的指令都会把高32位置为0
- al是rax的低八位(不同的寄存器表示可能只是相同位置的不同位)
- movs(符号位扩展(原大于目标时)并移动)、movz
栈与数据传送指令


算数与逻辑运算指令
- leaq s,d(load effective address,地址都是q)往目标写地址的值
- (也可以用来计算)

- (也可以用来计算)
- 移位指令SAL,SHL,SAR,SHR,其操作数中的移位量只能是立即数或者特定寄存器%cl中的数
指令和条件码
ALU计算同时会根据计算结果设置条件码寄存器
- 条件码寄存器(condition code register)

cmp指令类似sub:只是不改变寄存器的值
test指令类似and

- 比较a<b,指令应该是sub b a


跳转指令与循环

数据的条件转移代替控制的条件转移(执行效率才更高)


- cmov(条件传送指令,效率更高)
- loop是通过测试与跳转实现的(jle:jump if less or equal)

- switch:实际上是生成了一个跳转表(数组,存放的是位置的指针)
过程(procedures)
- e.g:函数p调用函数q:q执行时p会暂时挂起
- 函数执行所需的空间超过寄存器容量时,就会借助栈上的空间,这部分就叫栈帧
- 调用函数时将其返回地址压入栈中(该地址指明函数结束之后从哪里继续,该过程的汇编指令不是push而是专门的call:将函数第一条指令的地址写入程序指令寄存器)
- 函数参数个数大于六的时候需要使用栈来传递

- 局部变量(放栈上面)


- 在栈中:函数的参数需要八字节对齐,局部变量不需要
- 为了避免寄存器的值覆盖:会有调用时将寄存器的值复制到栈中
- 递归调用容易栈溢出

结构体和联合体
- 对齐:要求起始地址偏移量必须是其本身大小的整数倍

- 联合体的应用:知道其字段是互斥的(非叶子节点无值的二叉树)
- 另一种应用:直接查看某字段的位表示
缓冲区溢出
- 当栈溢出的时候返回可能导致程序访问很奇怪的内存(历史上很多病毒就是这么干的)
- 栈随机化:地址空间布局随机化的一种(ASLR)每次运行同样程序的栈地址都不一样
- 栈破坏检测(返回时看金丝雀值是否被修改)

- 限制可执行代码区域:有的仅可读,不可执行
bomblab
- cmp都是后减前
- jne:如果zo为零则不跳转(test当且仅当全为零才设0)
gdb

b *address |

输出日志
(gdb) set logging file dump.txt |
gdbinit
# 创建当前目录下的 .gdbinit 文件 |
- jmp:无条件跳转
example
0000000000400efc <phase_2>: |
; 函数 phase_3 的入口点 |
- phase:
mov %fs:0x28,%rax栈溢出检测 - phase_6:一段一段的分析作用!(注意前面的名称的提示作用)
;链表 |
Dump of assembler code for function phase_6: |
attacklab
- 0x0a就是换行符的ascii码
- 利用缓冲区溢出,就是程序的栈中分配某个字符数组来保存一个字符串,而输入的字符串可以包含一些可执行代码的字节编码或者一个指向攻击代码的指针覆盖返回地址。那么就能直接实现直接攻击或者在执行ret指令后跳转到攻击代码。
- phase_1 溢出的详解:调用读取函数读取之前会有sub 0x28 %rsp开辟栈空间,函数执行结束之后返回时返回的是当前栈顶的值,只要我的缓冲区将开辟的空间占满了,返回时的栈指针指向的是我的某个输入,就可以恶意执行该输入所指向的代码
- phase_2:
- 找机器码:

- 修改寄存器值并执行:
- 缓冲区溢出到的地方只是我接下来要执行的命令的地址
- 之后call函数(绝对地址编码移到寄存器中再call)(不太好,因为call了之后还会回来)
- ret=pop $rip(栈中地址退出作为下一条命令)
- 使用pushq+ret更好


- 找机器码:
- phase_3:
- man ascii
- 当内存在函数内部随机初始化:就没办法了
- 本来在栈低地址放了东西:然后调用函数,创造栈帧,栈顶向低地址增长,破坏了(解决方法,放在缓存地址之后)
- 安全区:缓冲区之后+返回地址之后
- 字符串末尾这种输入方法不能直接0x0a因为会直接截断输入,使用00隔开就行
- phase_4:
- thwart:反对
- demarcate:区分
- return-oriented programming:利用gadget(常为ret结尾的代码块)
- 两种技术让直接不再可用:
- 栈随机化,之前可以在栈上放代码然后ret指针指过去,现在你不知道会放在哪里了(随机初始化,偏移量还是一样的)
- 让栈上的数据不再可执行
- 有些时候ret结尾的语句可能不太够,但是在x86-64指令集上总是能找到一些(可能是截取了字节码片段)
- 两种技术让直接不再可用:
找字节序列:用正则表达式清理


- 0x90为空,可忽略
00 00 00 00 00 00 00 00 |

ret=>rip=rsp,rsp+=8
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.



