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.寄存器传参过程有点看不懂……

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