菜菜
EasyUPX
https://hgame.vidar.club/games/8/challenges?challenge=121
根据题目名称,
upx -d main.exe
成功解包,拖入IDA, 先看看Strings
![EasyUPX-1]()
显然这是一个b64换表,dump下custom_table和ciphertext解码即可
Z333333
https://hgame.vidar.club/games/8/challenges?challenge=105
先看Strings,
![Z333333-1]()
获知flag格式,且需要使用MD5加密
然后分析pseudocode
有一个reverse函数,看一下
![Z333333-2]()
发现实现了一串数字中每组偶数项和奇数项交换,这是一个预处理
然后看到有check1,check2
![Z333333-3]()
发现这其实是对用户输入argv[1]这串数字的一系列约束,
v5,v4分别表示是否满足约束,
在main中最后的判断中,若v5,v4为true,且满足一个额外条件
的输入即为plain
接下来编写脚本即可~~(这题暴搜脚本写起来太麻烦了,请gpt出手)~~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| def reverse_pairs(s: str) -> str: lst = list(s) for i in range(0, len(lst)//2 * 2, 2): lst[i], lst[i+1] = lst[i+1], lst[i] return ''.join(lst)
def verify(post: str) -> bool: a = [ord(c) for c in post] if len(a) < 10: return False array_dword = 0x00000AF0 d404024 = 0x98 d404028 = 0x9F4 d40402C = 0x33 d404030 = 0x53 d404034 = 0x6B d404038 = 0x5 d40403C = 0x145
if not (a[2] + a[1] == d404034 and a[2] - a[1] == d404038): return False if (a[5] * a[8]) >> 3 != d40403C: return False if a[0] * a[2] != array_dword: return False if a[5] + a[4] + a[3] != d404024: return False if a[3] * a[5] + a[4] != d404028: return False if a[8] + a[6] - a[7] != d40402C: return False if ((a[9] + a[7]) ^ a[8]) != d404030: return False if a[6] != 50: return False return True
digits = list(range(48, 58))
solutions = []
fixed = {0: None, 1:51, 2:56}
array_dword = 0x00000AF0 a2 = 56 a0 = array_dword // a2 if array_dword % a2 != 0: raise SystemExit("无法整除,假设数字范围可能不止 0-9。") fixed[0] = a0
for a3 in digits: for a5 in digits: a4 = 152 - (a3 + a5) if a4 < 48 or a4 > 57: continue if a3 * a5 + a4 != 2548: continue if (a5 * 2600) % a5 != 0: pass if 2600 % a5 != 0: continue a8 = 2600 // a5 if a8 < 48 or a8 > 57: continue need_diff = 51 - a8 for a6 in digits: a7 = a6 - need_diff if a7 < 48 or a7 > 57: continue for a9 in digits: if ((a9 + a7) ^ a8) == 83: a = [fixed[0], fixed[1], fixed[2], a3, a4, a5, a6, a7, a8, a9] post = ''.join(chr(x) for x in a) if verify(post): solutions.append(post)
for post in solutions: original = reverse_pairs(post) print("post-Reverse 用于校验的字符串:", post) print("程序实际接收的 argv[1]:", original)
|
ok,这样就只剩最后一步——MD5(32bit)加密,直接调用python库散列函数生成digest
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import hashlib
def md5_encrypt(text: str, length: int = 32) -> str: md5_value = hashlib.md5(text.encode()).hexdigest() if length == 16: return md5_value[8:24] return md5_value
s = "3208441202" print("MD5(32位):", md5_encrypt(s, 32)) print("MD5(16位):", md5_encrypt(s, 16))
|
这道题一开始上传的是没有argv[1][6]==50约束的版本,存在多解
以至于苯人做了半天没做出来,怀疑人生了
好在shiori测过之后紧急更换了版本
simple tea
https://hgame.vidar.club/games/8/challenges?challenge=171
先分析pseudocode,
发现有tea_encrypt函数,知道这里有tea加密
其实也可以用findcrypt插件,但这里没必要
do-while这段循环事实上是用来约束用户输入字符串长度的,
依据v8,v9,v10的检验,可以看出plain长度为3*8=24字节,
tea_encrypt的三次调用说明程序是将用户24字节输入拆成3段qword分段加密,
点进加密函数看一下,
![simple_tea-1]()
可以发现是标准TEA
那么接下来只需要获取key,编写dec即可
点开key看看
![simple_tea-2]()
这似乎不是真正的key
(当然这是基于猜测,也可以去验证一下)
这时候看看有没有什么隐藏的初始化函数,改变key的硬编码的
发现在main上方有一个init_key,
点开看看,
![simple_tea-3]()
建议多按按H将decimal全部转成hex,有时候会下意识搞混
显然,key的硬编码在加密前被改成了
74 68 69 73 5F 69 73 5F 72 65 61 6C 5F 6B 79 21
this_is_real_ky!
《疑问句改肯定句》
这样就获得了正确的key,dec如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <bits/stdc++.h> using namespace std;
uint32_t cipher[3][2] = { {0xE66A6B7B, 0xA3ECA28E}, {0x6CCF6CF4, 0x99043B89}, {0x7EFD20CC, 0xD5536FC9} };
uint32_t key[4] = { 0x73696874,0x5F73695F, 0x6C616572,0x21796B5F };
void decrypt(uint32_t *v, uint32_t *k) { uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i; uint32_t delta = 0x9e3779b9; uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; for (i = 0; i < 32; i++) { v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3); v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1); sum -= delta; } v[0] = v0; v[1] = v1; }
int main() { for (int i = 0; i < 3; i++) { decrypt(cipher[i], key); cout.write(reinterpret_cast<char *>(&cipher[i][0]), sizeof(cipher[i][0])); cout.write(reinterpret_cast<char *>(&cipher[i][1]), sizeof(cipher[i][1])); } return 0; }
|
Easyxtea
https://hgame.vidar.club/games/8/challenges?challenge=99
分析pseudocode,
![Easyxtea-1]()
非常简洁清晰的流程,
读入用户32字节字符串,存入Buf2,
将xmmword_140003270的数据读入Buf1,
四组tea家族加密,
点击v12,v13,v14发现这是在栈上连续的32字节空间,
![Easyxtea-2]()
也就是plain存到Buf2的时候事实上后24位是注入到v12,v13,v14的
即
Buf2 = [0~7]
v12 = [8~15]
v13 = [16~23]
v14 = [24~31]
这就弄清了每组加密是在加密哪段plain,
点进去看一下是什么茶
![Easyxtea-3]()
标准的x茶,
数一下魔数的出现次数为16次,do-while循环两次,即16*2=32轮
接下来就是获取key和cipher,
pseudocode里有xmmword_1400032XX的数据,点开看看
![Easyxtea-4]()
获得key和cipher,
cipher为两组128bit,对应四组x茶加密,
注意这里有一个坑点,在组合加密后plain的时候,是先xmmword_140003290,后xmmword_140003280
也就是倒过来,90地址段的是cipher前16字节,80段是后16字节,
编写dec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <bits/stdc++.h> using namespace std;
uint32_t cipher[4][2] = { {0x9F249FB2, 0x917A3218}, {0x8CA57C30, 0xD07FB03B}, {0xE9F399AA, 0x7BF711F4}, {0x6AA17CFB, 0x31CADE93} };
uint32_t key[4] = {1,2,3,4};
void decrypt(uint32_t* v, uint32_t* k) { uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=0xC6EF3720; for (uint32_t i=0; i<32; i++) { v1 -= (((v0<<4) ^ (v0>>5)) + v0) ^ (sum + k[(sum>>11) & 3]); sum -= delta; v0 -= (((v1<<4) ^ (v1>>5)) + v1) ^ (sum + k[sum & 3]); } v[0]=v0; v[1]=v1; }
int main() { for (int i = 0; i < 4; i++) { decrypt(cipher[i], key); cout.write(reinterpret_cast<char *>(&cipher[i][0]), sizeof(cipher[i][0])); cout.write(reinterpret_cast<char *>(&cipher[i][1]), sizeof(cipher[i][1])); } return 0; }
|