題目越來越難啦…
Likemyasp
- 看題目就知加了殼,但又找不到脫殼工具,只好自己手脫了(
瞎G8亂脫 )
- 64位程序,使用
x64dbg
來脫殼,先來到EP( 這個EP是加殼後的EP,而脫殼的目的是找到沒加殼時的OEP )
按ctrl+n
,在VirtualProtect
下斷點
- 然後按
F9
執行( 2次 ),發現總共會在VirtualProtect
函數中斷下兩次
- 在第2次時,按
alt+f9
執行到返回,然後一直單步F8
,直到看到如下圖的地方
- 綠框中的地方很像
**main
函數的參數( argv、argc ),因此這附近可能就存在我們想要的東西( 未必是OEP )**,單步F7
進入紅框那個call
- 發現應該是找對了,下面也有提示成功or失敗的字串
- 然後直接用x64dbg自帶的
Scylla
dump出來( 直接按Scylla中的dump就可以 )
- 注:這樣dump出來的文件並不能直接運行,但也可以在IDA中進行分析
- 將dump出來的exe拉入IDA,雖然會提示錯誤,但不要管繼續進入
- 在
start
函數中可以看到程序的加密邏輯( 下圖是我重命名後的結果,一開始IDA並不能識別出printf
、scanf
這些函數)
- 可以看出該加密是個可逆的加密,直接寫腳本就可解出flag
腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <iostream> #include <string> #include <windows.h> using namespace std;
int main() { __int64 enc[6] = { 0xD803C1FC098,0x0E20360BC097,0x0FE02A1C00A0,0x0FA0121040CB,0x0F2032104092,0x0D6015884082 }; for (int i = 0; i < 6; i++) { cout << (char)(((enc[i] >> 37) ^ 0xa) % 256); cout << (char)(((enc[i] >> 23) ^ 0x14) % 256); cout << (char)(((enc[i] >> 14) ^ 0x1E) % 256); cout << ((unsigned char)(~enc[i])); } }
|
FindME
拉入IDA,發現一層又一層的函數調用,當時懶得繼續點,直接用angr
試試看,結果成功了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import angr import sys
def is_good(state): return b'You Gift' in state.posix.dumps(1)
def is_bad(state): return b'Wait' in state.posix.dumps(1)
def main(argv): bin_path = argv[1] p = angr.Project(bin_path)
init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state,veritesting = True) sm.explore(find = is_good, avoid = is_bad)
if sm.found: found_state = sm.found[0] print("Solution: {}".format(found_state.posix.dumps(0)))
if __name__ == "__main__": main(sys.argv)
|
Petals
找到main函數,整體邏輯十分清析,除了紅框中兩個未知函數
進入sub_5618355E5209
函數,是個比較普通的加密,應該可以直接腳本爆破
進入sub_5618355E560C
函數,是個比較
腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> #include <string> #include <windows.h> using namespace std;
unsigned __int64 __fastcall func(char* input, unsigned int len) { int i; unsigned int j; __int64 v5[33]; unsigned __int64 v6;
memset(v5, 0, 256); for (i = 0; i <= 255; ++i) *((BYTE*)v5 + i) = ~(i ^ len); for (j = 0; len > j; ++j) input[j] = *((BYTE*)v5 + (unsigned __int8)input[j]); return 0; }
int main() { char enc[] = { 0xD0, 0xD0, 0x85, 0x85, 0x80, 0x80, 0xC5, 0x8A, 0x93, 0x89, 0x92, 0x8F, 0x87, 0x88, 0x9F, 0x8F, 0xC5, 0x84, 0xD6, 0xD1, 0xD2, 0x82, 0xD3, 0xDE, 0x87,0 }; char flag[56] = { 0 }; for (int i = 0; i < 25; i++) { for (int j = 0; j < 256; j++) { flag[i] = j; func(flag, 25); if (flag[i] == enc[i]) { cout << (char)j; break; } } }
}
|
根據提示可知需要將解出的東西進行md5加密,隨便找個線上網站加密一下。有4個結果,本來想每個都試一下,結果一下就撞對了( d780c9b2d2aa9d40010a753bc15770de
)
前可见古人,后得见来者
拉入IDA,找到main
函數,看上去挺清晰的,進入加密函數看看
加密函數也不難,可以寫腳本直接爆破
腳本如下,輸出結果為pvkq{loqsx_kxn_oxn_bo_kxn_iye}
,一看就不是flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <iostream> #include <string> using namespace std;
int dword_41A000 = 3;
int __cdecl sub_4118C0(string& input, int len) { int result; int j; int i;
for (i = 0; i < len; ++i) { if (input[i] < 65 || input[i] > 'Z') { if (input[i] >= 97 && input[i] <= 122) input[i] = (input[i] + dword_41A000 - 97) % 0x1Au + 97; } else { input[i] = (input[i] + dword_41A000 - 65) % 0x1Au + 65; } } for (j = 0; ; ++j) { result = j; if (j >= len) break; input[j] ^= 0x22u; } return result; }
int main() { string enc = "Q[LVYMPVTC}LCS}PCS}GP}LCS}N@J_"; string flag = "Q[LVYMPVTC}LCS}PCS}GP}LCS}N@J_"; int len = enc.length(); for (int j = 0; j < len; j++) { for (int i = 1; i < 256; i++) { flag[j] = (char)i; sub_4118C0(flag, len);
if (flag[j] == enc[j]) { cout << (char)i; break; } } } }
|
- 後來發現加密函數中的
dword_41A000
這個變量被TlsCallback_0_0
函數交叉引用過
- 而TLS回調函數( 可上網查一下什麼是TLS )的調用時機在
main
函數之前,因此dword_41A000
很有可能在TlsCallback_0_0
被修改過
進入TlsCallback_0_0
查看,發現果然如此,所以只要將上方腳本中的dword_41A000
的值+10就可以得到真的flag
ur_so_naive
- 是個apk文件,拉入
jadx
分析,去到com.new_star_ctf.u_navie/MainActivity
查看程序的邏輯
- 可以看到加密函數
encry
,它前面有個native
關鍵字,代表函數在native層中實現
- native層實現的函數保存在
.so
文件中,.so
文件位於apk文件解壓後的\lib
目錄下
將libencry.so
拉入IDA,找到encry
函數,會發現一堆不明所以的東西,這是由一種叫做**JNI
的東西導致IDA分析失敗(** 點擊查看JNI具體是什麼 )
解決方法:對應JNI函數原型( 在上方網址中可找到 ),手動修改encry
函數參數類型( 快捷鍵y
),修改後如下圖所示:
- 修改完之後IDA就能把一些函數正常分析出來,如下圖所示
- 接下來分析加密邏輯,可以看到
input
經過一大<<
、|
、^
加密,但這些都不是重點,重點是最後一步的input[index++]=v14^input[v13]
,這一句相當於input[i]^=input[i+1]
(當i == size - 1時,input[i]^=input[0]
),這樣相當於是一個”環形加密”,無法直接爆破
- 要解密必須要知道明文中的1-2個字符,而明文通常為
flag{XXXX}
,因此明文猜測前4個字串為flag
,以此爆破後面的所有字符
腳本如下( enc
、key
都可在jadx中輕鬆獲取 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <iostream> #include <string> #include <windows.h> using namespace std;
#define LOBYTE(x) (*((BYTE*)&(x)))
void func(char* input,char* key,int size) { __int64 index; unsigned int v12; __int64 v13; unsigned int v14; bool v18; unsigned int v15, v16, v17;
index = 0LL; do { v12 = (unsigned __int8)input[index]; if (size - 1LL == index) v13 = 0LL; else v13 = index + 1; input[index] = ((unsigned __int8)input[index] >> 1) & 0x7F | (input[index] << 7); v14 = ((v12 >> 1) & 0xFFFF807F | ((unsigned __int8)v12 << 7)) ^ *(unsigned __int8*)key; v15 = ((BYTE)v14 << 6) | ((unsigned __int8)v14 >> 2); input[index] = v15; v16 = (32 * (v15 ^ key[1])) | ((unsigned __int8)(v15 ^ key[1]) >> 3); input[index] = v16; v17 = (16 * (v16 ^ key[2])) | ((unsigned __int8)(v16 ^ key[2]) >> 4); input[index] = v17; LOBYTE(v14) = v17 ^ key[3]; input[index] = v14; v18 = size == index + 1; input[index++] = v14 ^ input[v13]; } while (!v18);
}
int main() { char enc[] = { -36, 83, 22, -117, -103, -14, 8, 19, -47, 47, -110, 71, 2, -21, -52, -36, 24, -121, 87, -114, -121, 27, -113, -86 }; int len = 24; char flag[25] = { 0 }; char key[] = "FALL"; flag[0] = 'f'; flag[1] = 'l'; char tmp[25] = { 0 };
for (int i = 2; i < 24; i++) { for (int j = 0; i < 256; j++) { strcpy(tmp, flag); tmp[i] = j; func(tmp, key, len); if (tmp[i - 1] == enc[i - 1]) { flag[i] = j; break; } } } cout << flag; }
|