1. 程式人生 > >170917 逆向-WHCTF(BabyHack)

170917 逆向-WHCTF(BabyHack)

1625-5 王子昂 總結《2017年9月17日》 【連續第350天總結】
A. XCTF(武漢站)-Reverse
B.

EasyHook

剛開始看這個名字還以為要注入DLL啥的呢(:з」∠)忐忑不安地打開發現結構挺簡單:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // [email protected]
  HANDLE FileHandle; // [email protected]
  DWORD NumberOfBytesWritten; // [sp+4h] [bp-24h]@3
char Buffer; // [sp+8h] [bp-20h]@1 sub_401370(aPleaseInputFla); scanf(a31s, &Buffer); if ( strlen(&Buffer) == 19 ) // 輸入長度為19 { sub_401220(); // 將Re_writeFile函式的地址覆蓋WriteFile FileHandle = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);// 建立檔案 WriteFile(FileHandle, &Buffer, 0x13
u, &NumberOfBytesWritten, 0);// 執行WriteFile函式,實際上是Re_writeFile sub_401240(&Buffer, &NumberOfBytesWritten); // 比較函式(偽) if ( NumberOfBytesWritten == 1 ) sub_401370(aRightFlagIsYou); // 錯誤提示 else sub_401370(aWrong); system(aPause); result = 0; } else { sub_401370(aWrong); system(aPause); result = 0
; } return result; }

關鍵就在sub_401220函式上
本來以為它是處理函式,但是開啟以後發現通過API取得了kernel32.dll中WriteFile的地址,之後就不太清晰了
OD動態除錯也可以發現,sub_401220前後並沒有使得buffer的值發生改變
單步除錯發現關鍵變化發生於call WriteFile
這明明是個API,怎麼會改變檔案內容呢
F7跟進去發現轉入了使用者模組的sub_401080

這下就清楚了,sub_401220取到了WriteFile的地址,然後用sub_401080覆蓋之;使得呼叫WriteFile的時候變為呼叫sub_401080
這個就是hook技術了

接下來分析sub_401080:呼叫了sub_401000處理input,然後就執行真正的WriteFile
而在sub_401000中終於是真正的處理演算法:

signed int __cdecl flag(int str, signed int length)
{
  char v2; // [email protected]
  char v3; // [email protected]
  char v4; // [email protected]
  int v5; // [email protected]
  signed int result; // [email protected]

  v2 = 0;
  if ( length > 0 )
  {
    do
    {
      if ( v2 == 18 )                           // 第19個字元異或0x13
      {
        *(_BYTE *)(str + 18) ^= 0x13u;
      }
      else
      {
        if ( v2 % 2 )                           // 偶數字符(下標為奇數),str[n] = n ^ (str[n]-i)
          v3 = *(_BYTE *)(v2 + str) - v2;
        else
          v3 = *(_BYTE *)(v2 + str + 2);        // 奇數字符(下標為偶數),str[n] = n ^ str[n+2]
        *(_BYTE *)(v2 + str) = v2 ^ v3;
      }
      ++v2;
    }
    while ( v2 < length );
  }
  v4 = 0;
  if ( length <= 0 )
  {
LABEL_13:
    result = 1;
  }
  else
  {
    v5 = 0;
    while ( byte_40A030[v5] == *(_BYTE *)(v5 + str) )// 驗證處理過後的結果是否與byte_40a030相等
    {
      v5 = ++v4;
      if ( v4 >= length )
        goto LABEL_13;
    }
    result = 0;
  }
  return result;
}

最下面的地方迴圈比較,剛開始沒有注意,成功的話會返回1否則返回0
觀察了一下返回1的話會使得
*lpNumberOfBytesWritten = 1

演算法比較簡單,稍微繞一下就能寫出還原指令碼

而從main函式中可以看到,要提示正確,需要NumberOfBytesWritten == 1
這個變數則取決於sub_401240
開啟可以看到,是一個字串比較迴圈:
input的另一邊是“This_is_not_the_flag”

放入解密指令碼發現上字串的長度是20,與輸入不對應,做出來的字串也有些問題
比如說,解密後文本的第一個字元不會影響源字串,而最後一個字元則會決定最後一個和倒數第三個字元;
‘a’對應的倒數第三個字元是’b’,意味著沒有與其對應的原字串

檢查了好久演算法沒有出問題,花去了兩三個小時
本來在考慮是否要爆破通過前一個驗證,
後來突然想起來在sub_401000中最後還有一個驗證迴圈,雖然return不大對,不過死馬當活馬醫了
用IDC指令碼將值dump下來,進行解密
嘿,還真的生成了有意義的字串:
lag{Ho0k_w1th_Fun}
第一個字元很明顯是a,提交正確

雖然後面那個字串已經明白告訴This is not the flag了結果還是掉坑裡了OTZ
看起來lpNumberOfBytesWritten是NumberOfBytesWritten的指標,因此取值賦1也可以達到效果

如果不跳坑裡能拿900+Pt的,被拖了兩三個小時就只有500Pt啦,有點可惜~
練習不夠,對lp不夠敏感,還要慢慢學習

附上python加解密指令碼:

# origin pro
s = "1234567890123456789"
flag = ''
for i in range(18):
    if(i%2):
        flag += chr(i ^ (ord(s[i])-i))
    else:
        flag += chr(i ^ (ord(s[i+2])))

flag += chr(ord(s[18]) ^ 0x13)
print flag

# rev pro
# s = "This_is_not_the_flag" 被坑了許久的假字串
# s = "307234?69.9,9*9()6*"  1-9的驗證加密結果
s = "ajygkFm.\x7f_~-SV{8mLn" byte_40A030的真字串
flag = ['' for x in range(19)]
flag[18] = chr(ord(s[18]) ^ 0x13)
# s[18] = flag[18]
for i in range(18):
    i=17-i
    if((i)%2):
        flag[i] = chr((ord(s[i]) ^ i) + i)
    else:
        if(i>1):
            flag[i] = chr(ord(s[i-2]) ^ (i-2))
print ''.join(flag)

C. 明日計劃
ARP欺騙