cgctf_2019 write up

发布于 2019-05-14  72 次阅读


Reverse

WxyVM2

WxyVM2

大概是要读这个程序里面的一段汇编代码,这段汇编代码非常长,连ida都无法反编译,但是非常有规律,可以通过写脚本来还原 这是初始化代码,估计是对加密的字符串进行还原的:

.text:00000000004005FE                 mov     cs:dword_694140, 9C06AA99h
.text:0000000000400608                 mov     cs:dword_694144, 0B4B2B03Fh
.text:0000000000400612                 mov     cs:dword_694148, 0C51F73CFh
.text:000000000040061C                 mov     cs:dword_69414C, 223520F8h
.text:0000000000400626                 mov     cs:dword_694150, 0C0C53B9h
.text:0000000000400630                 mov     cs:dword_694154, 0B59C78EAh
.text:000000000040063A                 mov     cs:dword_694158, 0F7DE2D34h
.text:0000000000400644                 mov     cs:dword_69415C, 0B27EEE2Ch

类似于下面这样的

.text:000000000048BAF1                 mov     edx, cs:dword_694174
.text:000000000048BAF7                 mov     eax, cs:dword_694178
.text:000000000048BAFD                 xor     eax, edx
.text:000000000048BAFF                 mov     cs:dword_694178, eax
.text:000000000048B93B                 movzx   eax, cs:byte_694116
.text:000000000048B93C                 add     eax, 7Dh
.text:000000000048B93F                 mov     cs:byte_694116, al
.text:000000000048B95C                 movzx   eax, cs:byte_694100
.text:000000000048B95C                 xor     eax, 6Ah
.text:000000000048B95F                 mov     cs:byte_694100, al
.text:000000000041DD09                 movzx   eax, cs:byte_69410F
.text:000000000041DD09                 sub     eax, 6Eh
.text:000000000041DD0C                 mov     cs:byte_69410F, al

还有一种 我 mov 我 自 己

.text:000000000040093E                 movzx   eax, cs:byte_694114
.text:0000000000400945                 mov     cs:byte_694114, al

ida可以修改配置文件,这样就可以反编译了 byte_694100是字符串读入的起始地址 读取18h字节

.text:00000000004005CD                 call    _puts
.text:00000000004005D2                 mov     esi, offset byte_694100
.text:00000000004005D7                 mov     edi, offset format ; "%s"
.text:00000000004005DC                 mov     eax, 0
.text:00000000004005E1                 call    _ssanf

694100~694117 查看汇编代码后发现很多操作都超出了这个内存的范围,而且最后也没有对这个内存进行任何修改,那么就应该直接舍去 先将所有汇编代码dump下来,用脚本筛掉垃圾代码

def get_string(line):
    l = []
    s = ""
    for i in range(len(line)):
        if(line[i] == ' ' or line[i] == ',' or i == len(line) - 1):#如果读取到了末尾
            if(len(s) != 0):
                l.append(s)
                s = ""
        else:
            s = s + line[i]

    return l

i = 0
f = open("code.dump", "r")
fout = open("decode.txt", "w")
#fout.write("Below is decoded code.\n(format:) addr [opt] num\n")
while(1):
    line = f.readline()
    i += 1
    if(line == ""):
        print("正常退出")
        fout.close()
        break
    tab = get_string(line)#获取到一个汇编指令
    if(tab[1] == "mov"):#如果是垃圾指令,就直接跳过
        line = f.readline()
        i += 1
        line = f.readline()
        i += 1
        line = f.readline()
        i += 1

    elif(tab[1] == "movzx"):
        addr = tab[3]
        addr = "0x" + addr[8::]#获取地址信息
        addr_num = int(addr, 16)
        addr_num -= 6897920 #减去起始地址 得到偏移量
        addr = str(addr_num)

        line = f.readline()
        i += 1

        tab = get_string(line)

        if(tab[1] != "mov"):#只有当这个指令不是mov的时候才进行判断,因为有一种垃圾指令是自己mov自己
            if(tab[1] == "add"):
                opt = "+="
            elif(tab[1] == "xor"):
                opt = "^="
            elif(tab[1] == "sub"):
                opt = "-="
            else:
                print("something goes wrong! " + tab[1] + " at :" + str(i))
                break

            s = tab[3]
    #        s = s[:-1:]
    #        s = "0x" + s

            if(s[-1] == "h"):
                s = s[:-1]
            s = "0x" + s

            s = str(int(s, 16) % 128)

            line = f.readline()
            i += 1
            fout.write(addr + " " + opt + " " + s + "\n")

    else:
        print("something goes wrong! " + tab[1] + " at :" + str(i))
        break

