1. 程式人生 > >巔峰極客線上第一場部分ctf

巔峰極客線上第一場部分ctf

16px inter put 等價 個數字 技術分享 位置 unsigned 正常

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