第三章

  • 程序计数器(%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
p $rax # 打印寄存器 rax 的值,且要求指定数据类型(*)
p $rsp # 打印栈指针的值
p/x $rsp # 打印栈指针的值,以十六进制显示
p/d $rsp # 打印栈指针的值,以十进制显示

x/2x $rsp # 以十六进制格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/2d $rsp # 以十进制格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/2c $rsp # 以字符格式查看栈指针 %rsp 指向的内存位置 M[%rsp] 开始的两个单位。
x/s $rsp # 把栈指针指向的内存位置 M[%rsp] 当作 C 风格字符串来查看。

x/b $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 1 字节。
x/h $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 2 字节(半字)。
x/w $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 4 字节(字)。
x/g $rsp # 检查栈指针指向的内存位置 M[%rsp] 开始的 8 字节(双字)。

info registers # 打印所有寄存器的值
info breakpoints # 打印所有断点的信息

delete breakpoints 1 # 删除第一个断点,可以简写为 d 1


输出日志

(gdb) set logging file dump.txt
(gdb) set logging on
(gdb) x/s 0x401338
(gdb) set logging off

gdbinit

# 创建当前目录下的 .gdbinit 文件
touch .gdbinit
# 创建 .config/gdb 文件夹
mkdir -p ~/.config/gdb
#若存在也行
# 允许 gdb 预加载根目录下所有的文件
echo "set auto-load safe-path /" > ~/.config/gdb/gdbinit

# ./gdbinit
# 设置默认文件输入,这样我们不必每次手动输入答案
set args psol.txt

# 可以为 explode_bomb 函数设置断点,这样我们就可以在爆炸之前打断程序的执行
# 但是由于其会打印输出信息,所以后面有更具有针对性的设置,跳过信息发送函数
# 所以这里就不再设置断点了
# b explode_bomb

# 为各个 phase 函数设置断点,用以观察其执行过程
# 如果你做完了某个 phase,可以将其注释掉,这样就不会再进入该 phase 了
b phase_1
b phase_2
b phase_3
b phase_4
b phase_5
b phase_6

# 为校验函数设置断点
b phase_defused
# 为此断点编程
command
# 直接跳到返回语句处,跳过校验流程
jump *(phase_defused + 0x2A)
end


# 以下代码务必保留!!!

# 为 explode_bomb 中触发 send_msg 函数的地方设置断点
b *(explode_bomb + 0x44)
# 为此断点编程
command
# 直接跳到 exit 退出函数处,跳过发送信息流程
j *(explode_bomb + 0x81)
end

# 炸弹已经安全化,可以放心地拆弹了,开始运行程序
r
  • jmp:无条件跳转

example

0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 call 40145c <read_six_numbers> ;可以结合函数名(
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) ;注意()比的是寄存器中指向内存的值,直接比的是寄存器本身的值
400f0e: 74 20 je 400f30 <phase_2+0x34>
400f10: e8 25 05 00 00 call 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34> ;无条件跳转
400f17: 8b 43 fc mov -0x4(%rbx),%eax ;这里实际上应该是数组往后退(取这个地址中的内容)
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 call 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b>
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 ret
; 函数 phase_3 的入口点
0x0000000000400f43 <+0>: sub $0x18,%rsp
; 在栈上分配 0x18 (24) 字节的空间,为局部变量预留位置。

0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
; 加载栈地址 rsp+12 到 rcx 寄存器。这是为 sscanf 准备的第三个参数,
; 用于存放读取的第二个整数。

0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
; 加载栈地址 rsp+8 到 rdx 寄存器。这是为 sscanf 准备的第二个参数,
; 用于存放读取的第一个整数。
;sscanf第一个参数是输入缓冲区,第二个是格式字符串
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
; 将格式化字符串的地址(假设内容为 "%d %d")加载到 esi 寄存器,
; 作为 sscanf 的第一个参数。

0x0000000000400f56 <+19>: mov $0x0,%eax
; 在调用 sscanf 之前,将 eax 清零。这是 x86-64 调用约定中
; 对于可变参数函数(如 sscanf)的要求。

0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
; 调用 sscanf 函数,从输入中读取两个整数,并存放到之前设置的栈地址上。
; 函数的返回值(成功匹配并赋值的项数)存放在 eax 中。

0x0000000000400f60 <+29>: cmp $0x1,%eax
; 比较 sscanf 的返回值 eax 和 1。

0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
; "Jump if Greater"。如果 eax > 1(即成功读取了至少 2 个整数),
; 则跳转到 <phase_3+39>,跳过引爆炸弹的指令。

0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
; 如果只读取了 0 个或 1 个整数,则调用 explode_bomb 引爆炸弹。

0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
; 将存放在 rsp+8 的第一个输入整数与 7 进行比较。

0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
; "Jump if Above"。如果第一个输入整数大于 7 (无符号比较),则跳转到 <phase_3+106>,
; 那里会引爆炸弹。这意味着第一个整数的有效范围是 0 到 7。

0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
; 将第一个输入整数(位于 rsp+8)移动到 eax 寄存器。

0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8)
; 这是一个跳转表(Jump Table),等价于 C 语言中的 switch-case 语句。
; 它会根据 eax 的值(0 到 7)跳转到不同的代码块。
; 跳转地址的计算方式是:基地址(0x402470) + 索引(eax) * 8字节。

