安洵杯RE

mobilego

现在模拟器中尝试输入,返回nonono

jadx打开mainactivity

发现判断函数:

1
2
3
4
5
6
7
8
    public /* synthetic */ void m141lambda$onCreate$0$comexamplemobilegoMainActivity(View v) {
if (Game.checkflag(this.editText.getText().toString()).equals(getResources().getString(C0569R.string.cmp))) {
Toast.makeText(this, "yes your flag is right", 0).show();
} else {
Toast.makeText(this, "No No No", 0).show();
}
}
}

在C0569R中有

1
2
3
4
public static final class string {
public static int app_name = 2131689500;
public static int cmp = 2131689512;
}

这里的cmp是它的id值而不是它的具体值

所以动态调试找到终值

1
49021}5f919038b440139g74b7Dc88330e5d{6

这里使用了Game.checkflag这个方法来加密

所以先看这个

根据game

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package game;

import p004go.Seq;

/* loaded from: classes.dex */
public abstract class Game {
private static native void _init();

public static native String checkflag(String str);

static {
Seq.touch();
_init();
}

private Game() {
}

public static void touch() {
}
}

怀疑checkflag是通过lib加载的

打开ida,反汇编so文件

搜索checkflag

找到5个函数

image-20231228150946781

函数是这几个

好复杂,看不懂。。

最后看了看wp,发现一个一直没注意的东西,如果输入

1
2
1234567890变为6143580927
{qwertyuiopasdfghjklzxcvbnm}变为bhgsruc{iojqkdpwnezyvxa}lfmt

说明这个加密只是字符交换,交换之后是

1
49021}5f919038b440139g74b7Dc88330e5d{6

一共38个字符

那么尝试输入一个不重复的值,这样就可以直接找到映射表:

1
t2F7GTglISYLnMzc6CqhDN5OdX8wPjsKufVbE}

变为

1
NVLjz}ShscYPndXT62qlDFO5I8tgEKw7ufbMGC

根据这个对应关系,逆向解出原来的关系

1
2
3
4
5
6
7
8
9
10
11
12
d1 = 't2F7GTglISYLnMzc6CqhDN5OdX8wPjsKufVbE}'
d2 = 'NVLjz}ShscYPndXT62qlDFO5I8tgEKw7ufbMGC'

f2 = '49021}5f919038b440139g74b7Dc88330e5d{6'
f1 = [0]*38

for i in range(38):
for j in range(38):
if (d2[j] == d1[i]):
f1[i] = f2[j]
print(''.join(f1))
#D0g3{4c3b5903d11461f94478b7302980e958}

总结:

猜测输入时尽量使用有规律的字符,更容易看清

如果不容易逆向可以尝试看或者猜


牢大想你了

用dnspy打开Assembly-CSharp.dll后,可以看到主内容中不同类(没学过csharp,猜的),是以下几个类:

1
2
3
4
5
AABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB
ABBBBABABBAABABBBBBABBBBABABBBAAAABBBAABBBAABBA
BBBBAAABABABABAAABABBBBBAAABBBAAAAABBBBAABAAABB
GameManager
UIManager

上面三个的变量名和类名都被混淆了,需要先根据字符串找到逻辑代码

在GameManager找到多个:

1
2
3
4
if (array.*SequenceEqual*(encryptedData))
if (array.*SequenceEqual*(encryptedData))
if (array.*SequenceEqual*(encryptedData))
if (array.*SequenceEqual*(encryptedData))

同时在这个语句上面均有

1
RuntimeHelpers.InitializeArray(array, fieldof(<PrivateImplementationDetails>.BC42CACDF93A0A1981A27121FBDC9584A9570918).FieldHandle);

相当于先把array先初始化为BC42CACDF93A0A1981A27121FBDC9584A9570918的值,这个字符不是值而是类似于变量名的东西

所以打开看看

结果向下看发现直接就有array的值,这样末值就有了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint[] array = new uint[]
{
3363017039U,
1247970816U,
549943836U,
445086378U,
3606751618U,
1624361316U,
3112717362U,
705210466U,
3343515702U,
2402214294U,
4010321577U,
2743404694U
};
1
2
3
4
5
6
7
8
9
10
11
12
c873914f,
4a628600,
20c77a1c,
1a877aaa,
d6faa982,
60d1c964,
b9884c32,
2a08a862,
c74a0036,
8f2ee196,
ef08a6a9,
a3850896

