- 审题
首先看题目:
他说让学习arm,那么问题来了,arm是啥?首先排除字面意思“胳膊”。又看到他给了汇编代码,emmm,可能跟汇编有关系。
那先放一放这个问题,看看提供的代码吧。
首先是C的代码:
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
然后是汇编代码:
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)
从C的代码不难看出,只要输入的key值跟key1、key2、key3函数返回值之和相等即可。而key1/2/3的内容都是汇编。那么接下来就要解决一开始的问题,arm和汇编的关系。
- arm汇编
把arm和汇编放一起搜一下,确实有这么个东西,叫arm汇编。我真是个小天才。
arm同intel一样也是一种处理器,但是arm的不同在于,它包含的指令集更小,但寄存器更多。
我就学了一些与本题相关的知识点,列举如下:
arm寄存器:
首先需要弄明白它都有哪些寄存器,每个寄存器都是干嘛的,有大佬列了个表,我就直接截图放下面了,参考链接见最后。
然后是
arm寄存器与intel寄存器的对照:
关于这道题,需要知道的就是R0寄存器存放的是函数返回值,LR寄存器存放的是函数返回地址,R1-R10都是通用寄存器,PC寄存器放的是下一条指令的地址就可以了。
但是,重点来了!PC寄存器存放的并不是当前指令的下一条指令的地址。这是因为arm处理器采用了三级流水线结构,包括执行(execute)、译码(decode)、取指(fetch)三级。当前指令处在执行级,下一条指令处在译码级,再下一条指令处在取指级。而PC寄存器存放的是处于取指级指令的地址,也就是当前指令的下下一条指令的地址。
arm汇编指令:
除了寄存器以外,还需要知道arm汇编的指令含义,如下表:
arm模式和thumb模式:
arm处理器有两种主要的操作状态,分别是arm模式和thumb模式。
这道题需要知道的主要就是,arm模式的指令集宽度是32位,thumb模式的指令集宽度是16位;
还有就是如果要从arm模式切换到thumb模式时,可以使用BX或BLX指令,将目的寄存器最低位置1。
- 解
掌握了以上的基础知识,基本就可以解这道题了,一共三个函数,一个一个看。
这里面主要是看R0寄存器存放了哪些内容,这个内容就是返回值。
key1很好理解,就是把PC寄存器的值传给R3,再把R3的值传给R0,所以返回的就是传PC值时,PC的值,也就是当前指令的下下一条指令的地址。传PC值指令的地址为0x00008cdc,它下下一条指令的地址就是0x00008ce4;
key2,这里就用到了arm模式和thumb模式的切换,开头三条指令就是将BX的目的寄存器最低位置1,从而从arm模式切换到thumb模式。这一点可以从C代码中的“.code 16”看出,以及从汇编中BX指令后的指令宽度从4字节变成2字节看出。然后看到R0寄存器存放的是R3的值,所以只需看后面对R3寄存器的操作即可。可以看出,R3寄存器首先存放了PC的值0x00008d08,又在此基础上加了4。所以key2的返回值就是0x00008d0c。
key3是将LR寄存器的内容传给了R0,已经知道LR寄存器存的是函数的返回地址,也就是函数调用指令的后一条指令的地址,这里就是0x00008d80。
综上,key1+key2+key3=0x0001a770,转化成十进制就是108400。
运行程序,输入108400,拿到flag,搞定!
- 参考链接
https://www.cnblogs.com/hilfloser/p/10516610.html
https://etenal.me/archives/972#C9