Securinets Sunshine encrypt 三场CTF逆向writeup
apeng CTF 7222浏览 · 2019-04-07 23:27

近期三场ctf,题目都比较简单。

Securinets Prequals 2K19

AutomateMe

获取输入后比对长度3296,然后是3296个if一个一个的比对,一共有下面两种比对方式:

.text:0000000000000774 ; ----------------------------------------------------------
.text:0000000000000774
.text:0000000000000774 loc_774:                                ; CODE XREF: main+82↑j
.text:0000000000000774                 mov     rax, [rbp+var_20]
.text:0000000000000778                 add     rax, 8
.text:000000000000077C                 mov     rax, [rax]
.text:000000000000077F                 add     rax, 1
.text:0000000000000783                 movzx   eax, byte ptr [rax]
.text:0000000000000786                 cmp     al, 68h ; 'h'
.text:0000000000000788                 jz      short loc_7A0
.text:000000000000078A                 lea     rdi, aNope      ; "nope :( "
.text:0000000000000791                 mov     eax, 0
.text:0000000000000796                 call    _printf
.text:000000000000079B                 jmp     locret_283A0
.text:00000000000007A0 ; ----------------------------------------------------------
.text:00000000000007A0
.text:00000000000007A0 loc_7A0:                                ; CODE XREF: main+AE↑j
.text:00000000000007A0                 mov     rax, [rbp+var_20]
.text:00000000000007A4                 add     rax, 8
.text:00000000000007A8                 mov     rax, [rax]
.text:00000000000007AB                 movzx   eax, byte ptr [rax+2]
.text:00000000000007AF                 mov     [rbp+var_1], al
.text:00000000000007B2                 xor     [rbp+var_1], 0EBh
.text:00000000000007B6                 cmp     [rbp+var_1], 8Eh
.text:00000000000007BA                 jz      short loc_7D2
.text:00000000000007BC                 lea     rdi, aNope      ; "nope :( "
.text:00000000000007C3                 mov     eax, 0
.text:00000000000007C8                 call    _printf
.text:00000000000007CD                 jmp     locret_283A0
.text:00000000000007D2 ; ----------------------------------------------------------

由于代码都是相似的,比对的过程也是从头到尾,所以直接找到特定的代码提取出数据就行了

f = open('bin','rb')
f.read(0x74C)
g = open('flag.txt','ab')

with open('bin','rb') as f:
    f.read(0x74C)
    while(True):
        temp = f.read(1)
        if temp =='':
            break
        temp = ord(temp)
        if temp == 0x3C:
            t = f.read(1)
            k = f.read(1)
            if k=='\x74':
                g.write(t)
        elif temp ==0x80:
            if ord(f.read(1))==0x75:
                f.read(1)
                t = ord(f.read(1))
                f.read(1)
                f.read(1)
                f.read(1)
                k = ord(f.read(1))
                g.write(chr(k^t))

flag就在输出的中间一部分。

warmup

先将输入base64编码,然后在25个check里比对某一位的字符或某两位的相对位置。细心一点把25个check求一遍就行了。

table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
k = [0 for i in range(36)]
k[0] = table[28]
k[1] = table[54]
k[2] = k[10] = table[((28+54)>>2) + 1]
k[3] = 'j'
k[4] = chr(ord(k[0])+1)
k[12] = k[22] = k[24] = chr(ord(k[4])-1)
k[23] = k[11] = chr(48)
k[35] = chr(ord(k[11])+9)
k[6] = chr(ord(k[3]) - 32)
k[8] = chr(ord(k[0]) - 1)
k[27] = k[31] = chr(ord(k[4]) + 2)
k[25] = k[9] = chr(ord(k[27]) + 7)
k[13] = k[17] = k[21] = chr(ord(k[1]) + 1)
k[7] = 'p'
k[15] = chr(ord(k[7]) + 3)
k[14] = chr(ord(k[15]) + 1)
k[19] = 'z'
k[34] = chr(ord(k[0]) - 33)
k[5] = k[20] = k[29] = k[33] = 'X'
k[26] = chr(49)
k[16] = k[28] = chr(ord(k[9]) - 32)
k[1] = chr(50)
k[18] = k[30] = chr(ord(k[7]) - 30)
k[32] = k[4]
t = ''
for i in range(36):
    t+=k[i]