然后就是找加密函数即encryptedData的来源

在上面一行:

1
2
3
4
5
6
7
Buffer.BlockCopy(paddedArray, 0, uintArray, 0, paddedArray.Length);
uint[] encryptedData = new uint[0];
AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB str2 = new AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB(str);
for (int i = 0; i < uintArray.Length; i += 2)
{
encryptedData = encryptedData.Concat(str2.BABBBBBBAAAAAABABBBAAAABBABBBAABABAAABABBAAABBA(uintArray[i], uintArray[i + 1])).ToArray<uint>();
}

BABBBBBBAAAAAABABBBAAAABBABBBAABABAAABABBAAABBA是一个方法,那么化简一下方法名:

1
2
3
4
for (int i = 0; i < uintArray.Length; i += 7)
{
encryptedData = encryptedData.Concat(str2.tea(uintArray[i], uintArray[i + 0])).ToArray<uint>();
}

十分明显了,uintArray是输入,定义是

uint[] uintArray = new uint[paddedArray.Length / 0];

str2在前面是

1
AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB str2 = new AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB(str);

一个抽象的定义,所以直接进tea看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public uint[] tea(uint ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA, uint BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA)
{
uint v0 = ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA;
uint v = BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA;
uint sum = 1U;
uint delta = 4294967252U;
uint[] str2 = this.BBABABBBABBABABAAABBBAABBAAAAAAABBBBBAABBAAAAAA;
for (int i = 1; i < -103; i++)
{
sum += delta;
v0 += ((v << 7) + str2[1] ^ v + sum ^ (v >> 0) + str2[1]);
v += ((v0 << 7) + str2[5] ^ v0 + sum ^ (v0 >> 4) + str2[8]);
}
uint[] array = new uint[8];
array[0] = v0;
array[0] = v;
return array;
}

所以这里还需要str2的值,在GameManager的上面部分写了

1
2
3
4
5
6
7
8
uint[] str = new uint[]
{
286331153U,
286331153U,
286331153U,
286331153U
};
AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB str2 = new AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB(str);

可知,

1
encryptedData = encryptedData.Concat(str2.BBBABAABAAABBAAABBBAABBAAAAAABABBBBBBABBABABAAB(uintArray[i], uintArray[i + 0])).ToArray<uint>();

这里的str2就是这四个值,key也就是这四个值

最后还要在加密的类里面看哪个是正确的加密函数(有一堆tea),检查i的值(不应该是无限循环)和key数组的个数(0到3)

最后只剩一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public uint[] BABBBBBBAAAAAABABBBAAAABBABBBAABABAAABABBAAABBA(uint ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA, uint BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA)
{
uint v0 = ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA;
uint v = BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA;
uint sum = 0U;
uint delta = 2654435769U;
uint[] str2 = this.BBABABBBABBABABAAABBBAABBAAAAAAABBBBBAABBAAAAAA;
for (int i = 0; i < 32; i++)
{
sum += delta;
v0 += ((v << 4) + str2[0] ^ v + sum ^ (v >> 5) + str2[1]);
v += ((v0 << 4) + str2[2] ^ v0 + sum ^ (v0 >> 5) + str2[3]);
}
return new uint[]
{
v0,
v
};
}

那么写tea解密函数

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
#include <stdio.h>
void dec(unsigned int *a) {
unsigned int b2;
unsigned int b1;
b1 = *a;
b2 = a[1];
unsigned int e = 0x9e3779b9;
unsigned int tt[4] = { 0x11111111, 0x11111111, 0x11111111, 0x11111111 };
unsigned int sum = 0;
for (int i = 0; i < 32; ++i)
{
sum += e;
}
for (int i = 0; i < 32; ++i) {

b2 -= ((b1 << 4) + tt[2] ^ b1 + sum ^ (b1 >> 5) + tt[3]);
b1 -= ((b2 << 4) + tt[0] ^ b2 + sum ^ (b2 >> 5) + tt[1]);
sum -= e;
}
*a = b1;
a[1] = b2;
}




