pupububu 发表于 2021-11-29 15:44

2021NCTF RE

# Re



### Shadowbringer

c++64位程序,ida载入

```c
std::string::string(v4, "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!", &v6);
std::allocator<char>::~allocator(&v6);
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Welcome.Please input your flag:\n");
std::operator>><char>(refptr__ZSt3cin, (std::string *)input);
std::string::string((std::string *)v8, (const std::string *)input);// strcpy
base64encode1((std::string *)v7, (std::string *)v8);// 换表的base64
std::string::operator=((std::string *)input, (const std::string *)v7);// 赋值 第一层密文
std::string::~string((std::string *)v7);
std::string::~string((std::string *)v8);
std::string::string((std::string *)v10, (const std::string *)input);// 复制一个对象v10
base64encode2((std::string *)v9, (std::string *)v10); // base64换表 两次 不同的表
std::string::operator=((std::string *)input, (const std::string *)v9);
std::string::~string((std::string *)v9);
std::string::~string((std::string *)v10);
if ( (unsigned __int8)std::operator==<char>(input, v4) )
    std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right.");
else
    std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.");
std::string::~string((std::string *)v4);
```

主要用到了c++ string类来进行处理,结合动调,大致经过了两次base64变表加密,在和v4进行比较。
第一组表,
‘#$%&’,27h,‘()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ’+‘^_`ab’

第二组表
‘ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)(’,27h,‘&’‘+’%$#’

两次加密的代码大致相同,主要通过表的长度和每次处理的二进制长度判断为base64.

```c
std::bitset<8ull>::to_string(v13, v14);   // 转为2进制
for ( j = 0; j < (unsigned __int64)std::string::size((std::string *)&v9); j += 6 )// 6个二进制一组
v7 = (char *)std::string::operator[](&hisoralce, v6);//表索引

while ( (std::string::size(a1) & 3) != 0 )
{
    std::operator+<char>(v19, a1, '!');
    std::string::operator=(a1, (const std::string *)v19);
    std::string::~string((std::string *)v19);
}
//不为4的倍数就不断+!
```

了解流程后写解密脚本即可

```python
import base64
table1='#$%&'+'\x27'+'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ'+'^_`ab'
table2='ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('+'\x27'+'&%$#'
base='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
enc='U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!'
#enc='FsJ7M?b<U->2M>U:'#123456789测试
def newbase(enc,table):
    m=''
    for i in range(len(enc)):
      if enc in table:
            m+=base)]
      else:
            m+='='
    print(base64.b64decode(m))
    return base64.b64decode(m)
c1=newbase(enc,table2).decode()
newbase(c1,table1)
#NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}
```

### 鲨鲨的秘密

32位程序,ida载入

```c
IpAdress = malloc(0x20u);
VirtualProtect(IpAdress, 0x20u, 0x40u, &flOldProtect);
dword_404E48 = (int)IpAdress;
*(_BYTE *)IpAdress = 0xC3;                  // ret的机器码
((void (*)(void))IpAdress)
```

刚载入就有种莫名其妙的熟悉感,和西湖论剑的一道逆向题思路差不多,又是体力活。
是一种修改代码的操作数并单语句执行的SMC,通过一个数组来确定赋值代码长度的大小,和选定相应的操作数和修改的位置。

挖出汇编代码,结合动调分析语句还原算法。

```
mov   ds:dword_403474, 0FFFFFFFFh            // mov output ,0xffffffff
mov   ecx, ds:dword_403464                         // mov ecx,index(0)
mov   dl, byte ptr ds:VirtualProtect      // mov dl,input
mov   byte ptr ds:dword_403470, dl             // mov temp,dl
movzx   eax, byte ptr ds:dword_403470         //mov eax, temp输入传给eax
xor   eax, ds:dword_403474                        //xor   eax,output 取反类似
mov   byte ptr ds:dword_403470, al            //   mov temp , al保存取反的值
movzx   ecx, byte ptr ds:dword_403470      //   mov ecx,temp
and   ecx, 0FFh                                             //   andecx,0xff
mov   byte ptr ds:dword_403470, cl            //   mov temp,cl
mov   edx, ds:dword_403474                      //    mov edx,output
shr   edx,   8                                                 //    shr   edx,8
mov   ds:dword_403474, edx                      //    mov output,edx
movzx   eax, byte ptr ds:dword_403470       //    mov eax,temp
mov   ecx, ds:dword_403474                         //    mov ecx,output
xor   ecx, dword ptr ds:byte_403058   //xor ecx, sbox   //174841BCxor sbox 结果保存到output
mov   ds:dword_403474, ecx                              
mov   edx, ds:dword_403464                            ....
mov   al,                
mov   byte ptr ds:dword_403470, al               
movzx   ecx, byte ptr ds:dword_403470               
xor   ecx, ds:dword_403474               
mov   byte ptr ds:dword_403470, cl      
mov   edx, ds:dword_403474
shr   edx, 8
mov   ds:dword_403474, edx
movzx   eax, byte ptr ds:dword_403470
mov   ecx, ds:dword_403474
xor   ecx, dword ptr ds:byte_403058   //xor ecx,sbox//eax 0xdd
mov   ds:dword_403474, ecx                            //mov output,ecx
mov   edx, ds:dword_403474                            //mov edx,output
xor   edx, 0FFFFFFFFh                           //xor   edx, 0FFFFFFFFh
mov   ds:dword_403474, edx                        //mov    output edx
```

python代码如下

```python
input='a'*40
output=0xffffffff
for index in range(0,40,2):
      tmp=(ord(input)^output)&0xff
      output=output>>8
      output=output^somebox
      #print("%x %x"%(tmp,output))
      tmp = (ord(input) ^ output) & 0xff
      output = output >> 8
      output = output ^ somebox
      output=output^0xffffffff
      print("%x %x" % (tmp, output))