f.close()
fout.close()

得到类似于如下的数据

23 += 11
3 += 31
5 += 78
23 += 109
2 -= 25
1 -= 48
11 += 123
13 ^= 38
23 ^= 89
12 += 3
11 -= 5
13 += 75
4 ^= 72
5 -= 88
4 -= 107
0 ^= 60
23 ^= 27

再写python进行逆向计算:

def get_string(line):
    l = []
    s = ""
    for i in range(len(line)):
        if(line[i] == ' ' ):#如果读取到了末尾
            if(len(s) != 0):
                l.append(s)
                s = ""
        else:
            s = s + line[i]
    if(len(s) != 0):
        l.append(s)

    return l

enc =[0xC0,0x85,0xF9,0x6C,0xE2,0x14,0xBB,0xe4,0xd,0x59,0x1c,0x23,0x88,0x6e,0x9b,0xca,0xba,0x5c,0x37,0xfff,0x48,0xd8,0x1f,0xab,0xa5]

f = open("decode.txt", "r")
all = []

i = -1
while(1):
    line = f.readline()
    if(line == ""):
        break
    i += 1
    all.append(line)

while(i >= 0):
    line = all[i]
    tab = get_string(line)
    i -= 1

    if(tab[1] == "+="):
        enc[int(tab[0])] -= int(tab[2])
    elif(tab[1] == "-="):
        enc[int(tab[0])] += int(tab[2])
    else:
        enc[int(tab[0])] ^= int(tab[2])
    enc[int(tab[0])] %= 128
ans = ""
for c in enc:
    ans = ans + chr(c)
print(ans)

HumuraVM

太丢脸了,这么简单一个vm还是做了三个多小时

homuraVM

概况

这道题是WxyVM的一个加强版,即使把vm的指令翻译出来了,也会发现得到的是一串鸟语,俗称类brainf**k语言

程序的大概思路是这样的,读入一串数据,然后把这段数据载入内存,随后执行保存在程序里面的一段虚拟机代码,虚拟机的各种函数已经给出,最终生成的加密结果也能找到,逆向程序。

对vm的代码进行读取

自己写的IDA中的注释:

while ( 1 )
  {
    result = *(unsigned __int8 *)(COUNTER + a1);
    if ( !(_BYTE)result )                       // 没有指令了 就退出
      return result;
    v2 = *(char *)(COUNTER + a1);
    switch ( (unsigned int)off_1048 )
    {
      case 0x43u:                               // *j -= 2 * (*i & *a)    C
        *(_DWORD *)global_j -= 2 * (*(_DWORD *)global_i & *(_DWORD *)array_input);
        ++COUNTER;
        break;
      case 0x47u:                               // (*j)--              G
        --*(_DWORD *)global_j;
        ++COUNTER;
        break;
      case 0x4Du:                               // *j = *i + a[0]                  M
        *(_DWORD *)global_j = *(_DWORD *)global_i + *(_DWORD *)array_input;
        ++COUNTER;
        break;
      case 0x54u:                               // (*j)++            T
        ++*(_DWORD *)global_j;
        ++COUNTER;
        break;
      case 0x5Bu:                               // 如果a[0]!=0 就循环,否则直接跳出    [
        if ( *(_DWORD *)array_input )
        {
          ++COUNTER;
        }
        else
        {
          do
            v3 = COUNTER++;
          while ( *(_BYTE *)(v3 + a1) != 93 );
        }
        break;
      case 0x5Du:                               // 如果a[0]!=0 就返回[继续循环           ]
        if ( *(_DWORD *)array_input )
        {
          do
            --COUNTER;
          while ( *(_BYTE *)(COUNTER + a1) != 91 );
          ++COUNTER;
        }
        else
        {
          ++COUNTER;
        }
        break;
      case 0x61u:                               // (*i)--          a
        --*(_DWORD *)global_i;
        ++COUNTER;
        break;
      case 0x68u:                               // a[0] += 4       h
        array_input = (char *)array_input + 4;
        ++COUNTER;
        break;
      case 0x6Du:                               // a[0]自加         m
        ++*(_DWORD *)array_input;
        ++COUNTER;
        break;
      case 0x6Fu:                               // a[0] -= 4          o
        array_input = (char *)array_input - 4;
        ++COUNTER;
        break;
      case 0x72u:                               // (*i)++        r
        ++*(_DWORD *)global_i;
        ++COUNTER;
        break;
      case 0x75u:                               // a[0]--         u
        --*(_DWORD *)array_input;
        ++COUNTER;
        break;
      case 0x76u:                               // j = i             v
        *(_DWORD *)global_j = *(_DWORD *)global_i;
        ++COUNTER;
        break;
      case 0x7Bu:
        if ( *(_DWORD *)global_j )              // *j !=0 就进入循环,否则直接跳出
        {
          ++COUNTER;
        }
        else
        {
          do
            v4 = COUNTER++;
          while ( *(_BYTE *)(v4 + a1) != 125 );
        }
        break;
      case 0x7Du:
        if ( *(_DWORD *)global_j )              // 如果*j不为0就返回{处
        {
          do
            --COUNTER;
          while ( *(_BYTE *)(COUNTER + a1) != 123 );
          ++COUNTER;
        }
        else
        {
          ++COUNTER;
        }
        break;
      default:
        continue;
    }
  }

清除无用虚拟机指令

首先我尝试着把无效命令先清除掉:

str = """h[ur]ovMCh{mG}hv{aG}[ur]ovaaaMCh{mG}hv{aG}[ur]
ovrrMCh{mG}hv{aG}[ur]ovrararaMCh{mG}hv{aG}[ur]ovrarar
rrMCh{mG}hv{aG}[ur]ovararaaMCh{mG}hv{aG}[ur]ovrararra
raMCh{mG}hv{aG}[ur]ovrrrarrrMCh{mG}hv{aG}[ur]ovaarrar
rMCh{mG}hv{aG}[ur]ovaaarrarMCh{mG}hv{aG}[ur]ovrrrarrM
Ch{mG}hv{aG}[ur]ovaarrraaMCh{mG}hv{aG}[ur]ovarraarMCh
{mG}hv{aG}[ur]ovrrraaarrMCh{mG}hv{aG}[ur]ovaaarrrrarr
MCh{mG}hv{aG}[ur]ovrrrraarrarrMCh{mG}hv{aG}[ur]ovrrar
raMCh{mG}hv{aG}[ur]ovaaraarMCh{mG}hv{aG}[ur]ovrrarraM
Ch{mG}hv{aG}[ur]ovaarrrarMCh{mG}hv{aG}[ur]ovrraarraMC
h{mG}hv{aG}[ur]ovrrarMCh{mG}hv{aG}[ur]ovaarrarMCh{mG}
hv{aG}[ur]ovrrraarMCh{mG}hv{aG}[ur]ovrrrraaMCh{mG}hv{
aG}[ur]ovrrarraMCh{mG}hv{aG}[ur]ovrrrrrrMCh{mG}hv{aG}
[ur]ovaaaarMCh{mG}hv{aG}[ur]ovrraaaMCh{mG}hv{aG}[ur]o
vaarraMCh{mG}hv{aG}[ur]ovrrarMCh{mG}hv{aG}[ur]ovaarra
aMCh{mG}hv{aG}[ur]ovaarraraMCh{mG}hv{aG}[ur]ovaarrara
rMCh{mG}"""

str = str.replace("\n", "")
old = ""
while(old != str):
    old = str
    str = str.replace("ra", "")
    str = str.replace("ar", "")
    str = str.replace("oh", "")
    str = str.replace("ho", "")
    str = str.replace("um", "")
    str = str.replace("mu", "")