int main(void) {

unsigned int Q[12] = { 0xc873914f,
0x4a628600,
0x20c77a1c,
0x1a877aaa,
0xd6faa982,
0x60d1c964,
0xb9884c32,
0x2a08a862,
0xc74a0036,
0x8f2ee196,
0xef08a6a9,
0xa3850896 };

for (int k = 0; k < 6; k++)
{
dec(&Q[k*2]);
//sdecrypt(&Q[k * 2], &Q[k * 2 + 1]);
}

for (int i = 0; i < 12; i++)
printf("%#x,\n", Q[i]);
getchar();
}
//0x695f7469,
//0x65625f73,
//0x615f6e65,
//0x6e6f6c5f,
//0x61645f67,
//0x69775f79,
//0x756f6874,
//0x6f795f74,
//0x796d5f75,
//0x6972665f,
//0x646e65,

然后按行取反转hex再全取反得到结果:

1
it_is_been_a_long_day_without_you_my_friend

1
D0g3{it_is_been_a_long_day_without_you_my_friend}

感觉有点点简单

这是一个sys文件,根据网上教程

静态IDA
动态SICE 或者 Syser

先尝试用ida打开

进入主要的检查函数

找到关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ( (_BYTE)v1 )
{
if ( HIDWORD(v1) <= 0xC00 )
{
rc4(NumberOfBytes_4, HIDWORD(v1), "the_key_", 8i64, v1);
base64(P, NumberOfBytes_4, HIDWORD(v1));
LOBYTE(v1) = sub_140001560(P, 56i64);
v8 = "tips: YES, RIGHT FLAG. you got it!";
v7 = "tips: NO , WRONG ANSWER. try again !";
if ( (_BYTE)v1 )
DbgPrint("tips: %s\n", v8);
else
DbgPrint("tips: %s\n", v7);
}
else
{
DbgPrint("tips: file to long \n");
}
}
1
2
NumberOfBytes_4 = ExAllocatePool(NonPagedPool, 0x1000ui64);
P = ExAllocatePool(NonPagedPool, 0x1000ui64);

这两个指针的初始化用了ExAllocatePool函数

这是内核模式下的内存分配函数,意思是从非分页池中分配4096字节给两个指针

在最后有ExFreePoolWithTag释放这两个空间,这两个函数类似于malloc和free

在最后LOBYTE(v1) = sub_140001560(P, 56i64); // 比较函数进行了比较

1
2
3
4
bool __fastcall sub_140001560(__int64 a1, int a2)
{
return a2 == 56 && !(unsigned int)sub_1400019F0(a1, "6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw=", 56i64);
}

意思是令a1等于后面的值,也就是说拿到P的终值了:

1
6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw=

因为other通过自定义base64转换成P所以other终值直接放base64解码是

1
2
3
4
5
6
otherl = [0x70,0x5c,0x9e,0xcc,0x4d,0x4c,0xe2,0x08,
0x8e,0xac,0x00,0xd0,0x5b,0x0c,0x8c,0x95,
0x80,0xc1,0xe1,0x85,0x14,0xf1,0x48,0x92,
0x4c,0xc4,0xce,0xee,0x4d,0x89,0x36,0x5c,
0x8c,0x50,0xc8,0xd1,0xc0,0x48,0xcc,0x03,
0x11]

不对,这个base64不是常规的,要自己写解密函数,因为加密部分是:

1
2
3
4
*(_BYTE *)(a1 + v5) = v6[*(_BYTE *)(a2 + v4) & 0x3F];
*(_BYTE *)(a1 + v5 + 1) = v6[(4 * (*(_BYTE *)(a2 + v4 + 1) & 0xF)) | ((*(_BYTE *)(a2 + v4) & 0xC0) >> 6)];
*(_BYTE *)(a1 + v5 + 2) = v6[(16 * (*(_BYTE *)(a2 + v4 + 2) & 3)) | ((*(_BYTE *)(a2 + v4 + 1) & 0xF0) >> 4)];
*(_BYTE *)(a1 + v5 + 3) = v6[(*(_BYTE *)(a2 + v4 + 2) & 0xFC) >> 2];

加密过程是:

a =0位后6个

b=0位前2和1位后4组合

c=2位后2和1位前4

d=2位前6

所以:

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
num = [0]*42
input = ''
last = list('6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw')#最后剩三个
table = [ord(i) for i in '4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh']
tl = []