```

可知是两个字节为一组进行的处理,z3解因为涉及下标问题不好下手,所以直接爆破。

```python
somebox=
enc=
s=''
enc=
for iin range(20):
    form in range(32,128):
      for n in range(32,128):
            output = 0xffffffff
            tmp=(m^output)&0xff
            output=output>>8
            output=output^somebox
            #print("%x %x"%(tmp,output))
            tmp = (n ^ output) & 0xff
            output = output >> 8
            output = output ^ somebox
            output=output^0xffffffff
            if output==enc:
                s+=chr(m)+chr(n)

print(s)
#NCTF{rLdE57TG0iHA39qUnFZp6LeJyYEBcxMNL7}
```

### 狗狗的秘密

**挺不错的,解完想暴打出题人。**
32位程序,ida载入获得假flag一枚
![](https://i.imgur.com/gAmoAa5.png)
不过main之前有个TlsCallback函数,直接下个断点,动态分析。

```c
if ( !v9 && !IsDebuggerPresent() )
    {
      off_825014 = (int (__cdecl *)(_DWORD))sub_823000;
      v8 = (unsigned int *)((char *)sub_823000 + 256);
      for ( i = 0; i < 24; ++i )
      v8 += 2;
      for ( j = 0; j < 24; ++j )
      {
      v8 -= 2;
      sub_8211F0(v8);
      }
    }
