直接就退出程序了
找到关键函数了,好像用到了 hook
还有一个
嘿嘿,看着就是像 base64 只是 补‘=’改成了‘ ! ’
交叉引用啊,翻到一个应该是最后比较函数
1UTAOIkpyOSWGv/mOYFY4R!!
那一坨对 a1数组的操作没看懂
先总结一下就是 有一个大小写转换,类base64,两两交换位置,1,2,3
先解码得到乱码,估计就是后面两个先,123,132,312,321应该就这四种情况
试了两个没搞出来,烦 0_0
靠,是对base表进行了大小写转换
看wp感觉自己分析的还是有点问题
这是main函数报红处汇编,扒到一个函数
都是在对字节的操作
就是 a2字节序改变-->v5[0-3] , 对a1异或-->v5[4-35],最后 v5[32-35]取字节-->a3
v5[i] = v5[i+4]^sub_411760(v5[i+1]^v5[i+2]^v5[i+3]^(key+4*i))
en , y 是 用户输入 ,知道 y , z 就可以求出 x
那就对 y z 进行交叉引用
对 x
没有与输入的操作,可以直接动调获取
Handler_0第9行,这里注册了一个UEH函数,看看
在Windows编程中,UEH(Unhandled Exception Handler)通常指未处理异常处理器,处理程序在应用程序发生未处理的异常时执行特定的操作。在Windows中,可以使用 SetUnhandledExceptionFilter
函数来设置未处理异常的处理程序,可以帮助你在未处理的异常发生时更好地调试和维护应用程序。
额,别人的是这样的
(v3 >> (6 * (3 - k))) & 0x3F
(6*(3-k))计算位移量 ,
(v3>>x)&0x3f -->偏移后结果只取其二进制的低6位
v3 >> (6 * (3 - 1)) = v3 >> 12
v3 = 0x12345678
0x12345678 >> 12 = 0x00012345
0x00012345 & 0x3F = 0x05 // 二进制: 00000101
对Table进行凯撒偏移24位
UEH这个函数返回时eip被修改为sub_121136,去看看,发现了最初比较函数
其实还没完,Hadler_0(对x交叉引用找到的)是如何被调用的,对其交叉引用
是注册了一个VEH函数来调用的
在Windows操作系统中,VEH(Vectored Exception Handling)是一种高级的异常处理机制,允许应用程序注册和管理异常处理程序。VEH提供了一种机制,可以在异常处理的各个阶段(包括未处理异常阶段)插入自定义处理程序。
再后面的 wp 就更有点懵逼了
继续往上面翻,找到了这个
int __cdecl sub_1227B0(int a1, char *String2, int a3)
{
DWORD LastError; // eax
DWORD flOldProtect[3]; // [esp+D0h] [ebp-90h] BYREF
int (__stdcall *v6[3])(int, int, int, int); // [esp+DCh] [ebp-84h] BYREF
LPCVOID lpAddress; // [esp+E8h] [ebp-78h]
struct _MEMORY_BASIC_INFORMATION Buffer; // [esp+F4h] [ebp-6Ch] BYREF
LPVOID lpBaseAddress; // [esp+118h] [ebp-48h]
int v10; // [esp+124h] [ebp-3Ch]
char *String1; // [esp+130h] [ebp-30h]
int i; // [esp+13Ch] [ebp-24h]
int v13; // [esp+148h] [ebp-18h]
int v14; // [esp+154h] [ebp-Ch]
v14 = a1;
v13 = a1 + *(_DWORD *)(a1 + 60) + 24;
for ( i = *(_DWORD *)(v13 + 104) + a1; i; i += 20 )
{
String1 = (char *)GetModuleHandleW(0) + *(_DWORD *)(i + 12);
if ( !stricmp(String1, String2) )
break;
}
if ( !i )
return 0;
v10 = *(_DWORD *)(i + 16) + a1;
if ( !v10 )
return 0;
while ( 1 )
{
if ( !*(_DWORD *)v10 )
return 0;
lpBaseAddress = (LPVOID)v10;
if ( *(_DWORD *)v10 == a3 )
break;
v10 += 4;
}
lpAddress = (LPCVOID)(v10 >> 12 << 12);
VirtualQuery(lpAddress, &Buffer, 0x3E8u);
VirtualProtect((LPVOID)lpAddress, Buffer.RegionSize, 0x40u, &Buffer.Protect);
v6[0] = sub_121023;
if ( WriteProcessMemory((HANDLE)0xFFFFFFFF, lpBaseAddress, v6, 4u, 0) )
{
VirtualProtect(Buffer.BaseAddress, Buffer.RegionSize, Buffer.Protect, flOldProtect);
return 1;
}
else
{
LastError = GetLastError();
printf("%d\n", LastError);
return 0;
}
}
对里面调用的函数进行符号还原,前面就是找到 user32.dll 对应的 IMAGE_IMPORT_DESCRIPTOR 结构体地址,然后找到 MessageBoxW 对应的 IMAGE_THUNK_DATA 结构体地址,用VirtualProtect修改页属性为可写,用WriteProcessMemory将IMAGE_THUNK_DATA字段覆写为sub_121023函数地址
总结一下,典型的IAT hook,将MessageBoxW的IAT地址替换为了sub_411023的函数地址,该函数完成了VEH的注册
还没完,继续分析这个IAT hook函数是被谁调用的,引用回溯到了rdata这里,往上翻翻
就是sub_121e40继续往上
这个地方被tmainCRTStartup调用了 ?
就是上面那个调用是在tmainCRTStartup里应该
看看tmainCRTStartUp函数,32行这里initterm_e调用了rdata区域里保存的函数,对全局/静态C++类的构造函数进行了初始化
// write access to const memory has been detected, the output may be wrong!
int __tmainCRTStartup()
{
int v1; // [esp+18h] [ebp-24h]
signed __int32 v2; // [esp+1Ch] [ebp-20h]
signed __int32 v3; // [esp+20h] [ebp-1Ch]
v2 = *(_DWORD *)(j__NtCurrentTeb() + 4);
v1 = 0;
while ( 1 )
{
v3 = _InterlockedCompareExchange(dword_12A6EC, v2, 0);
if ( !v3 )
break;
if ( v3 == v2 )
{
v1 = 1;
break;
}
}
if ( dword_12A6FC == 1 )
{
j__amsg_exit(31);
goto LABEL_13;
}
if ( dword_12A6FC )
{
dword_12A2DC = 1;
goto LABEL_13;
}
dword_12A6FC = 1;
if ( !j__initterm_e((_PIFV *)&First, (_PIFV *)&Last) )
{
LABEL_13:
if ( dword_12A6FC == 1 )
{
j__initterm((_PVFV *)&dword_127000, (_PVFV *)&dword_127208);
dword_12A6FC = 2;
}
if ( dword_12A6FC != 2
&& CrtDbgReportW(
2,
L"f:\\dd\\vctools\\crt\\crtw32\\dllstuff\\crtexe.c",
553,
0,
L"%s",
L"__native_startup_state == __initialized") == 1 )
{
__debugbreak();
}
if ( !v1 )
_InterlockedExchange(dword_12A6EC, 0);
if ( dword_12A714 )
{
if ( j___IsNonwritableInCurrentImage(&dword_12A714) )
dword_12A714(0, 2, 0);
}
CrtSetCheckCount(1);
_initenv = envp;
main(argc, (const char **)argv, (const char **)envp);
}
return 255;
}
en, 从结果一步步推导原因,这就很 reverse
上面的分析是根据结果查找原因,倒着推回去的比较乱,下面再梳理总结下
异常处理的注册
- 程序初始化时,调用链为start->tmainCRTStartUp->initterm_e->IAT hook,修改MessageBoxW函数的IAT表,在主函数中调用MessageBoxW,实际调用的是注册VEH的函数,并对base64编码表进行了变换
- 在main函数中对SEH进行了注册
- 在VEH handler中对UEH进行了注册
异常处理的回调
需要知道一个知识点,Windows 用户态异常发生先找调试器,没有再找 VEH,VEH 处理不了再找 SEH, SEH 还处理不了找 UEF
main函数中触发内存写异常,本程序各级异常处理的返回状态都是未完成处理,会继续往下级调异常处理函数,所以本程序的调用顺序为VEH->SEH->UEH
VEH中对sm4的box进行了初始化
SEH中对input进行sm4加密,获得output
UEH中对output进行变种的base64加密,获得Str1,并且和变换过的固定字符串Str2进行比较
总之,题目的算法分析和还原很简单,麻烦的是这些算法没有集中在main中,而是分散到了各个异常处理函数里面,跳来跳去的对分析造成了干扰,不过只要足够有耐心,不断向上查找引用,还是能分析完的
https://www.cnblogs.com/z5onk0/p/17506136.html
嗯,也是一道经典的 hook 题了,很多相关知识需要学学。
被误导了,不是凯撒
import base64
import sm4
table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# base表大小写转换
new_table=''
for char in table:
tmp=ord(char)
if 97<=tmp<=122:
new_table+=chr(tmp-32)
elif 65<=tmp<=90:
new_table+=chr(tmp+32)
else:
new_table+=char
# base表 %24
table+='='
test=new_table
new_table=new_table[24:]+new_table[:24]+'!'
def kaisha(enc):
str=''
for char in enc:
tmp=ord(char)
if 97<=tmp<=122:
if tmp+24<=122:
str+=chr(tmp+24)
else:
str+=chr(tmp+24-122+97)
elif 65<=tmp<=90:
if tmp+24<=90:
str+=chr(tmp+24)
else:
str+=chr(tmp+24-90+65)
else:
str+=char
return str
print(new_table)
print(kaisha(test)+'!')
str2="1UTAOIkpyOSWGv/mOYFY4R!!"
# 字符串两两交换
str2_swap=''
for i in range(0,len(str2)-1,2):
str2_swap+=str2[i+1]+str2[i]
# base64解密
b64str=''
for str in str2_swap:
b64str+=table[new_table.find(str)]
enflag=base64.b64decode(b64str)
# sm4 解密
key=sm4.SM4Key(b"where_are_u_now?")
flag=key.decrypt(enflag)
print(flag)