; --- 以下是 switch-case 的各个分支 ---

0x0000000000400f7c <+57>: mov $0xcf,%eax ; case 0: eax = 207
=> 0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>

0x0000000000400f83 <+64>: mov $0x2c3,%eax ; case 1: eax = 707
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>

0x0000000000400f8a <+71>: mov $0x100,%eax ; case 2: eax = 256
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>

0x0000000000400f91 <+78>: mov $0x185,%eax ; case 3: eax = 389
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>

0x0000000000400f98 <+85>: mov $0xce,%eax ; case 4: eax = 206
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>

0x0000000000400f9f <+92>: mov $0x2aa,%eax ; case 5: eax = 682
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>

0x0000000000400fa6 <+99>: mov $0x147,%eax ; case 6: eax = 327
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>

; --- switch-case 的 default 分支 (处理大于 7 的情况) ---
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
; 如果第一个整数大于 7,会跳转到这里引爆炸弹。

0x0000000000400fb2 <+111>: mov $0x0,%eax ; 这个分支在正常流程中似乎不会被执行。
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>

; --- switch-case 的最后一个分支 ---
0x0000000000400fb9 <+118>: mov $0x137,%eax ; case 7: eax = 311

; --- switch-case 结束后,所有分支汇合于此 ---
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
; 将存放在 rsp+12 的第二个输入整数与 eax 中(由 switch 语句设置)的值进行比较。

0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
; "Jump if Equal"。如果第二个整数等于 eax 的值,则跳转到 <phase_3+134>,成功通过。

0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
; 如果两个值不相等,引爆炸弹。

0x0000000000400fc9 <+134>: add $0x18,%rsp
; 释放之前在栈上分配的 24 字节空间。

0x0000000000400fcd <+138>: ret
; 函数成功返回。
  • phase:mov %fs:0x28,%rax栈溢出检测
  • phase_6:一段一段的分析作用!(注意前面的名称的提示作用)
;链表
0x6032d0 <node1>: 332 1 6304480 0
0x6032e0 <node2>: 168 2 6304496 0
0x6032f0 <node3>: 924 3 6304512 0
0x603300 <node4>: 691 4 6304528 0
0x603310 <node5>: 477 5 6304544 0
0x603320 <node6>: 443 6 0 0
Dump of assembler code for function phase_6:
=> 0x00000000004010f4 <+0>: push %r14 # 保存被调用者保存寄存器 r14
0x00000000004010f6 <+2>: push %r13 # 保存 r13
0x00000000004010f8 <+4>: push %r12 # 保存 r12
0x00000000004010fa <+6>: push %rbp # 保存 rbp
0x00000000004010fb <+7>: push %rbx # 保存 rbx
0x00000000004010fc <+8>: sub $0x50,%rsp # 为栈上局部变量开辟 0x50 字节

0x0000000000401100 <+12>: mov %rsp,%r13 # r13 = 输入数字存储起始地址
0x0000000000401103 <+15>: mov %rsp,%rsi # rsi = 将被 read_six_numbers 写入的位置
0x0000000000401106 <+18>: call 0x40145c <read_six_numbers> # 读取用户输入的 6 个整数,存到栈上

0x000000000040110b <+23>: mov %rsp,%r14 # r14 = 输入数组起始地址
0x000000000040110e <+26>: mov $0x0,%r12d # r12d = 外层循环计数 i = 0

0x0000000000401114 <+32>: mov %r13,%rbp # rbp = 当前比较的数字地址(当前 i)
0x0000000000401117 <+35>: mov 0x0(%r13),%eax # eax = input[i]
0x000000000040111b <+39>: sub $0x1,%eax # eax = input[i]-1
0x000000000040111e <+42>: cmp $0x5,%eax # 若 input[i]-1 > 5,则数字超范围(>6)
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> # <=5 合法
0x0000000000401123 <+47>: call 0x40143a <explode_bomb> # 否则爆炸(数字必须 1~6)