```

反调试,off_825014在主函数出现过,但是个假逻辑,所以这部分内容是SMC修改技术。

```c
unsigned int __cdecl sub_8211F0(unsigned int *a1)
{
unsigned int result; // eax
int i; //
unsigned int v3; //
unsigned int v4; //

v4 = *a1;
v3 = a1;
for ( i = 0; i < 64; ++i )
{
    v3 -= (dword_825004[(*(_DWORD *)delta >> 11) & 3] + *(_DWORD *)delta) ^ (v4 + ((v4 >> 5) ^ (16 * v4)));
    *(_DWORD *)delta += dword_825000;
    v4 -= (dword_825004 & 3] + *(_DWORD *)delta) ^ (v3 + ((v3 >> 5) ^ (16 * v3)));
}
*a1 = v4;
result = v3;
a1 = v3;
return result;
}
```

直接改eip绕过这个反调试即可,同时修改代码用的xTea,不过delta的值有个小坑,main函数中有个创建线程的函数,将delta赋值为0xDA76C600,patch进行修改,后面下个断F9。
修复函数后拿到真正处理逻辑。

```c
int __cdecl sub_823000(const char *a1)
{
signed int v2; //
unsigned int v3; //
signed int v4; //
int v5; //
int v6; //
char v7; //
signed int Size; //
unsigned int v9; //
int k; //
unsigned __int8 *v11; //
int i; //
signed int j; //
signed int m; //
signed int n; //
signed int ii; //
char v17; //
int v18; //
int v19; //
int v20; //
int v21; //
__int16 v22; //

v3 = strlen(a1);
Size = 146 * v3 / 0x64 + 1;
v4 = 0;
v11 = (unsigned __int8 *)malloc(Size);
v17 = 82;
v17 = -61;
v17 = 26;
v17 = -32;
v17 = 22;
v17 = 93;
v17 = 94;
v17 = -30;
v17 = 103;
v17 = 31;
v17 = 31;
v17 = 6;
v17 = 6;
v17 = 31;
v17 = 23;
v17 = 6;
v17 = 15;
v17 = -7;
v17 = 6;
v17 = 103;
v17 = 88;
v17 = -78;
v17 = -30;
v17 = -116;
v17 = 15;
v17 = 42;
v17 = 6;
v17 = -119;
v17 = -49;
v17 = 42;
v17 = 6;
v17 = 31;
v17 = -104;
v17 = 26;
v17 = 62;
v17 = 23;
v17 = 103;
v17 = 31;
v17 = -9;
v17 = 58;
v17 = 68;
v17 = -61;
v17 = 22;
v17 = 51;
v17 = 105;
v17 = 26;
v17 = 117;
v17 = 22;
v17 = 62;
v17 = 23;
v17 = -43;
v17 = 105;
v17 = 122;
v17 = 27;
v17 = 68;
v17 = 68;
v17 = 62;
v17 = 103;
v17 = -9;
v17 = -119;
v17 = 103;
v17 = -61;
v18 = 0;
v19 = 0;
v20 = 0;
v21 = 0;
v22 = 0;
memset(v11, 0, Size);
v9 = 0;
for ( i = 0; i < 256; ++i )
{
    v7 = byte_825018;
    byte_825018 = byte_825018[(i + *((unsigned __int8 *)&delta + i % 4)) % 256];// delta变为0了
    byte_825018[(i + *((unsigned __int8 *)&delta + i % 4)) % 256] = v7;
}
while ( v9 < strlen(a1) )
{
    v5 = a1;
    for ( j = 146 * v3 / 0x64; ; --j )
    {
      v6 = v5 + (v11 << 8);
      v11 = v6 % 47;
      v5 = v6 / 47;
      if ( j < v4 )
      v4 = j;
      if ( !v5 && j <= v4 )
      break;
    }
    ++v9;
}
for ( k = 0; !v11; ++k )
    ;
for ( m = 0; m < Size; ++m )
    v11 = byte_825118];             // 单表替换
while ( m < Size )
    v11 = 0;
v2 = strlen((const char *)v11);
for ( n = 0; n < v2; ++n )
    v11 ^= byte_825018];            // 异或处理
for ( ii = 0; ii < v2; ++ii )
{
    if ( v11 != (unsigned __int8)v17 )
    {
      printf("Wrong!\n", v2);
      exit(0);
    }
}
printf("Right!\n", v2);
return 0;
}
```

delta的值是0,需要注意一下,接着就是写脚本逆向,z3不好直接求解,加密流程是先将输入转为47进制下每位的值存在数组v11中,找到第一个非0值的下标k,接着进行单表替换和异或。
因为涉及到表索引和本身异或不好逆向还原,所以想着在爆破v11数组。

```python
enc=
c=[]
for i in range(61):
    temp=[]
    for j in range(47):
      tmp=tb2
      tmp=tmp^table
      if tmp==enc:
            temp.append(j)
    if len(temp)==1:
      c.append(temp)
    else:
      c.append(temp)
c.insert(0,0)

#, 44, 30, 40, 8, 23, , , , , , , 24, , , 29, , , 13, 5, 23, 41, , 35, , 9, 14, 35, , , 3, , 10, 24, , , 38, 1, 25, 0, 30, 6, 42, , 36, 30, 10, 24, 21, 42, 26, 28, 25, 25, 10, , 38, 9, ]
```

v11第一位是0,根据加密的最后一位是0xc3,或者多次测试都可知,不过这解有点多,下面就是对c进行排列组合,之后47进制转,long_to_bytes下即可,不过这…tmd,dfs不太会写,直接硬爆破了,大概跑了半小时,直接整emo了。
完整如下

```python
from Crypto.Util.number import *


def dododo(c):
    sum = 0
    for i in range(62):
      sum += c * pow(47, 61 - i)
    m=long_to_bytes(sum)
    try:
      flag=m.decode()
      print(flag)
      exit(0)
    except:
      pass


enc=
table=
#delta=
tb2=

c=[]

for i in range(61):
    temp=[]
    for j in range(47):
      tmp=tb2
      tmp=tmp^table
      if tmp==enc:
            temp.append(j)
    if len(temp)==1:
      c.append(temp)
    else:
      c.append(temp)
c.insert(0,0)
print(c)
for i in c:
    if isinstance(i,int):
      pass
    else:
      print(i)
c=45
tblen=[]
l=1
index=[]
for i in range(len(c)):
    try:
      tblen.append(len(c))
      l*=len(c)
      index.append(i)
    except:
      tblen.append(1)

