2020网鼎杯青龙组逆向部分WriteUp

Jocker:

main函数中有个omg函数 没锤子用:

image-20200510155646169

真正的函数在下边 是个SMC问题:

还原代码如下:


1
2
3
4
auto i;
for(i=0;i<186;i++){
PatchByte(0x00401500+i,Byte(0x00401500+i)^0x41);
}

image-20200510160012286

还原后得到encrypt函数:

image-20200510160116906

简单的异或:


1
2
3
4
5
6
7
8
key = 'hahahaha_do_you_find_me?'
cipher1 = [
    0x0000000E, 0x0000000D, 0x00000009, 0x00000006, 0x00000013, 0x00000005, 0x00000058, 0x00000056,
    0x0000003E, 0x00000006, 0x0000000C, 0x0000003C, 0x0000001F, 0x00000057, 0x00000014, 0x0000006B,
    0x00000057, 0x00000059, 0x0000000D
]
for i in range(len(cipher1)):
    tmp+=chr(cipher1[i]^ord(key[i]))

但是解密出来只有19位, 少了5位:

image-20200510160235421

此时想到下边有个finaly函数:

image-20200510160347340

flag的最后一位为:}

image-20200510160446883

尝试将这段字符串与0x71异或,得到后五位,拼接起来即为flag, 最终脚本如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
key = 'hahahaha_do_you_find_me?'
cipher1 = [
    0x0000000E, 0x0000000D, 0x00000009, 0x00000006, 0x00000013, 0x00000005, 0x00000058, 0x00000056,
    0x0000003E, 0x00000006, 0x0000000C, 0x0000003C, 0x0000001F, 0x00000057, 0x00000014, 0x0000006B,
    0x00000057, 0x00000059, 0x0000000D
]
cipher2 = [
    '%',
    't',
    'p',
    '&',
    ':',
    ]

tmp = ''
for i in range(len(cipher1)):
    tmp+=chr(cipher1[i]^ord(key[i]))
tmp += ''.join([chr(ord(i)^71) for i in cipher2])
print tmp

image-20200510160543566

Signal:

逻辑很清晰,没啥乱七八糟的东西 是一个虚拟机指令分析的题目:

image-20200510160750611

vm_operad代码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
int __cdecl vm_operad(int *a1, int a2)
{
  int result; // eax
  char v3[100]; // [esp+13h] [ebp-E5h]
  char v4[100]; // [esp+77h] [ebp-81h]
  char v5; // [esp+DBh] [ebp-1Dh]
  int v6; // [esp+DCh] [ebp-1Ch]
  int v7; // [esp+E0h] [ebp-18h]
  int v8; // [esp+E4h] [ebp-14h]
  int v9; // [esp+E8h] [ebp-10h]
  int v10; // [esp+ECh] [ebp-Ch]

  v10 = 0;
  v9 = 0;
  v8 = 0;
  v7 = 0;
  v6 = 0;
  while ( 1 )
  {
    result = v10;
    if ( v10 >= a2 )
      return result;
    switch ( a1[v10] )
    {
      case 1:
        v4[v7] = v5;
        ++v10;
        ++v7;
        ++v9;
        break;
      case 2:
        v5 = a1[v10 + 1] + v3[v9];
        v10 += 2;
        break;
      case 3:
        v5 = v3[v9] - LOBYTE(a1[v10 + 1]);
        v10 += 2;
        break;
      case 4:
        v5 = a1[v10 + 1] ^ v3[v9];
        v10 += 2;
        break;
      case 5:
        v5 = a1[v10 + 1] * v3[v9];
        v10 += 2;
        break;
      case 6:
        ++v10;
        break;
      case 7:
        if ( v4[v8] != a1[v10 + 1] )
        {
          printf("what a shame...");
          exit(0);
        }
        ++v8;
        v10 += 2;
        break;
      case 8:
        v3[v6] = v5;
        ++v10;
        ++v6;
        break;
      case 0xA:
        read(v3);
        ++v10;
        break;
      case 0xB:
        v5 = v3[v9] - 1;
        ++v10;
        break;
      case 0xC:
        v5 = v3[v9] + 1;
        ++v10;
        break;
      default:
        continue;
    }
  }
}

虚拟机每次根据外边传进来的a1作为虚拟机的参数来做简单的位运算:


1
2
3
a1 = [
    0x0000000A, 0x00000004, 0x00000010, 0x00000008, 0x00000003, 0x00000005, 0x00000001, 0x00000004, 0x00000020, 0x00000008, 0x00000005, 0x00000003, 0x00000001, 0x00000003, 0x00000002, 0x00000008, 0x0000000B, 0x00000001, 0x0000000C, 0x00000008, 0x00000004, 0x00000004, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000003, 0x00000021, 0x00000001, 0x0000000B, 0x00000008, 0x0000000B, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000051, 0x00000008, 0x00000004, 0x00000024, 0x00000001, 0x0000000C, 0x00000008, 0x0000000B, 0x00000001, 0x00000005, 0x00000002, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000002, 0x00000036, 0x00000008, 0x00000004, 0x00000041, 0x00000001, 0x00000002, 0x00000020, 0x00000008, 0x00000005, 0x00000001, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000041, 0x00000008, 0x0000000C, 0x00000001, 0x00000007, 0x00000022, 0x00000007, 0x0000003F, 0x00000007, 0x00000034, 0x00000007, 0x00000032, 0x00000007, 0x00000072, 0x00000007, 0x00000033, 0x00000007, 0x00000018, 0x00000007, 0xFFFFFFA7, 0x00000007, 0x00000031, 0x00000007, 0xFFFFFFF1, 0x00000007, 0x00000028, 0x00000007, 0xFFFFFF84, 0x00000007, 0xFFFFFFC1, 0x00000007, 0x0000001E, 0x00000007, 0x0000007A
    ]

在case 0xA中: 使用自定义的read函数获取了用户的输入,并且确定了输入长度为15:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
size_t __cdecl read(char *a1)
{
  size_t result; // eax

  printf("string:");
  scanf("%s", a1);
  result = strlen(a1);
  if ( result != 15 )
  {
    puts("WRONG!\n");
    exit(0);
  }
  return result;
}

case1和case8 是累加器, 用来存放我们的输入,并使用v3和v4存放:

image-20200510161144609

image-20200510161157620

case7 处做了判定,判断输入的是否正确,最终比较数据为v4:

image-20200510161307998

然后开始在每个case处下断进行动调,

image-20200510161504706

每次循环大致步骤如下: 先运算->再给v3赋值->使用v5做交换变量->然后给v4赋值.....

我们手动还原虚拟机,将他的算法写下来

因为字符串不长,虚拟机也不是很复杂, 所以我比较喜欢用动调的方式一步一步写:

(当然也有其他方法实现,比如每个功能封装成一个函数这种..)

脚本如下(enc1代表v3,enc2代表v4):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#num = '123456789012345'

lst = [
    0x0000000A, 0x00000004, 0x00000010, 0x00000008, 0x00000003, 0x00000005, 0x00000001, 0x00000004, 0x00000020, 0x00000008, 0x00000005, 0x00000003, 0x00000001, 0x00000003, 0x00000002, 0x00000008, 0x0000000B, 0x00000001, 0x0000000C, 0x00000008, 0x00000004, 0x00000004, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000003, 0x00000021, 0x00000001, 0x0000000B, 0x00000008, 0x0000000B, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000051, 0x00000008, 0x00000004, 0x00000024, 0x00000001, 0x0000000C, 0x00000008, 0x0000000B, 0x00000001, 0x00000005, 0x00000002, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000002, 0x00000036, 0x00000008, 0x00000004, 0x00000041, 0x00000001, 0x00000002, 0x00000020, 0x00000008, 0x00000005, 0x00000001, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000041, 0x00000008, 0x0000000C, 0x00000001, 0x00000007, 0x00000022, 0x00000007, 0x0000003F, 0x00000007, 0x00000034, 0x00000007, 0x00000032, 0x00000007, 0x00000072, 0x00000007, 0x00000033, 0x00000007, 0x00000018, 0x00000007, 0xFFFFFFA7, 0x00000007, 0x00000031, 0x00000007, 0xFFFFFFF1, 0x00000007, 0x00000028, 0x00000007, 0xFFFFFF84, 0x00000007, 0xFFFFFFC1, 0x00000007, 0x0000001E, 0x00000007, 0x0000007A
    ]

def vm_enc(plaint):
    enc1 = ''
    enc1+= chr(ord(plaint[0])  ^  lst[1+1])
    enc1+= chr(ord(plaint[1])  ^  lst[7+1])
    enc1+= chr(ord(plaint[2])  -  lst[13+1])
    enc1+= chr(ord(plaint[3])  +  1)
    enc1+= chr(ord(plaint[4])  *  lst[0x19+1])
    enc1+= chr(ord(plaint[5])  -  1)
    enc1+= chr(ord(plaint[6])  ^  lst[0x21+1])
    enc1+= chr(ord(plaint[7])  +  lst[0x27+1])
    enc1+= chr(ord(plaint[8])  +  1)
    enc1+= chr(ord(plaint[9])  *  lst[0x31+1])
    enc1+= chr(ord(plaint[10])  +  lst[0x37+1])
    enc1+= chr(ord(plaint[11])  +  lst[0x3d+1])
    enc1+= chr(ord(plaint[12])  *  lst[0x43+1])
    enc1+= chr(ord(plaint[13])  ^  lst[0x49+1])
    enc1+= chr(ord(plaint[14])  +  lst[0x4f+1])
    # enc1 =>  21 12 31 35 9f 35 3e 89 3a 60 67 52 99 3d 3e

    enc2 = ''
    enc2+=chr(ord(enc1[0])  -  lst[4+1])
    enc2+=chr(ord(enc1[1])  *  lst[0xa+1])
    enc2+=chr(ord(enc1[2])  -  1)
    enc2+=chr(ord(enc1[3])  ^  lst[0x14+1])
    enc2+=chr(ord(enc1[4])  -  lst[0x1a+1])
    enc2+=chr(ord(enc1[5])  -  1)
    enc2+=chr(ord(enc1[6])  -  lst[0x24+1])
    enc2+=chr(ord(enc1[7])  ^  lst[0x2a+1])
    enc2+=chr(ord(enc1[8])  -  1)
    enc2+=chr(ord(enc1[9])  +  lst[0x34+1])
    enc2+=chr(ord(enc1[10])  ^  lst[0x3a+1])
    enc2+=chr(ord(enc1[11])  *  lst[0x40+1])
    enc2+=chr(ord(enc1[12])  +  lst[0x46+1])
    enc2+=chr(ord(enc1[13])  -  lst[0x4c+1])
    enc2+=chr(ord(enc1[14])  +  1)
    return enc2
    # enc2 => 1c 36 30 31 7e 34 1e ad 39 85 26 52 be 1d 77
vm_enc('123456789012345')

这里我填充了123456789012345 作为15个长度的字符串

得到的数据为enc2 => 1c 36 30 31 7e 34 1e ad 39 85 26 52 be 1d 77

在判定处下断, 比较一下,和v4长度一致

image-20200510163049753

image-20200510163417377

然后将虚拟机里进行的一些运算给逆运算一下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def vm_dec(plaint):
    dec = []
    dec.append(ord(plaint[0])  ^  lst[1+1]  +  lst[4+1])
    dec.append(ord(plaint[1])  /  lst[0xa+1]  ^  lst[7+1])
    dec.append(ord(plaint[2])  +  lst[13+1]  +  1)
    dec.append(ord(plaint[3])  -  1  ^  lst[0x14+1])
    dec.append((ord(plaint[4])  +  lst[0x1a+1])  /  lst[0x19+1])
    dec.append(ord(plaint[5])  +  1  +  1)
    dec.append(ord(plaint[6])  ^  lst[0x21+1]  +  lst[0x24+1])
    dec.append((ord(plaint[7])  ^  lst[0x2a+1])  -  lst[0x27+1])
    dec.append(ord(plaint[8])  -  1  +  1)
    dec.append((ord(plaint[9])  -  lst[0x34+1])  /  lst[0x31+1])
    dec.append((ord(plaint[10])  ^  lst[0x3a+1])  -  lst[0x37+1])
    dec.append(ord(plaint[11])  -  lst[0x3d+1]  /  lst[0x40+1])
    dec.append((ord(plaint[12])  -  lst[0x46+1])  /  lst[0x43+1])
    dec.append(ord(plaint[13])  ^  lst[0x49+1]  +  lst[0x4c+1])
    dec.append(ord(plaint[14])  -  lst[0x4f+1]  -  1)
    return dec

这里有些坑,其实也不算坑 ,就是优先级问题,搞错了就差一个字符是输入不对(当时卡了我好久)

然后就是确定密文了:

在第一次做比较判定的时候,发现他的比较字符串放在了a1的第54+1的位置:

image-20200510163841821

然后在下边 v10 每次+2

那么我们提取一下(在这里 出现了0xfffff的数字, 这是因为符号位导致的,因为都是正数,所以减去0xffffff00就好):


1
2
3
4
5
6
7
8
9
10
11
12
def get_cipher():
    tmp = ''
    for i in range(0x54+1,len(lst),2):
        if lst[i] >=0xffff:
            tmp+= chr(lst[i] - 0xffffff00)
        else:
            tmp+= chr(lst[i])
    return tmp
cipher = get_cipher()
print cipher.encode('hex')

# cipher = 22 3f 34 32 72 33 18 58 31 0e 28 7b 3e 1e 7a

然后放回我们写好的vm_dec()函数中,即可得到flag

完整的解密脚本如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#coding:utf-8
lst = [
    0x0000000A, 0x00000004, 0x00000010, 0x00000008, 0x00000003, 0x00000005, 0x00000001, 0x00000004, 0x00000020, 0x00000008, 0x00000005, 0x00000003, 0x00000001, 0x00000003, 0x00000002, 0x00000008, 0x0000000B, 0x00000001, 0x0000000C, 0x00000008, 0x00000004, 0x00000004, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000003, 0x00000021, 0x00000001, 0x0000000B, 0x00000008, 0x0000000B, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000051, 0x00000008, 0x00000004, 0x00000024, 0x00000001, 0x0000000C, 0x00000008, 0x0000000B, 0x00000001, 0x00000005, 0x00000002, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000002, 0x00000036, 0x00000008, 0x00000004, 0x00000041, 0x00000001, 0x00000002, 0x00000020, 0x00000008, 0x00000005, 0x00000001, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002, 0x00000041, 0x00000008, 0x0000000C, 0x00000001, 0x00000007, 0x00000022, 0x00000007, 0x0000003F, 0x00000007, 0x00000034, 0x00000007, 0x00000032, 0x00000007, 0x00000072, 0x00000007, 0x00000033, 0x00000007, 0x00000018, 0x00000007, 0xFFFFFFA7, 0x00000007, 0x00000031, 0x00000007, 0xFFFFFFF1, 0x00000007, 0x00000028, 0x00000007, 0xFFFFFF84, 0x00000007, 0xFFFFFFC1, 0x00000007, 0x0000001E, 0x00000007, 0x0000007A
    ]
def vm_enc(plaint):
    enc1 = ''
    enc1+= chr(ord(plaint[0])  ^  lst[1+1])
    enc1+= chr(ord(plaint[1])  ^  lst[7+1])
    enc1+= chr(ord(plaint[2])  -  lst[13+1])
    enc1+= chr(ord(plaint[3])  +  1)
    enc1+= chr(ord(plaint[4])  *  lst[0x19+1])
    enc1+= chr(ord(plaint[5])  -  1)
    enc1+= chr(ord(plaint[6])  ^  lst[0x21+1])
    enc1+= chr(ord(plaint[7])  +  lst[0x27+1])
    enc1+= chr(ord(plaint[8])  +  1)
    enc1+= chr(ord(plaint[9])  *  lst[0x31+1])
    enc1+= chr(ord(plaint[10])  +  lst[0x37+1])
    enc1+= chr(ord(plaint[11])  +  lst[0x3d+1])
    enc1+= chr(ord(plaint[12])  *  lst[0x43+1])
    enc1+= chr(ord(plaint[13])  ^  lst[0x49+1])
    enc1+= chr(ord(plaint[14])  +  lst[0x4f+1])
    # enc1 =>  21 12 31 35 9f 35 3e 89 3a 60 67 52 99 3d 3e

    enc2 = ''
    enc2+=chr(ord(enc1[0])  -  lst[4+1])
    enc2+=chr(ord(enc1[1])  *  lst[0xa+1])
    enc2+=chr(ord(enc1[2])  -  1)
    enc2+=chr(ord(enc1[3])  ^  lst[0x14+1])
    enc2+=chr(ord(enc1[4])  -  lst[0x1a+1])
    enc2+=chr(ord(enc1[5])  -  1)
    enc2+=chr(ord(enc1[6])  -  lst[0x24+1])
    enc2+=chr(ord(enc1[7])  ^  lst[0x2a+1])
    enc2+=chr(ord(enc1[8])  -  1)
    enc2+=chr(ord(enc1[9])  +  lst[0x34+1])
    enc2+=chr(ord(enc1[10])  ^  lst[0x3a+1])
    enc2+=chr(ord(enc1[11])  *  lst[0x40+1])
    enc2+=chr(ord(enc1[12])  +  lst[0x46+1])
    enc2+=chr(ord(enc1[13])  -  lst[0x4c+1])
    enc2+=chr(ord(enc1[14])  +  1)
    return enc2
    # enc2 => 1c 36 30 31 7e 34 1e ad 39 85 26 52 be 1d 77

def vm_dec(plaint):
    dec = []
    dec.append(ord(plaint[0])  ^  lst[1+1]  +  lst[4+1])
    dec.append(ord(plaint[1])  /  lst[0xa+1]  ^  lst[7+1])
    dec.append(ord(plaint[2])  +  lst[13+1]  +  1)
    dec.append(ord(plaint[3])  -  1  ^  lst[0x14+1])
    dec.append((ord(plaint[4])  +  lst[0x1a+1])  /  lst[0x19+1])
    dec.append(ord(plaint[5])  +  1  +  1)
    dec.append(ord(plaint[6])  ^  lst[0x21+1]  +  lst[0x24+1])
    dec.append((ord(plaint[7])  ^  lst[0x2a+1])  -  lst[0x27+1])
    dec.append(ord(plaint[8])  -  1  +  1)
    dec.append((ord(plaint[9])  -  lst[0x34+1])  /  lst[0x31+1])
    dec.append((ord(plaint[10])  ^  lst[0x3a+1])  -  lst[0x37+1])
    dec.append(ord(plaint[11])  -  lst[0x3d+1]  /  lst[0x40+1])
    dec.append((ord(plaint[12])  -  lst[0x46+1])  /  lst[0x43+1])
    dec.append(ord(plaint[13])  ^  lst[0x49+1]  +  lst[0x4c+1])
    dec.append(ord(plaint[14])  -  lst[0x4f+1]  -  1)
    return dec

def get_cipher():
    tmp = ''
    for i in range(0x54+1,len(lst),2):
        if lst[i] >=0xffff:
            tmp+= chr(lst[i] - 0xffffff00)
        else:
            tmp+= chr(lst[i])
    return tmp

print vm_enc('123456789012345').encode('hex')

cipher = get_cipher()
# cipher = 22 3f 34 32 72 33 18 58 31 0e 28 7b 3e 1e 7a
print cipher.encode('hex')
# print "code1 >>>> "+hex(lst[0x14+1])
# print "code2 >>>> "+hex(lst[0xa+1])
print 'flag{'+''.join([chr(i) for i in vm_dec(cipher)])+'}'
# flag{757515121f3d478}

image-20200510164314876

bang:

根据题目提示,知道这个是一个带壳的apk:

image-20200510164813236

搜索一些特征发现,他像是梆梆安全的壳

使用frida,hook一下我们需要的包,并dump脱壳后的明文dex文件(脚本参考: ):

image-20200510173012473

导出dex文件进行分析

image-20200510173403015


1
./adb pull /data/data/com.example.how_debug/1925304.dex .

ida打开我们dump出来的dex文件 明文搜索得到flag

image-20200510173611359

flag{borring_things}

发表评论

电子邮件地址不会被公开。 必填项已用*标注