uglycode - AliCTF - 2016 - Reverse

先用IDA看看main部分

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // [email protected]
  char s1; // [sp+0h] [bp-110h]@1
  void *v5; // [sp+108h] [bp-8h]@3
  sub_400930(a1, a2, a3);
  puts("input you passcode:");
  gets(&s1);
  if ( !strcmp(&s1, "xx xx xx xx xx xx") )
  {
    v5 = &unk_6030C0;
    ((void (__fastcall *)(signed __int64, const char *))unk_6030C0)(2LL, "xx xx xx xx xx xx");
    result = 0LL;
  }
  else
  {
    result = 0LL;
  }
  return result;
}

首先程序让你输入passcode,就是”xx xx xx xx xx xx”,接下来调用unk_6030C0处的函数(注意那个参数2)。所以跳到此处看。

.data:00000000006030C0 unk_6030C0      db  55h ; U             ; DATA XREF: main:loc_401126o
.data:00000000006030C1                 db  48h ; H
.data:00000000006030C2                 db  89h ;
.data:00000000006030C3                 db 0E5h ;
.data:00000000006030C4                 db  48h ; H
.data:00000000006030C5                 db  81h ;
.data:00000000006030C6                 db 0ECh ;

出现这样的东西,说明IDA没有将其当作函数。按一下P创建函数,再按F5即可。

int __fastcall sub_6030C0(unsigned int a1)  //注意这里a1=2
{
  int result; // [email protected]
  char v2[112]; // [sp+10h] [bp-1B0h]@1
  signed int v3; // [sp+14h] [bp-1ACh]@1
  signed int v4; // [sp+18h] [bp-1A8h]@1
  signed int v5; // [sp+1Ch] [bp-1A4h]@1
  int v6; // [sp+20h] [bp-1A0h]@1
  char v7[268]; // [sp+80h] [bp-140h]@1
  char v8[268]; // [sp+87h] [bp-139h]@12 //注意这个+87h和上面的+80h
  int v9; // [sp+18Ch] [bp-34h]@1
  char *v10; // [sp+190h] [bp-30h]@1
  int (__fastcall *v11)(char *, _QWORD); // [sp+198h] [bp-28h]@1
  void (__fastcall *v12)(char *); // [sp+1A0h] [bp-20h]@1
  void (__fastcall *v13)(char *); // [sp+1A8h] [bp-18h]@1
  void (__fastcall *v14)(char *); // [sp+1B0h] [bp-10h]@1
  int i; // [sp+1B8h] [bp-8h]@1
  char v16; // [sp+1BFh] [bp-1h]@1

  v14 = (void (__fastcall *)(char *))((((signed int)a1 + 3752700333LL) ^ 0xDEADBEEFLL) >> 2);//4005d0 - puts (以后经常出现这样的语句,请写个脚本来计算)
  v13 = (void (__fastcall *)(char *))((((signed int)a1 + 3752699821LL) ^ 0xDEADBEEFLL) >> 2);//400650 - gets (计算完后及时注释上去)
  v12 = (void (__fastcall *)(char *))((((signed int)a1 + 3752697657LL) ^ 0xDEADBEEFLL) >> 2);//400875 - 跳到此处按P创建函数
  v11 = (int (__fastcall *)(char *, _QWORD))((((signed int)a1 + 3744295917LL) ^ 0xDEADBEEFLL) >> 2);//603540 - 跳到此处按P创建函数
  v10 = v2;
  *(_DWORD *)v2 = 1773735261;
  v3 = 499976301;
  v4 = -111343463;
  v5 = 961141164;
  v6 = 0;
  v12(v2);
  v14(v2);
  v13(v7);   //获取用户的输入
  *(_DWORD *)v10 = 1228499401;
  *((_DWORD *)v10 + 1) = 15571229;
  v12(v2);
  v16 = 1;
  v9 = 0;
  for ( i = 0; v7[i]; ++i )
  {
    if ( i <= 6 && v7[i] != v2[i] )
      v16 = 0;
  }
  if ( v16 )
  {
    *(_DWORD *)v10 = 125;
    v12(v2);
    if ( v7[i - 1] == v2[0] )
      v7[i - 1] = 0;
    else
      v16 = 0;
  }
  if ( v16 != 1 || (result = v11(v8, a1) ^ 1, (_BYTE)result) )
  {
    *(_DWORD *)v10 = -1991698168;
    *((_DWORD *)v10 + 1) = -1443264184;
    *((_DWORD *)v10 + 2) = 0;
    v12(v2);
    result = ((int (__fastcall *)(char *))v14)(v2);
  }
  return result;
}

