170928 逆向-Reversing.kr(Direct3D_FPS)

来源:互联网 发布:禄宏微交易 知乎 编辑:程序博客网 时间:2024/06/01 23:57

1625-5 王子昂 总结《2017年9月28日》 【连续第361天总结】
A. Reversing.kr-Direct3D_FPS
B.
这次DX让我有点怂……
一直对这种大型程序都心有畏惧,可能是因为没写过不了解其原理吧

解压出来可以看到包含一个DX库,一个Data文件夹下放置着各种图片和音效资源,一个exe

运行一打开就把我吓了一跳,差点以为不小心打开的恐怖游戏……
这里写图片描述
这囧囧的怪物,还有后面那张好像兵库北的微笑的图片(:з」∠)

惯例拖入IDA查找字符串,这次核心函数就在WinMain中
由于函数太长就不放全部内容了
大体上可以根据游戏画面中左下角显示的10和失败提示找到HP
根据枪声音效找到射击控制部分
根据按键的Switch结构找到移动部分
另外射击的后坐力和音效是有一定的间隔的,这意味着一定有一个计时器(否则将会不间断的连续产生枪声音效,很违和)

试玩了一下,很简陋不过五脏俱全了,基本上开5枪才能杀死一个怪;另外随着距离增加还有随机的散射效果

基本理清整个程序的结构,怪物和自己应该都是对象,在内存中各占132个字节
不过没分析清楚类的成员;只能看出来有存活标识和HP两部分

沿着GameClear!的字符串可以找到胜利检查函数:

int *Is_win(){  int *result; // eax@1  result = dword_E49194;//怪物[0]地址  while ( *result != 1 )//遍历,发现有存活就跳出  {    result += 132;//指针指向下一个怪物    if ( (signed int)result >= (signed int)&unk_E4F8B4 )//遍历完全    {      MessageBoxA(hWnd, "CkfkbuliLE\\E_ZF\x1C\a%%)p\x1749\x01\x16IL \x15\v\x0F麟褒爰籼跓躔栉皓", "Game Clear!", 0x40u);//显示flag      return (int *)SendMessageA(hWnd, 2u, 0, 0);    }  }  return result;}

从遍历完全的e4f8b4和开始的e49194作差,除以单位长度可以得知一共是50个单位

很明显flag的值不全是可见字符,说明在别的地方还有解密操作,通过交叉引用可以找到
在这里:

int __thiscall hit(void *this){  int result; // eax@1  int v2; // ecx@2  int v3; // edx@2  result = sub_E43440(this);//得到被击中的怪物指针  if ( result != -1 )  {    v2 = 132 * result;    v3 = dword_E49190[132 * result];//该怪物的血量,OD中查到是100    if ( v3 > 0 )    {      dword_E49190[v2] = v3 - 2;//每单位时间hp-2,实际上一枪经过了10单位时间(枪声间隔),即一枪是20hp    }    else    {      dword_E49194[v2] = 0;      flag[result] ^= byte_E49184[v2 * 4];//flag的第n个字节与第n个怪物对象的某个数据进行异或    }  }  return result;}

乍一看不一定能识别出来这个函数是做什么的(没有注释的话啦
但是查看交叉引用就能发现这个函数是在下述过程中被调用的:

          if ( dword_E47BD4 )          {            hit(v12);            if ( dword_E47BD8 < 5 )//也许是后坐力导致的镜头偏移?但是从游戏观察来看镜头上抬是和音效同步的,即一次枪声上抬一次;不明白这里的gap的作用              sub_E41880((int)&flt_E47BE0, dword_E47D70, (int)&v21);            else              dword_E47BD8 = 0;            ++dword_E47BD8;            if ( shoot_gap >= 10 )              // 每10单位时间播放一次音效            {              shoot_gap = 0;              PlaySoundA("data\\Shoot.wav", 0, 1u);            }            ++shoot_gap;          }

很明显,是射击函数
由此可以推断hit函数中进行的是运算等逻辑部分

我直接用IDC打印了一下对象的值,发现是空的;说明还有动态申请的过程
在IDA中直接运行游戏,然后IDC脚本打印:

IDC>auto i;for(i=0;i<50;i++)Message(“%d “, Byte(0xe49184 + i*132*4));
0 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 104 108 112 116 120 124 128 132 136 140 144 148 152 156 160 164 168 172 176 180 184 188 192 196

还好,就是i*4,很好处理
直接python脚本跑一下就能得到:
先Dump字符串的ASCII值

IDC>auto i;for(i=0;i<50;i++)Message(“%d, “, Byte(0xe47028 + i));
67, 107, 102, 107, 98, 117, 108, 105, 76, 69, 92, 69, 95, 90, 70, 28, 7, 37, 37, 41, 112, 23, 52, 57, 1, 22, 73, 76, 32, 21, 11, 15, 247, 235, 250, 232, 176, 253, 235, 188, 244, 204, 218, 159, 245, 240, 232, 206, 240, 169,

然后脚本处理

s = [67, 107, 102, 107, 98, 117, 108, 105, 76, 69, 92, 69, 95, 90, 70, 28, 7, 37, 37, 41, 112, 23, 52, 57, 1, 22, 73, 76, 32, 21, 11, 15, 247, 235, 250, 232, 176, 253, 235, 188, 244, 204, 218, 159, 245, 240, 232, 206, 240, 169]for i in range(50):    print(chr(s[i] ^ i*4), end='')

Congratulation~ Game Clear! Password is Thr3EDPr0m

这题中i*4的值是很简单的,如果很困难怎么办呢?
其实还可以写外挂,增伤锁血之类的
将hit函数中的hp-2改为-100(实践来看,远距离的散射效果似乎是按照每单位时间进行的,也就是说如果离得远很有可能一枪的1/10没打中啥的……奇妙的判定)

这样可以快速将怪打完,剩最后一只了合影留个念
这里写图片描述
但是最后clear了也没弹窗,再检查一下内存:

IDC>auto i;for(i=0;i<50;i++)Message(“%d “, Byte(0xe49194 + i*132*4));
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

最后有2个单位还没死……想了一下,应该一个是中间那个大圆球,一个是自己吧
自杀理论上来说跟最后一只怪倒是说不定有可能;但是中间那个大圆球被命中的时候不会触发hit的击中判定(下断未触发),所以也就不会被扣血量,更不可能杀死……

╮(╯_╰)╭没辙,正常游玩下是不可能得到flag的
有点好奇会不会有人五枪一只、慢慢打死48只怪兽,然后发现啥都没有呢……太恶意了OTZ

整体来说,逆向C++的类只能算初入门,还欠缺很多……
(IDA的动态调试真难用,但是ODDump内存更不方便啊QAQ)

C. 明日计划
算法学习
Reversing.kr

原创粉丝点击