假期集训逆向

假期集训逆向

七月 19, 2019

假期留校集训,关在实验室自生自灭,做了几道简单的逆向题,各大平台的都有。

实验吧

whatamitoyou

给了提示“你会唱歌吗?”,ELF文件,先用IDA静态分析,main函数开始先进行了很长很长的声明很多变量,变量赋值,再把变量的地址赋值给另个变量,赋值的部分把int值按R转化成字符串会发现是倒序的一些英文句子,倒序是因为数字转化成字符串存在内存中的小端存储问题,在x86结构的内存中数字是高位存在低地址,把转化成字符串就会调转顺序:

根据提示猜测这些句子可能是歌词,于是Google搜到了这首歌:

发现题中的歌词是乱序的,猜测解题和顺序有关。
“准备工作”结束后进行了一系列看不大懂的运算:

Linux下edb动态调试,开始的声明,赋值直接跳过后在漫长的“准备工作”最后,结合ida可以发现把第一句歌词的地址赋给了[rbp-8]:

这里开始操作输入的字符串,把字符放入[rbp-0x11]:

跟进跳转到这里用字符对歌词地址进行操作“旧地址+(字符-0x41+0x20)*8=新地址”

结合IDA找到第二句歌词"I should have just told you"的地址,有两个,都进行逆运算得出输入的第一个字符应该是67='C'

一直重复就能找出全部字符串,其实也有规律,在栈里存每一段歌词的地址下面不远处有连续四个地方存的是其他歌词的地址,字符ABCD运算下来分别就对应着这四句歌词的位置,这样很快就能得出输入的字符串:CBDABCADBCCABBABBABACBCCABDADBABABB

逆向观察

ELF文件,IDA查看:

src拷贝给dest,然后遍历dict里的字符串,如果有字符串同时与dest和输入的字符串相等则输入正确,很简单,只有一个小端问题,上一题有介绍。

FLAG

给的是一个网址,当时很懵逼,这TM不是逆向么…查看网页源码可以看见一段JS代码:

如果输入的字符串满足这串超级长的判断则correct……
这串判断是由很多判断用&&连接起来的,分开之后按长度排序是这样的:

这就可以看出端倪了,按照这个顺序一个个判断的话每一次判断可以确定一个字符,上脚本吧:

1
2
3
4
5
6
7
8
9
10
11
src='超长判断'.split('&&')
a=list(range(47))
src.sort(key=lambda x:len(x))
for i in range(47):
for j in range(256):
a[i]=j
if eval(src[i]):
break
for i in a:
print(chr(i),end='')
#flag{wh47_my5ter10us-do3s,the+phe45ant/c0nta1n}

证明自己吧

主函数是这样的:

输入V4,然后跟进判断函数:

输入的数据异或0x20v55,之后比较,直接把v5的数据加5再异或0x20就可以了。

BUUCTF

刮开有奖

IDA查看主函数,跟进找到判断函数:

输入长度为8的字符串给String之后进行判断:

跟进sub4010F0分析得知是一个排序算法,将v7后连续10个数据进行排序,sub401000是base64编码,再根据后面的if判断,可以知道String第一位是排序后的v7,第二位是排序后的v11,后六位分别是V1Axak1w的base64解码,拼在一起得到flag。

CG-CTF

WxyVM1

ELF文件,IDA查看:

结构很简单,输入,,执行sub4005B6,判断长度,比较,要比较的字符串是给了的,只需要分析sub4005B6这个加密函数,跟进:

0x6010C0开始取15000个数据来对输入进行加密,每三个一组,三个中第一个决定操作方式,第二个决定操作输入字符串中的第几位的字符,第三位决定进行操作的数据,IDC逆向脚本跑一下就能跑出输入的字符串

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
auto i;
for ( i = 14997; i >= 0; i = i - 3 )
{
auto v0 = Byte(0x6010C0+i);
auto v3 = Byte(0x6010C0+(i + 2));
auto result = v0;
if(v0==1){
result =Byte(0x6010C0+i + 1);
PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)-v3);
}
if(v0==2){
result =Byte(0x6010C0+i + 1);
PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)+v3);
}
if(v0==3){
result =Byte(0x6010C0+i + 1);
PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)^v3);
}
if(v0==4){
result =Byte(0x6010C0+i + 1);
PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)/v3);
}
if(v0==5){
result =Byte(0x6010C0+i + 1);
PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)^Byte(0x601060+v3*4));
}
else
continue;
}
for(i=0;i<24;i++)
{
Message("%c",Byte(0x601060+i*4));
}
//nctf{Embr4ce_Vm_j0in_R3}

也可以dump下来用python脚本跑。

maze

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
const char *v3; // rsi
signed __int64 v4; // rbx
signed int v5; // eax
char v6; // bp
char v7; // al
const char *v8; // rdi
__int64 v10; // [rsp+0h] [rbp-28h] //划重点,64位整型

v10 = 0LL;
puts("Input flag:");
scanf("%s", &s1, 0LL);
if ( strlen(&s1) != 24 || (v3 = "nctf{", strncmp(&s1, "nctf{", 5uLL)) || *(&byte_6010BF + 24) != 125 ) //判断flag格式
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v4 = 5LL; //v4是遍历下标,从5开始,因为前5位是flag格式,上面已经判断过
if ( strlen(&s1) - 1 > 5 ) //上面已经判断过长度,这里是干扰代码
{
while ( 1 )
{
v5 = *(&s1 + v4);
v6 = 0;
if ( v5 > 78 )
{
v5 = (unsigned __int8)v5;
if ( (unsigned __int8)v5 == 79 ) //若字符=='O'
{
v7 = sub_400650((char *)&v10 + 4, v3); //v10低32位减1,并判断是否越界
goto LABEL_14;
}
if ( v5 == 111 ) //若字符=='o'
{
v7 = sub_400660((char *)&v10 + 4, v3); //v10低32位加1,并判断是否越界
goto LABEL_14;
}
}
else
{
v5 = (unsigned __int8)v5;
if ( (unsigned __int8)v5 == 46 ) //若字符=='.'
{
v7 = sub_400670(&v10, v3); //v10高32位减1,并判断是否越界
goto LABEL_14;
}
if ( v5 == 48 ) 若字符=='0'
{
v7 = sub_400680(&v10, v3); //v10高32位加1,并判断是否越界
LABEL_14:
v6 = v7;
goto LABEL_15;
}
}
LABEL_15:
v3 = (const char *)HIDWORD(v10);
if ( !(unsigned __int8)sub_400690(asc_601060, HIDWORD(v10), (unsigned int)v10) ) //用高32位和低32位表示行和列,判断在地图中是否撞墙
goto LABEL_22;
if ( ++v4 >= strlen(&s1) - 1 ) //判断字符串读取结束
{
if ( v6 ) //判断是否撞墙
break;
LABEL_20:
v8 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * (signed int)v10 + SHIDWORD(v10)] != 35 ) //判断是否到达终点"#"
goto LABEL_20;
v8 = "Congratulations!";
LABEL_21:
puts(v8);
return 0LL;
}

地图是在判断是否越界的函数中可以看出是8*8,判断是否撞墙的函数中可以找到地图数据,人脑遍历地图路线得到答案,地图和答案如下:

1
2
3
4
5
6
7
8
9
  ******
* * *
*** * **
** * **
* *# *
** *** *
** *
********
o0oo00O000oooo..OO

杭电CTF

Beat our dice game and get the flag

代码看起来比较畸形,用ollydbg动态调试看字符串可以大体猜出程序,一个扔骰子的游戏,要求多次按enter随机扔骰子,必须都扔到程序规定的数,甚至还要求扔到七:

跟进找到每一个判断跳转,改掉没有扔到指定数的跳转,让程序一直运行下去到最后flag就会自己冒出来了:

这种没有输入的题,一般会比较简单,因为不用输入就表示flag就藏在文件中,只要能运行到某些命令就能拿到flag,不需要逆向算法什么的,这种应该只能叫破解,不能称为逆向才对。

从文件中找flag

这个题好像实验吧也有,文件是一个叫keylead的文件,WinHex查看文件头发现是一个tar.xz的压缩包,Linux下解压后是一个ELF文件,IDA查看C语言伪代码,还是一个扔骰子…一样的方法,只是要在Linux环境下调试,用edb把关键跳转改掉,就可以得到flag

flag我就不放了,懒得再去复盘截图,跟上一题一样的操作。

Bugku

Mountain climbing

有一个UPX的壳,脱壳细节我在这里不细说,图简单的话吾爱破解有UPX脱壳工具,脱壳后先上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
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

int arr[20][20];
int sum = 0, maxx = 0, top;
char maxs[20], s[20];

void fun(int i, int j)
{
sum += arr[i][j];
if (i == 19)
{
if (sum > maxx)
{
strcpy(maxs, s);
maxx = sum;
}
sum -= arr[i][j];
return;
}
else
{
s[top++] = 'L';
fun(i + 1, j);
top--;
s[top++] = 'R';
fun(i + 1, j + 1);
top--;
}
sum -= arr[i][j];
return;
}

int main()
{
srand(0xc);
for (int i = 0; i < 20; i++)
{
for (int j = 0; j <= i; j++)
{
arr[i][j] = rand() % 100000;
}
}
fun(0, 0);
cout << maxs << endl;
return 0;
}
//RRRRRLLRRRLRLRRRLRL

交上去却是错的,回去慢慢看发现还有一个加密,是将输入的偶数位异或0x4,IDA看比较丑,可以在ollydbg里面观察输入数据的变化很明显就能看出来(此处我输入的是'0'*19):

把之前得到的字符串再异或一下就可以得到flag了。

Take the maze

迷宫,IDA查看:

先让输入key,然后有个isdebuggerpresent反调试,之后判断key长度,第17位异或1,之后有个奇怪的函数,之后判断必须全是十六进制字符,不然goto16就结束了,然后进入关键判断,跟进,代码如下:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
BOOL __cdecl sub_463480(int a1)
{
int v1; // ST18_4
int v2; // ST18_4
BOOL result; // eax
int v4; // ST18_4
int v5; // [esp+D8h] [ebp-38h]
int v6; // [esp+E4h] [ebp-2Ch]
int v7; // [esp+F0h] [ebp-20h]
int v8; // [esp+FCh] [ebp-14h]
int v9; // [esp+108h] [ebp-8h]

v9 = 0;
v6 = 0;
v5 = 0;
while ( 2 )
{
v1 = v9++;
if ( v1 >= 12 ) //24个字符,遍历12次,表示是两个字符一组
return v5 == 311; //v5==311则返回真,flag正确
if ( sub_45B1C7() ) //又是一个IsDebuggerPresent
j__exit(0);
v2 = *(char *)(v6++ + a1);//v2一组两个字符的前一个
switch ( v2 )
{
case '0':
v8 = 0;
goto LABEL_12;
case '1':
v8 = 1;
goto LABEL_12;
case '2':
v8 = 2;
goto LABEL_12;
case '3':
v8 = 3;
goto LABEL_12;
case '4':
v8 = 4;
LABEL_12:
v4 = *(char *)(v6++ + a1);//v4表示后一个
switch ( v4 )
{
case '5':
v7 = 5;
goto LABEL_25;
case '6':
v7 = 6;
goto LABEL_25;
case '7':
v7 = 7;
goto LABEL_25;
case '8':
v7 = 8;
goto LABEL_25;
case '9':
v7 = 9;
goto LABEL_25;
case 'a':
v7 = 10;
goto LABEL_25;
case 'b':
v7 = 11;
goto LABEL_25;
case 'c':
v7 = 12;
goto LABEL_25;
case 'd':
v7 = 13;
goto LABEL_25;
case 'e':
v7 = 14;
goto LABEL_25;
case 'f':
v7 = 15;
LABEL_25:
switch ( byte_541168[v8] ) //byte_541168="delru0123456789"
{
case 'd': //down
sub_45CC4D((int)&v5, byte_541168[v7] - 48);//向下走byte_541168[v7]-48步
continue;
case 'l': /left
sub_45D0A3((int)&v5, byte_541168[v7] - 48);//向左走
continue;
case 'r': //right
sub_45CB0D(&v5, byte_541168[v7] - 48);//向右走
continue;
case 'u': //up
sub_45D0E9(&v5, byte_541168[v7] - 48);//向上走
continue;
default:
result = 0;
break;
}
break;
default:
result = 0;
break;
}
break;
default:
result = 0;
break;
}
return result;
}
}

上下左右的函数长得都很像,大概像这样:

其他三个结构都是一样的,不再赘述,result的赋值都没有意义,因为函数并没有返回有用的值到上层,从这里走到下一行需要加26还有到第11行(下标为10)再继续走就会越界可知地图是11*26+25的规模,关键在于判断是否能够这样走的那个判断,IDC脚本把0到311的异或结果给跑出来看看:

1
2
3
4
5
6
7
8
9
10
auto i;
for(i=0;i<311;i++)
{
if(Byte(0x540548+4*i)^Byte(0x540068+i*4))
Message("1 ");
else
Message("0 ");
if((i+1)%26==0)
Message("\n");
}

结果如下:

这是地图中每一个点能否往下走的dump结果,其他三个方向一样的方法,最后把四个方向的dump结果统一在一张地图上,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
d=[1,1,0,1,0,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]
l=[0,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,]
r=[0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,]
u=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,]
for i in range(311):
if(i%26==0):
print('')
if(d[i]):
print("d",end='')
else:
print(' ',end='')
if(l[i]):
print("l",end='')
else:
print(" ",end='')
if(r[i]):
print("r",end='')
else:
print(" ",end='')
if(u[i]):
print("u",end='')
else:
print(" ",end='')
print("|",end='')

地图如下:

其中u,d,l,r分别代表在此坐标往上,下,左,右可行,地图很简单就可以找到一条路:

照着路线逆向出字符串"06360836063b0839173e0639"交上去不对,回头看之前那个奇怪的加密函数:

这个函数里面看起来很丑,不好分析,用ollydbg动态调试发现就是个遍历字符串每个字符异或下标i,此处我的输入样例为'0'*24

第17位异或1:

异或下标:

脚本:

1
2
3
4
5
a = list("06360836063b0839073e0639")
a[16] = chr(ord(a[16]) ^ 1)
for i in range(24):
print(chr(ord(a[i])^i), end='')
#07154=518?9i<5=6!&!v$#%.

得到flag。

file

IDA先看main函数:

有个flllag像flag但是交上去不对,在这里只是一个字符串,用于判断,程序先读取文件内容(此处文件缺失),然后与现有的数据进行异或比较判断文件正确与否,文件md5就是flag,flllagsttr_home都能跟进找到,只需再分析sub_400EB9函数对sttr_home的加密,跟进:

用同一个函数处理两个参数再加起来,再跟进这个函数:

就是一个十六进制转数字的函数,上层函数中第一个参数的返回值乘16再加第二个参数的返回值,合在一起就是把两个十=十六进制字符转化成一个数字,至此就已分析完毕,上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
str1='664e06226625425d562e766e042d422c072c45692d125c7e6552606954646643'
str2='flag{hello_player_come_on_hahah}'
str3=''
arr1=[]
def f(c):
if 48<=ord(c)<=57:
return ord(c)-48
if(65<=ord(c)<=70):
return ord(c)-55
if(ord(c)<=96 or ord(c)>102):
return 0xffffffff
return ord(c)-87
for i in range(0,63,2):
arr1.append(16*f(str1[i])+f(str1[i+1]))
for i in range(32):
str3+=chr(ord(str2[i])^arr1[i]^i)
with open('file.txt','w') as f:
f.write(str3)

在线计算所创建文件的md5为 914a7b9df69eab5b74b9edb7070e53e8,加上flag{}就ok了。

not only smc

有个UPX的壳,shellcode里对upx进行了检测,脱壳后无法运行,就用ollydbg带壳调试,还有smc(自改变代码),下VirtualAlloc断点,找smc解密,断点会断好几次,在程序结束前最后一次断下的时候Ctrl+F9运行到返回,找到到smc解密,是取输入字符串第九位开始后面的五位反复与给定字符串异或:

因为自解密出来是一个函数,而VC下stdcall的函数头基本是固定的,像是这样:

由此可以猜测五位解密字符,反复尝试后确定是jUnk_,之后步过解密出来的函数,输入的数据会发生变化,说明函数中对输入数据进行了加密,步入分析,有很多junk code(垃圾代码),用来迷惑我们的,在输入的数据处下内存断点找到对数据进行加密的关键代码,是将输入的字符串与给定字符串一一异或:

这个函数结束后继续步过,到后面又有一个函数再次改变了输入的数据,步入分析,还是有junk code,还是靠断点找到关键位置,分析得出这是一个三重嵌套循环异或,输入字符串的不同位之间异或是两层,外层还循环了65次

总的解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ans = [0xE8, 0x90, 0x24, 0xE6, 0x0A, 0xE3, 0xF7, 0xA8, 0x09, 0xC0, 0x35, 0x74, 0x26, 0x4D, 0xA0, 0x2D,
0xD6, 0x1A, 0x5A, 0x5C, 0x16, 0x2D, 0xF0, 0x46, 0x44, 0x10, 0x5F, 0x83, 0x5B, 0xBE, 0x86, 0xAC]
key = [0xBB, 0x35, 0xAF, 0x29, 0xA3, 0x1D, 0x97, 0x11, 0x8B, 0x05, 0x7F, 0xF9, 0x73, 0xED, 0x67, 0xE1,
0x5B, 0xD5, 0x4F, 0xC9, 0x43, 0xBD, 0x37, 0xB1, 0x2B, 0xA5, 0x1F, 0x99, 0x13, 0x8D, 0x07, 0x81]
for i in range(65):
j = 0x10
while j > 0:
for k in range(j):
ans[j+k] = ans[j+k] ^ ans[k]
j = j >> 1
for i in range(32):
ans[i]^=key[i]
print(chr(ans[i]),end='')
#SMc_AnD_jUnk_C0de_1s_s0_fuunn~!

RE_Cirno

给了一张图片,下载下来WinHex打开发现有PK头,于是改后缀名为zip打开得到exe,IDA看main函数,很简单的程序,给定一个字符串,遍历字符串全部加9但是却没有保存,然后有个提示:

“9层栅栏”指栅栏密码,“反向”指逆序,先把字符串加9的结果算出来为"of}e8lhz9n~r:9J{t8p{jg|",这个东西栅栏解码后也不可能是flag,再看看汇编代码:

发现加9之后还异或了9,此处是IDA7.0伪代码处理的bug,换IDA6.8就会正常显示异或9,重新计算结果:

1
2
3
4
5
6
arr1=[115,94,97,114,103,47,107,114,65,48,49,105,117,118,101,48,113,95,99,47,92,116,93,102]
str1=''
for i in range(24):
str1+=chr((arr1[i]+9)^9)
print(str1[::-1])
#fotl1eas0gvw{30Cr}1yrcnu

这个看起来就顺眼多了,有”flag”,有花括号,也有”Cirno”,接下来就是解栅栏密码,根据开头"flag{"的格式还有提示的"9层",确定应该是3*9的栅栏,但是字符串只有24位,应该要加3位,边做边猜最后确定字符串修改为"fotl1eas0gvw{30Cr}1y_rc_nu_"解码后得到""flag{C1rno1sv3rycute0w0}___,将加上的下划线去掉得flag。

babyLoginPlus

有VM加密,IDA先看看,主函数长这样:

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
sub_401510();
sub_4013C0((int)&unk_40B0B0);
return (int)sub_401540((char *)&unk_408240, dword_40823C, 1);
}

跟进第一个函数,里面对byte_408034进行赋值,长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int sub_401510()
{
int v0; // esi
int result; // eax
char *v2; // ecx
char v3; // dl

v0 = dword_408030;
result = 0;
if ( dword_408030 > 0 )
{
v2 = (char *)&unk_408160;
do
{
v3 = *v2;
v2 += 2;
byte_408034[result++] = (v3 ^ 0x66) - 1;
}
while ( result < v0 );
}
return result;
}

值我们不用算,动态调试的时候可以看,之后第二个函数是申请内存之后进行4个拷贝,长这样:

ollydbg里面看这四个拷贝的地方长这样(最上面16个字符是输入的字符串):

在输入的字符串处下内存访问断点,一步一步找能找到程序把输入的字符串一个一个的转移到另一个地方:

跟过去下内存断点就能找到转移后对字符串进行的一系列操作,最终解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
src = [0x32, 0x26, 0x18, 0x21, 0x41, 0x23, 0x2A, 0x57, 0x44, 0x29, 0x35, 0x12, 0x20, 0x17, 0x45, 0x1C,
0x68, 0x2D, 0x7A, 0x79, 0x47, 0x7F, 0x44, 0x09, 0x1E, 0x75, 0x41, 0x2A, 0x19, 0x34, 0x76, 0x47,
0x14, 0x50, 0x52, 0x76, 0x58]
key = [0x57, 0x65, 0x6C, 0x63, 0x6F, 0x6D, 0x65, 0x5F, 0x74, 0x6F, 0x5F, 0x73, 0x64, 0x6E, 0x69, 0x73,
0x63, 0x5F, 0x32, 0x30, 0x31, 0x38, 0x5F, 0x42, 0x79, 0x2E, 0x5A, 0x65, 0x72, 0x6F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00]
flag = ''
for i in range(len(src)):
flag += chr(((src[i]-0x6) ^ 0x26 ^ key[i])+9)
print(flag)
#flag{_p1us_babyL0gin_pPpPpPpPp_p1us_}