print("\n\n\n")
h_cnt = 0
o_cnt = 0
for c in str:
    if(c == 'h'):
        h_cnt += 1
        if(h_cnt == 3):
            h_cnt = 1
            o_cnt = 0
            print(" ")
    elif(c == 'o'):
        o_cnt += 1
    print(c, end="")

可得以下字符串(VScode显示有点问题,打印到文件中就行)

h[ur]ovMCh{mG}
hv{aG}[ur]ovaaaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrrrrrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovrrrrMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrrrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrrrrrMCh{mG}
hv{aG}[ur]ovaaaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovMCh{mG}

得到正向算法(递推公式)

//MCh{mG}hv{aG}[ur]ov
*j = *i + *a - 2 * (*i & *a)

//{mG}:   *a += j  j清空
while(*j):
    *a++
    *j--

//{aG}:    i -= j  j清空 
while(*j):
    *i--
    *j--

//[ur]:    i = *a   a清空
while(*a)  
    *a--
    *i++

//r:
    *i++
//a:
    *i--
//v:
    j = i

经过研究

“v{ag}” 没有起到任何作用,

“v” 将i的值取到了j中

然后i和j一同被清空了

“ov”中的“v”也没有起到作用,因为在后面j的值会被刷新

再次精简:(去掉v{aG} )

h[ur]oMCh{mG}
h[ur]oaaaMCh{mG}
h[ur]orrMCh{mG}
h[ur]oMCh{mG}
h[ur]orrrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]orMCh{mG}
h[ur]orrrrrMCh{mG}
h[ur]orMCh{mG}
h[ur]oaMCh{mG}
h[ur]orrrrMCh{mG}
h[ur]oaMCh{mG}
h[ur]oMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrrrrMCh{mG}
h[ur]orrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]orrMCh{mG}
h[ur]orMCh{mG}
h[ur]orMCh{mG}
h[ur]orrMCh{mG}
h[ur]oMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrrrrrMCh{mG}
h[ur]oaaaMCh{mG}
h[ur]oaMCh{mG}
h[ur]oaMCh{mG}
h[ur]orrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]oaMCh{mG}
h[ur]oMCh{mG}

h 指针加一

[ur] 将*(a+1)全部转移到 i 中

o 指针减一

ar index改变 i = *(a+1) + index

M j = (a+1) + index + a

C j -= (((a+1) + index) & a) * 2

MC: j = (a+1) + index + a - (( (a+1) + index) & a) * 2

h 指针加一

{mG} 把j的内容放到*(a+1)

递推公式:new((a+1)) = (a+1) + index + a - (( ( a+1) + index) & a) 2

(index为 一大堆rraa 最后得到的值)

解密

知道了(a+1) 后面的新值,知道了 (a+1) ,要求原来的 *(a+1)的值,只能通过枚举

enc = [27, 114, 17, 118, 8, 74, 126, 5, 55, 124, 31, 88, 104, 7,
112, 7, 49, 108, 4, 47, 4, 105, 54, 77, 127, 8, 80, 12, 109, 28, 127, 80, 29, 96]

index = []
f = open("index.txt", "r")
for i in range(34):
    cnt = 0
    s = f.readline()
    for c in s:
        if(c == 'r'):
            cnt += 1
        if(c == 'a'):
            cnt -= 1
    index.append(cnt)

ans = ""
for i in range(33, 0, -1):
    for c in range(128):
        if(enc[i] == c + index[i] + enc[i-1] - 2 * ((c + index[i]) & enc[i-1])):
            ans = chr(c) + ans
            break

print(ans)

输出:lag{D3v1L_H0mur4_f**k_y0uR_bra1N}

由于如下代码(IDA dump出来的)

__isoc99_scanf("%s", s);
  for ( i = 0; ; ++i )
  {
    v3 = i;
    if ( v3 >= strlen(s) )
      break;
    *((_DWORD *)array_input + i + 1LL) = s[i];
  }
  *(_DWORD *)array_input = s[strlen(s) - 1];    // 读取方式有点奇怪,最后一个字符放在开头的

输入的字符串并没有按照顺序存储,而是将输入的最后一个字符放到了内存开头,说明之后的操作全部都是从第二位开始的

