BUUCTF逆向工程-2

相册

打开后可以找到发送邮件的函数原型:

根据交叉引用找到调用它的地方:

可以知道C2.MAILSERVER应当为邮箱,根据交叉引用可以找到:

这个函数加载了一个core文件,然后调用里面的NativeMethod.m()函数,查看它的Library:

有一个libcore.so文件,将它用IDA打开,在java_com_net_cn_NativeMethod_m()函数中可以看见:

查看base64方法中的shared可以拿到加密表:

解密得到flag:18218465125@163.com

[MRCTF2020]hello_world_go

代码写得根本看不懂在干什么,但是瞎点一通发现unk_4D3C58里就是flag:hello_world_gogogo
从结果来看应该是一个go语言,无语。。。

[WUSTCTF2020]level3

打开看见它想让我破译一个base64:

点进加密函数可以跟踪到密码表:

用这个密码表解码发现解出来是乱码…想到密码表可能被修改过,查看它的交叉引用:

写个简单的脚本得到flag:Base64_is_the_start_of_reverse

[GWCTF 2019]xxor

打开看见:

前面一段对输入进行TEA,之后判断结果,解码脚本在此:

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

void decrypt (int *v, int *k)
{
unsigned int v0 = v[0], v1 = v[1], i;
int delta = 1166789954;
int sum = 0;
for(i = 0; i < 64; i++)
{
sum += delta;
}
for (i = 0; i < 64; i++)
{
v1 -= (v0 + sum + 20) ^ ((v0 << 6) + k[2]) ^ ((v0 >> 9) + k[3]) ^ 0x10;
v0 -= (v1 + sum + 11) ^ ((v1 << 6) + k[0]) ^ ((v1 >> 9) + k[1]) ^ 0x20;
sum -= delta;
}
v[0] = v0;
v[1] = v1;
return;
}

int main()
{
int v[2] = {}, k[4] = {2,2,3,4};
unsigned int a1[6] = {-548868226, 550153460, 3774025685, 1548802262, 2652626477, -2064448480};
int i, j;
for(i = 0; i <= 2; i++)
{
v[0] = a1[i*2];
v[1] = a1[i*2+1];
decrypt(v, k);
a1[i*2] = v[0];
a1[i*2+1] = v[1];
}
for(i = 0; i <= 5; i++)
{
for(j = 2; j >= 0; j--)
{
printf("%c", *((char *)&a1[i] + j));
}
}
return 0;
}

得到flag:re_is_great!
说实话,写完这题我仍然不是很理解HIDWORD、LODWORD和ELF的逆序存储,全靠IDA动调测算出数据的存储方式,然后进行逆推,再根据输出调整顺序。。。
虽说篇幅不长,但是这个b干了我将近4个小时

[FlareOn4]IgniteMe

打开IDA,果然,C++写出来的程序最恶心了(虽然我喜欢用C++):

sub_4010F0是输入函数,sub_401050是加密函数:

其中sub401000函数里面是一个__ROL4__函数,百度了半天我也没看明白,但是IDA动调之后可以看见最后v4=4,所以也可以写出脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

using namespace std;

int main()
{
int ans[99] = {13, 38, 73, 69, 42, 23, 120, 68, 43, 108,
93, 94, 69, 18, 47, 23, 43, 68, 111, 110,
86, 9, 95, 69, 71, 115, 38, 10, 13, 19,
23, 72, 66, 1, 64, 77, 12, 2, 105};
int flag[99];
int i;
int v4 = 4;
for(i = 38; i >= 0; i--)
{
flag[i] = v4 ^ ans[i];
v4 = flag[i];
}
for(i = 0; i <= 38; i++)
{
cout << char(flag[i]);
}
return 0;
}

得出flag:R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com

[WUSTCTF2020]Cr0ssfun

没啥技术含量
flag:cpp_@nd_r3verse_@re_fun

[FlareOn6]Overlong

IDA中代码很短,理论上运行一遍就有答案:

其中unk_402008是给定的175空间的数组,但是sub_401160中对于次数组的调用最多只有28*4=112空间,因此推测所传参数28太小,所以拖入x32dbg修改对应二进制值1C为AE:

之后运行起来,即可得到结果:

得到flag:I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com

[UTCTF2020]basic-re

shift+F12:

得到flag:str1ngs_1s_y0ur_fr13nd

[FlareOn3]Challenge1

简单的变表base64,得到flag:sh00ting_phish_in_a_barrel@flare-on.com

[ACTF新生赛2020]Oruga

这是一道比较抽象的迷宫题:

由这个可以推出地图是一个16*16的空间,长这个样子:

看一看它的校验部分:

它的v2在校验中就进行了更改,而下一次循环又不会对v2进行回溯,因此它走迷宫的路线会变成直来直去的样子。一直向某个方向走到底直到遇见非空的格子,同时还不能越界,因此路线为:

得到flag:MEWEMEWJMEWJM

[BJDCTF2020]BJD hamburger competition

下载之后非常懵逼,是一个Unity的游戏,打开Visual Studio发现Unity的游戏使用C#写的,所以我们要在它给的一大坨东西里面找一个C#写的dll文件。发现在BJD hamburger competition_Data\Managed下有一个Assembly-CSharp.dll,使用dnSpy打开它,找到加密部分:

跟踪Md5(),发现它截取了答案的前20位:

写出脚本:

1
2
3
4
5
6
7
import hashlib

str = "DD01903921EA24941C26A48F2CEC24E0BB0E8CC7"
for i in range (10000000000000):
if hashlib.sha1(i.__str__().encode()).hexdigest().upper() == str:
break
print('flag{' + hashlib.md5(i.__str__().encode()).hexdigest().upper()[0:20] + '}')

得到flag:B8C37E33DEFDE51CF91E

特殊的 BASE64

简单的变表base64,不得不说,C++的逆向代码看起来确实恶心
得到flag:Special_Base64_By_Lich

[Zer0pts2020]easy strcmp

主函数内只有这些,提交发现这不是正确答案:

返回去看看init函数有没有进行一些什么操作:

发现它其实调用了一些函数,只不过是通过直接访问地址的形式调用的,双击funcs_889可以看到:

它调用了两个函数sub_6E0和函数sub_795,其中第一个没什么卵用,跟进第二个:

发现这个函数很神奇,将strcmp函数的地址传给了qword_201090,将sub_6EA函数的地址传给了off_201028,双击sub_6EA查看:

发现sub_6EA对参数做了一通操作,然后返回地址在qword_201090的函数的值,而这个函数恰好就是刚赋完值的strcmp函数。在sub_795函数里将sub_6EA函数的地址传给了off_201028,双击off_201028查看:

发现off_201028地址上的函数恰好又是strcmp函数。所以主函数里执行strcmp时其实先执行了sub_6EA函数,再执行strcmp函数,可以写出脚本:

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

using namespace std;

int main()
{
char p[99] = "zer0pts{********CENSORED********}";
long long k[4] = {0, 0x410A4335494A0942, 0x0B0EF2F50BE619F0, 0x4F0A3A064A35282B};
for (int i = 0; i < 4; i++)
{
*(long long *)&(p[i * 8]) += k[i];
}
cout << p;
}

得到flag:l3ts_m4k3_4_DETOUR_t0d4y

[ACTF新生赛2020]Universe_final_answer

是个简单的z3,此题大多数时间用在了z3的安装和调查使用教程,算出key的值之后用IDA动调一下即可:

得到flag:F0uRTy_7w@_42

[WUSTCTF2020]level4

这是个算法题,给出二叉树的中序遍历和后序遍历,求先序遍历。有一点神奇的是在IDA里静态或者动调的时候都没有找到它存放这些数据的地方…
得到flag:This_IS_A_7reE

crackMe

代码逻辑非常清晰,但是代码很长,很恶心:

先后输入用户名和密码,根据用户名在sub_401090()中创建byte_416050[],在loc_4011A0()中创建Format[]和v4[],跟进loc_4011A0()会有一个花指令,nop掉之后可以得知Format是正确提示且v3恒等于1,因此需要sub_401830()返回true,跟进sub_401830():


第一部分将password当成16进制数两两一组分割,存入v14[]。第二部分用v14[]和byte_416050[]创建v16[],最后再用v16创建v13,进行判断。根据v5<8可以得知v14[]长度为8。在sub_410470()中可以得知v16的值是“dbappsec”。跟进sub_401710():

虽然它判断了一堆情况,但是v4是用户名“welcomebeijing”的长度,v3最大为8,因此它只会执行else if的内容。将sub_401830()中第二部分内的两次反动调在汇编码中将jz改为jmp绕过,动调得到创建v16时的byte_416050[]的值。要注意在汇编码中,程序将byte_416050[v12+v7]的值传给了v12,再用v12进行异或,所以如果只看伪代码手动生成是不对的。写出脚本:、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

using namespace std;

int main()
{
char ans[99] = "dbappsec";
char name[99] = "welcomebeijing";
int XOR[99] = {0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD};
int i, j;
for(i = 0; i < 8; i++)
{
ans[i] ^= name[i];
}
for(i = 0; i < 8; i++)
{
ans[i] ^= XOR[i];
}
for(i = 0; i < 8; i++)
{
printf("%x", ans[i] & 255);
}
return 0;
}

得到密码:39d09ffa4cfcc4cc,将程序中所有反动调全部patch掉然后动调检验,或者直接运行程序会看见程序退出证明成功(因为失败会让你继续输入):

将其md5之后提交显示不正确。百度搜索得知“正确”的解答中并没有分析sub_401710(),得到密码4eb5f3992391a1ae,很明显它不正确:

得到
可以通过提交的flag:d2be2981b84f2a905669995873d6a36c
可以通过程序的flag:84ab2835640e510eb81f86e0ced4d91c

[网鼎杯 2020 青龙组]signal

这个东西滴加密比较复杂,有一张加密流程表,根据表单内容对输入进行各种赋值、异或、加减乘除。逆过来的话有一定的难度,所以,我决定请出angr:

1
2
3
4
5
6
7
8
import angr

p = angr.Project('signal.exe', auto_load_libs = False)
init_state = p.factory.entry_state()
s = p.factory.simgr(init_state)
s.explore(find = 0x0040179E, avoid = 0x00401539)

print(s.found[0].posix.dumps(0))

得到flag:757515121f3d478
超!这angr真nm好用!

[GUET-CTF2019]number_game

很奇妙的一道题:

不是很能理解为什么sub_400758()返回值明明是个指针,但在其内部却可以把指针变量赋值给整型变量。在sub_400917()内查看矩阵的样子:

得到flag:1134240024

findKey

又一个非常恶心的C++,主函数东西很少,shift+F12找到关键词“flag{}”,双击跟进发现花指令,nop掉:

之后进入真正恶心的地方,关注函数sub_40101E:

和sub_401005

sub_40101E进去发现:

0x8003u是MD5加密的特征值,因此判定整个函数用于MD5。
对于第一个sub_401005,就是将那一长串绿色的东西每一位异或‘S’,得到的东西应该为MD5之后的输入,在线解密得到123321。而第二个sub_401005并没有找到v5-492对应的值,猜测就是输入,结果证明我猜对了。附上解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

int main()
{
int key[99] = {87, 94, 82, 84, 73, 95, 1, 109, 105, 70, 2, 110, 95, 2, 108, 87, 91, 84, 76};
char ans[99] = "0kk`d1a`55k222k2a776jbfgd`06cjjb";
int i;
for(i = 0; i <= 31; i++)
{
ans[i] ^= 'S';
cout << char(ans[i]);
}
cout << endl;
char k[9] = "123321";
for(i = 0; i <= 18; i++)
{
key[i] ^= k[i % 6];
cout << char(key[i]);
}
return 0;
}

得到flag:n0_Zu0_n0_die
确实,no zuo no die,没事别碰逆向,这个b又干了我4个小时。

[羊城杯 2020]easyre

这个没啥,就是繁,encodeone进行base64,encodetwo乾坤大挪移,encodethree凯撒,逆过来就行
得到flag:672cc4778a38e80cb362987341133ea2

[网鼎杯 2020 青龙组]jocker

打开看见SMC,所以它前面的东西一定是假的,看都不用看。修改完之后得到两个被隐藏的函数encrypt()和Finally()。

encrypt()函数逻辑很清晰,用输入去异或Buffer得到unk_403040。

但是这个Finally()函数中以time(0)作为种子取随机数,也就是说,你是极大概率得不到出题人当时的情况的。经百度,这里需要你去猜,同样也是异或加密,而且是跟v2异或。

根据猜到的结果写出脚本:

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

using namespace std;

int main()
{
int ans[999] = {14, 0, 0, 0, 13, 0, 0, 0, 9, 0,
0, 0, 6, 0, 0, 0, 19, 0, 0, 0,
5, 0, 0, 0, 88, 0, 0, 0, 86, 0,
0, 0, 62, 0, 0, 0, 6, 0, 0, 0,
12, 0, 0, 0, 60, 0, 0, 0, 31, 0,
0, 0, 87, 0, 0, 0, 20, 0, 0, 0,
107, 0, 0, 0, 87, 0, 0, 0, 89, 0,
0, 0, 13};
char XOR1[99] = "hahahaha_do_you_find_me?";
char XOR2[99] = "%tp&:";
int flag[99], temp;
int i;
for(i = 0; i < 19; i++)
{
flag[i] = ans[i*4] ^ XOR1[i];
}
temp = '}' ^ ':';
for(i = 0; i < 5; i++)
{
flag[i+19] = temp ^ XOR2[i];
}
for(i = 0; i < 24; i++)
{
printf("%c", flag[i]);
}
return 0;
}

得到flag:d07abccf8a410cb37a
不是很认可这种本地打不通的题。。。

[FlareOn5]Minesweeper Championship Registration

是个java的程序,没逆过,但是拖进IDA就能得到flag:GoldenTicket2018@flare-on.com

firmware

固件逆向,不想学,看这里吧。
得到flag:33a422c45d551ac6e4756f59812a954b

[ACTF新生赛2020]SoulLike

一起来感受3000行屎山代码的美,没有含金量,flag:b0Nf|Re_LiT!

[GWCTF 2019]re3

第一次见,代码自修改问题:

在file/Script command中编写idc脚本:

转成代码并构造函数,得到被隐藏的内容:

使用findcrypt插件可以得知sub_400A71()和sub_40196E()和AES加密有关。unk_603170为秘钥。找到生成它的地方:

插件提示sub_401CF9()为md5加密。从伪代码来看秘钥只与第一和第五个md5有关,然而手动生成与动调结果并不一致。写出解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import AES
from Crypto.Util.number import *

a = [0xBC, 0x0A, 0xAD, 0xC0, 0x14, 0x7C, 0x5E, 0xCC, 0xE0, 0xB1, 0x40, 0xBC, 0x9C, 0x51, 0xD5, 0x2B, 0x46, 0xB2, 0xB9, 0x43, 0x4D, 0xE5, 0x32, 0x4B, 0xAD, 0x7F, 0xB4, 0xB3, 0x9C, 0xDB, 0x4B, 0x5B]
k = [0xCB, 0x8D, 0x49, 0x35, 0x21, 0xB4, 0x7A, 0x4C, 0xC1, 0xAE, 0x7E, 0x62, 0x22, 0x92, 0x66, 0xCE]
ans = ""
key = ""

for i in range(len(a)):
ans += "{:02x}".format(a[i])
for i in range(len(k)):
key += "{:02x}".format(k[i])
ans = int(ans, 16)
key = int(key, 16)

aes = AES.new(long_to_bytes(key), mode = AES.MODE_ECB)
flag = aes.decrypt(long_to_bytes(ans))

print(flag)

得到flag:924a9ab2163d390410d0a1f670

[GXYCTF2019]simple CPP

今天也是讨厌C++逆向的一天呢~

异或的东西可以通过动调得到

然后是

接着是

最后那一大坨考虑使用z3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from z3 import *

s = Solver()

x = BitVec('x', 64)#用Int进行不了~运算
y = BitVec('y', 64)
z = BitVec('z', 64)
w = BitVec('w', 64)

s.add(z & (~x) == 0x11204161012)
s.add(0x3E3A4717373E7F1F ^ w == 0x3E3A4717050F791F)
s.add(0x11204161012 | (x & y) | (z & (~y)) | (x & (~y)) == 0x3E3A4717373E7F1F)
s.add((z & (~y)) & x | z & ((x & y) | y & ~x | ~(y | x)) == 0x8020717153E3013)
s.add(0x11204161012 | (x & y) | y & z == ~x & z | 0xC00020130082C0C)

if s.check():
r = s.model()
for i in r:
print(i, hex(r[i].as_long()))

将得到的答案在异或回去:

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

int main()
{
long long ans[99];
char XOR[] = "i_will_check_is_debug_or_not";
long long i, j, flag[99];
ans[1] = 865872043546520588;
ans[0] = 4483973367147818765;
ans[3] = 842073600;
ans[2] = 577031497978884115;
for(i = 0; i < 4; i++)
{
if(i == 3)
{
for(j = 0; j < 4; j++)
{
flag[i*8+j] = (ans[i] & 0xFF000000) >> 24;
ans[i] = ans[i] << 8;
}
}
else
{
for(j = 0; j < 8; j++)
{
flag[i*8+j] = (ans[i] & 0xFF00000000000000) >> 56;
ans[i] = ans[i] << 8;
}
}
}
for(i = 0; i < 27; i++)
{
flag[i] ^= XOR[i];
printf("%c", flag[i]);
}
return 0;
}

然后就可以得到乱码We1l_D0ndaQbg�_Slgebra_am_i
经百度,是题错了,z3解出来不止一组解。挺神奇的,5个条件约束不住4个数。
得到flag:We1l_D0ne!P0or_algebra_am_i

[FlareOn5]Ultimate Minesweeper

get了一个dnSpy的新玩法。拖进去容易找到加密函数:

但是仔细看一看发现它用revealedCells作为种子生成了一个随机数数组array,之后在加密输出。因此想法是动调,查看一下会让我们失败的函数:

根据英文提示,第一个if就是失败判定,把它右键、编辑方法,给它注释掉:

右下角编译之后Ctrl+shift+s保存为新程序,运行新程序,找到三个非雷地块:

之后再在正常的程序里点击这三个地块,得到flag:Ch3aters_Alw4ys_W1n@flare-on.com

[MRCTF2020]PixelShooter

附件是一个apk,但是里面有很多unity的关键字,所以去找Assembly-CSharp.dll:

导出后拖进dnSpy,在UIControler方法中找到:

得到flag:Unity_1S_Fun_233

[FlareOn1]Bob Doge

又是一个C#的程序,拖入dnSpy,容易找到加密函数:

发现需要寻找dat_secret,可惜找不到(话说,这玩意儿是怎么藏起来的?),所以准备动调:

在数据栏中得到flag:3rmahg3rd.b0b.d0ge@flare-on.com

[2019红帽杯]xx

红帽杯的题总能让我眼前一黑
首先判断输入位数为19位,然后判断输入的值是不是在特定字符串里。

然后构造key,发现key保留了输入的前4个,其余的赋0,再将其转为4个32位数用于XXTEA。然后你需要坚定不移地猜输入的前4个就是"flag"。XXTEA的代码是真心没看懂,但好在他是标准的。通过动调可以知道如果输入为1234,在XXTEA中input会变成34333231,解密时要注意大小端序。

对加密结果进行打乱,并且进行了一个离谱的异或。解异或需要从后往前解。

最后进行对比,这种赋值方式也会产生大小端序的问题,算不过来的话…动调吧!骚年!

写出脚本:

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
#include <stdio.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void XXTEA(unsigned int *v, int n, unsigned int key[4])
{
unsigned int y, z, sum;
unsigned p, rounds, e;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}

int main()
{
int ans[99] = {206, 188, 64, 107, 124, 58, 149, 192, 239, 155, 32, 32, 145, 247, 2, 53, 35, 24, 2, 200, 231, 86, 86, 250}, enc[99];
int v21 = 23, v22 = 23, i, j;
for(; v21 >= 1; v22--)
{
int v23 = 0;
if(v21 / 3 > 0)
{
do
{
ans[v22] ^= ans[v23];
v23++;
} while (v23 < v21 / 3);
}
v21--;
}
enc[2] = ans[0];
enc[0] = ans[1];
enc[3] = ans[2];
enc[1] = ans[3];
enc[6] = ans[4];
enc[4] = ans[5];
enc[7] = ans[6];
enc[5] = ans[7];
enc[10] = ans[8];
enc[8] = ans[9];
enc[11] = ans[10];
enc[9] = ans[11];
enc[14] = ans[12];
enc[12] = ans[13];
enc[15] = ans[14];
enc[13] = ans[15];
enc[18] = ans[16];
enc[16] = ans[17];
enc[19] = ans[18];
enc[17] = ans[19];
enc[22] = ans[20];
enc[20] = ans[21];
enc[23] = ans[22];
enc[21] = ans[23];
unsigned int flag[99];
for(i = 0; i < 6; i++)
{
for(j = 3; j >= 0; j--)
{
flag[i] = (flag[i] << 8) + enc[i*4+j];
}
}
unsigned int key[4] = {0x67616c66, 0, 0, 0};
XXTEA(flag, 6, key);
for(i = 0; i < 6; i++)
{
for(j = 0; j < 4; j++)
{
printf("%c", flag[i] & 0xFF);
flag[i] >>= 8;
}
}
return 0;
}

得到flag:CXX_and_++tea

[CFI-CTF 2018]IntroToPE

这道题就比较水了,打开找到判断函数:

可以看出,应当使validatePassword.verifyPassword()返回真值,双击跟踪:

看见是一个base64,理论上应该接着去找加密使用的对应表,可惜没找到,就用默认的吧。
得到flag:.NetC#_1s_@w3s0m3

equation

非常sb的一道题,先jsfuck,再z3。
得到flag:A_l0ng_10NG_eqU4Ti0n_1s_E4Sy_W1Th_z3