直接就退出程序了
找到关键函数了,好像用到了 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; // eaxDWORD flOldProtect[3]; // [esp+D0h] [ebp-90h] BYREFint (__stdcall *v6[3])(int, int, int, int); // [esp+DCh] [ebp-84h] BYREFLPCVOID lpAddress; // [esp+E8h] [ebp-78h]struct _MEMORY_BASIC_INFORMATION Buffer; // [esp+F4h] [ebp-6Ch] BYREFLPVOID 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+=charreturn 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)