0x400875那个地方的函数其实是一种简单的解密变换,这个变换其实不复杂,可以写个函数来模拟,但目前这个变换并没有作用到我们的输入上,所以可以先不管。用动态跟踪的方式可以方便的得到变换后的结果,我们在0x400929(即0x400875函数返回前)设个断点就可以很清楚的看到结果了:

后面会对比用户输入和某个字符串是否相同,同样会调用这个函数,所以我们只要轻轻按一下c就可以了。

原来是这意思,后面还有判断字符串结尾是’}’(也不难猜到)。最后调用v11,也就是0x603540,注意参数,第一个是v8,对比v7可以看出v8=v7+7,相当于去掉前面的”alictf”(也可以动态跟踪来确认),第二个参数还是a1=2。所以点进去看这个函数。

int __fastcall sub_603540(__int64 a1, unsigned int a2)
{
  int result; // [email protected]
  char *v3; // [sp+20h] [bp-20h]@1
  char *v4; // [sp+28h] [bp-18h]@1
  _BYTE *i; // [sp+38h] [bp-8h]@1
  char *j; // [sp+38h] [bp-8h]@6
  char *k; // [sp+38h] [bp-8h]@11

  v4 = (char *)((((signed int)a2 + 3744296941LL) ^ 0xDEADBEEFLL) >> 2);// 603440
  v3 = (char *)((((signed int)a2 + 3744293869LL) ^ 0xDEADBEEFLL) >> 2);// 603740
  for ( i = (_BYTE *)((((signed int)a2 + 3744297197LL) ^ 0xDEADBEEFLL) >> 2);// 603380
        i != (_BYTE *)(((((signed int)a2 + 3744297197LL) ^ 0xDEADBEEFLL) >> 2) + 163);
        ++i )
  {
    *i ^= 0x37u;
  }
  if ( (unsigned __int8)((int (__fastcall *)(__int64, _QWORD))((((signed int)a2 + 3744297197LL) ^ 0xDEADBEEFLL) >> 2))(
                          a1,
                          a2) ^ 1 )
  {
    result = 0;
  }
  else
  {
    for ( j = (char *)((((signed int)a2 + 3744296941LL) ^ 0xDEADBEEFLL) >> 2); j != v4 + 250; ++j )
      *j ^= 0x49u;
    if ( (unsigned __int8)((int (__fastcall *)(__int64, _QWORD))v4)(a1 + 2, a2) ^ 1 )
    {
      result = 0;
    }
    else
    {
      for ( k = (char *)((((signed int)a2 + 3744293869LL) ^ 0xDEADBEEFLL) >> 2); k != v3 + 672; ++k )
        *k ^= *(_BYTE *)(a1 + 5) ^ *(_BYTE *)(a1 + 6);
      result = ((int (__fastcall *)(__int64, _QWORD))v3)(a1 + 6, a2);
    }
  }
  return result;
}

程序对0x603380处163字节进行了异或操作,再调用之。说明这个函数曾经进行了加密操作,可以写个脚本去修改文件,不过在写Writeup的时候发现了更好的方法。

哗的一下就解密了,真牛X,比手动改文件不知道高到哪里去了。然后去这个解密后的函数那里看看。

分析失败,堆栈不平衡。到函数底部发现:

.data:0000000000603419                 pop     rbp
.data:000000000060341A                 pop     rdx
.data:000000000060341B                 jmp     rdx
.data:000000000060341B sub_603380      endp ; sp-analysis failed

其实,pop rdx + jmp rdx 不就是 ret 么?(我不相信rdx的值会被用到)于是修改一下,让IDA不会分析错误:

IDC>PatchByte(0x60341A,0xC3) //0xC3是ret
IDC>PatchByte(0x60341B,0x90) //0x90是nop

接着按F5就可以分析了。

__int64 __fastcall sub_603380(_WORD *a1)
{
  int v2; // [sp+14h] [bp-8h]@1
  char v3; // [sp+19h] [bp-3h]@1
  signed __int16 i; // [sp+1Ah] [bp-2h]@1

  v3 = 0;
  v2 = 0;
  for ( i = 1; i < *a1 + 1; ++i )
  {
    if ( i & 1 )
    {
      if ( i == 3 * (((unsigned int)(21846 * i) >> 16) - (i >> 15)) )
        ++v2;
    }
    else
    {
      ++v2;
    }
  }
  if ( v2 == 19509 )
    v3 = 1;
  return (unsigned __int8)v3;
}

注意一下,a1是我们输入字符串除掉”alictf{“的首地址,这里强制为WORD类型,就相当于拿到前两个字节。上面这段代码还是挺好理解的,可以写个脚本拿到这两个字节的内容了:

>>> ans=0
>>> for i in range(1,0xffff):
    if i%2==1 and i==3*(((21846*i)>>16)-(i>>15)):
        ans+=1
    elif i%2==0:
        ans+=1
    if ans==19509:
        print(hex(i))
        break

0x7250
>>> chr(0x50)
‘P‘
>>> chr(0x72)
‘r‘

回到原来的函数,继续解密:

IDC>decrypt(0x603440,250,0x49)

来到0x603440(也需要修正返回处代码):

__int64 __fastcall sub_603440(_DWORD *a1)
{
  __int64 v2; // [sp+1Ch] [bp-10h]@1
  char v3; // [sp+27h] [bp-5h]@1
  signed int i; // [sp+28h] [bp-4h]@1

  v3 = 0;
  v2 = 0LL;
  for ( i = 1; *a1 + 1 > i; ++i )
  {
    if ( i & 1 )
    {
      if ( i % 3 )
      {
        if ( i % 5 )
        {
          if ( i == 7 * (i / 7) )
            ++v2;
        }
        else
        {
          ++v2;
        }
      }
      else
      {
        ++v2;
      }
    }
    else
    {
      ++v2;
    }
  }
  if ( v2 == 665543088 )
    v3 = 1;
  return (unsigned __int8)v3;
}

注意这里传参的时候是前面的a1+2,就是接下来的字符,因为是DWORD所以这次我们可以拿到4个字符。这段代码的意思是,[1,n]的整数中能被2,3,5,7中至少一个整除的共有665543088个,求n。这次因为DWORD的范围比较大,直接遍历恐怕会比较耗时,所以用容斥原理结合二分法来做。

>>> def f(n):#小于等于n的被2,3,5或7整除的整数个数
    s=0
    s+=n//2
    s+=n//3
    s+=n//5
    s+=n//7
    s-=n//6
    s-=n//10
    s-=n//14
    s-=n//15
    s-=n//21
    s-=n//35
    s+=n//30
    s+=n//42
    s+=n//70
    s+=n//105
    s-=n//210
    return s

>>> def find(a,b):#寻找a,b之间满足题意的数,因为f是不减函数所以可以这样
    if a+1>=b:
        return a
    ans=665543088
    c=(a+b)//2
    n=f(c)
    if n==ans:
        return c
    if n>ans:
        return find(a,c)
    return find(c,b)

>>> hex(find(1,0xffffffff))
‘0x336c6230‘
>>> 0x336c6230.to_bytes(4,‘little‘).decode()
‘0bl3‘

这样连起来就是”Pr0bl3”,下一个不出意外的话是”m”咯?

回到上一层函数,下面又要对0x603740异或解密了,为了方便我这里再引用一下代码:

for ( k = (char *)((((signed int)a2 + 3744293869LL) ^ 0xDEADBEEFLL) >> 2); k != v3 + 672; ++k )
    *k ^= *(_BYTE *)(a1 + 5) ^ *(_BYTE *)(a1 + 6);

a1 + 5 处就是’3’,a1 + 6 我不知道啊?这次他怎么不按基本法来?我当时猜了是”m”结果不对……然后可以猜0x603740处的值异或之后为0x55,因为这是push ebp,函数体开头基本都有这句话;此外前面两个函数异或之后都是0x55开头的。

IDC>0x2b^0x55
        126.       7Eh         176o
IDC>decrypt(0x603740,672,126)

果然没错,顺便可以算一下第7个字符,原来是大写的’M’……现在可以进入下一层函数了。注意调用下一层函数的时候,参数a1又被加了6,所以是从M开始的字串。

int __fastcall sub_603740(__int64 a1, unsigned int a2)
{
  int result; // [email protected]
  char v3; // [sp+10h] [bp-120h]@1
  signed int v4; // [sp+14h] [bp-11Ch]@1
  signed int v5; // [sp+18h] [bp-118h]@1
  signed int v6; // [sp+1Ch] [bp-114h]@1
  int v7; // [sp+20h] [bp-110h]@1
  char v8; // [sp+80h] [bp-B0h]@1
  int (__fastcall *v9)(_QWORD, _QWORD); // [sp+E8h] [bp-48h]@6
  _BYTE *v10; // [sp+F0h] [bp-40h]@3
  char *v11; // [sp+F8h] [bp-38h]@1
  int (__fastcall *v12)(_QWORD, _QWORD); // [sp+100h] [bp-30h]@1
  int (__fastcall *memcmpf)(char *, char *, signed __int64); // [sp+108h] [bp-28h]@1
  void (__fastcall *v14)(__int64, char *); // [sp+110h] [bp-20h]@1
  void (__fastcall *v15)(char *); // [sp+118h] [bp-18h]@1
  void (__fastcall *putsf)(char *); // [sp+120h] [bp-10h]@1
  _BYTE *v17; // [sp+128h] [bp-8h]@3

  putsf = (void (__fastcall *)(char *))((((signed int)a2 + 3752700333LL) ^ 0xDEADBEEFLL) >> 2);// 4005d0
  v15 = (void (__fastcall *)(char *))((((signed int)a2 + 3752697657LL) ^ 0xDEADBEEFLL) >> 2);// 400875
  v14 = (void (__fastcall *)(__int64, char *))((((signed int)a2 + 3752698741LL) ^ 0xDEADBEEFLL) >> 2);// 400766
  memcmpf = (int (__fastcall *)(char *, char *, signed __int64))((((signed int)a2 + 3752699565LL) ^ 0xDEADBEEFLL) >> 2);// 400610
  v12 = (int (__fastcall *)(_QWORD, _QWORD))((((signed int)a2 + 3744290541LL) ^ 0xDEADBEEFLL) >> 2);// 603a00
  v11 = &v3;
  *(_DWORD *)&v3 = 1555013445;
  v4 = 1322911880;
  v5 = 729268039;
  v6 = -1545661451;
  v7 = 0;
  v15(&v3);
  v14(a1, &v8);
  if ( memcmpf(&v3, &v8, 16LL) )
  {
    result = 0;
  }
  else
  {
    *(_DWORD *)v11 = 1845058824;
    *((_DWORD *)v11 + 1) = -917915384;
    *((_DWORD *)v11 + 2) = -648471288;
    *((_DWORD *)v11 + 3) = 489249032;
    *((_DWORD *)v11 + 4) = -1724298791;
    *((_DWORD *)v11 + 5) = 141138184;
    *((_DWORD *)v11 + 6) = 1845058824;
    *((_DWORD *)v11 + 7) = -637990663;
    *((_DWORD *)v11 + 8) = 185;
    v15(&v3);
    putsf(&v3);
    v17 = v12;
    v10 = (char *)v12 + 349;
    while ( v17 != v10 )
      *v17++ ^= 0xEFu;
    v9 = v12;
    result = v12(a1 + 5, a2);
  }
  return result;
}

其中0x400766是陌生的函数,点击去经过几层会来到0x4014FE,这个函数一看就和md5有关(如果看不出,可以去网上搜一下用到的常数,比如第一个680876936)。最后通过大胆猜想和动态跟踪验证,可以确定0x400766整个函数就是用来对一个字符串进行md5变换。

跟着原来在0x400875里面设过的断点继续(注意不要设断点在被解密函数内部,原理清楚吧),先拿到md5变换后正确的值,为35faf651b1a72022e8ddfed1caf7c45f,放cmd5上果然解不开……

现在来验证一下0x400766是不是md5函数:

我输入的flag是”alictf{Pr0bl3M_haha_}”,这里最后的’}’没有存在,应该是前面哪个地方把它调成0了我没有留意。对比下确实是md5:

由于我们无法解出,所以先跳过这里看下面。这个函数也要解密,解密后:

signed __int64 __fastcall sub_603A00(__int64 a1, int a2)
{
  signed __int64 result; // [email protected]
  char v3; // [sp+10h] [bp-90h]@1
  signed int v4; // [sp+14h] [bp-8Ch]@1
  signed int v5; // [sp+18h] [bp-88h]@1
  char *v6; // [sp+80h] [bp-20h]@1
  int (__fastcall *strcmpf)(char *, __int64); // [sp+88h] [bp-18h]@1
  void (__fastcall *v8)(char *); // [sp+90h] [bp-10h]@1
  void (__fastcall *putsf)(char *); // [sp+98h] [bp-8h]@1

  putsf = (void (__fastcall *)(char *))(((a2 + 3752700333LL) ^ 0xDEADBEEFLL) >> 2);// 4005d0
  v8 = (void (__fastcall *)(char *))(((a2 + 3752697657LL) ^ 0xDEADBEEFLL) >> 2);// 400875
  strcmpf = (int (__fastcall *)(char *, __int64))(((a2 + 3752699501LL) ^ 0xDEADBEEFLL) >> 2);// 400620
  v6 = &v3;
  *(_DWORD *)&v3 = 1095556380;
  v4 = 1842214177;
  v5 = 5869004;
  v8(&v3);
  if ( strcmpf(&v3, a1) )
  {
    result = 0LL;
  }
  else
  {
    *(_DWORD *)v6 = -1184249511;
    *((_DWORD *)v6 + 1) = 145357193;
    *((_DWORD *)v6 + 2) = 72;
    v8(&v3);
    putsf(&v3);
    result = 1LL;
  }
  return result;
}

