汇编基础

前言

复习一下汇编

一、X86

1.汇编指令

(1)跳转

依据ZF(Zero)、SF(Symbol))、OF(Overflow)、PF(Parity)、CF(Carry)五种状态标志位有对应的跳转JZ(JNZ)/JS(JNS)等五种跳转

状态标志位除了以上五种还有AF(Assistant)辅助进位

此外还有Jmp无条件跳转

(2)标志位

以上的六种状态标志位外,还有以下三种标志位

TF(trap):跟踪标答志位:单步标志

IF(interregnum):中断标志位

DF(direction):方向标志位

2.寄存器

(1)传参

64位:rdi rsi rdx rcx r8 r9 栈

32位:栈传参

(2)栈指针

RSP、RBP

二、ARM

1.寄存器关系

32

  • R0R3:函数调用参数,代表第04个参数,剩下的参数从右向左依次入栈,函数返回值保存在R0中。(对应在aarch64中为R0R7,但是gdb调试或者IDA中一般显示X0X30,同时还有低32位的W0~W30)

  • SP:类似rsp,esp,栈指针

  • FP:类似ebp,栈指针

  • LR:当发生函数调用时,会保存调用函数处的地址,退出函数时赋值给PC。

  • PC:类似eip,rip,存储下一条指令的地址。

  • R11:类似ebp,栈指针,其实就差不多是FP

2.汇编指令

(1)寄存器传送MOV

寄存器之间还是MOV指令,比如MOV R2,R0;但是如果涉及到内存上的,则需要先加载到寄存器中,或者把寄存器中的值存储到内存中

(2)寄存器-内存传送

STR(store reg)/LDR(load reg)以及STM(store multiple)/LDM(load multiple)

①STR/LDR模式

STR Ra [Rb]:

Ra中的数据存储到Rb指向的内存中

LDR Ra [Rb]

Rb指向的内存中的值加载到Ra中

此外还有如下情形