通过最后一位和第一位的值同样可以逆推出原值,直接根据格式猜也行,flag嘛缺个"f"hhhh

flag{D3v1L_H0mur4_f**k_y0uR_bra1N}

本题用到了程序语言的化简,还有寻找规律的相关方法,遇到这样的题最好是先自己模拟一个循环节,找到规律再尝试写脚本解。因为并不是所有的虚拟机都是每步操作都可逆的23333

simple_machine

simple-machine

general

这道题说起来是vm,实际上和vm的挂钩并不大,就是用高级语言去实现了汇编,把变量都当做了寄存器来使用,其中还有各种压栈出栈过程,不过跟本题关系不大,也就不用过于细究了

fun1

fun1主要是通过输入的字符串,与预置的feed进行异或,得到一串加密的字符串,存在内存中

int __cdecl fun1(int str, unsigned int a2)
{
  unsigned int v2; // eax
  int v3; // eax

  push(base_addr_804B15C);
  base_addr_804B15C = esp1;
  push(temp);
  push(temp2);
  esp1 -= 48;
  *(_DWORD *)(base_addr_804B15C - 29) = 'deef'; // 0x804B13F
  *(_DWORD *)(base_addr_804B15C - 25) = 'daed';
  *(_DWORD *)(base_addr_804B15C - 21) = 'feeb';
  *(_DWORD *)(base_addr_804B15C - 17) = 'efac';
  *(_BYTE *)(base_addr_804B15C - 13) = 0;
  for ( *(_DWORD *)(base_addr_804B15C - 12) = 0; ; ++*(_DWORD *)(base_addr_804B15C - 12) )// i = base_addr_804B15C - 12
  {
    eaxx = *(_DWORD *)(base_addr_804B15C - 12); // i
    if ( eaxx >= a2 )
      break;                                    // 判断是否退出循环
                                                // 
                                                // 
                                                // 
    ebxx = *(_DWORD *)(base_addr_804B15C - 12); // i
    eaxx = str;                                 // str
    temp = ebxx + str;                          // str + i
    ebxx = *(_DWORD *)(base_addr_804B15C - 12); // i
    eaxx = ebxx + str;                          // str + i
    eaxx = *(unsigned __int8 *)(ebxx + str);    // 取当前字符串
    temp1 = eaxx;
    *(_BYTE *)(base_addr_804B15C - 41) = eaxx;  // 把当前字符串保存到一个地址里面
                                                // 
                                                // 
                                                // 
    temp2 = *(_DWORD *)(base_addr_804B15C - 12);
    esp1 -= 12;
    eaxx = base_addr_804B15C - 29;              // 取feed
    push(base_addr_804B15C - 29);               // push(feed)
    v2 = strlen(*(const char **)esp1);          // v2 是 feed的长度
    esp1 += 16;
    len = v2;
    eaxx = temp2;
    ebxx = 0;
    sub_80485AB(v2);                            // j = i % v2  
                                                // i = i / v2
    eaxx = ebxx;
    eaxx = *(unsigned __int8 *)(ebxx - 29 + base_addr_804B15C);// 算feed中的偏移
    temp1 = eaxx;
    temp1 = *(_BYTE *)(base_addr_804B15C - 41) ^ eaxx;// c ^ feed[]
    *(_BYTE *)temp = temp1;
    v3 = eaxx;
    LOBYTE(v3) = 0;
    eaxx = v3 + (unsigned __int8)temp1;
  }
  sub_80485A5();
  esp1 = base_addr_804B15C - 8;
  pop(&temp2);
  pop(&temp);
  return pop(&base_addr_804B15C);
}

fun2

fun2中有两个循环,看似很复杂实则简单

看到两个寄存器在之间移来移去,发现很多操作都是无效的