print(t.decode('base64'))

25个check比较繁琐,需要细心一点。话说应该可以用angr,但是翻了好多文档试了好多脚本都求不出,有时间再仔细学学angr

Matrix of Hell

加密逻辑:

gets(s);
if ( strlen(s) != 14 || (sub_5593FC1E383A(), !v3) )
{
  printf("ACCESS DENIED");
  exit(0);
}
v16 = 0;
for ( k = 0; k < strlen(s); ++k )
{
  for ( l = 0; l <= 4; ++l )
  {
    for ( m = 0; m <= 4; ++m )
    {
      if ( matrix[m + 6LL * l] == s[k] )
      {
        s1[v16] = l + 65;
        v4 = v16 + 1;
        s1[v4] = m + 49;
        v16 = v4 + 1;
      }
    }
  }
}
for ( n = 0; n < strlen(s1); ++n )
  s2[n] = n % 4 ^ s1[n];
if ( strcmp(s3, s2) )
{
  printf("ACCESS DENIED", s2);
  exit(0);
}

先生成一个5*6的固定的矩阵,然后获取输入,输入长度需为14

从输入的第一位开始,在矩阵中找到对应的元素,行数 l+65 放到 字符串s1的2*i位置,列数 m+49放到字符串s2的2*i+1位置,最后将字符串的每一位异或位数(n%4)得到s2,再和常量字符串s3对比。

解密脚本:

t1 = [0x41, 0x42, 0x43, 0x44, 0x45, 0x0, 0x46, 0x47, 0x48, 0x49, 0x4B, 0x0, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x0, 0x51, 0x52, 0x53, 0x54, 0x55, 0x0, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x0]
s = 'B0C2A2C6A3A7C5@6B5F0A4G2B5A2'
s1 = []
for i in range(len(s)):
    s1.append(ord(s[i])^(i%4))
pswd = ''
for i in range(14):
    l = s1[2*i] - 65
    m = s1[2*i+1] - 49
    pswd += chr(t1[m + 6*l])
print(pswd)

Vectors

输入一个64位的十六进制数

接下来对数据的操作用到了vector,静态分析比较蛋疼,直接动调。先将输入的数据分成8个字节放入一个vector,在sub_F37可以看到它将另7个不同的8位16进制数放入另一个vector。直接调试到sub_F37可以看到它将两个vector排好序了

[heap]:0000564A82C794B0 dword_564A82C794B0 dd 0Ch
[heap]:0000564A82C794B0 dd 0Dh
[heap]:0000564A82C794B0 dd 0ADh
[heap]:0000564A82C794B0 dd 0BEh
[heap]:0000564A82C794B0 dd 0DEh
[heap]:0000564A82C794B0 dd 0EDh
[heap]:0000564A82C794B0 dd 0EFh

[heap]:0000564A82C794E0 dword_564A82C794E0 dd 11h
[heap]:0000564A82C794E0 dd 22h
[heap]:0000564A82C794E0 dd 33h
[heap]:0000564A82C794E0 dd 44h
[heap]:0000564A82C794E0 dd 55h
[heap]:0000564A82C794E0 dd 66h
[heap]:0000564A82C794E0 dd 77h
[heap]:0000564A82C794E0 dd 88h

之后就直接比对结果了。直接输入0C0DADBEDEEDEF并不能的到flag,虽然显示通过了check但是输出的flag并不对:

Welcome resreveR!...

PASSCODE:0C0DADBEDEEDEF
GOOD JOB U GOT THIS, HERE IS UR FLAG:s62ine'

因为一共有7!种排列方式。用itertools爆破求出flag即可