print(tblen)
print(index)

sum=0
t1=
t2=
t3=
t4=
t5=


fora1 in range(3):
    c=t1
    for a2 in range(2):
      c=t2
      for a3 in range(2):
            c=t2
            for a4 in range(3):
                c=t3
                for a5 in range(3):
                  c=t3
                  for a6 in range(2):
                        c=t2
                        for a7 in range(3):
                            c=t3
                            for a8 in range(2):
                              c=t4
                              for a9 in range(3):
                                    c=t3
                                    for a10 in range(3):
                                        c=t1
                                        for a11 in range(2):
                                          c=t4
                                          for a12 in range(3):
                                                c=t3
                                                for a13 in range(3):
                                                    c=t3
                                                    for a14 in range(2):
                                                      c=t2
                                                      for a15 in range(2):
                                                            c=t5
                                                            for a16 in range(3):
                                                                c=t1
                                                                for a17 in range(2):
                                                                  c=t2
                                                                  for a18 in range(2):
                                                                        c=t5
                                                                        for a19 in range(3):
                                                                            c=t1
                                                                            for a20 in range(3):
                                                                              c=t1
                                                                              dododo(c)

#NCTF{ADF0E239-D911-3781-7E40-A575A19E5835}
```



### easy_mobile

so被ollvm了,1400行。

!(https://i.loli.net/2021/11/29/82dlUtqwkIPuSfJ.png)

但是我用deflat去混淆,最后只剩三分一的代码?,没法只能用原来的看。

我尝试去动调,发现下不了断点?

想到只有1400行而已决定手撕。

然后我比较菜,花了一个多小时才整理了逻辑。

首先,我在719行找到了获取输入

!(https://i.loli.net/2021/11/29/zVwW3jqc2N149uR.png)

然后发现判断长度为24

!(https://i.loli.net/2021/11/29/2LCtozrBMSQN3b4.png)

交叉索引我们的输入

!(https://i.loli.net/2021/11/29/RMze2mvBskUt8VG.png)
发现,程序会将前16个字节分成4个字节一组分成四组的进行操作,最后8个字节分成两组。

我先看了程序对后面八个字节进行了什么操作

!(https://i.loli.net/2021/11/29/Q1wGKPuMH5pfRy2.png)

他作为输入传进了下面这个函数。并且上面还将一个长为16字节的字符串传了进去

跟进去看了一下

!(https://i.loli.net/2021/11/29/aKUGRvBjImeEsw6.png)



我顿时反应过来了,这和tea怎么那么像?

然后我手动恢复了一下

!(https://i.loli.net/2021/11/29/ERkbxQVGSie6Bp7.png)

这不就是改了delta但还是原汁原味的tea吗?而且他a2就是我们传入的第二个字符串,那就是KEY

我直接用我祖传脚本梭哈得到了我们后8个字符。

```c
#include <stdio.h>
#include <stdint.h>

//加密函数
void encrypt(uint32_t* v, uint32_t* k) {
    uint32_t v0 = v, v1 = v, sum = 0, i;         /* set up */
    uint32_t delta = 0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0 = k, k1 = k, k2 = k, k3 = k;   /* cache key */
    for (i = 0; i < 32; i++) {                     /* basic cycle start */
      sum += delta;
      v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
      v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    }                                              /* end cycle */
    v = v0; v = v1;
}
//解密函数
void decrypt(unsigned int* v, unsigned int* k) {
    unsigned int v0 = v, v1 = v, i;
    int sum = 0x12345678 << 5;/* set up */
    uint32_t delta = 0x12345678;// 0x9e3779b8                   /* a key schedule constant */
    uint32_t k0 = k, k1 = k, k2 = k, k3 = k;   /* cache key */
    for (i = 0; i < 32; i++) {                         /* basic cycle start */
      v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
      v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
      sum -= delta;
    }                                              /* end cycle */
    v = v0; v = v1;
}