0x0000000000401128 <+52>: add $0x1,%r12d # i++
0x000000000040112c <+56>: cmp $0x6,%r12d # 是否处理 6 次?
0x0000000000401130 <+60>: je 0x401153 <phase_6+95> # 6 个数字全部验证范围后跳出

0x0000000000401132 <+62>: mov %r12d,%ebx # 内层循环 j = i
0x0000000000401135 <+65>: movslq %ebx,%rax # rax = j (64bit)
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax # eax = input[j]
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) # 比较 input[j] == input[i] ?
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> # 不等则继续
0x0000000000401140 <+76>: call 0x40143a <explode_bomb> # 相同数字 => 爆炸(要求 6 数互不重复)

0x0000000000401145 <+81>: add $0x1,%ebx # j++
0x0000000000401148 <+84>: cmp $0x5,%ebx # j <= 5 ?
0x000000000040114b <+87>: jle 0x401135 <phase_6+65> # 内层继续循环

0x000000000040114d <+89>: add $0x4,%r13 # 移到下一个 input[i]
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> # 外层循环继续(验证所有数字)

# 到此:数字要求 1~6 且互不重复

# ----------------------------
# 接下来:将 input[i] 替换为 7 - input[i]
# ----------------------------

0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi # rsi = rsp + 0x18 (结束地址)
0x0000000000401158 <+100>: mov %r14,%rax # rax = array pointer
0x000000000040115b <+103>: mov $0x7,%ecx
0x0000000000401160 <+108>: mov %ecx,%edx # edx = 7
0x0000000000401162 <+110>: sub (%rax),%edx # edx = 7 - input[i]
0x0000000000401164 <+112>: mov %edx,(%rax) # input[i] = 7 - input[i]
0x0000000000401166 <+114>: add $0x4,%rax # rax += 4 (下一个数)
0x000000000040116a <+118>: cmp %rsi,%rax # 循环 6 次完成变换
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>

# 接下来根据转换后的数字选择链表节点

0x000000000040116f <+123>: mov $0x0,%esi # i = 0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163> # 跳到循环入口

0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx # rdx = rdx->next
0x000000000040117a <+134>: add $0x1,%eax # eax++
0x000000000040117d <+137>: cmp %ecx,%eax # 是否走到 input[i] 次?
0x000000000040117f <+139>: jne 0x401176 <phase_6+130> # 否则继续 next

0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148> # 找到了,跳转

0x0000000000401183 <+143>: mov $0x6032d0,%edx # head = node1(链表头:值 1)
# 假定链表 1→2→3→4→5→6

0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) # 保存选出的节点指针到数组
0x000000000040118d <+153>: add $0x4,%rsi # i += 4(因为存放指针,每步占4?)
0x0000000000401191 <+157>: cmp $0x18,%rsi # 做完 6 次?
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>

0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx # ecx = input[i]
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> # 若 input[i] == 1,则直接选第1节点

0x000000000040119f <+171>: mov $0x1,%eax # 从链表头开始
0x00000000004011a4 <+176>: mov $0x6032d0,%edx # rdx = head (node1)
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130> # 否则根据 input[i] 走链表

# 选出的6个链表节点已按顺序存入数组

0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx # rbx = node[input[0]]
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax # rax = 第二个元素地址
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi # rsi = 终点地址
0x00000000004011ba <+198>: mov %rbx,%rcx # rcx = 当前链尾
0x00000000004011bd <+201>: mov (%rax),%rdx # rdx = node[i]
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) # 当前->next = node[i]
0x00000000004011c4 <+208>: add $0x8,%rax # 下一个节点地址
0x00000000004011c8 <+212>: cmp %rsi,%rax # 是否完成6节点链表重建?
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx # rcx = 当前链尾 = node[i]
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>

0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) # 最后一个节点 next = NULL

# 现在检查新链表是否递减(大到小)
# 若不是严格递减 => 爆炸

0x00000000004011da <+230>: mov $0x5,%ebp # 循环 5 次(有 6 个节点)
0x00000000004011df <+235>: mov 0x8(%rbx),%rax # 取 next 节点
0x00000000004011e3 <+239>: mov (%rax),%eax # eax = next->value
0x00000000004011e5 <+241>: cmp %eax,(%rbx) # 当前->value >= next->value ?
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250> # 若递减则 OK
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb> # 否则爆炸

0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx # rbx = next,继续
0x00000000004011f2 <+254>: sub $0x1,%ebp # 次数--
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235> # 继续比较链表

# 通过检查,安全退出
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: ret
End of assembler dump.

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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00//填充
ab 19 40 00 00 00 00 00//受害函数返回时栈顶,指向gadget1
fa 97 b9 59 00 00 00 00//gadget1执行时的栈顶,使用pop可以弹出
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

ret=>rip=rsp,rsp+=8