1. 程式人生 > >170928 逆向-Reversing.kr(Direct3D_FPS)

170928 逆向-Reversing.kr(Direct3D_FPS)

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; // [email protected]

  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; // [email protected]
  int v2; // [email protected]
  int v3; // [email protected]

  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