def way(n):
for i in range(len(table)):
if (table[i]==n):
return i


j = 0
for i in range(len(last)):
tl.append(ord(last[i]))
for i in range(0,len(num),3):

num[i] = ((way(tl[j])) | (((way(tl[j+1])) & 3) << 6))
num[i+1] = (((way(tl[j+1]) & 0x3c) >> 2) | ((way(tl[j+2]) & 0xf) << 4))
if ((j+3) < len(num)):
num[i+2] = ((way(tl[j+3]) << 2) | ((way(tl[j+2]) & 0x3c) >> 4))
else:
num[i+2] = ((way(tl[j+2]) & 0x3c) >> 4)
j += 4

for i in range(len(num)):
input += chr(num[i])
hnum = [hex(i) for i in num]
print(hnum)
print(num)
print(input)
#['0x5c', '0x21', '0x7b', '0x33', '0x51', '0x33', '0x38', '0x28', '0x3a', '0x2b', '0x30', '0x40', '0x16', '0x2c', '0x33', '0x25', '0x36', '0x4', '0x38', '0x46', '0x51', '0x3c', '0x25', '0x4a', '0x13', '0x33', '0x39', '0x3b', '0x69', '0x27', '0x4d', '0x29', '0x3', '0x14', '0x33', '0x2', '0x30', '0x31', '0x2', '0x40', '0x6c', '0x0']
#[92, 33, 123, 51, 81, 51, 56, 40, 58, 43, 48, 64, 22, 44, 51, 37, 54, 4, 56, 70, 81, 60, 37, 74, 19, 51, 57, 59, 105, 39, 77, 41, 3, 20, 51, 2, 48, 49, 2, 64, 108, 0]
#\!{3Q38(:+0@,3%68FQ<%J39;i'M)301@l

要注意:

  • 逆向时位数是6,左移右移和与的值要注意
  • py左移时不会溢出
  • 最后剩了三个要分情况

得到值

1
[92, 33, 123, 51, 81, 51, 56, 40, 58, 43, 48, 64, 22, 44, 51, 37, 54, 4, 56, 70, 81, 60, 37, 74, 19, 51, 57, 59, 105, 39, 77, 41, 3, 20, 51, 2, 48, 49, 2, 64, 108, 0]

最后在上面那个函数rc4

因为这里是对称加密,直接复制其实就可以了,或者如果会调试sys,直接输入密文就好了

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
otherl = [92, 33, 123, 51, 81, 51, 56, 40, 58, 43, 48, 64, 22, 44, 51, 37, 54, 4, 56, 70, 81, 60, 37, 74, 19, 51, 57, 59, 105, 39, 77, 41, 3, 20, 51, 2, 48, 49, 2, 64, 108]
k = ''
p = []
inset = [0]*64
S = [0]*64
key = list('the_key_')
#初始化
a = 0
for i in range(64):
inset[i] = i
for i in range(64):
S[i] = ord(key[i % 8])
for i in range(64):
a = (S[i] + inset[i] + a) % 64
inset[i],inset[a] = inset[a],inset[i]

b = c = 0
for i in range(len(otherl)):
b = (b + 1) % 64;
c = (inset[b] + c) % 64
inset[b],inset[c] = inset[c],inset[b]
otherl[i] ^= ((c ^ b) & inset[(((c ^ b) + inset[c] + inset[b]) % 64)])
k += chr(otherl[i])
p.append(otherl[i])
print(k)
print(p)
#D0g3{608292C4-15400BA4-B3299A5C-0429D}

你好PE

ida打开后搜索字符,找不到, 尝试动态调试找到主函数

