安洵杯2023复现

  1. 感觉有点点简单
  2. MobileGo
  3. 你好PE

感觉有点点简单

sys文件,没碰到过先拉进ida里看看,很容易定位到关键代码

1
2
3
4
5
6
7
8
9
10
11
12
if ( NumberOfBytes[0] <= 0xC00 )
{
sub_1400011F0(*(__int64 *)&NumberOfBytes[1], NumberOfBytes[0], (__int64)"the_key_", 8u);
sub_140001360((__int64)P, *(__int64 *)&NumberOfBytes[1], NumberOfBytes[0]);
v1 = sub_140001560((__int64)P, 56);
v8 = "tips: YES, RIGHT FLAG. you got it!";
v7 = "tips: NO , WRONG ANSWER. try again !";
if ( v1 )
DbgPrint("tips: %s\n", v8);
else
DbgPrint("tips: %s\n", v7);
}

其中sub_1400011F0函数和sub_140001360为关键的加密函数,sub_140001560为加密后的check函数,而两个加密函数可以看出是魔改的rc4和base64,先对base64分析,过程比较简单直接爆破就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def b64(str):
table = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"
data = [0] * 4
flag = [0] * 3
for i in range(0xff):
for j in range(0xff):
for k in range(0xff):
flag[0]=i
flag[1]=j
flag[2]=k
data[0] = ord(table[flag[0]&0x3f])
data[1] = ord(table[(4 * (flag[1]&0xf)) | ((flag[0]&0xc0)>>6)])
data[2] = ord(table[(16 * (flag[2]&3)) | ((flag[1]&0xf0)>>4)])
data[3] = ord(table[(flag[2]&0xfc)>>2])

if data == str:
for m in flag:
print(f"{m},",end = '')
return 0
flag = [ord(x) for x in enc]
for i in range(0,len(enc),4):
b64(flag[i:i+4])

每三个为组进行爆破,很快就能解出来

对魔改的rc4进行分析

就是把加密算法中的256改成了64,然后最后异或的地方也有一点不一样,直接写脚本就行

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
import hashlib
def crypt(data,key) :
s = [0] * 64
for i in range(64) :
s[i] = i
# print(s)
j = 0
for i in range(64) :
j = (j + s[i] + key[i % len(key)]) % 64
# print(j)
s[i], s[j] = s[j], s[i]
i = 0
j = 0
res = ""
for c in data :
i = (i + 1) % 64
j = (j + s[i]) % 64
s[i], s[j] = s[j], s[i]
res = res + chr(c ^ ((i^j)&s[((i^j)+s[i]+s[j])%64]))
return res

import base64

key2 = "the_key_"
key = []
tem=''
for i in key2:
key.append(ord(i))
# print (key)
data= [92,33,123,51,81,51,56,40,58,43,48,64,22,44,51,37,54,4,56,70,81,60,37,74,19,51,57,59,105,39,77,41,51,20,51,70,48,49,50]

print(crypt(data,key))

MobileGo

先拉进jadx分析

1
2
3
4
5
6
7
8
public /* synthetic */ void m44lambda$onCreate$0$comexamplemobilegoMainActivity(View v) {
if (Game.checkflag(this.editText.getText().toString()).equals(getResources().getString(R.string.cmp))) {
Toast.makeText(this, "yes your flag is right", 0).show();
} else {
Toast.makeText(this, "No No No", 0).show();
}
}

很容易找到密文cmp,看着很像是打乱顺序的flag

1
'49021}5f919038b440139g74b7Dc88330e5d{6'

关键就在于checkflag函数,跟进后发现是一个so层的函数,ida查看后并不容易分析,但既然很可能是打乱顺序的加密,那就可以尝试通过动态调试来得到相应的映射关系从而解出flag,因为只要知道checkflag返回的值就可以了,所以使用jeb来调试就行

成功得到了映射关系

1
2
str = 'abcdefghijklmnopqrstuvwxyz0123456789+-'
str2 = 'v8l3o-jt4pk2myzfqbshucxwi0ag+51d679ner'

简单写一个脚本即可

1
2
3
4
5
6
7
8
9
10
str = 'abcdefghijklmnopqrstuvwxyz0123456789+-'
str2 = 'v8l3o-jt4pk2myzfqbshucxwi0ag+51d679ner'
enc = '49021}5f919038b440139g74b7Dc88330e5d{6'
num = [0]*len(str)
for i in range(len(str)):
num[i] = str2.index(str[i])
for i in num:
print(enc[i],end='')

#D0g3{4c3b5903d11461f94478b7302980e958}

你好PE

拖进ida后发现找不到关键的加密函数而且字符表里也找不到运行时打印的字符,尝试通过调试来找到关键函数

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
int sub_1005F820()
{
int *v1; // [esp+D0h] [ebp-8h]

sub_1005B16F(&unk_1014000F);
kernel32_VirtualAlloc(0, 65548, 12288, 4);
v1 = (int *)sub_1005A260();
if ( !v1 )
return -1;
v1[1] = 0x10000;
*v1 = 0;
v1[2] = (int)(v1 + 3);
sub_10059572(v1[2], 0, v1[1]);
sub_10058BC7((int)"[out]: PLZ Input FLag \n");
sub_10058BC7((int)"[in ]: ");
sub_100581A4((int)&unk_10114B68, v1[2]);
*v1 = sub_1005B5BB(v1[2]);//计算输入的长度
if ( *v1 == 41 )
{
*v1 = '0';
sub_1005A242((int)v1);
if ( sub_10058AA0(v1[2], (int)&byte_1013C008, 48) )
sub_10058BC7((int)"[out]: WRONG FLAG\n");
else
sub_10058BC7((int)"[out]: RIGHT FLAG\n");
kernel32_VirtualFree((int)v1, 0, 49152);
sub_1005A260();
return 0;
}
else
{
sub_10058BC7((int)"[out]: len error\n");
kernel32_VirtualFree((int)v1, 0, 49152);
sub_1005A260();
return -1;
}
}

通过调试可以知道v1存放的是输入字符串,‘ sub_10058AA0(v1[2], (int)&byte_1013C008, 48) ’这里应该是check函数,byte_1013C008应该就是密文,‘sub_1005A242((int)v1);’这里应该就是加密的函数了

跟进查看

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
_DWORD *__cdecl sub_1005F6F0(_DWORD *a1)
{
_DWORD *result; // eax
int v2; // edx
int v3; // edx
int j; // [esp+D0h] [ebp-30h]
unsigned int i; // [esp+DCh] [ebp-24h]
__int64 v6; // [esp+E8h] [ebp-18h]
__int64 *v7; // [esp+F8h] [ebp-8h]

sub_1005B16F(&unk_1014000F);
for ( i = 0; ; ++i )
{
result = a1;
if ( i >= *a1 >> 3 ) //*a1=0x30
break;
v7 = (__int64 *)(a1[2] + 8 * i);
v6 = *v7;
for ( j = 0; j < 64; ++j )
{
if ( v6 < 0 )
{
LODWORD(v6) = *(_DWORD *)&byte_1013C000 ^ sub_10059F9F(v6, 1u);
HIDWORD(v6) = unk_1013C004 ^ v3;
}
else
{
LODWORD(v6) = sub_10059F9F(v6, 1u);
HIDWORD(v6) = v2;
}
}
*v7 = v6;
}
return result;
}

里面有一个循环,跳出循环的条件就是i>=*a1>>3,正好也和下面v7的取值对应上了,即每8位一起进行加密

其中sub_10059F9F(v6, 1u)就是实现了一个左移一位的操作,然后unk_1013C004里存的都是0,0^本身还是本身,v2和v3在调试的时候可以发现就是v6的高四位

现在整体的加密就比较清晰了,直接上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def decode(data):
for i in range(0,12,2):
data0=data[i]
data1=data[i+1]
for j in range(64):
#根据加密函数可知如果异或了0x54aa4a9那么最后一位一定是1,如果仅仅只是左移那么最后一位一定是0,所以用&1来判断有没有异或
if(data0 & 1):
data0 = (0x54aa4a9 ^ data0) & 0xffffffff
data0 = ((data0>>1)&0xfffffffff | ((data1 & 1)<<31))
data1 = ((data1>>1)&0xfffffffff | (1<<31))
else:
data0 = ((data0>>1)&0xffffffff | ((data1 & 1)<<31))
data1 = (data1>>1)&0xffffffff
data[i] = data0
data[i+1] = data1

cmp = [0x2976B84D, 0x599EA9F5, 0xC4B15655, 0x302C212F, 0x177879B3, 0xDBF7EDA8, 0xDBF053E1, 0x5E5103E9, 0xDF00C109, 0xC1FC96F0, 0x9562E6B5, 0x00000001]
decode(cmp)
for i in range(len(cmp)):
print(hex(cmp[i]),end = ', ')
print()
import struct
flag = b''.join(struct.pack("<I", i) for i in cmp)
print(flag)

struct.pack() 是一个 Python 的内置函数,用于将数据转换为指定的二进制格式。

它的语法如下:

1
2
struct.pack(format, v1, v2, ...)

其中,format 是一个字符串,指定了数据的格式。常用的格式字符包括:

  • b :有符号字节(signed char
  • B :无符号字节(unsigned char
  • h :有符号短整数(short
  • H :无符号短整数(unsigned short
  • i :有符号整数(int
  • I :无符号整数(unsigned int
  • l :有符号长整数(long
  • L :无符号长整数(unsigned long
  • f :单精度浮点数(float
  • d :双精度浮点数(double

v1, v2, ... 是要转换的数据,可以是一个或多个。

返回值是一个字节串,包含了按照指定格式转换后的数据。

例如,使用 struct.pack() 函数将一个整数转换为4字节的小端字节序:

1
2
3
4
5
6
7
8
import struct

num = 1234567890
packed_data = struct.pack("<I", num)
print(packed_data)

复制

输出结果为:b'\xd2\x02\x96\x49'

这里的 "<I" 表示使用小端字节序 (<) 来表示无符号整数 (I)。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com

💰

×

Help us with donation