1
2
STR  R0,[R1, #12]  // R0 --> [R1+12]
LDR R4,[R5, R6] // R4 <-- [R5+R6]

②STM/LDM模式

STM R0, {R4,R5}

R4的值传送给R0+x对应的内存单元,然后R5的值传给R0+x+x对应的内存单元

这个x即由后缀决定,分别有以下几种

  • DB(Decrease Before):每次传送前R0加上x,x为负数

  • DA(Decrease After):每次传送后R0加上x,x为负数

  • IB(Increase Before):每次传送前R0加上x,x为正数

  • IA(Increase After):每次传送后R0加上x,x为正数

而X则在不同的CPU位数下不同,32为4,64则为8。

此外堆栈的增长方向可以不同,也可以在不同情形下作为后缀,效果一样的

  • FD(Full Descent):满递减堆栈
  • FA(Full Ascent):满递增堆栈
  • ED(Empty Descent):空递减堆栈
  • EA(Empty Ascent):空递增堆栈

满则代表栈指针指向栈顶,空则代表栈指针不指向栈顶

(3)跳转指令

①分支跳转(Branch)

B

无条件跳转

BX <Rm>

由寄存器给出地址

若 Rm 的 bit[0] 为1,切换到 Thumb 指令执行;

若 Rm 的 bit[0] 为0,切换到 ARM 指令执行。

BL

类似于Call,会存入返回地址到LR寄存器

BLX/BLR

即类似对应组合

②条件跳转

依据CPSR寄存器下的ALU状态标志位

CPSR寄存器包含下面的ALU状态标志:

  N  Set when the result of the operation was Negative(结果为负数)

  Z Set when the result of the operation was Zero(结果为0)

  C Set when the operation result in a Carry(发生进位,或借位)

  V Set when the operation caused oVerflow(操作造成溢出)

  Q ARM architecture v5E only sticky flag

还有BEQ、BNE

三、MIPS

主要介绍mipsel架构的,小端序,大端的mips很少考到,而且也基本都是大同小异

1.寄存器关系

REGISTER NAME USAGE
$0 $zero 常量0(constant value 0)
$1 $at 保留给汇编器(Reserved for assembler)
$2-$3 $v0 - $v1 函数调用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3 函数调用参数(arguments)
$8-$15 $t0-$t7 暂时的(或随便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暂时的(或随便用的)
$28 $gp 全局指针(Global Pointer)
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp 帧指针(Frame Pointer)

说明

  • $0

即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。

1
2
3
move $t0,$t1
#实际为
add $t0,$0,$t1

使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。

  • $1

即 $at,该寄存器为汇编保留,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编保留$at的原因之一。

  • $2..$3:($v0-$v1)

用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。

  • $4..$7:($a0-$a3)

用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。

  • $8..$15:($t0-$t7)

临时寄存器,子程序可以使用它们而不用保留。

  • $16..$23:($s0-$s7)

保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。

  • $24..$25:($t8-$t9)

同($t0-$t7) $26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。 异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。
发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行。

  • $28:($gp)

为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针指向静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。

  • $29:($sp)

MIPS硬件并不直接支持堆栈,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。

  • $30:($fp)

GNU MIPS C编译器使用了帧指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。

  • $31:($ra)

存放返回地址,MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。

2.汇编指令

(1)不同类型的指令

R型指令:操作寄存器

常见指令:add, sub, and, or, nor, slt, sll, srl, jr

image-20211230150231733

  • opcod:操作码
  • rs:源寄存器的数量
  • rt:源寄存器的数量
  • rd:目标寄存器的编号
  • sham:移位量(操作移位的位数)
  • Func: 函数(操作码扩展)

Sham仅用于操作偏移量的指令(例如stl

I型指令:操作常量

常见指令:addi, lw, sw, lh, sh, lb, lbu, sb, ll, sc, lui, andi, ori, beq, bne, slti, sltiu

  • opcod:操作码
  • rs:源寄存器的数量
  • rd:源或目标寄存器的数量
  • imd: 立即值

J型指令:跳跃寻址

处理器直接跳跃到给定的地址,执行所在地址的指令

常见指令:j, jal

image-20211230150255746

  • Opcod:操作码
  • Imd:立即值

(2)常用指令

其中i表示立即数相关,u表示无符号相关。

①load/store 指令

la

将地址或者标签存入一个寄存器 eg:la $t0,val_1复制val_l的地址到$t0中,val_1是一个Label

li

将立即数存入通用寄存器 eg:li $t1, 40 $t1 = 40

lw

从指定的地址加载一个word类型的值到一个寄存器 eg:lw $s0, 0($sp) $s0=MEM[$sp+0]

sw

将寄存器的值,存于指定的地址word类型 eg:sw $a0, 0($sp) MEM[$sp+0] = $a0

move

寄存器传值 eg:move $t5, $t2 $t5 = $t2

②算数指令

算数指令的所有操作都是寄存器,不能是 RAM 地址或间接寻址。且操作数大小都是 word(4byte)

1
2
3
4
5
6
7
8
9
add $t0, $t1, $t2;		$t0=$t1+$t2;	带符号数相加
sub $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
addi $t0, $t1, 5; $t0=$t1+5; 有立即数的加法
addu $t0, $t1, $t2; $t0=$t1+$t2; 无符号数的加法
subu $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
mult $t3, $t3; (HI, LO) = $t3 * $t4
div $t5, $t6; $Hi=$t5 mod $t6
mfhi $t0; $t0 = $Hi
mflo $t1; $t1 = $Lo

③syscall

产生一个软化中断,实现系统调用;系统调用号存放在 $v0 中,参数在 $a0~$a3 中;

返回值在 $v0 中,如果出错,在 $a3 中返回错误号;在编写 shellcode 时,用到该指令机制

Write(1, “ABCn”, 5) 实现如下

1
2
3
4
5
6
7
8
9
addiu $sp, $sp, -32;
li $a0, 1;
lui $t6, 0x4142;
ori $t6, $t6, 0x430a;
sw $t6, $0($sp);
addiu $a1, $sp, 0;
li $a2, 5;
li $v0, 4004;
syscall;

④分支跳转指令

分支跳转指令本身可以通过比较两个寄存器决定如何跳转;如果想要实现与立即数的比较跳转,需要结合类跳转指令实现

1
2
3
4
5
6
7
b target;				无条件跳转到target处
beq $t0, $t1, target; 如果"$t0 == $t1”,跳转到target
blt $t0, $t1, target; 如果“$t0 < $t1”,跳转到target
ble $t0, $t1, target; 如果“$t0 <= $t1” 跳转到target
bgt;
blt;
bne; 类比上

⑤跳转指令

1
2
3
j target;			无条件跳转target
jr $t3; 跳转到$t3指向的地址处(Jump Register)
jal target; 跳转到target,并保存返回地址到$ra中

⑥子函数的调用

1
jal   sub_routine_label;复制当前PC的值到$ra中,(当前PC值就是返回地址)程序跳转到sub_routine_label

⑦子函数的返回

1
jr    $ra;如果子函数重复嵌套,则将$ra的值保存在堆栈中,因为$ra总是保存当前执行的子函数的返回地址

以上大多参考如下

MIPS汇编语言入门 - Sylvain’s Blog (valeeraz.github.io)

2021第四届强网拟态防御积分赛工控pwn eserver WP - 安全客,安全资讯平台 (anquanke.com)

3.流水线特点

以下指令的,strchr 函数的参数来自 $s0 而不是 $s2

1
2
3
mov $a0, $s2;
jalr strchr;
move $a0, $s0;

🔺注:流水线

即跳转之前会先一步加载下一条指令,所以在寻找ROP时,注意看下一步指令都是什么,不然跳转过去参数或者栈帧都有可能会出错的。

但是因为通常PWN题的MIPS通常是用qemu的user模式运行的,这个可能导致指令流水有时候表现不出来,所以不没有调用函数sleep(1)去将数据区刷新到指令区域也是可以拿到shell的;但是如果题目使用system模式部署,添加调用函数刷新数据区域再跳转到shellcode就很必要了,但如果是ROP来getshell倒是不用刷新数据。