pwnable.kr—leg
解题思路
这是一道和asm有关的题,题目提供了源代码、.asm文件、flag文件以及leg的可执行文件。虽然leg.c代码比较长,但是关键点只有四处,掌握好四处关键点,就可以迎刃而解了。
point1
首先查看main()函数,找到可以打开flag文件的路径,关键点为:
if( (key1()+key2()+key3()) == key )
那么我们的目标就是要计算出key1()+key1()+key3()的值,然后通过scanf()输入key值即可。
point2
接着计算key1(),查看.asm文件中的key1()反汇编代码:
(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.
其中的关键点只有两行:
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
这两句的意思是将pc(program point)值传给r3,接着传给r0,最后由r0作为函数的返回值,也就是key1()的值。即key1()=pc(当前值)。
由于执行的是asm指令,并且在asm体系下是流水线作业的,所以pc=pc(当前正在执行的指令地址)+8,即key1()=0x00008cdc+8
point3
接着计算key2(),查看.asm文件中的key2()反汇编代码:
(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.
其中有价值的代码在于:
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
第一行的含义是pc+8+1的值赋给r6,即r6=0x00008cfc+8+1;
第二行中有bx指令,这条指令表示r6是跳转的目的地址,并且根据地址的最低位确定是否状态切换。如果末尾是1则切换到thumb状态,否则保留在asm状态;当前状态下是应该切换到thumb状态。
第三行表示,将pc+4赋值给r3(这里因为有状态切换到thumb状态,所以pc+4;如果是asm指令的话pc+8);第四行将r3+4。此时r3=0x00008d04+4+4
第五行其实是???bx r6这条指令的跳转目的地址??;接下来都顺序执行;直到最后一行将函数返回值给r0。
综上,key2()=0x00008d04+4+4
point4
最后计算key3(),其反汇编代码为:
(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.
其中的关键点在于:
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
是将lr(link register)的值赋值给r3,r0;而lr寄存器中存储的是子函数的返回地址,那么应该在main()函数的反汇编代码中找key3()函数返回后下一条指令地址:
.......
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
.......
综上key3()=0x00008d80
final
所以key=key1()+key2()+key3();转化为十进制为108400
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!
收获
1.对asm体系的初步认识。内联汇编中的basic asm和extended asm;前者由三部分组成,后者由五部分组成;其中前者隐含volatile选项,因此在本题中,asm中的内容在gcc编译之后和原来的asm instructions是相同的。
2.instructions list的编写格式:可以由一对双引号表示、也可以由多对双引号表示;某一对双引号中如果存在多个指令,那么指令之间由;或\n或\n\t隔开;如果使用了多对双引号,那么除了最后一对双引号,其余的末尾都要以;或\n或\n\t结尾。
3.$符号没有特殊含义;\$1就是1;\$0x1就是0x1……;%是有一些特殊含义的。
4.Asm中使用了15个32bit的registers:r0~r15.其中r13:sp(stack point);r14:lr(link register);r15:pc(program counter)。
sp is used as a pointer to the active stack
lr is used to store the return address from a subroutine
pc shows next instruction’s address
5.asm中的跳转指令:
B{条件} 目标地址:直接跳转
BL{条件} 目标地址:带返回地址的跳转;即跳转之前在R14(lr)中保存pc当前内容
BX{条件} 目标地址:带状态切换的跳转;根据目标地址末尾决定是否状态切换;1切换到thumb状态、0不切换
BLX{条件} 目标地址:带状态切换以及返回地址
6.pc的值确定方法:在asm体系中,由于采用流水线作业“取址、译码、执行”的方式,所以pc(下一条待执行指令的地址)指向向后数第二条指令。由于每条asm指令占据4bytes,所以pc=当前执行指令地址+4*2;而每条thumb指令占据2bytes,所以pc=当前执行指令地址+2*2 。
7.ldr指令和ldr伪指令:前者将数据从内存(第二个参数为数据地址)移到寄存器;后者与mov指令相似,将第二个参数的值赋给第一个参数
8.python进制转换:
转十进制:int(“”,2/8/16)
转八进制:oct(0b101010/11/0xfff)
转十六进制:10-16:hex();2-16:hex(int(“”,2));8-16:hex(int(“”,8))
转二进制:10-2:bin();8-2:bin(int(“”,2));16-2:bin(int(“”,8))
仍存在的疑惑
1.key2()中在栈中的pop\push的顺序感觉有点乱,不知道是不是因为调用的原因?如果是跳转的话,中间那两句给r0赋值的指令还能读到吗?
2.寄存器传参过程有点看不懂……