int main()
{
    unsigned int v = { 0xC65AEDA,0xADBF8DB1 }, k = { 1634232689, 1852399976, 1851879017, 1835101793 };
   
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
    //printf("%x %xx\n", v, v);
    decrypt(v, k);
   printf("%x %x\n", v, v);
   
    puts((char*)v);
//58af2715c
    return 0;
}
```



然后去继续交叉索引到第一次被调用的地方

然后理解一下

!(https://i.loli.net/2021/11/29/CtrE4qpeDHB1KGl.png)

就是将我们的前4个字符,保存在一个int32x4的数据类型中,可以理解为他是一个大小为四的int32类型数组。

下面一样的操作,当取完前16个字节后,他会取一些常量,如下

!(https://i.loli.net/2021/11/29/zluhQLTgYRy74sv.png)

然后整理一下就是这也

```c
                  v48 = *v299;                  //
                  v447 = *v295;               //
                  v446 = v48;
                  v445 = vmulq_s32(v447, v48);
                  *v295 = v445;
                  v49 = *v300;                  //
                  v450 = *v295;
                  v449 = v49;
                  v448 = vsubq_s32(v450, vnegq_s32(v49));
                  *v295 = v448;               // enc0
                  v50 = *v299;                  //
                  v468 = *v296;               //
                  v467 = v50;
                  v466 = vmulq_s32(v468, v50);
                  *v296 = v466;
                  v51 = *v300;                  //
                  v465 = *v296;
                  v464 = v51;
                  v463 = vsubq_s32(v465, vnegq_s32(v51));
                  *v296 = v463;               // enc1
                  v52 = *v301;                  //
                  v462 = *v297;               //
                  v461 = v52;
                  v460 = vmulq_s32(v462, v52);
                  *v297 = v460;
                  v53 = *v302;                  //
                  v459 = *v297;
                  v458 = v53;
                  v457 = vsubq_s32(v459, vnegq_s32(v53));
                  *v297 = v457;               // enc2
                  v54 = *v299;                  //
                  v456 = *v298;               //
                  v455 = v54;
                  v454 = vmulq_s32(v456, v54);
                  *v298 = v454;
                  v55 = *v300;                  //
                  v453 = *v298;
                  v452 = v55;
                  v451 = vnegq_s32(vsubq_s32(vnegq_s32(v55), v453));// v451=-((-v300)-v453)v453=v451-v300
                  *v298 = v451;               // enc3
```



一开始尝试爆破,然后我发现我傻了,直接求逆不就行了?然后一把梭

```python
enc=[    0x00000CD7, 0x00000698, 0x00000D7C, 0x000006FA, 0x00000CB7, 0x000007CA, 0x0000079B, 0x000007D2,
    0x00000950, 0x00000AD9, 0x00000ADF, 0x000014A6, 0x00000CF7, 0x00000720, 0x00000732,0x000007F6]
v299=
v300=
v301=
v302=
def mul(a,b):
    c=[]
    for i in range(4):
      c.append(a * b)
    return c
#set0
c=[]
e=enc
for i in range(4):
    c.append(e-v300)

for i in range(4):
    print(chr(c//v299),end='')

#set1
c=[]
e=enc
for i in range(4):
    c.append(e-v300)

for i in range(4):
    print(chr(c//v299),end='')

#set2
c=[]
e=enc
for i in range(4):
    c.append(e-v302)

for i in range(4):
    print(chr(c//v301),end='')

#set3
c=[]
e=enc
for i in range(4):
    c.append(e-v300)

for i in range(4):
    print(chr(c//v299),end='')
#e0a0d966076ff437
```



e0a0d966076ff43758af2715

dreamingctf 发表于 2021-12-4 22:36

对“狗狗的秘密”这个题提出几个细节问题:
1)为了 patch 反调试,具体位置在哪里。我在TlsCallBack_0函数以及7811C0函数patch过程中,还是无法进入正确流程。
2)是在IDA里、还是在OD里运行完SMC,才能看关键指针函数783300的malloc(146 * strlen(a1) / 0x64 + 1); 等之后的代码。
现在挡住我的不是后面的算法。而是可以看到完整算法的流程和步骤我复现不出来。所以特来求助。希望您给回复。非常感谢。

qiaopf888 发表于 2021-12-10 09:17

看晕了,需要仔细研读才行。还有那一堆for循环,是不是可以换一种精简写法,如果 100000个for 层怎么办。。。

suxiaosu67 发表于 2021-12-1 11:10

向大佬学习

wendalao 发表于 2021-12-1 20:50

大佬nb{:1_921:}

ngq0530 发表于 2021-12-1 22:20

不明觉厉

xyz1234 发表于 2021-12-2 06:30

向大佬学习

sean123 发表于 2021-12-2 10:25

学习打卡

偶尔的曾经 发表于 2021-12-3 16:25

哎,这些对于我来说,搞不懂啊,只能呵呵了

cubolia 发表于 2021-12-4 20:45

大佬可以分享一下题目的原题文件吗?

sandon 发表于 2021-12-7 07:59

感谢分享!
页: [1] 2
查看完整版本: 2021NCTF RE