巔峰極客線上第一場部分ctf
Input your lucky number
要求輸入一個數字。
程序有ASLR,可以去掉便於分析。
F5
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // edx
char *v4; // eax
char *v5; // ecx
int v6; // edi
signed int v7; // esi
signed int v8; // esi
__m128i *v9; // edi
int v11; // [esp+0h] [ebp-3Ch]
int input; // [esp+14h] [ebp-28h]
char v13; // [esp+18h] [ebp-24h]
__int64 v14; // [esp+19h] [ebp-23h]
int v15; // [esp+21h] [ebp-1Bh]
int *v16; // [esp+2Ch] [ebp-10h]
int v17; // [esp+38h] [ebp-4h]
v16 = &v11;
printf(std::cout, "input your lucky number: ");
std::basic_istream<char,std::char_traits<char >>::operator>>(std::cin, &input);
v13 = 0;
v3 = 0;
v15 = 0;
v17 = 0;
v4 = (char *)&loc_401000 + 2;
v14 = 0i64;
v5 = (char *)&loc_401000 + 2;
while ( *(v5 - 2) != 0xC7u || *(v5 - 1) != 5 || *(int **)v5 != &dword_4043A8 || *((_DWORD *)v5 + 1) != 0x89898989 )
{
++v3; // 9
++v5;
if ( v3 >= 1000 )
return 0;
}
if ( v3 != -1 )
{
v6 = v3 + 10;
v7 = 0;
while ( *(v4 - 2) != 0xC7u || *(v4 - 1) != 5 || *(int **)v4 != &dword_4043A4 || *((_DWORD *)v4 + 1) != 0x98989898 )
{
++v7; // 0x74
++v4;
if ( v7 >= 1000 )
return 0;
}
if ( v7 != -1 )
{
v8 = v7 - v6; // 0x61
v9 = (__m128i *)((char *)&loc_401000 + v6);// 401013
sub_401100(v8, v9, input);
((void (__cdecl *)(const char *, char *))loc_401000)("seed_of_flag", &v13);
sub_401100(v8, v9, input);
}
}
return 0;
}
關鍵函數401100,傳了0x61、0x401013、我們輸入的值
反匯編時看到用到了XMM0、XMM1寄存器,用WinDBG調試。
我輸的是189,16進制是0xBD。
從上面可以看出,401100函數的作用是把0x401013到0x401013+0x61 = 0x401074的每個字節,跟我們輸入的這個數據進行異或。
PS:
從這兒看出是只取我們輸入的數據的低8位的。之前一直在拿1024在測,一直在踩雷orz。
然後會返回401000執行程序。
一般來說,會有許多的0出現在文件中,401013到401074出現較多的0X5A,試著輸一下0x5a = 90。
Simple Base-N
F5
int __cdecl main(int argc, const char **argv, const char **envp)
{
const char *v3; // ecx
char *v4; // eax
bool v5; // cf
unsigned __int8 v6; // dl
int v7; // eax
int v9; // eax
const char *v10; // edx
sub_401590(std::cout, "please input your flag:");
sub_4017D0(std::cin);
if ( (signed int)strlen(input) >= 10 )
{
change_i(input);
v3 = "guvf_vf_n_snxr_synt";
v4 = input;
while ( 1 ) // v7 = strcmp(input, "guvf_vf_n_snxr_synt");
{
v5 = (unsigned __int8)*v4 < *v3;
if ( *v4 != *v3 )
break;
if ( !*v4 )
goto LABEL_7;
v6 = v4[1];
v5 = v6 < v3[1];
if ( v6 != v3[1] )
break;
v4 += 2;
v3 += 2;
if ( !v6 )
{
LABEL_7:
v7 = 0;
goto LABEL_9;
}
}
v7 = -v5 | 1;
LABEL_9:
if ( !v7 )
{
sub_401590(std::cout, "try a little bit harder!\n");
return 0;
}
chang_table(v3);
base32(&input[1]);
v9 = strcmp(a0, "weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg");
if ( v9 )
v9 = -(v9 < 0) | 1;
v10 = "Congratulations!!!\n";
if ( v9 )
v10 = "soooooooooorry\n";
sub_401590(std::cout, v10);
system("pause");
}
return 0;
}
輸進來的字符串首先會做一個如下的變化:
signed int __thiscall sub_401100(const char *this)
{
const char *v1; // edi
unsigned int i; // esi
char v3; // cl
v1 = this;
i = 0;
if ( strlen(this) )
{
do
{
v3 = v1[i];
if ( (unsigned __int8)(v3 - 97) <= 0x19u )
v1[i] = (v3 - 84) % 26 + 97;
if ( (unsigned __int8)(v3 - 65) <= 0x19u )
v1[i] = (v3 - 52) % 26 + 65;
++i;
}
while ( i < strlen(v1) );
}
return 1;
}
然後main函數中間的while(1)等價於strcmp(input, "guvf_vf_n_snxr_synt"); 不能讓他們相等,不然就會直接return 0出去了。這裏的while(1)是個迷惑作用,真正要分析的在下面。
chang_table(v3); OD調試時,發現一串字符串。跟base32的table很像,也是26個字母加6個數字。
base32(&input[1]); IDA進到這個函數裏面有個sub_401170函數,sub_401170函數裏面是base32基本特征。有&0x1F、填充等號“=”“==”“===”“====”
而且這個函數裏的table跟chang_table裏出來的那個字符串一個地址,所以這是一個改變了table表的base32。
最後再跟“weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg”比較,要相等。
綜上,將“weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg”解變形base32再進行一個變換。
這裏直接把python自帶的base64裏table改掉了。
再進行解base32
再進行變換
# -*- coding: utf-8 -*- s = b"L@h_Xa@J_o@f332_@Aq_e0g13" flag = ‘‘ for i in range(len(s)): ch = s[i] if ((chr(ch) >= ‘a‘ and chr(ch) <= ‘z‘)): ch = (ch - 84)%26 + 97 if ((chr(ch) >= ‘A‘ and chr(ch) <= ‘Z‘)): ch = (ch - 52)%26 + 65 flag += chr(ch) print(flag)
flag:Y@u_Kn@W_b@s332_@Nd_r0t13
Interesting Pointer
F5
int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax int v4; // ebx size_t len; // eax int v6; // ebx char data[20]; // [esp+1Ch] [ebp-48h] int v8; // [esp+30h] [ebp-34h] int v9; // [esp+34h] [ebp-30h] int v10; // [esp+38h] [ebp-2Ch] int v11; // [esp+3Ch] [ebp-28h] int v12; // [esp+40h] [ebp-24h] int v13; // [esp+44h] [ebp-20h] signed int (__cdecl *func_ptr)(int, int, int); // [esp+48h] [ebp-1Ch] int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h] int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h] int v17; // [esp+54h] [ebp-10h] int v18; // [esp+58h] [ebp-Ch] FILE *v19; // [esp+5Ch] [ebp-8h] __main(); func_ptr = func0; // v8+a 跟 v8+b 交換 v15 = func1; // abs(m+n) - abs(m) - abs(n) + 2 ==> v10 v16 = func2; // abs(x) + abs(y) - abs(x+y) + 2 ==> v11 v8 = 0; v9 = 1; v10 = 2; v11 = 3; v12 = 3; v13 = 4; v19 = fopen("data", "rb"); if ( !v19 ) return -1; fseek(v19, 0, 2); // 文件尾 v18 = ftell(v19); // 返回讀寫位置 fseek(v19, 0, 0); // 文件頭 v17 = ftell(v19); // 返回讀寫位置 if ( v17 ) { puts("something wrong"); result = 0; } else { for ( i = 0; i < v18; ++i ) { v4 = i; data[v4] = fgetc(v19); // data } len = strlen(data); if ( len <= v18 ) { v18 = v11; i = 0; v17 = v13; while ( i <= 2 ) { v6 = i + 1; *(&v8 + v6) = (*(&func_ptr + i))((int)&v8, v12, v13);// // fun0 : 1 ==> v9 // fun1 : abs(m+n) - abs(m) - abs(n) + 2 ==> v10 // fun2 : abs(x) + abs(y) - abs(x+y) + 2 ==> v11 // v12 = ++i; v13 = i + 1; } if ( v11 ) { result = -1; } else { get_key(v18, v17); system("PAUSE"); result = 0; } } else { result = -1; } } return result; }
由上看出:要想進到get_key函數,要使v11的值為0。由它原本的程序走下來是不可能給v11賦到0的。
分析下具體的函數功能
func0(&v8,v12,v13):進行兩個整型數據的交換,&v8 + v12 和 &v8 + v13 兩個地址處的數據進行交換。且這個函數固定返回1.
func1(&v8,v12,v13):abs(m+n) - abs(m) - abs(n) + 2。m是地址為 &v8 + v12 處的整型數據,n是地址為 &v8 + v13 處的整型數據。
func2(&v8,v12,v13):abs(x) + abs(y) - abs(x+y) + 2。x是地址為 &v8 + v12 處的整型數據,y是地址為 &v8 + v13 處的整型數據。
for ( i = 0; i < v18; ++i ) { v4 = i; data[v4] = fgetc(v19); // data }
char data[20]; // [esp+1Ch] [ebp-48h] int v8; // [esp+30h] [ebp-34h] int v9; // [esp+34h] [ebp-30h] int v10; // [esp+38h] [ebp-2Ch] int v11; // [esp+3Ch] [ebp-28h] int v12; // [esp+40h] [ebp-24h] int v13; // [esp+44h] [ebp-20h] signed int (__cdecl *func_ptr)(int, int, int); // [esp+48h] [ebp-1Ch] int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h] int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h] int v17; // [esp+54h] [ebp-10h] int v18; // [esp+58h] [ebp-Ch] FILE *v19; // [esp+5Ch] [ebp-8h]
這裏存在溢出。並沒有限制data數組的大小,能覆蓋掉後面的變量,因為這幾個變量的初始化都在這個數組賦值之前。
絕對值不等式:|a| - |b| ≤ |a + b| ≤| a| + |b|
|a + b| ≤ |a| + |b| 取"="的條件是ab≥0
|a - b| ≤ |a| + |b| 取"="的條件是ab≤0
|a + b| ≥ |a| - |b| 取"="的條件是(a+b)b≤0
|a - b| ≥ |a| - |b| 取"="的條件是(a-b)b≥0
所以func2:abs(x) + abs(y) - abs(x+y) + 2 這個怎麽算都i是>=2的,根據正常的流程,中間的那個while循環中,func2的返回值就是給v11,v11怎麽也不可能為0,所以這個地方,即while第三次循環時*(&v8 + v6) = (*(&func_ptr + i))((int)&v8, v12, v13); (ps:第三次時v6 = 3,即 v11 = *(&func_ptr + 2)((int)&v8, v12, v13);) *(&func_ptr + 2) 即v16不能是func2。
通過溢出,這裏有許多解法。
1、理想解:
利用溢出將v12和v13重新覆蓋為7和8,這樣exchange就會把v15(&8+7)和v16(&v8+8)兩個變量裏面的值交換。這樣就是v9 = 1; v10 = func2(&v8, 1, 2); v11 = func1(&v8, 2, 3)
- 現在func2(&v8, 1, 2)就是把 x = *(&v8 +1); y = *(&v8 + 2) 即 x = v9 = 1,y = v10,來進行 abs(x) + abs(y) - abs(x+y) + 2 運算,這個結果是大於等於2的。這個運算結果再返回給v10。已經確定x為肯定為1,y如果取大於等於0的數,運算結果會總是2。取小於0的數,運算結果是4。
假設通過溢出覆蓋v10的時候是個大於等於0的數,那麽這裏v10 = func2(&v8, 1 ,2); 返回給v10的值為2。
- 再往下func1(&v8, 2, 3)就是把 m = *(&v8 +2); n = *(&v8 + 3) 即 m = v10 = 2,m = v11,abs(m+n) - abs(m) - abs(n) + 2 運算,按著上面我們的假設,這裏確定v10即m為2,且這裏個函數的返回值是要賦給v11的,所以這裏要使這個不等式運算結果為0。|2 + n| - 2 - |n| + 2 = 0 ==> 解得 n = -1。(但其實由於計算機的補碼、符號位,這裏還有一個解 0x7FFFFFFF (這是非預期解)。)即通過溢出覆蓋後v11的值要為-1(0xFFFFFFFF)。
所以綜上,這裏就是通過data數組的溢出來進行覆蓋,要確保v12跟v13為7和8(可以交換,也可以為8和7),還需確保v11為0x7FFFFFFF
get_key(-1,8)是他的預期解。所以v12跟v13為7跟8這樣會產生非預期。
AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
AA AA AA AA BB BB BB BB BB BB BB BB BB BB BB BB
FF FF FF FF 07 00 00 00 08 00 00 00
v12跟v13為7跟8的非預期:
2、非預期解
大佬們的神奇操作。
1)直接把v16原來是func2的地址直接換成了0x00401870:xor eax,eax; retn; 真的是妙。
這個就能產生很多的非預期的結果。
還有不交換,直接把v16的地址覆蓋成func1。
2)還有就是求解零解。思路按著預期解的思路走。
關於求零解
這兩個結果倒是一樣。
信息安全菜為原罪、
巔峰極客線上第一場部分ctf