這道題比較經典,涉及三個知識點,所以記錄一下。
首先給了一個文件,detect it easy看了下,是32位exe。
放入ida中,找下main函數,F5反編譯看一下偽代碼。
int __cdecl main(int argc, const char **argv, const char **envp)
{unsigned int v3; // edxunsigned int v4; // ecx__m128i si128; // xmm1unsigned int v6; // esiconst __m128i *v7; // eax__m128i v8; // xmm0int v9; // eaxchar v11[100]; // [esp+0h] [ebp-CCh] BYREFchar v12[100]; // [esp+64h] [ebp-68h] BYREFunsigned int v13; // [esp+C8h] [ebp-4h] BYREFprintf("please input your flah:");memset(v11, 0, sizeof(v11));scanf("%s", v11);memset(v12, 0, sizeof(v12));sub_A21000(v12, &v13, (unsigned __int8 *)v11, strlen(v11));v3 = v13;v4 = 0;if ( v13 ){if ( v13 >= 16 ){si128 = _mm_load_si128((const __m128i *)&xmmword_A34F20);v6 = v13 - (v13 & 0xF);v7 = (const __m128i *)v12;do{v8 = _mm_loadu_si128(v7);v4 += 16;++v7;v7[-1] = _mm_xor_si128(v8, si128);}while ( v4 < v6 );}for ( ; v4 < v3; ++v4 )v12[v4] ^= 0x25u;}v9 = strcmp(v12, "you_know_how_to_remove_junk_code");if ( v9 )v9 = v9 < 0 ? -1 : 1;if ( v9 )printf("wrong\n");elseprintf("correct\n");system("pause");return 0;
}
從后往前看,要得到correct結果,就需要v12和字符串you_know_how_to_remove_junk_code相等。
v12和v13先是和輸入的v11進行了一個函數sub_A21000的處理,然后又做了一段包含異或0X25的處理。
先看第一個函數。
知識點1:這里其實是一個base64解密函數。
int __fastcall sub_A21000(_BYTE *a1, unsigned int *a2, unsigned __int8 *a3, unsigned int a4) //a1是v12,a2是v13值,a3是輸入數據,a4是輸入數據長度。
{int v4; // ebxunsigned int v5; // eaxint v6; // ecxunsigned __int8 *v7; // ediint v8; // edxbool v9; // zfunsigned __int8 v10; // clchar v11; // cl_BYTE *v12; // esiunsigned int v13; // ecxint v14; // ebxunsigned __int8 v15; // clchar v16; // dlint v20; // [esp+14h] [ebp-4h]unsigned int v21; // [esp+14h] [ebp-4h]int i; // [esp+24h] [ebp+Ch]v4 = 0; // 記錄 '=' 出現的次數v5 = 0;// 當前輸入索引(掃描位置)v6 = 0; // 有效 Base64 數據字符數(不包括空格、換行、=)v20 = 0;// 同 v6,用于避免寄存器沖突(編譯器優化痕跡)if ( !a4 ) //長度為0就退出return 0;v7 = a3;do{v8 = 0;v9 = v5 == a4;if ( v5 < a4 ){do{if ( a3[v5] != 32 )// 跳過前導空格break;++v5;++v8;}while ( v5 < a4 );v9 = v5 == a4;}if ( v9 )break;if ( a4 - v5 >= 2 && a3[v5] == 13 && a3[v5 + 1] == 10 || (v10 = a3[v5], v10 == 10) )// 遇到換行繼續{v6 = v20;}else{if ( v8 )// Base64 中間不能有空格,返回錯誤return -44;if ( v10 == 61 && (unsigned int)++v4 > 2 )// 等于號的個數比2個多,非法return -44;if ( v10 > 0x7Fu )// 檢查字符是否超出 ASCII 127return -44;v11 = byte_A34E40[v10];if ( v11 == 0x7F || (unsigned __int8)v11 < 0x40u && v4 )return -44;v6 = ++v20;}++v5;}while ( v5 < a4 );if ( !v6 )return 0;v12 = a1;v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4; //這里用的是通用公式,取字節數減去填充 = 的數量if ( a1 && *a2 >= v13 ){v21 = 3; // 每組 4 個 Base64 字符生成最多 3 字節v14 = 0;for ( i = 0; v5; --v5 ){v15 = *v7;if ( *v7 != '\r' && v15 != '\n' && v15 != ' ' ){v16 = byte_A34E40[v15]; //byte_A34E40是base64的字母表v21 -= v16 == 64;v14 = v16 & '?' | (v14 << 6); //累加 6 位if ( ++i == 4 ) //每 4 個字符為一組{i = 0;if ( v21 )*v12++ = BYTE2(v14); //高字節if ( v21 > 1 )*v12++ = BYTE1(v14);//中字節if ( v21 > 2 )*v12++ = v14;//低字節}}++v7;}*a2 = v12 - a1;return 0;}*a2 = v13;return -42;
}
確定好該函數后,右擊rename函數為base64decode方便查看,繼續看代碼。
v3 = v13;v4 = 0;if ( v13 ){if ( v13 >= 16 ){si128 = _mm_load_si128((const __m128i *)&xmmword_A34F20);v6 = v13 - (v13 & 0xF);v7 = (const __m128i *)v12;do{v8 = _mm_loadu_si128(v7);v4 += 16;++v7;v7[-1] = _mm_xor_si128(v8, si128);}while ( v4 < v6 );}for ( ; v4 < v3; ++v4 )v12[v4] ^= 0x25u;}v9 = strcmp(v12, "you_know_how_to_remove_junk_code");
知識點2:這里是使用了SSE指令,即帶 _mm_前綴,可以通過在一個控制器上同時處理多個數據流,從而提高運算速度。
_mm_load_si128函數表示從內存中加載一個128bits值到暫存器,也就是16字節,**注意:**p必須是一個16字節對齊的一個變量的地址。返回可以存放在代表寄存器的變量中的值。
_mm_loadu_si128函數和_mm_load_si128一樣的,但是不要求地址p是16字節對齊。
v6 = v13 - (v13 & 0xF);
這個是先把v13進行對齊,否則無法使用SSE指令處理。
v4 += 16;
這里是取n個16位的v12和xmmword_A34F20進行異或,剩下的不足16位的就和0x25異或。
雙擊查看xmmword_A34F20,發現其就是16位的0x25。
.rdata:00A34F20 xmmword_A34F20 ?xmmword 25252525252525252525252525252525h
所以,就是輸入值進行base64解密后,和0x25異或,最后和you_know_how_to_remove_junk_code字符串比較是否一致。
import base64
s=list("you_know_how_to_remove_junk_code")
encrypted_bytes = bytes(ord(c) ^ 0x25 for c in s)
decoded_bytes = base64.b64encode(encrypted_bytes).decode('ascii')
print(decoded_bytes)
這里還有一個我犯的錯誤點是把字符轉ord異或后,應該轉為byte字節,再進行base64加密,但是我轉的是chr,這會導致一部分數據無法轉為chr,從而導致錯誤。