void __cdecl fun2(int a1, int a2, int a3)
{
  push(base_addr_804B15C);
  base_addr_804B15C = esp1;
  esp1 -= 16;
  for ( *(_DWORD *)(base_addr_804B15C - 4) = 0; // 循环三次
        *(_DWORD *)(base_addr_804B15C - 4) <= 2u;
        ++*(_DWORD *)(base_addr_804B15C - 4) )
  {
    for ( *(_DWORD *)(base_addr_804B15C - 8) = 0; ; ++*(_DWORD *)(base_addr_804B15C - 8) )
    {
      len = a3;
      ebxx = 1431655766;
      eaxx = a3;
      sub_80485DB(1431655766);                  // ebxx = (eaxx * 1431655766) >> 32
      ebxx -= (unsigned int)len >> 31;
      eaxx = ebxx;
      if ( *(_DWORD *)(base_addr_804B15C - 8) >= (unsigned int)ebxx )
        break;                                  // 前面一段是关于判断跳出条件的
      len = a3;
      ebxx = 0x55555556;
      eaxx = a3;
      sub_80485DB(0x55555556);                  // ebxx = (unsigned __int64)(a1 * (signed __int64)eaxx) >> 32;
                                                //   
      ebxx -= (unsigned int)len >> 31;
      eaxx = ebxx;                              // repeated
      eaxx = ebxx * *(_DWORD *)(base_addr_804B15C - 4);
      ebxx = eaxx;
      eaxx = *(_DWORD *)(base_addr_804B15C - 8);
      eaxx += ebxx;
      ebxx = eaxx;
      eaxx = a2;
      len = ebxx + a2;                          // 算偏移地址 addr = 0x804B100 + (i * 18) + j
      ebxx = *(_DWORD *)(base_addr_804B15C - 8);
      eaxx = 2 * ebxx;                          // j * 2
      ebxx *= 3;                                // j * 3
      eaxx = *(_DWORD *)(base_addr_804B15C - 4);// i
      eaxx += ebxx;                             // i +  j * 3
      ebxx = eaxx;                              // i +  j * 3
      eaxx += a1;
      eaxx = *(unsigned __int8 *)(a1 + ebxx);   // *(input + i + j * 3)
      temp1 = eaxx;
      *(_BYTE *)len = eaxx;                     // 修改上方的那个偏移地址
    }
  }
  sub_80485A5();
}

翻译过来是这样:

for(int i = 0; i <= 2; i++)
    for(int j = 0;;j++){
        addr[(i * 18) + j] = input + i + j * 3
    }

验证

比较fun1和fun2中生成的字符串是否相等

经高人指点,不是比较fun1和fun2的字符串,而是先经过fun1进行处理,然后再经过fun2处理,然后直接和内存中的一串字符串进行比较

solve

feed = "feeddeadbeefcafe"
enc = [0x00,0x03,0x09,0x3A,0x05,0x0E,0x02,0x16,0x0F,0x1F,0x12,0x56,0x3B,0x0B,0x51,0x50,0x39,0x00,0x09,0x1F,0x50,0x04,0x14,0x57,0x3B,0x12,0x07,0x3C,0x1C,0x3A,0x15,0x05,0x0B,0x08,0x06,0x01,0x04,0x12,0x16,0x39,0x05,0x0B,0x50,0x57,0x09,0x12,0x0A,0x27,0x13,0x17,0x0E,0x02,0x55,0x18]
dec = [0x00,0x03,0x09,0x3A,0x05,0x0E,0x02,0x16,0x0F,0x1F,0x12,0x56,0x3B,0x0B,0x51,0x50,0x39,0x00,0x09,0x1F,0x50,0x04,0x14,0x57,0x3B,0x12,0x07,0x3C,0x1C,0x3A,0x15,0x05,0x0B,0x08,0x06,0x01,0x04,0x12,0x16,0x39,0x05,0x0B,0x50,0x57,0x09,0x12,0x0A,0x27,0x13,0x17,0x0E,0x02,0x55,0x18]

for i in range(3):
    for j in range(18):
        dec[i + j * 3] = enc[i * 18 + j]

ans = ""
for i in range(54):
    dec[i] ^= ord(feed[i % 16])
    ans = ans + chr(dec[i])

print(ans)

single

single

思路

经观察 发现a1是一个矩阵

sub_400833

