2016kxctf第29题

2016kxctf第29题,没做完

Posted by gxkyrftx on January 2, 2019

1.程序分析

1.1 start

start 函数

public start
start proc near
push    0               ; lpModuleName
call    GetModuleHandleA
mov     hInstance, eax
push    0
push    0
push    0
call    sub_401039
call    InitCommonControls
push    0               ; dwInitParam
push    offset DialogFunc ; lpDialogFunc
push    0               ; hWndParent
push    65h             ; lpTemplateName
push    hInstance       ; hInstance
call    DialogBoxParamA
push    0               ; uExitCode
call    ExitProcess
start endp

, 分析sub_401039的原因,其他的call,都是系统函数。看看它的内容

1.2 sub_401039

void (__stdcall __noreturn *__stdcall sub_401039(int a1, int a2, int a3))(int, int, int)
{
  int v3; // ecx
  int v4; // edx
  void (__stdcall __noreturn *result)(int, int, int); // eax

  sub_401080();
  v3 = 0;
  v4 = 0;
  for ( result = start;
        (unsigned int)sub_401A02 >= (unsigned int)result;
        result = (void (__stdcall __noreturn *)(int, int, int))((char *)result + 1) )
  {
    LOBYTE(v4) = *(_BYTE *)result;
    v3 += v4;
  }
  return result;

调用了sub_401080();

1.3 sub_401080

_DWORD *sub_401080()
{
  v0 = GetModuleHandleA(ModuleName);//返回函数模块句柄(基址)
  v1 = (int)v0;
  v2 = (_DWORD *)sub_401377((int)v0);
  sub_40139E(v1, v2, (int)&unk_403AED);
  sub_40139E(v1, v3, (int)&unk_403AF8);
  sub_40139E(v1, v4, (int)&unk_403B03);
  sub_40139E(v1, v5, (int)&unk_403B0E);
  sub_40139E(v1, v6, (int)&unk_403B19);
  sub_40139E(v1, v7, (int)&unk_403B24);
  sub_40139E(v1, v8, (int)&unk_403B2F);
  v9 = GetModuleHandleA(aUser32Dll_0);
  v10 = (int)v9;
  v11 = (_DWORD *)sub_401377((int)v9);
  sub_40139E(v10, v11, (int)&unk_403AED);
  sub_40139E(v10, v12, (int)&unk_403AF8);
  sub_40139E(v10, v13, (int)&unk_403B03);
  sub_40139E(v10, v14, (int)&unk_403B0E);
  sub_40139E(v10, v15, (int)&unk_403B19);
  sub_40139E(v10, v16, (int)&unk_403B24);
  sub_40139E(v10, v17, (int)&unk_403B2F);
  sub_40139E(v10, v18, (int)&unk_403B3A);
  v19 = GetModuleHandleA(aKernel32Dll_0);
  v20 = (int)v19;
  v21 = (_DWORD *)sub_401377((int)v19);
  sub_40139E(v20, v21, (int)&unk_403B45);
  sub_40139E(v20, v22, (int)&unk_403B50);
  sub_40139E(v20, v23, (int)&unk_403B5B);
  sub_40139E(v20, v24, (int)&unk_403B66);
  sub_40139E(v20, v25, (int)&unk_403B71);
  sub_40139E(v20, v26, (int)&unk_403B7C);
  sub_40139E(v20, v27, (int)&unk_403B87);
  sub_40139E(v20, v28, (int)&unk_403B92);
  sub_40139E(v20, v29, (int)&unk_403B9D);
  sub_40139E(v20, v30, (int)&unk_403BA8);
  sub_40139E(v20, v31, (int)&unk_403BB3);
  sub_40139E(v20, v32, (int)&unk_403BBE);
  sub_40139E(v20, v33, (int)&unk_403BC9);
  sub_40139E(v20, v34, (int)&unk_403BD4);
  sub_40139E(v20, v35, (int)&unk_403BDF);
  sub_40139E(v20, v36, (int)&unk_403BEA);
  sub_40139E(v20, v37, (int)&unk_403BF5);
  sub_40139E(v20, v38, (int)&unk_403C00);
  sub_40139E(v20, v39, (int)&unk_403C0B);
  return sub_40139E(v20, v40, (int)&unk_403C16);
}

调用了sub_401377,分析sub_401377

1.4 sub_401377

int __stdcall __spoils<ecx> sub_401377(int a1)
{
  int result; // eax

  result = a1;
  if ( a1 )
    result = a1 + *(_DWORD *)(a1 + *(_DWORD *)(a1 + 0x3C) + 0x78);// a1 是基址,对于pe的操作,遍历导出表
  return result;
}

具体内容如下 伪代码的意思,用基址a1加上3C,然后取出这个地址里面的值,把这个值加上78,然后再取里面的值。与基址相加,这个其实看到的时候稍微有点印象,防病毒里面讲过,这是一波对于pe的操作,最终结果是找到了导出表的位置。随便找一个dll(32位下的),拖进010editor,验证如下。

1

2 3

1.5 sub_40139E

_DWORD *__stdcall __spoils<ecx> sub_40139E(int base, _DWORD *export, int a3)
{
  _DWORD *result; // eax
  unsigned int i; // ebx
  int v5; // edx
  int v6; // esi
  unsigned __int8 *v7; // ebx
  int v8; // eax
  unsigned int v9; // eax
  unsigned int v10; // [esp-14h] [ebp-124h]
  _DWORD *v11; // [esp+8h] [ebp-108h]

  result = export;
  v11 = (_DWORD *)(export[8] + base);           // 导出表结构体的第8个字段,NumberOfNames;以名称方式导出的函数的总数根据导出表结构推出来的
  if ( !*(_BYTE *)a3 )
  {
    for ( i = 0; i < export[6]; i = v10 + 1 )   // 导出表结构体的第6个字段,NumberOfFunctions;导出函数的总数,该循环,直接遍历
    {
      v10 = i;
      v5 = 0;
      v6 = 0;
      v7 = (unsigned __int8 *)(base + *v11);
      v8 = *v7;
      while ( (_BYTE)v8 )
      {
        if ( v5 == 3 )                          // 比较第三个字符
        {
          if ( (_BYTE)v8 != *(_BYTE *)(a3 + 5) )
            break;
        }
        else if ( v5 == 5 && (_BYTE)v8 != *(_BYTE *)(a3 + 6) )// 比较第五个字符
        {
          break;
        }
        v6 += v8;
        LOBYTE(v8) = *++v7;
        ++v5;
      }
      if ( v6 == *(_DWORD *)(a3 + 1) )          // 校验和
      {
        v9 = base + *(_DWORD *)(export[7] + base + 4 * *(unsigned __int16 *)(export[9] + base + 2 * v10));
        *(_BYTE *)a3 = 1;
        *(_WORD *)(a3 + 9) = v9;
        result = (_DWORD *)(v9 >> 16);
        *(_WORD *)(a3 + 7) = (_WORD)result;
        return result;
      }
      ++v11;
    }
    result = 0;
  }
  return result;
}

导出表结构如下

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;//未使用,总是定义为0
    DWORD   TimeDateStamp;//文件生成时间
    WORD    MajorVersion;//未使用,总是定义为0
    WORD    MinorVersion;//未使用,总是定义为0
    DWORD   Name;   //模块的真实名称的RVA
    DWORD   Base;   //基数,加上序数就是函数地址数组的索引值
    DWORD   NumberOfFunctions;//导出函数的总数
    DWORD   NumberOfNames;  //以名称方式导出的函数的总数
    DWORD   AddressOfFunctions;     // RVA from base of image指向输出函数地址的RVA
    DWORD   AddressOfNames;         // RVA from base of image指向输出函数名字的RVA
    DWORD   AddressOfNameOrdinals;  // RVA from base of image向输出函数序号的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

综上,1.3 sub_401080函数,就是遍历导出表,获取函数名称 回退到1.2 sub_401039。

2.反调试

指向非法地址

如图所示 4

逻辑:eax+ebx,两个函数偏移相加,进行比较,然后将两个偏移累加求和,得到的和,减取0下10,再将ecx与loc_401072比较,不相等就跳转至0x83648888,此地址为内核地址,ring0级别,用户不能访问,立刻报错,起到了反调试的作用。

解决方法,edit——patch program——assmeble,将jz改为jmp,不让它执行跳转到ring0的语句。 参考大佬的wp,反调试的地方还有

401505 jz short loc_40150E >> jmp
401847 jz short loc_401850 >> jmp

3. 俄罗斯方块

跟随流程分析,来到DialogFunc

3.1 DialogFunc

INT_PTR __userpurge DialogFunc@<eax>(int a1@<ebx>, HWND hDlg, UINT a3, WPARAM a4, LPARAM a5)
{
  int (__stdcall *v5)(_DWORD, _DWORD, int (__stdcall *)(int), _DWORD, _DWORD, _DWORD); // ebx

  switch ( a3 )
  {
    case 0x110u:
      hWnd = hDlg;
      if ( unk_403BA8 )
      {
        LOWORD(a1) = *(_WORD *)((char *)&unk_403BA8 + 7);
        v5 = (int (__stdcall *)(_DWORD, _DWORD, int (__stdcall *)(int), _DWORD, _DWORD, _DWORD))(a1 << 16);
        LOWORD(v5) = *(_WORD *)((char *)&unk_403BA8 + 9) + (_WORD)v5;
        if ( !v5(0, 0, sub_40149C, 0, 0, 0) )//创建线程
          ExitProcess(0);
      }
      break;
    case 0x111u:
      if ( (_WORD)a4 == 105 )
        sub_4012E7();
      break;
    case 0x10u:
      EndDialog(hDlg, 0);
      break;
    default:
      return 0;
  }
  return 1;
}

可以看到一个模块如下

mov     bx, [eax+7]
shl     ebx, 10h
add     bx, [eax+9]
push    0
push    0
push    0
push    offset sub_40149C
push    0
push    0
call    ebx
or      eax, eax
jnz     short loc_4012AB

根据大佬wp,在输入字符串之后,这是创建线程,来对字符串进行操作。点进去可以看到 sub_40149C这个函数,创建了五个类似的线程

mov     bx, [eax+7]
shl     ebx, 10h
add     bx, [eax+9]
push    0
push    0
push    0
push    offset loc_4016F0
push    0
push    0
call    ebx
or      eax, eax
jnz     short loc_40157F

3.2 sub_4012E7()

在另一个case调用了sub_4012E7(),具体分析见注释。

char *sub_4012E7()
{
  int v0; // edx
  signed int v1; // ebx
  CHAR *v2; // eax
  int v3; // ecx
  char *result; // eax
  CHAR String; // [esp+0h] [ebp-100h]

  RtlZeroMemory(&String, 256);
  GetDlgItemTextA(hWnd, 1001, &String, 256);    // sub_4012E7,check,获取输入,存入string
  if ( !dword_403037 )
    sub_401BA1();
  v0 = 0;
  v1 = 8;
  v2 = &String;
  v3 = (unsigned __int8)String;
  while ( v1 )                                  // v1=8,v1在循环中做减法,共计循环8次
  {
    v0 += v3;
    LOBYTE(v3) = *++v2;
    --v1;
  }                                             // v0是一个和,把v3,也就是输入字符串的每个值相加
  if ( (_BYTE)v3 )                              // v3不是0,弹出errorbox
    result = (char *)MessageBoxA(hWnd, Caption, Caption, 0);
  else
    result = sub_401B0A(v0);                    // v3是假,进入这个函数
  return result;
}

3.3 sub_401BA1()

int sub_401BA1()
{
  unsigned int v0; // eax
  int result; // eax
  unsigned int j; // [esp+0h] [ebp-8h]
  unsigned int i; // [esp+4h] [ebp-4h]

  memset(dword_40303B, 0, 0x9Au);
  for ( i = 0; i < 0xE; ++i )
  {
    for ( j = 0; j < 0xB; ++j )
    {
      if ( !j || j == 10 || i == 13 )
        *(_DWORD *)&dword_40303B[11 * i + j] = 1;
    }
  }
  v0 = time(0);
  srand(v0);
  result = sub_401C84();
  dword_403037 = 1;
  return result;
}

调用了sub_401C84()

3.4 sub_401C84()

int sub_401C84()
{
  unsigned int v0; // eax

  v0 = rand();
  dword_4030E1 = v0 % 0x1C;
  dword_4030DD = (int)&asc_4030ED[16 * (v0 % 0x1C)];
  dword_4030D5 = 3;
  dword_4030D9 = 0;
  return boundary(3, 0);
}

看一下asc_4030ED内容 5

这是俄罗斯方块,16个字节为一组

“* * * * ” 代表‘——’
“* * * * ” 代表‘|’,其他以此类推 所以至此3.3sub_401BA1()分析完毕,初始化俄罗斯方块

3.5 sub_401B0A

分析 3.2 sub_4012E7()中的另一个函数sub_401B0A,分析见注释。ida有点傻了,没有完全逆出来逻辑,结合ida-view和程序设计思想分析

ida_view

char *__stdcall sub_401B0A(int a1)
{
  if ( !dword_403037 )                          // 判断游戏是否完成
    return (char *)MessageBoxA(0, Caption, Caption, 0);
  ++byte_403C99;
  switch ( a1 )                                 // a1是输入字符串的和。共计五个分支,根据累加和,挑选一个分支执行
  {
    case 0x566:
      return (char *)sub_401DA0();
    case 0x79A:
      return (char *)sub_401DC1();
    case 0x86B:
      return sub_401E16();
    case 0x5D5:
      return (char *)sub_401DE2();
    case 0x325:                                 // 必须选这个,因为ASCII码,最大的z为7A,乘8之后,为0x3D0,最大为0x3D0
      return (char *)sub_401DE2();
  }
  byte_403C99 = 0;
  return (char *)MessageBoxA(0, Caption, Caption, 0);
}

传入的参数a1,是校验和,就是前面提到的,把8个字符的ascii相加,可以判断出在所有的可选的字符中,8个ascii最大值为0x3D0,根本不可能出现上面的case,只能选case 0x325

3.6 sub_401DA0()

int sub_401DA0()
{
  int result; // eax

  result = boundary(dword_4030D5 - 1, dword_4030D9);
  if ( !result )                                // 通过boundary函数进行计算,判断有没有触碰边界,如果碰到了,直接返回。没有触碰到,返回为假,把俄罗斯方块移动一步
    --dword_4030D5;                             // 移动方块
  return result;
}

这个是边界判断函数,判断方块是否能移动。

4.动态调试

2333333,不会,先缺着,没看懂。 得出条件 int (x7 x6 x5 x4 ^ 0x66) +int( x3 x2 x1 x0 ^ 0x66) = 0x32113442 push 004032b5 //“good”地址

5.破解

最终思路

  1. sn字节求和为325
  2. sn需要异或 66,记v
  3. dword,v0 + v1 = 32113442
  4. v作为key解码,y = (x + v1) ^ v0,解码的明文中含有 Good那个地址。

附一篇大佬破解程序

#include "stdafx.h"
#include <string.h>
 
const char *cch = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
int test2(char buf[62*62], unsigned char vv)
{
    int k = 0;
    char sn[4] = { 0 };
    for (int i = 0; i < 62; i++)
    {
        sn[0] = (unsigned char)cch[i];
        for (int j = 0; j < 62; j++)
        {
            sn[1] = (unsigned char)cch[j];
            if (((sn[0] ^ 0x66) + (sn[1] ^ 0x66)) == vv)
            {
                buf[k++] = sn[0];
                buf[k++] = sn[1];
            }
        }
    }
    return k;
}
 
unsigned char cc[] = {
    0x69, 0x92, 0x14, 0x09, 0x58, 0x1A, 0x65, 0x79, 0x60, 0x13, 0xCC, 0xD1, 0x28, 0x34, 0x0A, 0x2C,
    0x0F, 0x40, 0x0B, 0x5C, 0x83, 0x08, 0x4B, 0xF4, 0x30, 0x85, 0x39, 0x34, 0x18, 0x40, 0x0B, 0xCD,
    0xE9, 0x6F, 0xCA, 0x8C, 0x1F, 0x0F, 0x4B, 0xF4,
};
 
int test3(char *sn)
{
    char buf[16] = { 0 };
    for (int i = 0; i < 8; i++)
    {
        buf[i] = sn[i] ^ 0x66;
    }
 
    unsigned long *vv = (unsigned long *)buf;
    int s = vv[0] + vv[1];
    if (s != 0x32113442)
        return 0;
 
    unsigned long mm[0x0a];
    unsigned long *c = (unsigned long*)cc;
    for (int i = 0; i < 0x0a; i++)
    {
        int x = c[i];
        x += vv[1];
        x ^= vv[0];
        mm[i] = x;
    }
     
    int f = 0;
    for (int i = 0; i < 0x28; i++)
    {
        if (*(((unsigned char*)mm)+ i) == 0xb5)
        {
            if (*(((unsigned char*)mm) + i+1) == 0x32)
            {
                if (*(((unsigned char*)mm) + i + 2) == 0x40)
                {
                    if (*(((unsigned char*)mm) + i + 3) == 0x00)
                    {
                        f = 1;
                    }
                }
            }
        }
    }
    //004032B5
    if (f == 1)
    {
        printf("sn:%s\n", sn);
    }
    return 0;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    char buf[16] = { 0 };
 
    unsigned char a0[62 * 62] = { 0 };
    int k0 = test2((char*)a0, 0x42);
    unsigned char a1[62 * 62] = { 0 };
    int k1 = test2((char*)a1, 0x34);
    unsigned char a2[62 * 62] = { 0 };
    int k2 = test2((char*)a2, 0x11);
    unsigned char a3[62 * 62] = { 0 };
    int k3 = test2((char*)a3, 0x32);
 
    for (int i = 0; i < k0; i+=2)
    {
        int x0 = a0[i] + a0[i + 1];
        for (int j = 0; j < k1; j += 2)
        {
            int x1 = a1[j] + a1[j + 1];
            for (int m = 0; m < k2; m += 2)
            {
                int x2 = a2[m] + a2[m + 1];
                for (int n = 0; n < k3; n += 2)
                {
                    int x3 = a3[n] + a3[n + 1];
                    if ((x0 + x1 + x2 + x3) == 0x325)
                    {
                        buf[0] = a0[i]; buf[4] = a0[i+1];
                        buf[1] = a1[j]; buf[5] = a1[j+1];
                        buf[2] = a2[m]; buf[6] = a2[m+1];
                        buf[3] = a3[n]; buf[7] = a3[n+1];
                         
                        test3(buf);
                    }
                }
            }
        }
    }
 
    return 0;
}

6.总结

能力不足,只能分析一小部分,强行结尾了。。。


本文访问量: