[toc]

加花指令程序分析

image-20230310191852772

程序功能

​ 根据解除花指令,反编译后的代码分析可知,该程序获取用户的一个输入,然后对该输入进行三次运算,每一次都是先将输入与0x1453异或,然后左移一位,得到最后的结果. 如果这个结果等于50138则输出win.

花指令分析

首先,在函数列表里没有main函数,但是汇编里面有,说明main函数没有被正确反汇编,这里有问题,

在main前面按p解析函数发现报错,

1
2
3
4
5
6
7
8
9
10
11
12


.text:0000000000001257: The function has undefined instruction/data at the specified address.
Your request has been put in the autoanalysis queue.

.text:0000000000001253 jz short label2
.text:0000000000001255 jnz short label2
.text:0000000000001255 ; ---------------------------------------------------------------------------
.text:0000000000001257 db 0E9h



可以发现前面有 jz和jnz,肯定不会执行到1257,1257这里是花指令,改成nop即可

edit - patch program - change word 把E9 改成90 (0x90也就是nop,什么也不执行,往下走 即可)

image-20230310193522710

此时这里还是代表数据,用快捷键c转换成代码,然后再在开头用p转换成函数即可

然后就可以F5反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
v4 = 0;
printf("Input a number: ");
__isoc99_scanf("%d", &v4);
if ( (unsigned int)encrypt(v4) == 50138 )
printf("win");
return 0;
}

可以开到,关键函数是encrypt,进入这里,发现这里面也有问题,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall encrypt(unsigned int a1)
{
if ( a1 > 0x1000 )
exit(0);
JUMPOUT(0x11ECLL);
}


.text:00000000000011EB loc_11EB: ; CODE XREF: encrypt+58↓j
.text:00000000000011EB ; encrypt:loc_11EB↑j
.text:00000000000011EB EB FF jmp short near ptr loc_11EB+1 ; Jump
.text:00000000000011ED ; ---------------------------------------------------------------------------
.text:00000000000011ED C0 FF C8 sar bh, 0C8h ; Shift Arithmetic Right
.text:00000000000011F0 8B 45 FC mov eax, [rbp+var_4]
.text:00000000000011F3 31 45 EC xor [rbp+var_14], eax ; Logical Exclusive OR
.text:00000000000011F6 D1 45 EC rol [rbp+var_14], 1 ; Rotate Left
.text:00000000000011F9 83 45 F8 01 add [rbp+var_8], 1 ; Add

​ 通过gdb动态调试发现, jmp short near ptr loc_11EB+1 是要跳到00000000000011EC这里,(动态运行的话是0x5555555551ec,要加上装载地址),然后往下解析, 但是在ida里面,由于这条指令的存在,它会从00000000000011ED往后解析,(不太清楚这里面的机制原理),而jump到000011EC这里就jump错了地址,所以反编译有问题.

1
2
3
4
5
6
7
8
9
10
11
  0x5555555551fd <encrypt+84>    cmp    dword ptr [rbp - 8], 2
0x555555555201 <encrypt+88> jle encrypt+66 <encrypt+66>

0x5555555551eb <encrypt+66> jmp encrypt+67 <encrypt+67>

0x5555555551ec <encrypt+67> inc eax
0x5555555551ee <encrypt+69> dec eax
0x5555555551f0 <encrypt+71> mov eax, dword ptr [rbp - 4]
0x5555555551f3 <encrypt+74> xor dword ptr [rbp - 0x14], eax
0x5555555551f6 <encrypt+77> rol dword ptr [rbp - 0x14], 1
0x5555555551f9 <encrypt+80> add dword ptr [rbp - 8], 1

​ 所以要想解决的话,有多个办法,一个是,jmp 到0000011ED就可以了,将FF改成00,也就再往前走一步,这样就可以了,第二个办法是都改成nop就可以了,让指令往下滑.

​ 反编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall encrypt(unsigned int a1)
{
char v1; // bh
unsigned int v2; // [rsp+14h] [rbp-14h]
int i; // [rsp+20h] [rbp-8h]

v2 = a1;
if ( a1 > 0x1000 )
exit(0);
for ( i = 0; i <= 2; ++i )
{
v1 >>= 7;
v2 = __ROL4__(v2 ^ 0x1453, 1);
}
}

​ a1是输入的值,首先a1不能大于4096, 然后v1右移7位,v2 等于 v2和0x1453异或后 左移1位,( rol dword ptr [rbp - 0x14], 1)

​ 最后的结果是50138,逆向推导,右移一位,异或0x1453,这个操作做三次,就得到了答案789

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main()
{
int input = 50138;
input >>= 1;
input = input ^ 0x1453;
input >>= 1;
input = input ^ 0x1453;
input >>= 1;
input = input ^ 0x1453;
printf("%d",input);
return 0;
}

运行截图

image-20230310200841386

re1 asm

​ 得到一个asm.s文件,汇编文件,查看可以知道是由test.c汇编而来,可以选择硬读汇编来解,也可以进行编译成二进制文件后,再反汇编反编译成伪代码进行查看.

​ gcc asm.s 得到二进制文件 a.out,然后拖进ida进行反编译,得到结果

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
if ( a == 29488 )
return puts("you win");
else
return puts("you lose");
}

re2 bits

概要

​ 这是一道ctf的逆向题目,给了二进制文件和远程服务器连接方式.二进制文件是elf 64位,通过给程序一个正确的输入,当验证输入正确的时候,会从flag文件中读取flag.flag文件在远程服务器上,所以在本地调试好后要和服务器进行交互拿到flag.

​ 先对程序进行了逆向分析,对算法进行逆向编写,编写过程比较困难,最后失败.采取爆破的方法,利用angr工具求解.

image-20230310200230541

程序分析

程序伪代码

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
__pid_t v3; // eax
__pid_t v4; // ebx
int v5; // eax
unsigned int v6; // [rsp+0h] [rbp-40h] BYREF
unsigned int v7; // [rsp+4h] [rbp-3Ch]
unsigned int size; // [rsp+8h] [rbp-38h]
unsigned int size_4; // [rsp+Ch] [rbp-34h]
void *ptr; // [rsp+10h] [rbp-30h]
void *s; // [rsp+18h] [rbp-28h]
FILE *stream; // [rsp+20h] [rbp-20h]
unsigned __int64 v13; // [rsp+28h] [rbp-18h]

v13 = __readfsqword(0x28u);
if ( ptrace(PTRACE_TRACEME, 0LL, 1LL, 0LL) < 0 )
{
puts("Do not trace me!");
exit(0);
}
v3 = getpid();
v4 = getsid(v3);
if ( v4 != getppid() )
{
puts("Do not trace me!");
exit(1);
}
ptr = 0LL;
s = 0LL;
v7 = sub_BFA();
stream = fopen("code", "rb");
if ( !stream )
{
fwrite("Could not open file code!\n", 1uLL, 0x1AuLL, stderr);
exit(1);
}
fseek(stream, 0LL, 2);
size = ftell(stream);
fseek(stream, 0LL, 0);
ptr = malloc(size);
if ( fread(ptr, size, 1uLL, stream) != 1 )
{
fwrite("Error reading file code\n", 1uLL, 0x18uLL, stderr);
exit(1);
}
size_4 = sub_C31(v7, size, (__int64)ptr);
printf("Your number is %u\n", size_4);
printf("Your answer: ");
__isoc99_scanf("%d", &v6);
v5 = sub_C31(v6, size, (__int64)ptr);
if ( size_4 == v5 )
{
puts("Congrats!");
s = malloc(0x40uLL);
memset(s, 0, 0x40uLL);
stream = fopen("flag.txt", "rb");
fgets((char *)s, 64, stream);
puts((const char *)s);
free(s);
exit(0);
}
puts("Thanks for coming!");
free(ptr);
exit(0);
}
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
__int64 __fastcall sub_C31(unsigned int a1, int size, __int64 ptr)
{
int v3; // eax
int v4; // eax
unsigned __int8 v7; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v8; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v9; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v10; // [rsp+13h] [rbp-1Dh]
unsigned __int8 v11; // [rsp+13h] [rbp-1Dh]
char v12; // [rsp+13h] [rbp-1Dh]
int v13; // [rsp+14h] [rbp-1Ch]
unsigned int v14; // [rsp+18h] [rbp-18h]
unsigned int v15; // [rsp+1Ch] [rbp-14h]
int v16; // [rsp+20h] [rbp-10h]
int v17; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]

v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
for ( i = 0; i < size; ++i )
{
v7 = *(_BYTE *)(i + ptr);
if ( (v7 & 1) != 0 )
{
a1 ^= dword_202020[v13];
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v8 = v7 >> 1;
v3 = v8 & 3;
if ( v3 == 2 )
{
v15 = dword_202020[v13] & 0xAABBCCDD;
v13 = ((_BYTE)v13 + 1) & 0xF;
v9 = v8 >> 2;
}
else if ( v3 == 3 )
{
a1 += v14 + v15;
v15 = 0;
v14 = 0;
v9 = v8 >> 2;
}
else
{
if ( v3 == 1 )
{
v14 = dword_202020[v13] | 0xABCDABCD;
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v9 = v8 >> 2;
}
if ( (v9 & 1) != 0 )
a1 = ~a1;
v10 = v9 >> 1;
if ( (v10 & 1) != 0 )
a1 ^= (((a1 << 16) ^ a1) >> 16) ^ (a1 << 16);
v11 = v10 >> 1;
v4 = v11 & 3;
if ( v4 == 2 )
{
v17 = dword_202020[v13] - 539034144;
v13 = ((_BYTE)v13 + 1) & 0xF;
v12 = v11 >> 2;
}
else if ( v4 == 3 )
{
a1 += v16 + v17;
v17 = 0;
v16 = 0;
v12 = v11 >> 2;
}
else
{
if ( v4 == 1 )
{
v16 = 539034132 * dword_202020[v13];
v13 = ((_BYTE)v13 + 1) & 0xF;
}
v12 = v11 >> 2;
}
if ( (v12 & 1) != 0 )
a1 = a1 - (a1 & 7) - (a1 & 7) + 7;
}
return a1;
}

程序执行流程分析

image-20230310210811794

​ 关键流程在于,随机数v7和code经过计算后输出结果.需要根据结果反推随机值.

程序关键算法分析

​ sub_C31函数对code和随机值v7进行计算,运算过程较为复杂,包含了许多位移运算,与或运算.流程较长.采用直接逆向的方法较为困难.所以采用了angr爆破的方法.

工具使用

ida

​ ida用于静态分析,得到反汇编代码.

gdb

​ gdb用于动态分析,在编写利用代码以及分析程序时,可以观察运行到某一行代码时的内存、寄存器等值和状态.

angr

​ angr是一个基于python的二进制分析框架,能够实施动态符号执行,和不同的静态分析.对本题目来说,angr可以对key的各种约束条件进行求解,从而爆破得到key的值.

解题思路

减少循环、获取key的完整计算过程

​ angr对于循环的处理较为复杂和缓慢,所以可以通过在混淆代码中加入输出key的运算过程的代码,得到完整的运算流程.

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int hunxiao(int randnum, int size,int *ptr)
{
int v3; // eax
int v4; // eax
int v7; // [rsp+13h] [rbp-1Dh]
int v8; // [rsp+13h] [rbp-1Dh]
int v9; // [rsp+13h] [rbp-1Dh]
int v10; // [rsp+13h] [rbp-1Dh]
int v11; // [rsp+13h] [rbp-1Dh]
char v12; // [rsp+13h] [rbp-1Dh]
int v13; // [rsp+14h] [rbp-1Ch]
int v14; // [rsp+18h] [rbp-18h]
int v15; // [rsp+1Ch] [rbp-14h]
int v16; // [rsp+20h] [rbp-10h]
int v17; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]

v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
int dword_202020[16] = {0x24DD20CF, 0x3E4F0354, 0x18B2E85F, 0x2F2CAFB8, 0x5810ADCB,0x42F7FF85, 0x36E0D6C2, 0x5F3EF93F, 0x7F46E74A, 0x44DDC864,0x64959795, 0x39413451, 0x5DC36C45, 0x62037E7E, 0x5AEA541F,0x153F8FAC};

printf("hunxiao里面的输出%p\n",*ptr); //这里输出的有问题
printf("hunxiao里面的输出%p\n",*ptr); //这里输出的有问题
for ( i = 0; i < 2; ++i )
{
//v7 = ptr[i];
v7 = *((unsigned char*)(i + (unsigned long long)ptr));
//printf("%02x\n", *(ptr + i));
printf("v7的值:%x\n",v7);
//printf("v7 & 1的值%x\n",v7&1);
// 看code的每个字节的第一位是否是0,如果是0进行下面操作,
if ( (v7 & 1) != 0 )
{
randnum ^= dword_202020[v13];
//printf("randnum:%x\n",randnum);
v13 = ((unsigned char)v13 + 1) & 0xF;//这句话的作用是把它范围限制在0-15位
//printf("v13:%x\n",v13);
}
v8 = v7 >> 1; //code字节右移一位,
//printf("v8:%x\n",v8);
v3 = v8 & 3; // & 11,把v3限制在两位
// 然后根据v3的值进行选择分支运算
if ( v3 == 1 )
{
v14 = dword_202020[v13] | 0xABCDABCD;
v13 = ((unsigned char)v13 + 1) & 0xF;//这句话的作用是把它范围限制在0-15位
}
if ( v3 == 2 )
{
v15 = dword_202020[v13] & 0xAABBCCDD;
v13 = ((unsigned char)v13 + 1) & 0xF;//这句话的作用是把它范围限制在0-15位
}
if ( v3 == 3 )
{
randnum += v14 + v15;
v15 = 0;
v14 = 0;
}

v9 = v8 >> 2; //三个判断里都有,提出来

//上面那一层判断完之后,再到下面,继续进行乱七八糟的操作
if ( (v9 & 1) != 0 )
randnum = ~randnum;
v10 = v9 >> 1;
if ( (v10 & 1) != 0 )
randnum ^= (((randnum << 16) ^ randnum) >> 16) ^ (randnum << 16);

// 这一段和上面那个好像
v11 = v10 >> 1;
v4 = v11 & 3;

if ( v4 == 1 )
{
v16 = 539034132 * dword_202020[v13]; //0x20210214
v13 = ((unsigned char)v13 + 1) & 0xF;//这句话的作用是把它范围限制在0-15位
}
if ( v4 == 2 )
{
v17 = dword_202020[v13] - 539034144; //0x20210220
v13 = ((unsigned char)v13 + 1) & 0xF;//这句话的作用是把它范围限制在0-15位
v12 = v11 >> 2;
}
if ( v4 == 3 )
{
randnum += v16 + v17;
v17 = 0;
v16 = 0;
v12 = v11 >> 2;
}


v12 = v11 >> 2;
if ( (v12 & 1) != 0 )
randnum = randnum - (randnum & 7) - (randnum & 7) + 7;
}
return randnum;
}


int main()
{

void *s;
FILE *stream;
int size;
void *ptr;
int size_4;
int v0 = time(0);
srand(v0);
int v2 = rand();
//printf("%d\n",v2);
stream = fopen("./code","rb");
fseek(stream, 0, 2);
size = ftell(stream);
fseek(stream, 0, 0);
printf("%s\n",stream);
printf("size:%d\n",size);
ptr = malloc(size);
if ( fread(ptr, size, 1, stream) != 1 )
{
fwrite("Error reading file code\n", 1, 0x18, stderr);
exit(1);
}

printf("在外面的输出:%x\n",&ptr); //这里输出的有问题
printf("ptr:%p\n",ptr);
printf("&ptr:%p\n",&ptr); //&ptr的地址,里面存着ptr的地址,ptr地址里面存着数据
size_4 = hunxiao(v2,size,ptr);

return 0;

}

​ 运行代码,得到key的处理代码

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
key ^= dword_202020[0];
key=~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key ^= dword_202020[3];
key = key - (key & 7) - (key & 7) + 7;
key=~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 1256252113;
key ^= dword_202020[6];
key += 636006435;
key += 0;
key ^= dword_202020[7];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[10];
key=~key;
key += 1908961232;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[12];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 0;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[14];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[1];
key += 3791846473;
key=~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key=~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 3767795326;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[7];
key += 371756133;
key=~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[8];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;

编写key计算代码

​ 需要替换y值

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
#include <stdio.h>

unsigned int dword_202020[16] = {
0x24DD20CF, 0x3E4F0354, 0x18B2E85F, 0x2F2CAFB8, 0x5810ADCB, 0x42F7FF85, 0x36E0D6C2, 0x5F3EF93F,
0x7F46E74A, 0x44DDC864, 0x64959795, 0x39413451, 0x5DC36C45, 0x62037E7E, 0x5AEA541F, 0x153F8FAC};

int calc(unsigned int key)
{
key ^= dword_202020[0];
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key ^= dword_202020[3];
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 1256252113;
key ^= dword_202020[6];
key += 636006435;
key += 0;
key ^= dword_202020[7];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[10];
key = ~key;
key += 1908961232;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[12];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 0;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[14];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[1];
key += 3791846473;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 3767795326;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[7];
key += 371756133;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[8];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
return key;
}

int main(void) {
unsigned int x = 0;
unsigned int y = 0;
scanf("%u", &x);
y = calc(x);
if (y == 730447668) { //**比赛实际服务器返回的number**)
printf("right\n");
} else {
printf("wrong\n");
}
}

​ gcc -o f fk.c 得到f二进制文件

angr脚本,得到随机值

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
import angr
import claripy
import sys

def main():
print(" solving :", sys.argv[1])
p = angr.Project(sys.argv[1], load_options={"auto_load_libs": True})
state = p.factory.entry_state()

sm = p.factory.simgr(state)

def good(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'right'.encode() in stdout_output # :boolean

def bad(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'wrong'.encode() in stdout_output
sm.explore(find=good, avoid=bad)

if sm.found:
# print(sm.found[0].solver.eval(flag, cast_to=bytes)
# print(sm.found[0].posix.dumps(sys.stdout.fileno())
print(sm.found[0].posix.dumps(sys.stdin.fileno()))
else:
print("Not found")
print("Done")
if __name__ == '__main__':
main()

image-20230310213131937

参考资料

(一开始做了好久,硬逆向,发现确实不太好弄…本来想放弃了…搜了搜有原题..就参考了(抄)了一下)

https://www.52pojie.cn/thread-1717425-1-1.html

https://docs.angr.io