首先感谢littleNA大佬的文章https://bbs.pediy.com/thread-230312.htm,看完这篇文章后事半功倍。
第一题 EasyJob
首先为了方便动静结合调试,我先修改了PE文件头部使之跳过重定位。使用工具DIE
这样修改之后,使用ida和其他调试工具的时候地址就能对得上了,告别aslr。
用ida打开程序后,看一下main
函数,确定sub_45F650
和sub_460050
分别为检查request code
和verification code
的函数,做一下重命名。
值得注意的是check_request_code
和check_verification_code
有同一个参数,我把它命名为req_code_stru
。在后面的分析过程中,我得出该变量类型的结构体
struct req_code_stru
{
vector request_code_part;
__int64 num1;
__int64 num2;
__int64 num3;
__int64 num4;
};
其中vector
结构可以参考上面littleNA大佬的文章,在这个结构体中应该是vector<string>
,每个成员的作用会在后面讲到。
进入check_request_code
函数后,发现字符串
很明显是一个正则表达式,于是推测request code
需要符合该正则表达式。打开程序随便输入了一个符合该表达式的request code
,比如1111-2222-3333-4444
,输入后程序提示继续输入verification code
,表明推测是正确的。通过调试后还发现该函数会把request code
通过字符-
分成4个string
插入到req_code_stru->request_code_part
中。
接下来看一下check_vertification_code
。通过浏览后发现一个比较关键的函数sub_45F960
,通过req_code_stru->request_code_part
计算出4个num
,具体代码如下
int __thiscall sub_45F960(req_code_stru *req_code_stru)
{
char *v1_part1; // esi
char *v2_part1; // eax
char *v3_part4; // ebx
char *v4_part4; // ecx
int v5; // edi
char *v6_part1; // ecx
char *v7_part4; // eax
int v8; // edi
char *v9_part1; // edx
char *v10_part4; // eax
int v11; // edi
char *v12_part1; // edx
char *v13_part4; // eax
char *v14_part2; // eax
char *v15_part3; // edi
char *v16_part3; // ecx
char *v17_part2; // edx
char *v18_part3; // eax
char *v19_part2; // edx
char *v20_part3; // eax
char *v21_part1; // edx
char *v22_part3; // eax
char *v23_part1; // edx
char *v24_part1; // eax
char *v25_part2; // eax
char *v26_part2; // edx
char *v27_part3; // ecx
char *v28_part3; // eax
int v29; // ecx
char *v30_part4; // edx
char *v31_part4; // eax
char *v32_part1; // ecx
char *v33_part4; // eax
char *v34_part2; // edx
char *v35_part3; // eax
char *v36_part2; // eax
int v37; // eax
int v38; // edx
int v39; // ST24_4
int v40; // ecx
__int64 v41; // rdi
bool v42; // cf
unsigned __int64 v43; // rt0
__int64 v44; // kr38_8
__int64 v45; // rax
unsigned int v46; // ecx
unsigned int v47; // ecx
signed __int64 v48; // rax
int v49; // ecx
req_code_stru *v51_reg_code_stru; // [esp+10h] [ebp-18h]
int v52; // [esp+14h] [ebp-14h]
int v53; // [esp+14h] [ebp-14h]
int v54; // [esp+18h] [ebp-10h]
int v55; // [esp+1Ch] [ebp-Ch]
int v56; // [esp+24h] [ebp-4h]
int v57; // [esp+24h] [ebp-4h]
int v58; // [esp+24h] [ebp-4h]
int v59; // [esp+24h] [ebp-4h]
int v60; // [esp+24h] [ebp-4h]
int v61; // [esp+24h] [ebp-4h]
v1_part1 = req_code_stru->request_code_part._Myfirst;
v51_reg_code_stru = req_code_stru;
if ( *((_DWORD *)req_code_stru->request_code_part._Myfirst + 5) < 0x10u )
v2_part1 = req_code_stru->request_code_part._Myfirst;
else
v2_part1 = *(char **)v1_part1;
v3_part4 = v1_part1 + 72;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v4_part4 = v1_part1 + 72;
else
v4_part4 = *(char **)v3_part4;
v5 = (*v4_part4 ^ *v2_part1) << 8; // v5=(part4[0]^part1[0])<<8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v6_part1 = v1_part1;
else
v6_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v7_part4 = v1_part1 + 72;
else
v7_part4 = *(char **)v3_part4;
v8 = v6_part1[1] * v7_part4[1] + v5; // v8=part1[1]*part4[1]+v5
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v9_part1 = v1_part1;
else
v9_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v10_part4 = v1_part1 + 72;
else
v10_part4 = *(char **)v3_part4;
v11 = v9_part1[2] / v10_part4[2] + v8; // v11=part1[2]/part4[2]+v8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v12_part1 = v1_part1;
else
v12_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v13_part4 = v1_part1 + 72;
else
v13_part4 = *(char **)v3_part4;
v54 = v11 + v12_part1[2] % v13_part4[2]; // v54=part1[2]%part4[2]+v11
v14_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v14_part2 = *(char **)v14_part2;
v15_part3 = v1_part1 + 48;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v16_part3 = v1_part1 + 48;
else
v16_part3 = *(char **)v15_part3;
v56 = *v16_part3 * *v14_part2 << 8; // v56=part3[0]*part2[0]<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v17_part2 = v1_part1 + 24;
else
v17_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v18_part3 = v1_part1 + 48;
else
v18_part3 = *(char **)v15_part3;
v57 = (char)(v17_part2[1] ^ v18_part3[1]) + v56;// v57=(part2[1]^part3[1])+v56
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v19_part2 = v1_part1 + 24;
else
v19_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v20_part3 = v1_part1 + 48;
else
v20_part3 = *(char **)v15_part3;
v58 = v19_part2[2] % v20_part3[2] + 32 + v57; // v58=part2[2]%part3[2]+32+v57
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v21_part1 = v1_part1;
else
v21_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v22_part3 = v1_part1 + 48;
else
v22_part3 = *(char **)v15_part3;
v55 = v58 + v21_part1[2] / v22_part3[2]; // v55=part1[2]/part3[2]+v58
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v23_part1 = v1_part1;
else
v23_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v24_part1 = v1_part1;
else
v24_part1 = *(char **)v1_part1;
v59 = v23_part1[1] % v24_part1[3] << 8; // v59=part1[1]%part1[3]<<8
v25_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v26_part2 = v1_part1 + 24;
else
v26_part2 = *(char **)v25_part2;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v25_part2 = *(char **)v25_part2;
v60 = v26_part2[1] / v25_part2[3] + v59; // v60=part2[1]/part2[3]+v59
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v27_part3 = v1_part1 + 48;
else
v27_part3 = *(char **)v15_part3;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v28_part3 = v1_part1 + 48;
else
v28_part3 = *(char **)v15_part3;
v29 = v27_part3[1] * v28_part3[3] + 8 + v60; // v29=part3[1]*part3[3]+8+v60
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v30_part4 = v1_part1 + 72;
else
v30_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v31_part4 = v1_part1 + 72;
else
v31_part4 = *(char **)v3_part4;
v61 = v29 + (char)(v30_part4[1] ^ v31_part4[3]);// v61=(part4[1]^part4[3])+v29
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v32_part1 = v1_part1;
else
v32_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v33_part4 = v1_part1 + 72;
else
v33_part4 = *(char **)v3_part4;
v52 = (*v32_part1 ^ v33_part4[3]) << 8; // v52=(part1[0]^part4[3])<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v34_part2 = v1_part1 + 24;
else
v34_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v35_part3 = v1_part1 + 48;
else
v35_part3 = *(char **)v15_part3;
v53 = v34_part2[1] / v35_part3[2] + v52; // v53=part2[1]/part3[2]+v52
if ( *((_DWORD *)v1_part1 + 17) >= 0x10u )
v15_part3 = *(char **)v15_part3;
v36_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v36_part2 = *(char **)v36_part2;
v37 = v15_part3[2] * v36_part2[1]; // v37=part3[2]*part2[1]
if ( *((_DWORD *)v1_part1 + 23) >= 0x10u )
v3_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 5) >= 0x10u )
v1_part1 = *(char **)v1_part1;
v38 = v37 + v53 + v3_part4[3] % *v1_part1; // v38=part4[3]%part1[0]+v37+v53
v39 = v38;
v40 = (unsigned __int64)(unsigned int)v38 >> 29;
LODWORD(v41) = 20 * (v54 + 2 * v61); // v41=20*(v54+2*v61)
v42 = __CFADD__(8 * v38, v38);
v38 *= 9;
HIDWORD(v43) = v40 + v42;
LODWORD(v43) = v38;
HIDWORD(v41) = v43 >> 29;
v51_reg_code_stru->num1 = 30i64 * (unsigned int)v55
+ __PAIR__(20 * ((unsigned int)v54 + 2 * (unsigned __int64)(unsigned int)v61) >> 32, 8 * v38)
+ v41; // num1=30*v55+20*(v54+2*v61)+72*v38
v51_reg_code_stru->num2 = 23i64 * (unsigned int)v54
+ 32i64 * (unsigned int)v55
+ 42i64 * (unsigned int)v61
+ 54i64 * (unsigned int)v39;// num2=23*v54+32*v55+42*v61+54*v39
LODWORD(v41) = 25 * v55 + 38 * v61 + 67 * v39;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 29) + __CFADD__(8 * v54, v54);
LODWORD(v43) = 9 * v54;
HIDWORD(v41) = v43 >> 31;
v44 = __PAIR__(
(25i64 * (unsigned int)v55 + 38i64 * (unsigned int)v61 + 67 * (unsigned __int64)(unsigned int)v39) >> 32,
18 * v54)
+ v41;
HIDWORD(v41) = v51_reg_code_stru;
v51_reg_code_stru->num3 = v44;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 31) + __CFADD__(2 * v54, v54);
LODWORD(v43) = 3 * v54;
LODWORD(v41) = (__PAIR__(v43 >> 30, 12 * v54)
+ 45i64 * (unsigned int)v55
+ 33i64 * (unsigned int)v39
+ ((unsigned __int64)(unsigned int)v61 << 6)) >> 32;
v45 = SHIDWORD(v51_reg_code_stru->num1);
v46 = HIDWORD(v45) ^ LODWORD(v51_reg_code_stru->num1);
v42 = v46 < HIDWORD(v45);
LODWORD(v51_reg_code_stru->num1) = v46 - HIDWORD(v45);
v47 = v51_reg_code_stru->num2;
HIDWORD(v51_reg_code_stru->num1) = (HIDWORD(v45) ^ v45) - (v42 + HIDWORD(v45));
*(_QWORD *)(HIDWORD(v41) + 24) = __PAIR__(
(*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 28),
(*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ v47)
- __PAIR__(*(_DWORD *)(HIDWORD(v41) + 28) >> 31, *(_DWORD *)(HIDWORD(v41) + 28) >> 31);
v51_reg_code_stru->num3 = (signed __int64)(__PAIR__(
(*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 36),
(*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 32))
- __PAIR__(
*(_DWORD *)(HIDWORD(v41) + 36) >> 31,
*(_DWORD *)(HIDWORD(v41) + 36) >> 31))
% 20; // num3=(25*v55+38*v61+67*v39+18*v54)%20
v48 = (signed __int64)(__PAIR__(
((signed int)v41 >> 31) ^ (unsigned int)v41,
((signed int)v41 >> 31) ^ (unsigned int)(12 * v54 + 45 * v55 + 33 * v39 + (v61 << 6)))
- __PAIR__((signed int)v41 >> 31, (signed int)v41 >> 31))
% 20; // num4=(12*v54+45*v55+33*v39+v61*64)%20
v51_reg_code_stru->num4 = v48;
if ( SHIDWORD(v51_reg_code_stru->num3) >= SHIDWORD(v48) )
{
if ( SHIDWORD(v51_reg_code_stru->num3) > SHIDWORD(v48)// 如果num3>num4就互相交换
|| (LODWORD(v48) = v51_reg_code_stru->num3, (unsigned int)v48 > LODWORD(v51_reg_code_stru->num4)) )
{
v49 = v51_reg_code_stru->num3;
HIDWORD(v48) = HIDWORD(v51_reg_code_stru->num3);
LODWORD(v51_reg_code_stru->num3) = v51_reg_code_stru->num4;
LODWORD(v48) = HIDWORD(v51_reg_code_stru->num4);
HIDWORD(v51_reg_code_stru->num3) = v48;
LODWORD(v51_reg_code_stru->num4) = v49;
HIDWORD(v51_reg_code_stru->num4) = HIDWORD(v48);
}
}
return v48;
}
F5后看起来很乱,需要结合汇编一起分析。
接下来在sub_45FE00
计算出num5
num5
等于request_code
每个数字相加,比如1111-2222-3333-4444
计算出来的num5
就是40
接下来回到check_request_code
中把得到的4个part
和5个num
拼接
这里看F5可能看不太懂,可以看汇编
这里是先把4个operator<<
的参数全部压栈再一个个call,应该是编译器优化的结果。得到的结果就是一个字符串s=part1+part2+part3+part4+num1+num2+num3+num4+num5
最后将得到的字符串再做一个以part1
为key
的hmac_sha256
计算
因为用ida看了字符串发现OpenSSL 1.0.2m 2 Nov 2017
的字样所以事先用Rizzo打上了OpenSSL
的符号。关于OpenSSL
编译可以看https://cloud.tencent.com/developer/article/1343632。不过遗憾的是打上了符号也没办法识别出这个hmac
使用的hash
算法是什么,后来我是通过动态调试获得一组输入和输出暴力跑出了hash
为sha256
,做完hmac_sha256
后得到的结果即为verification_code
。
附上python3
注册机代码
import re
import hmac
def hmac_sha256(key,s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()
def get_verification_code(part1,part2,part3,part4):
v5=(ord(part4[0])^ord(part1[0]))<<8
v8=ord(part1[1])*ord(part4[1])+v5
v11=ord(part1[2])//ord(part4[2])+v8
v54=ord(part1[2])%ord(part4[2])+v11
v56=ord(part3[0])*ord(part2[0])<<8
v57=(ord(part2[1])^ord(part3[1]))+v56
v58=ord(part2[2])%ord(part3[2])+32+v57
v55=ord(part1[2])//ord(part3[2])+v58
v59=ord(part1[1])%ord(part1[3])<<8
v60=ord(part2[1])//ord(part2[3])+v59
v29=ord(part3[1])*ord(part3[3])+8+v60
v61=(ord(part4[1])^ord(part4[3]))+v29
v52=(ord(part1[0])^ord(part4[3]))<<8
v53=ord(part2[1])//ord(part3[2])+v52
v37=ord(part3[2])*ord(part2[1])
v38=ord(part4[3])%ord(part1[0])+v37+v53
v39=v38
v38=v38*9
v41=12*v54+45*v55+33*v39
num1=30*v55+20*(v54+2*v61)+8*v38
num2=23*v54+32*v55+42*v61+54*v39
num3=(25*v55+38*v61+67*v39+18*v54)%20
num4=(12*v54+45*v55+33*v39+v61*64)%20
if num3>num4:
num3,num4=num4,num3
num5=0
for i in range(4):
num5+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)
return hmac_sha256(part1,code)
if __name__=='__main__':
request_code=input('input request code: ')
reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
if reobj!=None:
part1=reobj.group(1)
part2=reobj.group(2)
part3=reobj.group(3)
part4=reobj.group(4)
print(get_verification_code(part1,part2,part3,part4))
else:
print('[+] Invalid code!')
第二题 Rotate
第二题和第一题相差不大,不同的地方是多了一个num6
以及hmac_sha256
的key
不同。看一下导入表,发现一些opencv
的函数
为了方便分析,去抠了一下cv::Mat
的结构体。
struct cvMat
{
int flags;
int dims;
int rows;
int cols;
unsigned __int8 *data;
unsigned __int8 *datastart;
unsigned __int8 *dataend;
unsigned __int8 *datalimit;
void *allocator;
void *u;
int *size_p;
unsigned __int64 *step_p;
unsigned __int64 step_buf[2];
};
其中MatStep
和MatSize
被我拆了,影响不是很大。
首先在check_verification_code
中使用cv::imread
读取了题目目录下的flag.jpg
这里应该是个cv::Mat
构造函数,被编译器优化成内联函数了。
接下来在sub_14007A2C0
中进行图像的旋转。
通过cv::getRotationMatrix2D
获得旋转的矩阵再用cv::warpAffine
进行仿射变换。
在sub_140079C50
进行图像的处理并计算num6
,首先是图像的处理
分别调用了cv::cvtColor
cv::resize
和cv::dct
然后根据图像的数据计算num6
v9 = *Dst.step_p; // 一行元素的字节数
v10 = &v45;
v11 = (double *)(Dst.data + 24);
do
{
v12 = *(v11 - 3);
*((double *)v10 - 1) = v12;
v13 = *(v11 - 2);
*(double *)v10 = v13;
v14 = *(v11 - 1);
*((double *)v10 + 1) = v14;
v15 = *v11;
v10[2] = *(_QWORD *)v11;
v16 = v11[1];
*((double *)v10 + 3) = v16;
v17 = v11[2];
*((double *)v10 + 4) = v17;
v18 = v11[3];
*((double *)v10 + 5) = v18;
v19 = v11[4];
*((double *)v10 + 6) = v19;
v5 = v12 * 0.015625 // 图像数据的平均数
+ v5
+ v13 * 0.015625
+ v14 * 0.015625
+ v15 * 0.015625
+ v16 * 0.015625
+ v17 * 0.015625
+ v18 * 0.015625
+ v19 * 0.015625;
v10 += 8;
v11 = (double *)((char *)v11 + v9); // 下一行
--v8;
}
while ( v8 ); // 8次
v20 = 0i64;
do // 图像每一点的数据与平均数对比
// 如果数值小于平均数则为0否则为1
// 得到一个长度为64格式为二进制的字符串
{
if ( *((double *)&v44 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v22 = v4;
else
v22 = *(string **)v4->_Buf;
v22->_Buf[v20] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v21 = v4;
else
v21 = *(string **)v4->_Buf;
v21->_Buf[v20] = '1';
}
if ( *((double *)&v45 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v24 = v4;
else
v24 = *(string **)v4->_Buf;
v24->_Buf[v20 + 1] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v23 = v4;
else
v23 = *(string **)v4->_Buf;
v23->_Buf[v20 + 1] = '1';
}
if ( *((double *)&v46 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v26 = v4;
else
v26 = *(string **)v4->_Buf;
v26->_Buf[v20 + 2] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v25 = v4;
else
v25 = *(string **)v4->_Buf;
v25->_Buf[v20 + 2] = '1';
}
if ( *(double *)&v47[v20] < v5 )
{
if ( v4->_Myres < 0x10 )
v28 = v4;
else
v28 = *(string **)v4->_Buf;
v28->_Buf[v20 + 3] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v27 = v4;
else
v27 = *(string **)v4->_Buf;
v27->_Buf[v20 + 3] = '1';
}
v20 += 4i64;
}
while ( v20 < 64 ); // 总共有8*8=64个数
得到的长度为64的字符串即为num6
最后拼接part
和num
得到字符串s=part1+part2+part3+part4+num1+num2+num3+num4+num5+num6
再以num6
为key
做hmac_sha256
得到verification code
,相关代码与第一题一致。
附上python3
注册机
import re
import hmac
import cv2
import numpy as np
def hmac_sha256(key,s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()
def get_num6(part1,part2,part3,part4):
img=cv2.imread('.\\flag.jpg',cv2.IMREAD_COLOR)
center=(img.shape[1]*0.5,img.shape[0]*0.5)
angle=0
for i in range(4):
angle+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
scale=1.0
trans=cv2.getRotationMatrix2D(center,angle,scale)
dsize=(img.shape[1],img.shape[0])
img=cv2.warpAffine(img,trans,dsize)
img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=np.float64(img)
dsize=(8,8)
img=cv2.resize(img,dsize)
img=cv2.dct(img)
total=0
for i in img:
for j in i:
total+=j
average=total/64
num6=''
for i in img:
for j in i:
if j<average:
num6+='0'
else:
num6+='1'
return num6
def get_verification_code(part1,part2,part3,part4):
v5=(ord(part4[0])^ord(part1[0]))<<8
v8=ord(part1[1])*ord(part4[1])+v5
v11=ord(part1[2])//ord(part4[2])+v8
v54=ord(part1[2])%ord(part4[2])+v11
v56=ord(part3[0])*ord(part2[0])<<8
v57=(ord(part2[1])^ord(part3[1]))+v56
v58=ord(part2[2])%ord(part3[2])+32+v57
v55=ord(part1[2])//ord(part3[2])+v58
v59=ord(part1[1])%ord(part1[3])<<8
v60=ord(part2[1])//ord(part2[3])+v59
v29=ord(part3[1])*ord(part3[3])+8+v60
v61=(ord(part4[1])^ord(part4[3]))+v29
v52=(ord(part1[0])^ord(part4[3]))<<8
v53=ord(part2[1])//ord(part3[2])+v52
v37=ord(part3[2])*ord(part2[1])
v38=ord(part4[3])%ord(part1[0])+v37+v53
v39=v38
v38=v38*9
v41=12*v54+45*v55+33*v39
num1=30*v55+20*(v54+2*v61)+8*v38
num2=23*v54+32*v55+42*v61+54*v39
num3=(25*v55+38*v61+67*v39+18*v54)%20
num4=(12*v54+45*v55+33*v39+v61*64)%20
if num3>num4:
num3,num4=num4,num3
num5=0
for i in range(4):
num5+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
num6=get_num6(part1,part2,part3,part4)
code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)+num6
return hmac_sha256(num6,code)
if __name__=='__main__':
request_code=input('input request code: ')
reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
if reobj!=None:
part1=reobj.group(1)
part2=reobj.group(2)
part3=reobj.group(3)
part4=reobj.group(4)
print(get_verification_code(part1,part2,part3,part4))
else:
print('[+] Invalid code!')
第三题 Invisiable
该题目录下的文件
打开Invisiable.exe
,首先看到有个tlscallback
反调试
通过NtQueryInformationProcess
查了DebugPort
,如果程序被调试了这个DebugPort
就不为0。检测到没有被调试后程序修改了某个函数,把这个函数填充成nop
。当然这个反调试没什么影响,因为我用了SharpOD
插件,所以基本不用管。
看一下main
中的主要部分
验证输入的函数并不是直接调用的,ida看起来也不方便,所以我选择通过动态调试获取了3个step的验证输入的函数
first step
在sub_404180
中判断number-2019000
是否为水仙花数。
如果是的话就就执行接下来的操作
读取题目目录下的invisiable.mp3
文件数据,然后通过sub_401C20
解密得到真实的文件decode.py
解密函数sub_401C20
如下
将原文件数据的每个字节与字符串ReverseEngineerIsEasy
的每个字符循环异或,得到真实文件数据,然后写回硬盘上。
然后对目录下的文件flag
也做了同样的事情,解密后得到nice.png
文件。
最后做了一个谜之操作,修改了main.dll
看了一下main.dll
偏移0x46c
处正是它的导出函数的位置,这里修改成了ret C
。
到这里first step就结束了
second step
首先判断了一下输入
如果符合条件就执行接下来的操作
读取目录下1.txt
的数据
然后判断文本内容
也就是说1.txt
的文本内容格式需要为xxxx-xxxx
,part1
长度暂时未知。
然后对这两个part
进行了和first step解密文件很像的操作,buf1
和buf2
分别为part1
和part2
然后将得到的结果做base64encode
计算,得到的结果需要是VFxQXkljVFo=
将VFxQXkljVFo=
做base64decode
后得到T\P^IcTZ
,也就是说我们的part1
长度必须为8。
然后就是写脚本跑一个符合该要求的输入出来了
def xor_asc(l):
s='T\\P^IcTZ'
a=[ord(x) for x in s]
b=[0 for i in range(8)]
for i in range(8):
b[i]=a[i]^l[i%len(l)]
return b
def inc_asc(l):
for i in range(len(l)):
l[len(l)-i-1]+=1
if l[len(l)-i-1]==ord('-'):
l[len(l)-i-1]+=1
if l[len(l)-i-1]>126:
l[len(l)-i-1]=33
else:
break
return l
def check_asc(l):
for i in l:
if i>126 or i<33:
return False
return True
def create_txt():
part1=[]
part2=[33,33,33,33]
while(True):
part1=xor_asc(part2)
if check_asc(part1):
s1=''.join([chr(x) for x in part1])
s2=''.join([chr(x) for x in part2])
s=s1+'-'+s2
print(s)
part2=inc_asc(part2)
if __name__=='__main__':
create_txt()
#u}6uhB2q-!!f+
为了方便输入,所以只取了可视的ascii字符(不包括空格)。这个脚本不会停,所以请自行ctrl+c
强制结束。随便取了一个结果u}6uhB2q-!!f+
保存到1.txt
后,再输入一个符合要求的number
,second step就结束了。
third step
在sub_404220
中对输入的number
进行了验证
需要输入一个长度为6的数字,然后每两个为一组拆成3个2位数,这3个数需要符合一个方程,解的结果为744110
。
输入正确结果后,程序加载main.dll
如果仅仅是这样,那么恭喜,程序崩溃。
还记得在first step中修改了main.dll
的导出函数吗?这就是程序崩溃的原因。为了能够得到正确结果,在third step输入数字前需要把未修改的main.dll
替换回去。最后符合题目要求,程序输出了flag。
但是我感觉这里过于诡异,所以不知道算不算正解。又看了一遍题目,要求输出的flag形式为flag{xxxxxxx}
。。。也就是说1.txt
中part1
的前五位是可以确定的,但是我不想改了,就这样吧。
还有一个诡异的地方就是,first step中创建的decode.py
和nice.png
在后面没用到,贴一下decode.py
的代码
# coding=utf-8
import cv2
import numpy as np
import random
import os
from argparse import ArgumentParser
ALPHA = 5
def build_parser():
parser = ArgumentParser()
parser.add_argument('--original', dest='ori', required=True)
parser.add_argument('--image', dest='img', required=True)
parser.add_argument('--result', dest='res', required=True)
parser.add_argument('--alpha', dest='alpha', default=ALPHA)
return parser
def main():
parser = build_parser()
options = parser.parse_args()
ori = options.ori
img = options.img
res = options.res
alpha = float(options.alpha)
if not os.path.isfile(ori):
parser.error("original image %s does not exist." % ori)
if not os.path.isfile(img):
parser.error("image %s does not exist." % img)
decode(ori, img, res, alpha)
def decode(ori_path, img_path, res_path, alpha):
ori = cv2.imread(ori_path)
img = cv2.imread(img_path)
ori_f = np.fft.fft2(ori)
img_f = np.fft.fft2(img)
height, width = ori.shape[0], ori.shape[1]
watermark = (ori_f - img_f) / alpha
watermark = np.real(watermark)
res = np.zeros(watermark.shape)
random.seed(height + width)
x = range(height / 2)
y = range(width)
random.shuffle(x)
random.shuffle(y)
for i in range(height / 2):
for j in range(width):
res[x[i]][y[j]] = watermark[i][j]
cv2.imwrite(res_path, res, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
if __name__ == '__main__':
main()
这就超出我的能力范围了(怎么还有快速傅里叶变换呢)。试着用了一下这个脚本
py -2 decode.py --original nice.png --image out.png --result res.png
得到图片
nice.png
和out.png
分别为
看了这几张图,总感觉第三题还有一点门路。然而小弟能力有限,只能到这了,希望大佬们能指导指导。