unsigned __int64 __fastcall sub_400833(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-28h]
  signed int j; // [rsp+1Ch] [rbp-24h]
  signed int k; // [rsp+1Ch] [rbp-24h]
  char s[24]; // [rsp+20h] [rbp-20h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  for ( i = 0; i <= 8; ++i )
  {
    memset(s, 0, 0xAuLL);
    for ( j = 0; j <= 8; ++j )
      ++s[*(unsigned __int8 *)(9 * i + j + a1)];
    for ( k = 1; k <= 9; ++k )
    {
      if ( s[k] != 1 )
        sub_4006F6();
    }
  }
  return __readfsqword(0x28u) ^ v6;
}

将a1矩阵的每一行所指向的地址加1 最后s[1] - s[9] 全部等于1

sub_4008FE

unsigned __int64 __fastcall sub_4008FE(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-28h]
  signed int j; // [rsp+1Ch] [rbp-24h]
  signed int k; // [rsp+1Ch] [rbp-24h]
  char s[24]; // [rsp+20h] [rbp-20h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  for ( i = 0; i <= 8; ++i )
  {
    memset(s, 0, 0xAuLL);
    for ( j = 0; j <= 8; ++j )
      ++s[*(unsigned __int8 *)(9 * j + i + a1)];
    for ( k = 1; k <= 9; ++k )
    {
      if ( s[k] != 1 )
        sub_4006F6();
    }
  }
  return __readfsqword(0x28u) ^ v6;
}

​ 将a1矩阵的每一列所指向的地址加1 最后s[1] - s[9] 全部等于1

其实做完这一步的时候我就反应出来这道题可能是一个数独了,并且验证最后一个函数的功能,发现确实是一个数独

sub_4009C9

unsigned __int64 __fastcall sub_4009C9(__int64 a1)
{
  signed int i; // [rsp+1Ch] [rbp-34h]
  int j; // [rsp+20h] [rbp-30h]
  signed int l; // [rsp+20h] [rbp-30h]
  int k; // [rsp+24h] [rbp-2Ch]
  signed int v6; // [rsp+28h] [rbp-28h]
  signed int v7; // [rsp+2Ch] [rbp-24h]
  char s[24]; // [rsp+30h] [rbp-20h]
  unsigned __int64 v9; // [rsp+48h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v6 = 3;
  v7 = 3;
  for ( i = 0; i <= 8; ++i )
  {
    memset(s, 0, 0xAuLL);
    for ( j = v6 - 3; j < v6; ++j )
    {
      for ( k = v7 - 3; k < v7; ++k )
        ++s[*(unsigned __int8 *)(9 * j + k + a1)];
    }
    for ( l = 1; l <= 9; ++l )
    {
      if ( s[l] != 1 )
        sub_4006F6();
    }
    if ( v7 == 9 )
    {
      v7 = 3;
      v6 += 3;
    }
    else
    {
      v7 += 3;
    }
  }
  return __readfsqword(0x28u) ^ v9;
}

​ 将a1矩阵分为九个正方形的小块 最后s[1] - s[9] 全部等于1

计算

程序先读入一个81位长的字符串

并且通过c -= '0' 来转化为数字

如果数独的某个位置上面已经有数的话,就必须输入0

dump

addr = 0x602080
for i in range(81):
    print Byte(addr+i),",",
    if((i+1) % 9 == 0):
        print ""

map

0 , 3 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 
6 , 0 , 0 , 0 , 3 , 2 , 4 , 9 , 0 , 
0 , 9 , 0 , 1 , 0 , 7 , 0 , 6 , 0 , 
7 , 4 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 
0 , 1 , 8 , 0 , 0 , 0 , 6 , 3 , 0 , 
0 , 0 , 0 , 0 , 0 , 0 , 1 , 4 , 7 , 
0 , 8 , 0 , 9 , 0 , 4 , 0 , 7 , 0 , 
0 , 7 , 4 , 2 , 1 , 0 , 0 , 0 , 6 , 
0 , 0 , 0 , 0 , 0 , 3 , 0 , 1 , 0 , 

solve

http://www.llang.net/sudoku/calsudoku.html 求解即可

401095728057800001802040305000321589500479002923586000105060203300008950269750804

加个flag

flag{401095728057800001802040305000321589500479002923586000105060203300008950269750804}


CTFer|NOIPer|CSGO|摸鱼|菜鸡