from pwn import *
from itertools import *
context.log_level = 'error'
a = [0xad,0xef,0xbe,0xde,0xc,0xed,0xd]
b = permutations(a,7)
for i in b:
    p = process('./bin')
    payload = ''
    for j in range(7):
        k = hex(i[j])[2:]
        if(len(k)==1):
            k = '0'+k
        payload+=k
    p.recv()
    p.sendline(payload)
    p.recvuntil('FLAG:')
    a = p.recv()
    if a.startswith('securinets'):
        print(a)
        exit()
    p.close()

RBOOM!

上来有个ptrace的反调试,直接jmp掉

读取输入后,写入文件"lla"。主要的加密逻辑在sub_93A里。从文件"lla"和文件“la”读取数据后,在sub_CCF加密。

v11 = 0;
  v13 = 0;
  for ( i = 0; i <= 255; ++i )
  {
    v18[i] = i;
    v17[i] = la[i % a4];
  }
  for ( j = 0; j <= 255; ++j )
  {
    v5 = (unsigned int)(((unsigned __int8)v18[j] + v11 + (unsigned __int8)v17[j]) >> 31) >> 24;
    v11 = (unsigned __int8)(v5 + v18[j] + v11 + v17[j]) - v5;
    v6 = v18[v11];
    v18[v11] = v18[j];
    v18[j] = v6;
  }
  v12 = 0;
  for ( k = 0; k < a2; ++k )
  {
    v13 = (unsigned __int8)((char *)&off_2F98
                          + 0xFFFFD069
                          + v13
                          + ((unsigned int)((signed int)((signed int)&off_2F98 + 0xFFFFD069 + v13) >> 31) >> 24))
        - ((unsigned int)((signed int)((signed int)&off_2F98 + 0xFFFFD069 + v13) >> 31) >> 24);
    v7 = (unsigned int)((v12 + (unsigned __int8)v18[v13]) >> 31) >> 24;
    v12 = (unsigned __int8)(v7 + v12 + v18[v13]) - v7;
    v8 = v18[v12];
    v18[v12] = v18[v13];
    v18[v13] = v8;
    a5[k] = v18[(unsigned __int8)(v18[v13] + v18[v12])] ^ input[k];
  }

看到有两个长度为256的循环,猜测一下是RC4。不太想分析太多,直接在input异或的地方00000F5F下断点,编辑断点添加condition:print(GetRegValue('ecx')),这样直接能在ida的输出窗口将它异或的东西输出出来,再之后就是和常量比较了。

当然也可以直接用RC4解密,看个人喜好

a = [219, 87, 247, 80, 74, 188, 141, 29, 127, 165, 123, 43, 219, 11, 64, 236, 244, 233, 240, 132, 136, 239, 180, 2, 232, 137, 128, 129, 139, 1, 251, 46, 19, 18, 176, 44, 71, 111, 163, 36, 109, 38, 229, 248, 92, 183, 230, 30, 75, 97, 236, 159, 242]
with open('ll','rb') as f:
    s = ''
    for i in range(33):
        s+=chr(a[i]^ord(f.read(1)))
    print(s)

monster

这题算法十分简单,但如果不熟悉大整数运算算法的话看起来会比较吃力

首先还是接受16进制的输入,主要加密逻辑在00000B52里面

void __cdecl encode(char *s, __int64 a3)//s:"tsebehtsignisrever" a3:input
{
  unsigned __int64 a1; // [esp+8h] [ebp-20h]
  size_t i; // [esp+18h] [ebp-10h]

  a1 = __PAIR__(HIDWORD(a3), (unsigned int)a3);
  for ( i = 0; strlen(s) > i; ++i )
  {
    temp[i] = a1 ^ s[i];
    a1 = mod(1337 * a1, 133713371337LL);
  }
}

当然原本是没有符号表的,这里是我改的。

这里的mod函数就是取模运算,不过是对64位大整数的取模运算,这是一个32位程序,所以要对64位数取模要用两个寄存器/Dword表示,会麻烦一些。一开始对着这个函数日了好久,最终发现是手动在32程序里写了一个64位整数的取模运算,也是浪费了一些时间= =

直接用z3约束求解器解吧

from z3 import *
from pwn import *
context.log_level = 'debug'
p = process('./rev',)
# p = remote('54.87.182.197','1337')
a= BitVec('a',64)
s = Solver()
const = [0xCA, 0x3D, 0x3B, 0x5B, 0x4C, 0x9D, 0xD2, 0xCB, 0xDD, 0x17, 0x8D, 0xDC, 0xB9, 0x49, 0x3B, 0xEA, 0x12, 0x25]
key = [  0x74, 0x73, 0x65, 0x62, 0x65, 0x68, 0x74, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x73, 0x72, 0x65, 0x76, 0x65, 0x72]

for i in range(len(key)):
    const[i]^=key[i]
    s.add(a &0xff == const[i])
    a = (a*1337)%133713371337
if s.check() == sat:
    m = s.model()
    payload = hex(m[m[0]].as_long())
    print(payload)
# payload = '564e9367e6e30be'
p.sendline(payload)
print(p.recv())

比赛的服务器已经关闭了,本地肯定能打

Sunshine CTF 2019

Patches' Punches

打开程序后直接结束了,看一下汇编有一个永真的语句,把00000540patch成与1比较,直接得到flag

.text:0000052B                 push    ecx
.text:0000052C                 sub     esp, 10h
.text:0000052F                 call    __x86_get_pc_thunk_ax
.text:00000534                 add     eax, 1AA4h
.text:00000539                 mov     [ebp+var_10], 1
.text:00000540                 cmp     byte ptr [ebp+var_10], 1
.text:00000544                 jnz     short lose
.text:00000546                 mov     [ebp+var_C], 0
.text:0000054D                 jmp     short win

Smash

加密逻辑在checkAccessCode函数里,把输入右移一定大小,然后跟常量对比。

a = [0x0E60, 0x3A8, 0x1B80, 0x0F60, 0x120, 0x0EA0, 0x188, 0x358, 0x1A0, 0x9A0, 0x184, 0x4E0, 0x0C40, 0x0C20, 0x5A0, 0x1C8, 0x1D4, 0x9C0, 0x1CC, 0x0B40, 0x0AE0, 0x62, 0x360, 0x340, 0x5A0, 0x180, 0x6E0, 0x0B40, 0x1540, 0x0FA0]
b = [5, 3, 6, 5, 2, 5, 3, 3, 3, 5, 2, 4, 6, 5, 5, 2, 2, 5, 2, 6, 5, 1, 3, 4, 5, 3, 4, 6, 6, 5]
s = ''
for i in range(len(a)):
    a[i] >>= b[i]
    s+=chr(a[i])
print(s)

The Whole Pkg

这题就有点意思了,直接打开是个文件读取系统

输入1看文件目录,输入2打印文件

输入3提示没有权限

其他的文件都能正常读

思考一下,可能输3之后会有个函数check,想办法定位到那个check,如果它是采用返回0/1的方式check,可以尝试修改返回值(eax)

用ida打开直接凉凉,里面一大堆库函数乱起八糟挺难分析的

用x64dbg动调,直接运行,先输入一个2,接下来先暂停再输入3回车,这样会暂停到输完3那一步,可以看到我们的输入“3\r\n”在栈里面。

在字符'3'下硬件断点,继续运行,会停到一个函数内,看起来像是strlen,返回了函数长度3。

继续运行,断到另一个函数,看起来像是strcpy,在copy的新地址下硬件断点。

继续运行,来到一个类似check的地方,直接运行到返回,返回值是1,尝试改成0(返回后也能看到它和1,-1,0比较),把之前的断点删除继续与运行,这时候又来到一个要求输入的地方,尝试输入一个3和回车,flag就直接被打印出来了。。。

做法很玄学,希望大佬们能讲解下具体细节= =

1 条评论
某人
表情
可输入 255