预备知识
1.关于ARM架构
ARM架构,过去称作高级精简指令集机器(英语:Advanced RISC Machine,更早称作Acorn精简指令集机器,AcornRISC Machine),是一个精简指令集(RISC)处理器架构家族,其广泛地使用在许多嵌入式系统设计。由于节能的特点,其在其他领域上也有很多作为。ARM处理器非常适用于移动通信领域,匹配其主要设计目标为低成本、高性能、低耗电的特性。另一方面,超级计算机消耗大量电能,ARM同样被视作更高效的选择。安谋控股开发此架构并授权其他公司使用,以供他们实现ARM的某一个架构,开发自主的系统单片机和系统模块(system-on-module,SoC)。
2.关于汇编语言
汇编语言(英语:assembly language)是一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。 一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。
3.树莓派安装参考
link
实验目的
通过该实验了解ARM汇编基础语法,这是我们学习ARM下的漏洞利用程序编写的基础。
实验环境
服务器:Ubuntu IP地址:随机分配 测试文件请在实验机内下载使用:http://tools.hetianlab.com/tools/T052.zip 启动树莓派命令:
$ qemu-system-arm -kernel ~/qemu_vms/qemu-rpi-kernel/kernel-qemu-4.4.34-jessie -cpu arm1176 -m 256 -M versatilepb -serial stdio -append "root=/dev/sda2 rootfstype=ext4 rw" -hda ~/qemu_vms/rasbian.img -redir tcp:5022::22 -no-reboot
SSH连接树莓派:
$ ssh pi@127.0.0.1 -p 5022
pi账户密码raspberry。
实验步骤一
任务描述:学习条件指令。 当特定条件满足时,借助条件指令, 通过跳转(分支)或执行某些特定指令来控制程序的流动方向。相关条件被描述为CPSR寄存器中的特定位的状态,这些位根据指令计算后的结果实时改变。比如,如果我们比较两个数并且他们相等,就将零标志位置位(Z=1),因为在系统底层发生了a-b=0。在这个例子里两个数是相等的,但如果第一个数字比第二个大,会得出大于结论。而相反的情况下得出小于结论。当然还有很多其他的条件,比如小于等于(LE),大于等于(GE)等等。 下表列出了可能的条件指令,他们的含义以及被检测的状态标志位: 结合上表,我们看段demo:
.global main
main:
mov r0, #2 /* setting up initial variable */
cmp r0, #3 /* R0与数字3比较,2小于3,所以N位置1*/
addlt r0, r0, #1 /* 如果R0比3小就将R0自增1*/
cmp r0, #3 /* 再次比较r0和3,此时2+1=3,所以Z标志位置1,N置0 */
addlt r0, r0, #1 /* 如果r0小于3给r0自增1*/
bx lr
结合注释,就很好理解了。 代码中,第一个CMP比较指令执行后触发了N标志位的置位(2-3=-1),这表明r0的值比数字3要小。随后,由于条件满足,所以执行了addlt指令。再次比较r0和3,此时2+1=3,所以Z标志位置1,N置0,此时条件不成立,结果就是第二个addlt没有执行,r0也没改变,程序退出并返回结果3。 接下来我们看看Thumb模式下的条件执行: 注意,只有在特定版本中(Thumb-2)才能执行条件执行指令。 语法结构:IT{x{y{z}}}cond(注:xyz指IT后最多再跟三个大写字母,大写字母可以是T,可以是E,T就是then,E就是else)。 cond规定了执行IT语句块里的第一条指令需要满足的条件。 x规定了执行的IT语句块中第二条指令需要满足的条件。 y规定了执行IT语句块里的第三条指令需要满足的条件。 z规定了执行IT语句块里的第四条指令需要满足的条件。 IT指令集的结构是:“IF-Then-(Else)”,它的语法结构由两个字母构成: IT代表If-Then(下一条指令是条件指令)。 ITT代表If-Then-Then(接下来的两条指令是条件指令)。 ITE代表If-Then-Else(接下来的两条指令是条件指令)。 ITTE代表If-Then-Then-Else(接下来的三条指令是条件指令)。 ITTEE代表If-Then-Then-Else-Else(接下来的四条指令是条件指令)。 IT语句块内的每个指令必须指定一个条件后缀,该条件后缀要么相同要么在逻辑上相反。这意味着,如果使用了ITE,第一和第二指令(If-Then)必须具有相同的条件后缀,而第三条指令(else语句)必须和前面两条语句逻辑相反。 下面是ARM参考是手册下的部分指令,已经给出的中文注释:
ITTE NE ; 接下来的三条指令是条件指令
ANDNE R0, R0, R1 ; ANDNE不更新条件标志位
ADDSNE R2, R2, #1 ; ANDNE更新条件标志位
MOVEQ R2, R3 ; 条件赋值指令
ITE GT ; 下面两条指令是条件指令
ADDGT R1, R0, #55 ; 如果GT为1执行条件的相加指令
ADDLE R1, R0, #48 ; 如果GT为0执行的条件相加指令
ITTEE EQ ; 下面四条指令是条件指令
MOVEQ R0, R1 ; 条件赋值指令
ADDEQ R2, R2, #10 ; 条件相加指令
ANDNE R3, R3, #1 ; 条件与指令
BNE.W dloop ; 条件分支指令只能用于IT语句块的结尾
一个典型的错误例子是这样子的:
IT NE ; 下面一条不是条件执行指令
ADD R0, R0, R1 ; 语法错误:IT语句块中没有使用条件执行指令
以下总结了条件指令和逻辑相反的指令:
实验步骤二
任务描述:了解这些基础之后,我们看一下一个实例。 代码在如下,在test7.s:
.syntax unified @ this is important!这句话很关键
.text
.global _start
_start:
.code 32
add r3, pc, #1 @ increase value of PC by 1 and add it to R3
bx r3 @ branch + exchange to the address in R3 -> switch to Thumb state because LSB = 1
.code 16 @ Thumb state
cmp r0, #10
ite eq @ if R0 is equal 10...
addeq r1, #2 @ ... then R1 = R1 + 2
addne r1, #3 @ ... else R1 = R1 + 3
bkpt
.code32 这段示例代码以ARM状态开始。第一条指令将PC里的地址值加1后传送给r3,接着跳转到分支地址R3。这样做会导致切换到Thumb状态,因为LSB(最低有效位)是1,因此不是4个字节。使用bx指令(分支+切换)达成这个目标,分支指令执行完成后,T(Thumb)标志位被置位,我们现在处于Thumb模式。 .code16 Thumb模式下首先用R0和立即数10比较。这会将N标志位置位(0-10=-10)。接着我们使用了一个 If-Then-Else语句块。这个块会跳过ADDEQ因为Z(零)标志位没有被置位,由于结果等于10,是NE的(not equal不等于0的),接着会执行ADDNE指令。 这里在Gdb中单步步过这段指令会把结果搞乱,因为在ITE语句块中两条语句都执行了。所以我们不设置断点运行代码,并且单步步过每条指令会产生正确的结果:R1=3。 编译链接: 使用gdb调试: 直接run查看寄存器r1的值,确实为3:
实验步骤三
任务描述:学习分支指令。 分支指令(也叫做跳转)允许我们跳转到另一个代码段运行。当我们需要跳过(或者重复)执行代码段或者跳向特定功能的函数时就显得尤为有用。这方面最好的范例就是IFs和循环。我们先看看IF是什么情况。 示例代码如下:
.global main
main:
mov r1, #2 /* setting up initial variable a */
mov r2, #3 /* setting up initial variable b */
cmp r1, r2 /* comparing variables to determine which is bigger */
blt r1_lower /* jump to r1_lower in case r2 is bigger(N==1) */
mov r0, r1 /* if branching/jumping did not occur, r1is bigger (or the same) so store r1 into r0 */
b end /* proceed to the end */
r1_lower:
mov r0, r2 /* We ended uphere because r1 was smaller than r2, so move r2 into r0 */
b end /* proceed tothe end */
end:
bx lr /* THE END */
这个逻辑用c语言伪代码表示出来就是:
int main() {
int max = 0;
int a = 2;
int b = 3;
if(a < b) {
max = b;
}
else {
max = a;
}
return max;
}
在test8.s: 编译链接后使用gdb调试。 在main下断点,然后run: 可以看到接下来要执行的两条语句分别是给r1,r2赋值: 第三条是进行比较r1,r2的大小。 我们使用nexti 2直接来到赋值之后的情况。 可以看到此时出现了分支: 分别是r1大于r2,和r1小于r2的情况,会把大的那个寄存器的值赋给r0。 继续nexti: 可以看到现在判断出来r2比较大,所以下一条指令会把r2的值赋给r0。 nexti后此时查看r0的值,确实和r2的值一样,为0x3。 我们再来看一个例子。 代码如下:
.global main
main:
mov r0, #0 /* setting up initial variable a */
loop:
cmp r0, #4 /* checking if a==4 */
beq end /* proceeding to the end if a==4*/
add r0, r0, #1 /* increasing a by 1 if the jump to the end did not occur */
bloop /*repeating the loop */
end:
bxlr /* THE END */
代码逻辑用c伪代码表示出来为:
int main() {
int a = 0;
while(a < 4) {
a= a+1;
}
return a;
}
代码在test9.s。 编译链接后用gdb调试,在main下断点,单步调试: run: 可以看到第一条是赋0给r0,然后比较r0和4,cmp指令会执行0-4,结果不等于0,所以会Z标志会置0,或者表示为!z。 我们使用nexti 2然后查看: 确实cmp的结果为!z,按照逻辑,r0会自增1,表示在汇编里就是add r0,r0,#1。 我们接下来同样nexti 2: 可以看到r0为0x1,后续的逻辑还是一样的,可以自行调试验证,此处不再演示。
|