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是等價的