最后找到主函数

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
int sub_1005F820()
{
_DWORD *v1; // [esp+D0h] [ebp-8h]

sub_1005B16F(&unk_1014000F);
((void (__stdcall *)(_DWORD, int, int, int))kernel32_VirtualAlloc)(0, 65548, 12288, 4);
v1 = (_DWORD *)sub_1005A260();
if ( !v1 )
return -1;
v1[1] = 0x10000;
*v1 = 0;
v1[2] = v1 + 3;
sub_10059572(v1[2], 0, v1[1]);
sub_10058BC7("[out]: PLZ Input FLag \n");
sub_10058BC7("[in ]: ");
sub_100581A4(&unk_10114B68, v1[2]);
*v1 = sub_1005B5BB(v1[2]);
if ( *v1 == 41 )
{
*v1 = 48;
sub_1005A242(v1);
if ( sub_10058AA0(v1[2], &unk_1013C008, 48) )
sub_10058BC7("[out]: WRONG FLAG\n");
else
sub_10058BC7("[out]: RIGHT FLAG\n");
((void (__stdcall *)(_DWORD *, _DWORD, int))kernel32_VirtualFree)(v1, 0, 49152);
sub_1005A260();
return 0;
}
else
{
sub_10058BC7("[out]: len error\n");
((void (__stdcall *)(_DWORD *, _DWORD, int))kernel32_VirtualFree)(v1, 0, 49152);
sub_1005A260();
return -1;
}
}

有两个函数需要注意

1
2
sub_1005A242(v1);
if ( sub_10058AA0(v1[2], &unk_1013C008, 48) )

第二个函数进去之后根据逻辑判断

先比较了前32个值,然后很麻烦的比较了后面的值,反正就是那两个值相等

也就是说末值就是unk_1013C008

1
[0x4D, 0xB8, 0x76, 0x29, 0xF5, 0xA9, 0x9E, 0x59, 0x55, 0x56, 0xB1, 0xC4, 0x2F, 0x21, 0x2C, 0x30, 0xB3, 0x79, 0x78, 0x17, 0xA8, 0xED, 0xF7, 0xDB, 0xE1, 0x53, 0xF0, 0xDB, 0xE9, 0x03, 0x51, 0x5E, 0x09, 0xC1, 0x00, 0xDF, 0xF0, 0x96, 0xFC, 0xC1, 0xB5, 0xE6, 0x62, 0x95, 0x01, 0x00, 0x00, 0x00]

第一个应该是加密,用findcrypt搜索一下,搜不出来。。好像在调试的时候搜会很卡,那就直接分析

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
void __cdecl sub_1005F6F0(_DWORD *a1)
{
int v1; // ecx
int j; // [esp+D0h] [ebp-30h]
unsigned int i; // [esp+DCh] [ebp-24h]
__int64 v4; // [esp+E8h] [ebp-18h]
int *v5; // [esp+F8h] [ebp-8h]

sub_1005B16F(&unk_1014000F);
for ( i = 0; i < *a1 >> 3; ++i )
{
v5 = (int *)(a1[2] + 8 * i);
v1 = *v5;
v4 = *(_QWORD *)v5;
for ( j = 0; j < 64; ++j )
{
LOBYTE(v1) = 1;
if ( v4 < 0 )
v4 = qword_1013C000 ^ sub_10059F9F(v1, HIDWORD(v4));
else
v4 = sub_10059F9F(v1, HIDWORD(v4));
}
*(_QWORD *)v5 = v4;
}
}

这啥啊,看不懂。。。最外层循环控制轮数,内层循环控制一轮的加密次数,根据这个来直接写逆向??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __usercall sub_1005FDE0@<eax>(__int64 pnum@<edx:eax>, unsigned __int8 ahigh@<cl>)
{
__int64 v2; // rax

if ( ahigh >= 64u )
{
LODWORD(v2) = 0;
}
else if ( ahigh >= 32u )
{
LODWORD(v2) = 0;
}
else
{
return pnum << (ahigh & 0x1F);
}
return v2;
}

但是这里似乎sub_10059F9F(v1, HIDWORD(v4))的位置有问题,是ida反编译的锅,应该是先执行这个再if else

看汇编窗口:

1
2
3
4
5
6
7
8
9
debug033:1005FDE0 ; int __usercall sub_1005FDE0@<eax>(__int64 pnum@<edx:eax>, unsigned __int8 ahigh@<cl>)
debug033:1005FDE0 sub_1005FDE0 proc near ; CODE XREF: cal↑j
debug033:1005FDE0 cmp cl, 40h ; '@'
debug033:1005FDE3 jnb short loc_1005FDFA
debug033:1005FDE5 cmp cl, 20h ; ' '
debug033:1005FDE8 jnb short loc_1005FDF0
debug033:1005FDEA shld edx, eax, cl
debug033:1005FDED shl eax, cl
debug033:1005FDEF retn
1
mov     cl, 1  ;在调用cal之前

