1. 程式人生 > 實用技巧 >2020 UNCTF RE 復現

2020 UNCTF RE 復現

base_on_rust

拖入IDA64,檢視字串,疑似base編碼

跟進引用函式

發現這裡並沒有進行編碼,只是初始化了base64,base32和base16的表

跟進到輸入處理函式,根據base編碼特徵修改反編譯程式碼,可得

提取出在off_140028AE8地址的字串RzQyVE1SSldHTTNUSU5SV0c1QkRNTVJXR0UzVEdOUlZHTTNER05CVklZM0RFTlJSRzRaVE1OSlRHTVpURU5LR0dZWkRNTUpYR00zREtNWlJHTTNES1JSV0dVM0VLTlJUR1pERE1OQldHVTJVTU5LR0dWRERPUkE9

解碼可得:unctf{base64_base32_base16_encode___}

Trap

拖入IDA64,跟進main函式

puts("Welcome To UNCTF2020_RE_WORLD !!!");
printf("Plz Input Key: ", a2);
__isoc99_scanf("%s", s1);
strcpy(dest, s1);
sub_400CBE();
if ( !strcmp(s1, s2) )
{
  puts("Success.");
  for ( i = 0; i <= 8479; ++i ){
    v3 = byte_6020E0[i];
    byte_6020E0[i] = s1[i % strlen(s1)] ^ v3;
  }
  s = fopen("/tmp/libunctf.so", "wb");
  fwrite(byte_6020E0, 1uLL, 0x2120uLL, s);
  getchar();
  handle = dlopen("/tmp/libunctf.so", 1)
  if ( !handle ){
    v5 = stderr;
    v6 = dlerror();
    fputs(v6, v5);
    exit(1);
  }
  v7 = (void (__fastcall *)(__int16 *, char *))dlsym(handle, "jo_enc");
  dlerror();
  v15 = 0;
  v16 = 0;
  v17 = 0;
  memset(&v18, 0, 0x28uLL);
  printf("plz Input Answer: ", "jo_enc", &v18);
  __isoc99_scanf("%s", &v15);
  v7(&v15, dest);
}
else{
  puts("Loser!!!");
}

大概意思是將處理後的輸入和已有字串做對比,執行的時候輸入正確的s1會異或解密整個動態連結庫檔案,然後寫入檔案並呼叫jo_enc函式對接下來的輸入以v15(第二次輸入的字串),dest(第一次輸入的字串)的順序進行呼叫檢查

首先將輸入與0x22異或, 然後建立了一個執行緒

v2 = strlen(s1);
for ( i = 0; i < v2; ++i )
  s1[i] ^= 0x22u;
pthread_create(&th, 0LL, (void *(*)(void *))start_routine, 0LL);
return pthread_join(th, 0LL);

接著呼叫sub_400BC0函式將s2與0x33異或並寫入檔案

v3 = strlen(s2);
sub_400B76(v0);//反除錯
for ( i = 0; ; ++i )
{
  result = i;
  if ((signed int)i >= v3 )
    break;
  s2[i] ^= 0x33u;
}
return result;

和呼叫sub_400C13函式對s1和s1長度做運算

for ( i = 0; ; ++i ){
  result = (unsigned int)i;
  if ( i >= v3 )
    break;
  sub_400C13(&s1[i], v3);
}

這裡的sub_400C13有簡單的花指令,

手動修復後其實就是個迴圈

__int64 __fastcall sub_400C13(_BYTE *a1, int a2)
{
  if ( !a2 )
    return 1LL;
  ++*a1;
  return sub_400C13(a1, (unsigned int)(a2 - 1));
}

那麼指令碼就很好寫了

s2=[26,23,18,23,17,44,124,27,46,45,125,124,125,46]
for i in s2:
    print(chr(((i^0x33)-len(s2))^0x22),end='')
#941463c8-2bcb-

將生成的libunctf.so拖入ida64分析jo_enc函式

__int64 __fastcall jo_enc(char *a1, char *a2)
{
  char *v2; // ST20_8
  size_t v3; // ST10_8
  int n; // [rsp+60h] [rbp-500h]
  int m; // [rsp+64h] [rbp-4FCh]
  int l; // [rsp+68h] [rbp-4F8h]
  int k; // [rsp+6Ch] [rbp-4F4h]
  int v9; // [rsp+70h] [rbp-4F0h]
  int j; // [rsp+74h] [rbp-4ECh]
  int v11; // [rsp+78h] [rbp-4E8h]
  signed int i; // [rsp+7Ch] [rbp-4E4h]
  int v13[48]; // [rsp+80h] [rbp-4E0h]
  int odd_number[128]; // [rsp+140h] [rbp-420h]
  int even_number[129]; // [rsp+340h] [rbp-220h]
  int v16; // [rsp+544h] [rbp-1Ch]
  char *input1; // [rsp+548h] [rbp-18h]
  char *input2; // [rsp+550h] [rbp-10h]

  input2 = a1;
  input1 = a2;
  v16 = 0;
  memset(even_number, 0, 0x200uLL);
  memset(odd_number, 0, 0x200uLL);
  memset(v13, 0, 0xC0uLL);
  for ( i = 0; i < 128; ++i )
  {
    even_number[i] = 2 * i;
    odd_number[i] = 2 * i + 1;
  }
  v11 = strlen(input2);
  for ( j = 0; j < v11; ++j )
  {
    v9 = input2[j];
    if ( !(v9 % 2) )
    {
      for ( k = 0; k < v9; k += 2 )
        v13[j] += even_number[k];
    }
    if ( v9 % 2 )
    {
      for ( l = 0; l < v9; l += 2 )
        v13[j] += odd_number[l];
    }
  }
  for ( m = 0; m < v11; ++m )
  {
    v2 = input1;
    v3 = strlen(input1);
    v13[m] = (16 * v2[m % v3] & 0xE827490C | ~(16 * v2[m % v3]) & 0x17D8B6F3) ^ (v13[m] & 0xE827490C | ~v13[m] & 0x17D8B6F3);
  }
  for ( n = 0; n < v11; ++n )
  {
    if ( v13[n] != *((_DWORD *)off_200FD8 + n) )
    {
      v16 = 0;
      exit(1);
    }
    ++v16;
  }
  if ( v16 == 22 )
    puts("Win , Flag is unctf{input1+input2}");
  return 0LL;
}

根據第一個輸入可得,v9(即第二個輸入的ascii碼)範圍在45~127,可以寫爆破指令碼

cipher_lst = [1668, 1646, 1856, 4118, 1899, 1752, 640, 2000, 4412, 1835, 820, 984, 968, 1189, 4353, 1646, 4348, 4561, 1564,1566, 5596, 1525]
input1 = '941463c8-2bcb-'
input2 = ''
even_number = [i * 2 for i in range(128)]
odd_number = [i * 2 + 1 for i in range(128)]
cipher_ = [((16 * ord(input1[m % 14])) & 0xE827490C | (~(16 * ord(input1[m % 14])) & 0x17D8B6F3)) ^ (cipher_lst[m] & 0xE827490C | ~cipher_lst[m] & 0x17D8B6F3) for m in range(22)]
for a in cipher_:
    for n in range(45,127):
        j = 0
        if not n % 2:
            for i in range(0, n, 2):
                j += even_number[i]
        else:
            for i in range(0, n, 2):
                j += odd_number[i]
        if j == a:
            input2 += chr(n)
print('unctf{' + input1 + input2 + '}')

之後經過師傅的點撥,原來 (str1 & random_hex1 | ~str1 & random_hex2) ^ (str2 & random_hex1 | ~str2 & random_hex2) 和 str1^str2是等價的