安洵杯re部分wp
安洵杯re复现
难度还可以,学了很多新东西。
mobilego
ida打开so文件,借助go parser分析go语言逻辑。但是不清楚为什么找不到随机数种子
贴一篇静态分析go语言的好文[原创]GoJni 协议加解密分析-Android安全-看雪-安全社区|安全招聘|kanxue.com
这里选择动调,因为根据密文49021}5f919038b440139g74b7Dc88330e5d{6
推测加密是对原文的简单乱序。使用jeb动调跟踪下原文的变化。
原文为FLAG{abcdefghijklmnopqrstuvwxyz012345}
,变换后为q3gyj}eozkfxhtuallncpasrdvfb50wg124i{m
用雷电模拟器调试时遇到jeb里v0 - v2寄存器存储的值均为void的问题,排查发现不是jeb的问题,更换为真机调试运行正常。也许是因为模拟器没有配置好。
脚本
a = "q3gyj}eozkfxhtualLncpAsrdvFb50wG124i{m"
b = "49021}5f919038b440139g74b7Dc88330e5d{6"
c = r"FLAG{abcdefghijklmnopqrstuvwxyz012345}"
flag = ''
for i in c:
for j in range(len(a)):
if i == a[j]:
flag += b[j]
print(flag)
你见过蓝色的小鲸鱼
无法识别关键函数,借助IDA动调,给MessageBoxA函数下断点后回溯栈帧。
初步找到显示最终正误结果的函数
检查交叉引用追踪到sub_4577E0
CHAR *__cdecl sub_4577E0(HWND hDlg)
{
CHAR *result; // eax
void *v2; // [esp+10h] [ebp-154h]
void *v3; // [esp+24h] [ebp-140h]
CHAR *v4; // [esp+114h] [ebp-50h]
CHAR *lpString; // [esp+120h] [ebp-44h]
HWND DlgItem; // [esp+12Ch] [ebp-38h]
HWND hWnd; // [esp+138h] [ebp-2Ch]
int WindowTextLengthA; // [esp+144h] [ebp-20h]
int Size; // [esp+150h] [ebp-14h]
__CheckForDebuggerJustMyCode(&unk_52105E);
hWnd = GetDlgItem(hDlg, 1003);
DlgItem = GetDlgItem(hDlg, 1004);
Size = GetWindowTextLengthA(hWnd);
WindowTextLengthA = GetWindowTextLengthA(DlgItem);
lpString = (CHAR *)j__malloc(__CFADD__(Size, 16) ? -1 : Size + 16);
result = (CHAR *)j__malloc(__CFADD__(WindowTextLengthA, 16) ? -1 : WindowTextLengthA + 16);
v4 = result;
if ( lpString && result )
{
GetWindowTextA(hWnd, lpString, Size + 16);
GetWindowTextA(DlgItem, v4, WindowTextLengthA + 16);
v3 = operator new(0x10u);
if ( v3 )
{
sub_451B43(0x10u);
v2 = (void *)sub_450CE3(v3);
}
else
{
v2 = 0;
}
sub_44FC2B(&unk_51D38C, 0x10u);
sub_45126F(lpString, Size, (int)v4, WindowTextLengthA); //加密函数
sub_450199(v2); //show result
j__free(lpString);
j__free(v4);
result = (CHAR *)v2;
if ( v2 )
return (CHAR *)sub_44F77B(1);
}
return result;
}
v4为密码框内容,lpstring为用户名框内容,可以确定sub_45126F为加密函数。进45126F可看到如下函数
int __thiscall sub_4571A0(int this, void *Src, size_t Size, void *a4, size_t a5)
{
const void *v5; // eax
int result; // eax
size_t v7; // [esp-4h] [ebp-12Ch]
int v8; // [esp+10h] [ebp-118h]
__CheckForDebuggerJustMyCode(&unk_52102F);
if ( operator new(0x2Cu) )
{
sub_451A4E(0x2Cu);
v8 = sub_450CBB(Src, Size);
}
else
{
v8 = 0;
}
sub_4521B5(&unk_51C048, &unk_51C000);
sub_451F08(a4, a5);
*(_DWORD *)(this + 12) = sub_44FEF6(v8);
*(_DWORD *)(this + 4) = sub_450315(*(_DWORD *)(this + 12));
v7 = *(_DWORD *)(this + 12);
v5 = (const void *)sub_4505A4(v8);
j__memmove(*(void **)(this + 4), v5, v7);
result = v8;
if ( v8 )
return sub_44FF0A(1);
return result;
}
4521B5引用的地址存放的是blowfish的sbox和pbox。另外FindCrypt也识别出了blowfish的特征,猜测是拿用户名做密钥对密码进行加密。
另外在sub_45126F前,sub_44FC2B访问了unk_51D38C地址,推测存储的是密文。写脚本解密得用户名。
from Crypto.Cipher import Blowfish
# Blowfish
key = bytes([0x55, 0x7A, 0x42, 0x74, 0x5A, 0x54, 0x42, 0x75, 0x5A, 0x56, 0x39, 0x45, 0x4D, 0x47, 0x63, 0x7A])
enc_b = bytes([0x11, 0xA5, 0x1F, 0x04, 0x95, 0x50, 0xE2, 0x50, 0x8F, 0x17, 0xE1, 0x6C, 0xF1, 0x63, 0x2B, 0x47])
cipher = Blowfish.new(key, Blowfish.MODE_ECB)
dec = cipher.decrypt(enc_b)
for i in dec:
print(chr(i), end='')
print()
得到16位密码QHRoZWJsdWVmMXNo
输入源程序测试成功
感觉有点点简单
用IDA64直接加载,主函数逻辑不复杂。
__int64 sub_1400016F0()
{
__int64 v1; // [rsp+20h] [rbp-78h] BYREF
PVOID NumberOfBytes_4; // [rsp+28h] [rbp-70h]
PVOID P; // [rsp+30h] [rbp-68h]
__int64 v4; // [rsp+38h] [rbp-60h]
__int64 v5; // [rsp+40h] [rbp-58h]
__int64 v6; // [rsp+48h] [rbp-50h] BYREF
const char *v7; // [rsp+50h] [rbp-48h]
const char *v8; // [rsp+58h] [rbp-40h]
struct _UNICODE_STRING DestinationString; // [rsp+60h] [rbp-38h] BYREF
char v10[40]; // [rsp+70h] [rbp-28h] BYREF
HIDWORD(v1) = 4096;
memset(&v6, 0, sizeof(v6));
RtlInitUnicodeString(&DestinationString, L"\\??\\C:\\Users\\Public\\flag.txt");
NumberOfBytes_4 = ExAllocatePool(NonPagedPool, 0x1000ui64);
P = ExAllocatePool(NonPagedPool, 0x1000ui64);
if ( NumberOfBytes_4 && P )
{
v4 = HIDWORD(v1);
memset(P, 0, HIDWORD(v1));
v5 = HIDWORD(v1);
memset(NumberOfBytes_4, 0, HIDWORD(v1));
qmemcpy(v10, &DestinationString, 0x10ui64);
LOBYTE(v1) = sub_140001040(v10, v6, NumberOfBytes_4, (char *)&v1 + 4);
if ( (_BYTE)v1 )
{
if ( HIDWORD(v1) <= 0xC00 )
{
sub_1400011F0(NumberOfBytes_4, HIDWORD(v1), "the_key_", 8i64, v1);//核心加密1,RC4
sub_140001360((char *)P, (__int64)NumberOfBytes_4, SHIDWORD(v1));//核心加密2,base64
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");
}
}
else
{
DbgPrint("tips: can not read|open file\n");
}
}
else
{
DbgPrint("tips: can not malloc\n");
}
if ( NumberOfBytes_4 )
{
ExFreePoolWithTag(NumberOfBytes_4, 0);
NumberOfBytes_4 = 0i64;
}
if ( P )
{
ExFreePoolWithTag(P, 0);
P = 0i64;
}
return 0i64;
}
sub_1400011F0为RC4加密,sub_140001360为base64加密
sub_1400011F0://RC4加密函数,解密直接异或回去即可
__int64 __fastcall sub_1400011F0(char *a1, unsigned int a2, char *a3, int a4)
{
__int64 result; // rax
unsigned __int8 v5; // [rsp+20h] [rbp-18h]
unsigned __int8 v6; // [rsp+21h] [rbp-17h]
unsigned int i; // [rsp+24h] [rbp-14h]
v5 = 0;
v6 = 0;
sub_1400015B0(a3, a4);
for ( i = 0; ; ++i )
{
result = a2;
if ( i >= a2 )
break;
v5 = (v5 + 1) % 64;
v6 = (byte_140003010[v5] + v6) % 64;
sub_1400018E0(&byte_140003010[v5], &byte_140003010[v6]);//交换
a1[i] ^= (v6 ^ v5) & byte_140003010[(unsigned __int8)(((v6 ^ v5) + byte_140003010[v6] + byte_140003010[v5]) % 64)];
}
return result;
}
sub_1400015B0://
__int64 __fastcall sub_1400015B0(char *a1, int a2)
{
__int64 result; // rax
int k; // [rsp+20h] [rbp-68h]
unsigned int i; // [rsp+24h] [rbp-64h]
int j; // [rsp+28h] [rbp-60h]
int v6; // [rsp+2Ch] [rbp-5Ch]
char v7[64]; // [rsp+30h] [rbp-58h] BYREF
for ( i = 0; i < 0x40; ++i )
byte_140003010[i] = i;
result = 0i64;
memset(v7, 0, sizeof(v7));
for ( j = 0; j < 64; ++j )
{
v7[j] = a1[j % a2];
result = (unsigned int)(j + 1);
}
v6 = 0;
for ( k = 0; k < 64; ++k )
{
v6 = ((unsigned __int8)v7[k] + byte_140003010[k] + v6) % 64;
sub_1400018E0(&byte_140003010[k], &byte_140003010[v6]);//交换
result = (unsigned int)(k + 1);
}
return result;
}
sub_140001360:
__int64 __fastcall sub_140001360(char *a1, char *a2, int a3)
{
int v4; // [rsp+0h] [rbp-88h]
int v5; // [rsp+4h] [rbp-84h]
char v6[80]; // [rsp+10h] [rbp-78h] BYREF
strcpy(v6, "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh");
v4 = 0;
v5 = 0;
while ( v4 < a3 )
{
a1[v5] = v6[a2[v4] & 0x3F];
a1[v5 + 1] = v6[(4 * (a2[v4 + 1] & 0xF)) | ((a2[v4] & 0xC0) >> 6)];
a1[v5 + 2] = v6[(16 * (a2[v4 + 2] & 3)) | ((a2[v4 + 1] & 0xF0) >> 4)];
a1[v5 + 3] = v6[(a2[v4 + 2] & 0xFC) >> 2];
v4 += 3;
v5 += 4;
}
if ( a3 % 3 == 1 )
{
a1[v5 - 2] = '=';
a1[v5 - 1] = '=';
}
else if ( a3 % 3 == 2 )
{
a1[v5 - 1] = 61;
}
return 0i64;
}
解密脚本:
def locateat(a):
pz="4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"
i=0
while(i<64):
if(pz[i]==a):
return i
i=i+1
def decrypt(a1):
key="the_key_"
byte_140003010=[]
for i in range(0x40):
byte_140003010.append(i)
v7=[]
for j in range(64):
v7.append(ord(key[j%8]))
v6 = 0
for k in range(64):
v6 = (int(v7[k]) + int(byte_140003010[k]) + int(v6)) % 64
byte_140003010[k], byte_140003010[v6]= byte_140003010[v6],byte_140003010[k]
v5=0
v6=0
for i in range(41):
v5 = (v5 + 1) % 64
v6 = (int(byte_140003010[v5]) + v6) % 64
byte_140003010[v5], byte_140003010[v6]=byte_140003010[v6], byte_140003010[v5]
a1[i] ^= (v6 ^ v5) & byte_140003010[(((v6 ^ v5) + int(byte_140003010[v6]) + int(byte_140003010[v5])) % 64)]
return a1
def debase64(a2):
a1=[]
i=0
j=0
while(i<40):
try:
a1.append(locateat(a2[j])|((locateat(a2[j+1])&(0x3))<<6))
a1.append((locateat(a2[j+1])& 0x3C)>>2|(locateat(a2[j+2])&0xF)<<4)
a1.append((locateat(a2[j+2])&0x30)>>4|locateat(a2[j+3])<<2)
except:
print(str(i)+" "+str(j))
i=i+3
j=j+4
return a1
if __name__=='__main__':
b="6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw"
a=decrypt(debase64(b))
i=0
c=[]
for i in a:
print(chr(i)+"")
#flag:D0g3{608292C4-15400BA4-B3299A5C-704C292D}
牢大想你了
游戏逆向,打开Assembly_CSharp.dll,定位到GameManager.OnValueChanged
// Token: 0x06000005 RID: 5 RVA: 0x00002240 File Offset: 0x00000440
public void OnValueChanged(string ABBAAAABBBBAAABABBBABAAABAABAABBABBBBABAABAABAB)
{
uint[] str = new uint[]
{
286331153U,
286331153U,
286331153U,
286331153U
};
byte[] strBytes = Encoding.UTF8.GetBytes(ABBAAAABBBBAAABABBBABAAABAABAABBABBBBABAABAABAB);
int paddingCount = 8 - strBytes.Length % 8;
byte[] paddedArray = new byte[strBytes.Length + paddingCount];
Array.Copy(strBytes, paddedArray, strBytes.Length);
uint[] uintArray = new uint[paddedArray.Length / 4];
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>();
}
uint[] array = new uint[]
{
3363017039U,
1247970816U,
549943836U,
445086378U,
3606751618U,
1624361316U,
3112717362U,
705210466U,
3343515702U,
2402214294U,
4010321577U,
2743404694U
};
MonoBehaviour.print(array);
if (array.SequenceEqual(encryptedData))
{
this.BBBAAAAABABABABBABAAAAABBABBAABBABABABABBBABAAB = 5;
this.ABAABAAABABABABABBBBBAAABBAABBBBBAABAAAABBABABB("port");
this.BAABAABBABABABABBBABBBBABBBBBBBABABBAABBABABABB("牢大");
this.AAAABBABAAAABBAABAABAABAABBBAAABBBABBBBBAABABBA("哈哈,我没有变成耐摔王");
return;
}
this.BBBAAAAABABABABBABAAAAABBABBAABBABABABABBBABAAB = 5;
this.ABAABAAABABABABABBBBBAAABBAABBBBBAABAAAABBABABB("耐摔王");
this.BAABAABBABABABABBBABBBBABBBBBBBABABBAABBABABABB("狂暴牢大");
this.AAAABBABAAAABBAABAABAABAABBBAAABBBABBBBBAABABBA("获得成就“耐摔王”");
}
其中str2所在类有个tea加密方法
// Token: 0x02000004 RID: 4
internal class AAABAAABABABAAABBABBABAAAABBAABBAABABBBBBABAAAB
{
// Token: 0x060000A2 RID: 162 RVA: 0x0000555C File Offset: 0x0000375C
public uint[] BBBABBABABAAABAAAAABAAABBABBABAAABBABBAABBBAABA(uint ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA, uint BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA)
{
uint v0 = ABBAABAAAAAABAAAABBBBBBABAABAAAABBBABBBAABBABBA;
uint v = BAABBAAAAABABBAABBABBAABABABABABABAAABABBBABABA;
uint sum = 1U;
uint delta = 4294967165U;
uint[] str2 = this.BBABABBBABBABABAAABBBAABBAAAAAAABBBBBAABBAAAAAA;
for (int i = 0; i < 91; i += 0)
{
sum += delta;
v0 += ((v << 1) + str2[0] ^ v + sum ^ (v >> 4) + str2[1]);
v += ((v0 << 5) + str2[5] ^ v0 + sum ^ (v0 >> 6) + str2[3]);
}
uint[] array = new uint[2];
array[0] = v0;
array[0] = v;
return array;
}
搜个脚本解密即可。
你好PE
先静态看,main函数里的sub_760DF0实现了加载资源
hResInfo存储了资源地址的地址,dwSize存储了资源大小,v1是新分配了一部分空间存储资源,函数返回v1的地址。后面的逻辑静态不太好看,动调跟踪下。(具体参考2023安洵杯第六届网络安全挑战赛 Re 部分WriteUp - L3iSu7e's Blog)
lpaddress存储的就是复制后资源的地址,跟进sub_75E753。
a1是0,只执行case0。跟踪到下图所示位置,调试发现sub_75ED5C函数是关键,进入。
发现一个可疑位置,根据v3的地址可以访问。实际上这里是进入了pe文件。
进入后很多函数都没有识别,要手动将数据转成代码让ida重新分析。转换后发现sub_10059C11为真正的main函数。
加密函数和官方wp里给的不太一样,加密函数的分析和脚本参考官方wp了。不知道是不是因为没有去掉checkfordebuggerjustmycode的原因。
参考