注意这里参数a1传入的时候加上了5。直接判断字符串相等,v3的值可以动态调试得到。

上图中判断相等的地方,把rax改成0可以跳过检测,接着再按一下c得到you can get the flag if you go on,这里不是,再按c,拿到字符串A1w4ys_H3re

所以flag的形式为alictf{Pr0bl3M????A1w4ys_H3re},且M????A1w4ys_H3re的md5值为35faf651b1a72022e8ddfed1caf7c45f,写个脚本搞一搞。

def hack():
    s=[chr(i) for i in range(0x20,0x7f)]
    s.insert(0,‘_‘) #因为猜测第一个未知字符是‘_‘,所以这么写加快速度
    for i in s:
        for j in s:
            for k in s:
                for l in s:
                    out=‘M‘+i+j+k+l+‘A1w4ys_H3re‘
                    m=hashlib.md5()
                    m.update(out.encode())
                    if m.hexdigest()==‘35faf651b1a72022e8ddfed1caf7c45f‘:
                        print(out)

hack()
M_1s_A1w4ys_H3re

答案是alictf{Pr0bl3M_1s_A1w4ys_H3re}

时间: 2024-10-12 18:27:49

uglycode - AliCTF - 2016 - Reverse的相关文章

Alictf Writeup

Alictf Writeup Reverse 1.    Ch1 根据题目描述,首先在Ch1.exe文件中搜索Secret.db字符串,如下所示. 之后定位文件创建和数据写入位置,如下所示. 可以看到,写入数据的地址位于esp+30h+var10,而在之前调用了data_handle函数对齐进行了处理,data_handle函数有三个参数,分别是pOutBuffer.pInBuffer.InBufferSize,如下所示. 传入数据如下所示. 处理完后,位于0x28EF3C处的数据如下所示. 可

聊一聊前端模板与渲染那些事儿

欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码): https://segmentfault.com/blog/frontenddriver 作为现代应用,ajax的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板.我们今天就来聊聊,拼装与渲染模板的那些事儿. 如果喜欢本文请点击右侧的推荐哦,你的推荐会变为我继续更文的动力 1 页面级的渲染 在刚有web的时候,前端与后端的交互,非常直白,浏览器端发出URL,后端返回一张拼好了的HTML串

使用 Raspberry Pi 上的传感器在 Node.js 中创建一个 IoT Bluemix 应用程序

先决条件 一个IBM Bluemix 帐号,一个 Raspberry Pi 2 或 3,一个 PIR 运动传感器 适用于本文的 Github 存储库 如果您是一位精明的 Bluemix 开发人员,您可能只想看看如何在 node.js 中与 IoT 建立连接,或者只想了解如何从此 github 存储库中拉取我的代码. git clone https://github.com/nicolefinnie/iot-nodejs-tutorial 以下是实现与 IBM IoT 平台连接在一起的 4 个 R

2016.5.16——leetcode:Reverse Bits(超详细讲解)

leetcode:Reverse Bits 本题目收获 移位(<<  >>), 或(|),与(&)计算的妙用 题目: Reverse bits of a given 32 bits unsigned integer.For example, given input 43261596 (represented in binary as 00000010100101000001111010011100), return 964176192 (represented in bin

2016 alictf Timer writeup

Timer-smali逆向 参考文档:http://blog.csdn.net/qq_29343201/article/details/51649962 题目链接: https://pan.baidu.com/s/1jINx7Fo   (在里面找相应的名字就行) 题目描述:每秒触发一次计算,共有200000秒,答案参与计算,不可能等待下去. 使用工具:Android Killer,jadx-gui 解题方法有多种,我参照网上的一种方法.通过对native层,代码的还原,计算出200000秒后的关

【数学】CSU 1810 Reverse (2016湖南省第十二届大学生计算机程序设计竞赛)

题目链接: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1810 题目大意: 一个长度为N的十进制数,R(i,j)表示将第i位到第j位翻转过来后的数字,求mod 109+7 题目思路: [数学] 这题换一种思路,看每个数字能够对答案的贡献情况.(可以手推01,10,001,010,100.....,也可以像我一样写个暴力打个10以内的表看看规律) 现在先考虑位置为i的数字为1的情况(最后乘上这个数字就行).可以发现贡献是对称的(第i位的1和第

2016第七季极客大挑战Writeup

第一次接触CTF,只会做杂项和一点点Web题--因为时间比较仓促,写的比较简略.以后再写下工具使用什么的. 纯新手,啥都不会.处于瑟瑟发抖的状态. 一.MISC 1.签到题 直接填入题目所给的SYC{We1c0m3_To_G33k_2O!6} 并且可以知道后边的题的Flag格式为 SYC{} 2.xiao彩蛋 题目提示关注微博,从Syclover Team 博客(http://blog.sycsec.com)可获取到三叶草小组微博,私信发送flag后即可得到. 3.闪的好快 一开始拖进PS分帧数

JavaScript编程题(含腾讯2016校招题)

作者:ManfredHu 链接:http://www.manfredhu.com/2016/04/02/15-veryGoodForUsing/ 声明:版权所有,转载请保留本段信息,否则请不要转载 几道觉得挺有意思的编程题,感觉做下来,自己对一些新方法的看法有了新的变化. 比如indexOf,reduce,Array.isArray,forEach这些方法,以前一看到兼容性是IE9+就有点害怕,项目中不敢用,导致后面越来越陌生,不过现在一想的话.其实只要用Polyfill或者提前fix掉就可以了

FFMpeg ver 20160219-git-98a0053 滤镜中英文对照 2016.02.21 by 1CM

FFMpeg ver 20160219-git-98a0053 滤镜中英文对照 2016.02.21 by 1CM T.. = Timeline support 支持时间轴 .S. = Slice threading 分段线程 ..C = Command support 支持命令传送 A = Audio input/output 音频 输入/输出 V = Video input/output 视频 输入/输出 N = Dynamic number and/or type of input/out