SMC逆向介紹
參考自CTF Wiki SMC
自修改程式碼(Self-Modified Code)是一類特殊的程式碼技術,即在執行時修改自身程式碼,從而使得程式實際行為與反彙編結果不符,同時修改前的程式碼段資料也可能非合法指令,從而無法被反彙編器識別。
自修改程式碼通常有兩種破解方式,第一種是根據靜態分析結果直接修改程式二進位制檔案,第二種則是在動態除錯時將解密後的程式從記憶體中 dump 下來。
以下例題採用第二種方法。
[磐石行動2024]ezRE
下載檔案檢視是否有殼
32位檔案,無殼,放入ida
flag長度30
簡單異或以及減法
比較字串
直接複製ida中unk_4040C0相關的所有內容到txt,程式碼提取十六進位制數值
with open('data_tianqi.txt', 'r') as file:
lines = file.readlines()
hex_values = []
for line in lines:
match = line.split('db')[1].strip().split()[0].rstrip('h')
if match:
hex_values.append(int(match, 16))
hex_values = [value for value in hex_values if value != 0] # 去除0,不需要這個功能的時候註釋掉
print(hex_values)
hexx_values = [hex(value) for value in hex_values]
hex_values_trimmed = [value[2:] for value in hexx_values]
result = ' '.join(hex_values_trimmed)
print(result)
將十六進位制資料進行反變換,減變成加,異或不變,程式碼如下:
hex_string = "66 6b 63 64 7f 63 69 70 57 60 79 54 78 5b 6b 50 67 54 73 61 7c 50 64 48 6c 56 7e 46 65 60"
hex_values = [int(char, 16) for char in hex_string.split()]
print(hex_values)
def crazy(s):
for i in range(len(s)):
if i % 2 == 1: # If the index is odd
s[i] = chr(s[i] + i)
else:
s[i] = chr(s[i] ^ i)
return ''.join(s)
output_string = crazy(hex_values)
print(output_string)
執行得到flag,提交發現不對,嘗試執行exe輸入flag驗證
flag{how_is_the_weather_today}
剛才得到的flag是假的,後續還有程式碼未執行,下斷點接著動態除錯,發現Ok,please go on.
後面執行的程式碼找不到位置了,而且my_function函式非常奇怪,無法正常反彙編
程式碼中有VirtualProtect
函式,猜測my_function函式進行了smc加密,所以沒法直接看到,需要在動態除錯過程中檢視程式碼
找到my_function函式的位置,然後在主函式中重新下斷點除錯
這裡的斷點應該下在((void (*)(void))lpAddress)()
位置上,斷點在上圖中的位置單步除錯費時費力,沒有必要
程式執行到斷點之後單步執行,直到執行call命令(呼叫函式),下一步就是進入my_function函式,找到my_function函式所在地址,如上圖
選中該函式名稱,使用按鍵U
將資料設定為無定義(或右鍵找到對應功能)
然後選中該函式涉及到的所有資料內容,使用按鍵C
,選擇Force選項轉換成程式碼
最後點選my_function使用按鍵P
定義為函式,再用F5
反編譯,得到my_function函式
my_function涉及到的程式碼如下,共三個函式my_function、xxx_init、xxx_crypt
void __cdecl __noreturn my_function(char *a1)
{
unsigned int v1; // eax
char Str[50]; // [esp+16h] [ebp-2D2h] BYREF
int v3[30]; // [esp+48h] [ebp-2A0h] BYREF
unsigned __int8 v4[256]; // [esp+C0h] [ebp-228h] BYREF
char v5[256]; // [esp+1C0h] [ebp-128h] BYREF
unsigned int v6; // [esp+2C0h] [ebp-28h]
unsigned int j; // [esp+2C4h] [ebp-24h]
int v8; // [esp+2C8h] [ebp-20h]
int i; // [esp+2CCh] [ebp-1Ch]
puts("please input your True flag:");
scanf("%40s", Str);
v6 = strlen(Str);
if ( v6 != 30 )
{
puts("Wrong!");
exit(0);
}
qmemcpy(v3, &unk_404040, sizeof(v3));
memset(v4, 0, sizeof(v4));
memset(v5, 0, sizeof(v5));
v1 = strlen(a1);
xxx_init(v4, (unsigned __int8 *)a1, v1);
for ( i = 0; i <= 255; ++i )
v5[i] = v4[i];
xxx_crypt(v4, (unsigned __int8 *)Str, v6);
v8 = 1;
for ( j = 0; ; ++j )
{
if ( j >= v6 )
goto LABEL_11;
if ( (unsigned __int8)Str[j] != v3[j] )
break;
}
v8 = 0;
LABEL_11:
if ( v8 )
puts("Good! have a beautiful day for you!");
else
puts("May be try again?");
exit(0);
}
char *__cdecl xxx_init(unsigned __int8 *a1, unsigned __int8 *a2, unsigned int a3)
{
char *result; // eax
_DWORD v4[64]; // [esp+7h] [ebp-111h] BYREF
unsigned __int8 v5; // [esp+107h] [ebp-11h]
int v6; // [esp+108h] [ebp-10h]
int i; // [esp+10Ch] [ebp-Ch]
v6 = 0;
v4[0] = 0;
v4[63] = 0;
result = 0;
memset((char *)v4 + 1, 0, 4 * ((((char *)v4 - ((char *)v4 + 1) + 256) & 0xFFFFFFFC) >> 2));
v5 = 0;
for ( i = 0; i <= 255; ++i )
{
a1[i] = i;
result = (char *)v4 + i;
*((_BYTE *)v4 + i) = a2[i % a3];
}
for ( i = 0; i <= 255; ++i )
{
v6 = (a1[i] + v6 + *((char *)v4 + i)) % 256;
v5 = a1[i];
a1[i] = a1[v6];
result = (char *)v5;
a1[v6] = v5;
}
return result;
}
unsigned int __cdecl xxx_crypt(unsigned __int8 *a1, unsigned __int8 *a2, unsigned int a3)
{
unsigned int result; // eax
unsigned __int8 v4; // [esp+Fh] [ebp-15h]
unsigned int i; // [esp+14h] [ebp-10h]
int v6; // [esp+18h] [ebp-Ch]
int v7; // [esp+1Ch] [ebp-8h]
v7 = 0;
v6 = 0;
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v7 = (v7 + 1) % 256;
v6 = (v6 + a1[v7]) % 256;
v4 = a1[v7];
a1[v7] = a1[v6];
a1[v6] = v4;
a2[i] ^= a1[(unsigned __int8)(a1[v7] + a1[v6])];
}
return result;
}
flag長度同樣為30,加密後用來對比的字串為unk_404040
同樣複製下來執行程式碼提取十六進位制
接著下斷點,動態除錯
動態除錯檢視引數的值,發現init函式的引數v4是256個0,a1是開始輸入的假flag字串,v1是十進位制的30,應該是長度。函式用於初始化v4。
crypt函式的引數v4是剛從init初始化的資料,str是my_function中輸入的字串,v6是30。
假flag:flag{how_is_the_weather_today}
分析init函式和crypt函式,發現對輸入的字串的加密只有一個異或,根據異或的性質,再異或同樣的資料一次就可以抵消,直接正向執行程式碼,不用作任何修改,用unk_404040的資料進行異或,得到flag。
hex_string = "4d d8 76 2d c 26 c 53 da c0 17 37 8c d7 f3 d9 d0 46 2b 15 98 67 f1 ad a6 e 7c 66 90 7f"
hex_values = [int(char, 16) for char in hex_string.split()]
print(hex_values)
def xxx_init(a1, a2, a3):
v4 = [0] * 256
v5 = 0
v6 = 0
v4[0] = 0
v4[63] = 0
for i in range(1, 65):
v4[i-1] = 0
for i in range(256):
a1[i] = i
v4[i] = a2[i % a3]
for i in range(256):
v6 = (a1[i] + v6 + v4[i]) % 256
v5 = a1[i]
a1[i] = a1[v6]
a1[v6] = v5
return a1
a1 = [0] * 256
a2 = "flag{how_is_the_weather_today}"
a2_list = [ord(char) for char in a2]
a3 = len(a2) # 30
xxx_init(a1, a2_list, a3)
# print("Initialized a1 array:", [hex(num) for num in a1])
def xxx_decrypt(a1, a2, a3):
v4 = 0
v6 = 0
v7 = 0
for i in range(a3):
v7 = (v7 + 1) % 256
v6 = (v6 + a1[v7]) % 256
v4 = a1[v7]
a1[v7] = a1[v6]
a1[v6] = v4
a2[i] ^= a1[(a1[v7] + a1[v6]) & 0xFF]
return a2
enc = hex_values
result = xxx_decrypt(a1, enc, 30)
ascii_symbols = [chr(num) for num in result]
print(''.join(ascii_symbols))
# flag{This_is_a_beautiful_day!}