0x01前言
这次偶然参加了这个CTF,没想到逆向全是exe,真让人头大。还有就是不得不吐槽的是,逆向你出一个算法题干嘛,又不是ACM 😦 。但还是有些题目很有质量,比如double code这种接近实战的逆向分析。也学到了很多windows方面逆向的一些知识吧。 还有要吐槽的,题目都能出错来,fake_game那道题我就说算出来flag怎么不对😓,暴打出题人就完事儿了。
0x02 题目
0x01 easy_re
使用exeinfope工具查看
可以看到有upx壳
直接用ida打开,ida也能识别
使用upx进行脱壳
命令:upx.exe -d re.exe
再将文件丢进ida中,就能看到清楚的函数了。
对逐个函数进行分析。
f3函数是对输入字符串进行reverse
总共32次,偶数次,就相当于没有变
然后是func函数,这个函数其实是base64加密,因为不太熟悉,一开始分析起来好困难😵
后面根据函数大概逻辑,三位变四位,大概能猜出来是base64.
void __cdecl func(char *x, char *y)
{
unsigned __int8 *v3; // rax
unsigned __int8 v4; // cl
int v5; // ecx
int v6; // eax
int v7; // ecx
int v8; // eax
int v9; // eax
int v10; // eax
unsigned __int8 a4[4]; // [rsp+29h] [rbp-67h]
unsigned __int8 a3[3]; // [rsp+2Dh] [rbp-63h]
char b[65]; // [rsp+30h] [rbp-60h] BYREF
int k; // [rsp+7Ch] [rbp-14h]
int j; // [rsp+80h] [rbp-10h]
int i; // [rsp+84h] [rbp-Ch]
int len; // [rsp+88h] [rbp-8h]
int index; // [rsp+8Ch] [rbp-4h]
char *xa; // [rsp+A0h] [rbp+10h]
xa = x;
index = 0;
len = strlen(x);
i = 0;
j = 0;
k = 0;
strcpy(b, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
while ( len-- )
{
v3 = (unsigned __int8 *)xa++;
v4 = *v3;
LODWORD(v3) = i++;
a3[(int)v3] = v4;
if ( i == 3 )
{
a4[0] = a3[0] >> 2;
a4[1] = ((16 * a3[0]) & 0x30) + (a3[1] >> 4);
a4[2] = ((4 * a3[1]) & 0x3C) + (a3[2] >> 6);
a4[3] = a3[2] & 0x3F;
for ( i = 0; i <= 3; ++i )
{
v5 = a4[i];
v6 = index++;
y[v6] = b[v5];
}
i = 0;
}
}
if ( i )
{
for ( j = i; j <= 2; ++j )
a3[j] = 0;
a4[0] = a3[0] >> 2;
a4[1] = ((16 * a3[0]) & 0x30) + (a3[1] >> 4);
a4[2] = ((4 * a3[1]) & 0x3C) + (a3[2] >> 6);
a4[3] = a3[2] & 0x3F;
for ( j = 0; i >= j; ++j )
{
v7 = a4[j];
v8 = index++;
y[v8] = b[v7];
}
while ( 1 )
{
v9 = i++;
if ( v9 > 2 )
break;
v10 = index++;
y[v10] = 61;
}
}
y[index] = 0;
}
将输入字符base64后与字符串a进行比较。
将字符串a进行base64解码得到flag
HDCTF{Y0u_h@v2_//@57er3d_7he_r3v3rs3}
0x02 easy_asm
这题的exe运行不了,就只能静态分析了,但是还好代码很少。
程序的大概流程图如下
有个地方没搞懂的是,对字符串进行操作时,都指向的asc_10000,而正确的字符串很明显是另一个字符串。
从汇编来看
0x52-0x47刚好等于11
就是字符串Not equal!$ 的长度。
所有对应的字符串指向,应该如下图所示。也符合程序的逻辑。
查看上半部分的汇编,将输入字符,对0x10异或后再对字符作比较。
根据之前的猜测,很明显0x59就是指向,后面的字符串了
根据IDA的提示知道$
是停止符,就不管。
得到flag
HDCTF{Hust_a_e3sy_aSm}
0x03 double_code
从start函数一路找,这里的Code参数名也算是一个提示了,点进去继续看。
从这里的分配内存,写入内存,创建线程,可以知道是shellcode loader。
对照windows官方文档,writememory函数
第三个参数应该是buffer的指针。
但是这里,IDA把这个指针识别成了函数🤣,就是说帮我们把shellcode逆向了
strcpy这里就是把flag放进了v15里,然后长度%5取余,分情况做不一样的计算。
(具体的不用看太懂,因为ida识别的不是很好,要么就自己分析汇编😂)
编写脚本如下:
code = [0x48,0x67,0x45,0x51,0x42,0x7b,0x70,0x6a,0x30,0x68,0x6c,0x60,0x32,0x61,0x61,0x5f,0x42,0x70,0x61,0x5b,0x30,0x53,0x65,0x6c,0x60,0x65,0x7c,0x63,0x69,0x2d,0x5f,0x46,0x35,0x70,0x75,0x7d]
for i in range(len(code)):
if(i%5 == 1):
code[i] ^= 0x23
if(i%5 == 2):
code[i] -= 2
if(i%5 == 3):
code[i] += 3
if(i%5 == 4):
code[i] += 4
if(i%5 == 5):
code[i] += 25
for i in code:
print(chr(i),end="")
得到flag
HDCTF{Sh3llC0de_and_0pcode_al1_e3sy}
这里我想问一下,i%5咋可能等于5啊,写个i%5==在这儿?是故意的还是不小心的👀
0x04 fake_game
看exe图标可以发现,是使用pyinstaller生成的exe.
使用工具pyinstxtractor-ng将exe转换为pyc文件。
使用python库uncompyle6进行反编译
pip3 install uncompyle6
uncompyle6.exe game.pyc > game.py
然后分析py代码,关键部分代码如下
xorr = [
0] * 4
ans = [0] * 55
flag = [178868, 188, 56953, 2413, 178874, 131, 56957, 2313, 178867, 156,
56933, 2377, 178832, 202, 56899, 2314, 178830, 167, 56924,
2313, 178830, 167, 56938, 2383, 178822, 217, 56859, 2372]
self.init_window()
self.init_plant_points()
self.init_map()
self.init_zombies()
while not GAMEOVER:
MainGame.window.fill((255, 255, 255))
MainGame.window.blit(self.draw_text('当前钱数$: {}'.format(MainGame.money), 26, (255,
0,
0)), (500,
40))
MainGame.window.blit(self.draw_text('当前关数{},得分{},距离下关还差{}分'.format(MainGame.shaoguan, MainGame.score, MainGame.remnant_score), 26, (255,
0,
0)), (5,
40))
self.load_help_text()
xorr[0] = MainGame.money
xorr[1] = MainGame.shaoguan
xorr[2] = MainGame.score
xorr[3] = MainGame.remnant_score
if xorr[0] * 256 - xorr[1] / 2 + xorr[2] * 23 + xorr[3] / 2 == 47118166:
if xorr[0] * 252 - xorr[1] * 366 + xorr[2] * 23 + xorr[3] / 2 - 1987 == 46309775:
if xorr[0] * 6 - xorr[1] * 88 + xorr[2] / 2 + xorr[3] / 2 - 11444 == 1069997:
if (xorr[0] - 652) * 2 - xorr[1] * 366 + xorr[2] * 233 + xorr[3] / 2 - 13333 == 13509025:
for i in range(len(flag)):
ans[i] = flag[i] ^ xorr[i % 4]
else:
with open('flag.txt', 'w') as (f):
f.write(''.join([chr(a) for a in ans]))
发现需要金钱、分数啥的满足四个方程,再用这四个参数对flag列表进行异或。
编写脚本
from scipy.optimize import fsolve
def solve_function(unsolved_value):
x, y, z, a= unsolved_value[0], unsolved_value[1], unsolved_value[2], unsolved_value[3]
return [
x * 256 - y / 2 + z * 23 + a / 2 - 47118166,
x * 252 - y * 366 + z * 23 + a / 2 - 1987 - 46309775,
x * 6 - y * 88 + z / 2 + a / 2 - 11444 - 1069997,
(x - 652) * 2 - y * 366 + z * 233 + a / 2 - 13333 - 13509025,
]
solved = fsolve(solve_function, [0, 0, 0,0])
print(solved)
xorr = [0] * 4
ans = [0] * 55
flag = [178868, 188, 56953, 2413, 178874, 131, 56957, 2313, 178867, 156,
56933, 2377, 178832, 202, 56899, 2314, 178830, 167, 56924,
2313, 178830, 167, 56938, 2383, 178822, 217, 56859, 2372]
xorr[0] = int(solved[0])
xorr[1] = int(solved[1]+1)
xorr[2] = int(solved[2]+1)
xorr[3] = int(solved[3]+2)
print(xorr)
for i in range(len(flag)):
ans[i] = flag[i] ^ xorr[i % 4]
print(''.join([chr(a) for a in ans]))
因为这题本身出的就有问题,所以需要根据HDCT这四个对应字符,调整一下xorr
最后得到flag
HDCTF{G0Od_pl2y3r_f0r_Pvz!!}
0x05 买了些什么呢
这道题是考算法的,01背包问题,
但是ida中反编译后的knapsack函数就相当于解法,所以还算有点逆向😅
直接拿来用就行
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
int dp[500][500];
int __cdecl knapsack(int W, int* wt, int* val, int n)
{
int j_0; // [esp+0h] [ebp-10h]
int i_0; // [esp+4h] [ebp-Ch]
int j; // [esp+8h] [ebp-8h]
int i; // [esp+Ch] [ebp-4h]
for (i = 0; i <= n; ++i)
{
for (j = 0; j <= W; ++j)
dp[i][j] = 0;
}
for (i_0 = 1; i_0 <= n; ++i_0)
{
for (j_0 = 1; j_0 <= W; ++j_0)
{
if (j_0 < wt[i_0 - 1])
{
dp[i_0][j_0] = dp[i_0 - 1][j_0];
}
else
{
dp[i_0][j_0] = dp[i_0 - 1][j_0];
if (dp[i_0 - 1][j_0 - wt[i_0 - 1]] + val[i_0 - 1] > dp[i_0][j_0])
dp[i_0][j_0] = val[i_0 - 1] + dp[i_0 - 1][j_0 - wt[i_0 - 1]];
}
}
}
return 0;
}
int main()
{
int W = 50;
int n = 40;
int wt[0xA0];
int val[0xA0];
srand(1);
for (int i = 0; i < n; i++)
{
wt[i] = rand() % 10 + 1;
val[i] = rand() % 10 + 1;
printf("| %d | %d | %d |\n", i + 1, wt[i], val[i]);
}
knapsack(W, wt, val, n);
std::cout << "动态规划数组为: " << std::endl;
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= W; j++)
{
std::cout << dp[i][j] << " ";
}
std::cout << std::endl;
}
std::vector<int> indices;
int w = W;
for (int i = n; i > 0 && w > 0; i--)
{
if (dp[i][w] != dp[i - 1][w])
{
indices.push_back(i - 1);
w -= wt[i - 1];
}
}
std::cout << "选择的物品的下标为: ";
for (int i = indices.size() - 1; i >= 0; i--)
{
std::cout << indices[i] << " ";
}
}
得到flag
HDCTF{0 4 6 10 11 13 16 18 21 22 24 26 31 33 34 36 39}
0x06 enc
这题主要考察一系列加密算法。
主函数逻辑如下
参考文章:https://www.anquanke.com/post/id/224198
对照可以发现sub_411523函数其实是tea加密算法。
但是这里ida把add 0x9E3779B9
识别成减法了,虽然效果一样,但第一眼有点让人困惑🙃
直接使用tea的解密算法解密就行。
#include <stdio.h>
#include <stdint.h> // 使用uint32_t数据类型需要包含此头文件
#include <string.h>
#include <iostream>
using namespace std;
void encrypt(uint32_t v[2], const uint32_t k[4]) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9E3779B9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
sum += delta;
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[2], const uint32_t k[4]) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i; /* set up; sum is 32*delta */
uint32_t delta = 0x9E3779B9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
uint32_t enc[2] = { 0x60FCDEF7,0x236DBEC };
uint32_t key[] = { 0x12,0x34,0x56,0x78 };
decrypt(enc, key);
cout << enc[0];
return 0;
}
解出v10 = 3
然后将它传入sub_4113DE函数,这个函数其实是一个smc函数
参考:https://www.anquanke.com/post/id/238645
查看代码段,可以很明显看到一段类似于shellcode的数据
smc函数对这段code做了xor处理。异或的数就是我们之前解出的3
编写idapython来还原
for i in range(5620):
rightVal = idc.Byte(0x41D00C + i) ^ 0x3
PatchByte(0x41D00C + i, rightVal)
修改后点击apply patch to input file
重新打开,就可以看到正常的sub_411302函数了。
这个函数用rc4加密输入的字符串,与另一个字符串作比较。
直接使用官方wp的脚本
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<string>
#include<cstring>
#include<list>
#include<stdlib.h>
using namespace std;
typedef int status;
typedef int selemtype;
/*初始化函数*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len)
{
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];
}
for (i = 0; i < 256; i++)
{
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
}
}
/*加解密*/
void rc4_crypt(unsigned char* s, unsigned char* Data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[x]和s[y]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}
int main()
{
unsigned char s[256] = { 0 }, s2[256] = { 0 };//S-box
char key[256] = { "you_are_master" };
unsigned char pData[512] = {
0xf,0x94,0xae,0xf2,0xc0,0x57,0xc2,0xe0,0x9a,0x45,
0x37,0x50,0xf5,0xa0,0x5e,0xcb,0x2c,0x16,0x28,0x29,
0xfe,0xff,0x33,0x46,0xe,0x57,0x82,0x22,0x52,0x26,
0x2b,0x6e,0xe4,0x82,0x24
};
unsigned long len = 35;
int i;
printf("pData=%s\n", pData);
printf("key=%s,length=%d\n\n", key, strlen(key));
rc4_init(s, (unsigned char*)key, strlen(key));//已经完成了初始化
printf("完成对S[i]的初始化,如下:\n\n");
for (i = 0; i < 256; i++)
{
printf("%02X", s[i]);
if (i && (i + 1) % 16 == 0)putchar('\n');
}
printf("\n\n");
for (i = 0; i < 256; i++)//用s2[i]暂时保留经过初始化的s[i],很重要的!!!
{
s2[i] = s[i];
}
rc4_crypt(s, (unsigned char*)pData, len);//加密
printf("现在解密:\n\n");
//rc4_init(s,(unsignedchar*)key,strlen(key));//初始化密钥
//rc4_crypt(s2, (unsigned char*)pData, len);//解密
printf("pData=%s\n\n", pData);
return 0;
}
得到flag
HDCTF{y0u_ar3_rc4_t3a_smc_m4ster!!}
直接用cyberchef也行
最后还有一道谜语题,就不复现了🤣
0x03 总结
总的来说还是学到了很多加密方式的逆向,熟悉了之后以后就能快速识别了。
也学到了很多windows相关的shellcode的逆向方法。还是有收获的🤠