题目描述
//人死后会去向何方?
大一时候的一道题,当时学长发了天堂之门的相关文章给我,没有复现,回来再复现这道题,发现可以学到蛮多知识的,关于异常处理和天堂之门,最近也是遇到了其他异常处理的题目,发现自己也的确不是很熟悉。
DubheCTF-Destination&Moon | Clovershrub
DubheCTF 2024 Reverse – lrhtony 的小站
DUBHEctf_Destination | fishjump’s blog
题目描述和题目本身其实已经提示了天堂之门的考点
主函数直接下断点调试会直接跑飞,但是主函数里面翻了一下没有反调试,我们就在主函数执行之前的下断点,发现程序运行到0x418279的j__initterm函数时会退出,那这个就是反调试了,搜索相关资料可以发现该函数接受两个指针,指向一个函数指针数组的起始和结束位置,然后依次执行数组中每个非空函数指针指向的函数。
从0x41E000到0x41E318,数组很大,但是大部分是NULL,先出现?pre_cpp_initialization@@YAXXZ,后续再出现三个反调试的相关函数。
出题人给了源码,反调试写的挺复杂的,绕过方法很简单,把j__initterm函数nop或者地址区间的值全部改成0就行。
然后进入主函数,主函数反编译代码很简单
for ( i = 0; i < 45; ++i )
*((_BYTE *)&dword_4234A8 + i) = getchar();
__debugbreak();
for ( j = 0; j < 48; ++j )
{
if ( *((unsigned __int8 *)&dword_4234A8 + j) == (unsigned __int8)byte_423038[j] )
++v6;
}
if ( v6 == 48 )
sub_411122("Good answer! You will be taken to HEAVEN!\n", v4);
else
sub_411122("Bad answer! You will be taken to HELL!\n", v4);
system("PAUSE");
return 0;
只有输入和判断部分,但是运行会发现,最后判断时输入会被加密,并且这里布过 __debugbreak()函数会触发一个软件断点异常。
那么就需要看汇编界面了,这里发现了一个int 3指令触发了异常,并且注册了异常处理的函数
这里的sub_4113D4其实就是调用sub_4140D7函数,所以两者等同,这里触发异常后我们在sub_4140D7打个断点进入sub_4140D7函数,然后就发现函数有很多花指令。
参考别人文章,也利用IDA的Instruction tracing功能跟踪一下程序
该函数有很多循环,,高亮大部分执行的指令后,发现花指令主要包含两种,并且有些分支会连在一起
第一种是call和retn组成的无用花指令,第二种就是永真跳转,手动nop的话有点麻烦,我们可以看看出题人给的idc去花脚本
#include <idc.idc>
static main()
{
auto last_eip;
auto eip = 0x4140D7;
auto offset;
auto sum = 1;
auto just_jump = 0;
while(Byte(eip) != 0xC3) //遇到retn指令停止
{
if(Byte(eip) == 0xE8 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00 && Byte(eip+1) == 0x00)
{ //当遇到无效call花指令
PatchByte(eip + 0, 0x90);
PatchByte(eip + 1, 0x90);
PatchByte(eip + 2, 0x90);
PatchByte(eip + 3, 0x90);
PatchByte(eip + 4, 0x90);
PatchByte(eip + 5, 0x90);
PatchByte(eip + 6, 0x90);
PatchByte(eip + 7, 0x90);
PatchByte(eip + 8, 0x90);
PatchByte(eip + 9, 0x90);
//去除call和retn组成的花指令
eip = eip + 10;
continue;
}
else if(Byte(eip) == 0xE9 && just_jump == 0)
{ //当遇到长跳转jmp指令
offset = Byte(eip+1);
offset = Byte(eip+2) * 256 + offset;
offset = Byte(eip+3) * 256 * 256 + offset;
offset = Byte(eip+4) * 256 * 256 * 256 + offset;
eip = eip + offset;
eip = eip + 5;
eip = eip & 0xffffffff;
print(eip);
//根据opcode计算跳转地址
sum = sum + 1;
just_jump = 1;
continue;
}
else if(Byte(eip) == 0x0F && Byte(eip+1) == 0x84 && just_jump == 0)
{ //当遇到永恒跳转花指令
last_eip = eip;
offset = Byte(eip+2);
offset = Byte(eip+3) * 256 + offset;
offset = Byte(eip+4) * 256 * 256 + offset;
offset = Byte(eip+5) * 256 * 256 * 256 + offset;
eip = eip + offset;
eip = eip + 6;
eip = eip & 0xffffffff;
//根据opcode计算跳转地址
print(eip);
sum = sum + 1;
just_jump = 1;
PatchByte(last_eip, 0x90);
PatchByte(last_eip+1, 0xE9);
//将永恒跳转花指令替换为jmp
continue;
}
else if(Byte(eip) == 0xEB && just_jump == 0)
{ //当遇到短跳转jmp指令
offset = Byte(eip+1);
eip = eip + offset;
eip = eip + 2;
// //根据opcode计算跳转地址
print(eip);
sum = sum + 1;
just_jump = 1;
continue;
}
else if(Byte(eip) == 0x74 && just_jump == 0)
{
last_eip = eip;
offset = Byte(eip+1);
eip = eip + offset;
eip = eip + 2;
print(eip);
sum = sum + 1;
just_jump = 1;
PatchByte(last_eip, 0xEB);
continue;
}
eip = eip + 1;
just_jump = 0;
}
print(sum);
return 0;
}