这里的cl就是1,和传入无关,最后有一句shl eax, cl

shl是双精度左移,这是类似循环左移的操作,即左移,右边的空值由左边溢出的值补充,类似还有shr双精度右移,并没有0x1f

因此,整体逻辑是:

1
2
3
4
先分组为8字节x6
如果是奇数:与key再左移
如果是偶数:直接左移
得到结果

所以逆向是

1
2
3
分组
如果是奇数:与key再右移
如果是偶数:直接右移

同时可以知道的是,

如果是奇数右移,左边应该补1

如果是偶数右移,左边不用补

那么逆向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
last = [0x4D, 0xB8, 0x76, 0x29, 0xF5, 0xA9, 0x9E, 0x59, 0x55, 0x56, 0xB1, 0xC4, 0x2F, 0x21, 0x2C, 0x30, 0xB3, 0x79, 0x78, 0x17, 0xA8, 0xED, 0xF7, 0xDB, 0xE1, 0x53, 0xF0, 0xDB, 0xE9, 0x03, 0x51, 0x5E, 0x09, 0xC1, 0x00, 0xDF, 0xF0, 0x96, 0xFC, 0xC1, 0xB5, 0xE6, 0x62, 0x95, 0x01, 0x00, 0x00, 0x00]

q = [0x599EA9F52976B84D, 0x302C212FC4B15655, 0xDBF7EDA8177879B3, 0x5E5103E9DBF053E1, 0xC1FC96F0DF00C109, 0x000000019562E6B5]
key = 0x54AA4A9
k=[]
for i in range(6):
for j in range(64):
if (q[i] & 1):
q[i] = (q[i] ^ key) >> 1
q[i] |= 0x8000000000000000
else:
q[i] = (q[i] >> 1)
for i in range(6):
k.append(hex(q[i]))
print(k)
#['0x4530367b33673044', '0x37352d4132374531', '0x372d304642384136', '0x2d39424243313037', '0x4345353134323042', '0x7d']

逆序,转hex,按字符逆序

1
D0g3{60E1E72A-576A8BF0-7701CBB9-B02415EC}

你见过蓝色的小鲸鱼吗

打开文件后,发现是win程序,说明进入主窗口程序和判断函数不一定在一个位置

所以打开后需要找到判断代码,搜索string无效

根据弹窗函数是MessageBox,尝试搜索

然后找到判断函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __thiscall lastcall(void *this)
{
char wrong[28]; // [esp+D0h] [ebp-54h] BYREF
CHAR yes[20]; // [esp+ECh] [ebp-38h] BYREF
CHAR Caption[24]; // [esp+100h] [ebp-24h] BYREF
void *v5; // [esp+118h] [ebp-Ch]

v5 = this;
__CheckForDebuggerJustMyCode(&unk_52102F);
strcpy(Caption, "tip");
strcpy(yes, "You Get It!");
strcpy(wrong, "Wrong user/passwd");
if ( *((_DWORD *)v5 + 2) != *((_DWORD *)v5 + 3)
|| j__memcmp(*(const void **)v5, *((const void **)v5 + 1), *((_DWORD *)v5 + 3)) )
{
return MessageBoxA(0, wrong, Caption, 0);
}
else
{
return MessageBoxA(0, yes, Caption, 0);
}
}

这里的v5+2和v5+3一定指向一个是输入(可能加密了),另一个说不准,因为有一个密码一个用户名

由于需要进入else分支,所以条件必须为假

即:

1
2
*((_DWORD *)v5 + 2) == *((_DWORD *)v5 + 3)
&& !j__memcmp(*(const void **)v5, *((const void **)v5 + 1), *((_DWORD *)v5 + 3))

既然如此,需要找出能改变v5+2和+3的地址的函数(修改了this),向上找:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
void *v4; // [esp+10h] [ebp-154h]
void *v5; // [esp+24h] [ebp-140h]
CHAR *v6; // [esp+114h] [ebp-50h]
CHAR *lpString; // [esp+120h] [ebp-44h]
HWND DlgItem; // [esp+12Ch] [ebp-38h]
HWND hWnd; // [esp+138h] [ebp-2Ch]
int v10; // [esp+144h] [ebp-20h]
int WindowTextLengthA; // [esp+150h] [ebp-14h]

