SMC逆向

skdtxdy發表於2024-05-27

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!}