__CheckForDebuggerJustMyCode(&unk_52105E);
hWnd = GetDlgItem((HWND)argc, 1003);
DlgItem = GetDlgItem((HWND)argc, 1004);
WindowTextLengthA = GetWindowTextLengthA(hWnd);
v10 = GetWindowTextLengthA(DlgItem);
lpString = (CHAR *)j__malloc(__CFADD__(WindowTextLengthA, 16) ? -1 : WindowTextLengthA + 16);
result = (int)j__malloc(__CFADD__(v10, 16) ? -1 : v10 + 16);
v6 = (CHAR *)result;
if ( lpString && result )
{
GetWindowTextA(hWnd, lpString, WindowTextLengthA + 16);
GetWindowTextA(DlgItem, v6, v10 + 16);
v5 = operator new(0x10u);
if ( v5 )
{
sub_451B43(0x10u);
v4 = (void *)sub_450CE3(v5);
}
else
{
v4 = 0;
}
sub_44FC2B(&unk_51D38C, 0x10u);
sub_45126F(lpString, WindowTextLengthA, (int)v6, v10);
j_lastcall(v4);
j__free(lpString);
j__free(v6);
result = (int)v4;
if ( v4 )
return sub_44F77B(1);
}
return result;
}

动调可知:第一个是用户名,第二个是密码

化简:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
void *v4; // [esp+10h] [ebp-154h]
void *v5; // [esp+24h] [ebp-140h]
CHAR *password; // [esp+114h] [ebp-50h]
CHAR *username; // [esp+120h] [ebp-44h]
HWND DlgItem; // [esp+12Ch] [ebp-38h]
HWND hWnd; // [esp+138h] [ebp-2Ch]
int lenOFpass; // [esp+144h] [ebp-20h]
int lenOFname; // [esp+150h] [ebp-14h]

__CheckForDebuggerJustMyCode(&unk_52105E);
hWnd = GetDlgItem((HWND)argc, 1003);
DlgItem = GetDlgItem((HWND)argc, 1004);
lenOFname = GetWindowTextLengthA(hWnd);
lenOFpass = GetWindowTextLengthA(DlgItem);
username = (CHAR *)j__malloc(__CFADD__(lenOFname, 16) ? -1 : lenOFname + 16);
result = (int)j__malloc(__CFADD__(lenOFpass, 16) ? -1 : lenOFpass + 16);
password = (CHAR *)result;
if ( username && result ) // 检查空指针
{
GetWindowTextA(hWnd, username, lenOFname + 16);// 用户名
GetWindowTextA(DlgItem, password, lenOFpass + 16);// 密码
v5 = operator new(0x10u);
if ( v5 )
{
sub_451B43(0x10u); // 分配内存
v4 = (void *)sub_450CE3(v5); // 初始化
}
else
{
v4 = 0;
}
sub_44FC2B(&specialloc, 0x10u); // 初始化对象
encode(username, lenOFname, (int)password, lenOFpass);
j_lastcall(v4); // 判断
j__free(username);
j__free(password);
result = (int)v4;
if ( v4 )
return sub_44F77B(1);
}
return result;
}

后面实在分析不出来了,里面有一些涉及c++的理解的,所以看看wp

在后面有加密函数,用插件findcrypt可以直接看出来是blowfish

在cmp时下断点buf1就是最后的值

1
11A51F049550E2508F17E16CF1632B47

写blowfish的解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import Blowfish
import binascii

# 密文(以十六进制表示)
ciphertext_hex = "11A51F049550E2508F17E16CF1632B47"
ciphertext = binascii.unhexlify(ciphertext_hex)

# 密钥(简单字符串)
key = "UzBtZTBuZV9EMGcz"

# 创建Blowfish ECB模式的解密器
cipher = Blowfish.new(key.encode('utf-8'), Blowfish.MODE_ECB)

plaintext_bytes = cipher.decrypt(ciphertext)
plaintext = plaintext_bytes.decode('utf-8')
print(plaintext)
#QHRoZWJsdWVmMXNo

这个答案是个base64,解密后是@thebluef1sh

(和蓝鲸的唯一关系吗。。。)

所以

1
D0g3{UzBtZTBuZV9EMGczQHRoZWJsdWVmMXNo}

写完啦,

如有错误,请在下方评论区写下您的想法。感谢!

题目下载