BUUCTF 逆向合集

前面很容易就解出来的题我就没放上来,都已经做过这么多题了,相信对你们来说也是so easy。

1. SimpleRev

可能做题做多了脑子抽了,这道题其实很容易结果我好久都没解出来。好吧,那你就是第1题啦。

ELF文件,载入IDA。

但长长整型转换的字符串是小端存储的,所以还要逆序!!这个超级容易错,我出来的flag不对就是因为这个!!

由于逆向v1有点困难,所以我们就不逆向算法了,直接将ASCII码表里所有字符按照原本算法进行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
text = list("killshadow")
key = list("ADSFKNDCLS")
ans = ""
v5 = len(key)
v3 = 0
for i in range(v5):
if key[v3%v5]>'@' and key[v3%v5]<='Z':
key[i] = chr(ord(key[v3%v5]) + 32)
v3 += 1
print(key[i],end="")
print()
for i in range(v5):
for j in range(32,128):
if (j>64 and j<91) or (j>96 and j<123):
if (j-39-ord(key[v3%v5])+97)%26+97 == ord(text[i]):
v3 += 1
ans += chr(j)
break
print(ans)
'''
adsfkndcls
KLDQCUDFZO
'''
1
flag{KLDQCUDFZO}

2. luck_guy

无壳,64位ELF,载入IDA分析一下。随着main函数进入关键函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 将f2串加上0x,再放入列表
tmp_list = []
index = 0
while index < 15:
tmp = eval('0x' + f2[index:index+2])
tmp_list.append(tmp)
index += 2

for i in range(len(tmp_list)):
if i % 2 == 1:
tmp_list[i] = tmp_list[i] - 2
else:
tmp_list[i] = tmp_list[i] - 1
ans += chr(tmp_list[i])
print(ans)
'''
GXY{do_not_hate_me}
'''
1
flag{do_not_hate_me}

3. 刮开有奖

v4解出来是jMp,v5解出来是WP1

1
2
3
4
5
6
String[2] = 'W'
String[3] = 'P'
String[4] = '1'
String[5] = 'j'
String[6] = 'M'
String[7] = 'p'

接下来算String[0]和String[1],发现如果直接代入上面的值,却得String[0]=|,String[1]=C,String[2]=a,String[3]=@。所以v7~v16应该是做了某些变换。sub_4010F0就是做变换的函数。

进去看它代码,有do…while…语句和递归,由于我用Python一直出错,可能是我逻辑出现了问题,所以最后还是用C照搬它的代码吧。

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
#include <stdio.h>
int sub_4010F0(char * a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx

result = a3;
for (i = a2; i <= a3; a2 = i)
{
v5 = i;
v6 = a1[i];
if (a2 < result && i < result)
{
do
{
if (v6 > a1[result])
{
if (i >= result)
break;
++i;
a1[v5] = a1[result];
if (i >= result)
break;
while (a1[i] <= v6)
{
if (++i >= result)
goto LABEL_13;
}
if (i >= result)
break;
v5 = i;
a1[result] = a1[i];
}
--result;
} while (i < result);
}
LABEL_13:
a1[result] = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}

int main()
{
char a1[12] = { 90,74,83,69,67,97,78,72,51,110,103 };
printf("%s\n", a1);
sub_4010F0(a1, 0, 10);
printf("%s", a1);
}
/**
ZJSECaNH3ng
3CEHJNSZagn
*/

呜呜呜不得不说,C真香啊,弄了我好久的Python,C一下就出来了,气人!

1
2
3
4
String[0] = '3' + 34 = 'U'
String[1] = 'J'
String[2] = 'W'
String[3] = 'P'

验证一下,String[2]和String[3]与上面Base64解密解出来的是一致的,所以是正确答案。

1
flag{UJWP1jMp}

4. pyre

下载下来是.pyc文件,这个是 Python 程序编译后得到的字节码文件,找一个python反编译的网站即可转化为python代码。

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
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
num = ((input1[i] + i) % 128 + 128) % 128
code += num

for i in range(l - 1):
code[i] = code[i] ^ code[i + 1]

print code
code = [
'\x1f',
'\x12',
'\x1d',
'(',
'0',
'4',
'\x01',
'\x06',
'\x14',
'4',
',',
'\x1b',
'U',
'?',
'o',
'6',
'*',
':',
'\x01',
'D',
';',
'%',
'\x13']

我们要做的工作就是将它逆向回去。

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
import string
code = [
'\x1f',
'\x12',
'\x1d',
'(',
'0',
'4',
'\x01',
'\x06',
'\x14',
'4',
',',
'\x1b',
'U',
'?',
'o',
'6',
'*',
':',
'\x01',
'D',
';',
'%',
'\x13']
l = len(code)
for i in range(l-2,0,-1):
code[i] = chr(ord(code[i]) ^ ord(code[i + 1]))
input = string.printable
ans = ""
for i in range(l):
for j in input:
if code[i] == chr(((ord(j) + i) % 128 + 128) % 128):
ans += j
print (ans)
'''
WHT{Just_Re_1s_Ha66y!}
'''
1
flag{Just_Re_1s_Ha66y!}

5. rsa

一道像密码题的逆向题。解压出来有两个文件,一个公钥文件和一个加密文件。

rsa第一种解法,通过解析公钥获得n和e,再将n分解成p和q,然后用Python中的rsa和gmpy2库解密。

rsa第二种解法,用RsaCtfTool直接解。kali安装RsaCtfTool,安装好后进入其目录在终端输入命令:python3 RsaCtfTool.py --publickey pub.key --uncipherfile flag.enc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──(v5le0n9㉿kali)-[~/RsaCtfTool]
└─$ python3 RsaCtfTool.py --publickey pub.key --uncipherfile flag.enc 1 ⨯
private argument is not set, the private key will not be displayed, even if recovered.

[*] Testing key pub.key.
[*] Performing system_primes_gcd attack on pub.key.
100%|███████████████████████████████| 7007/7007 [00:00<00:00, 1370394.86it/s]
[*] Performing fibonacci_gcd attack on pub.key.
100%|████████████████████████████████| 9999/9999 [00:00<00:00, 302473.41it/s]
[*] Performing pastctfprimes attack on pub.key.
100%|█████████████████████████████████| 113/113 [00:00<00:00, 2246238.64it/s]
[*] Performing smallq attack on pub.key.
[*] Performing factordb attack on pub.key.
[*] Attack success with factordb method !

Results for pub.key:

Unciphered data :
HEX : 0x00029d207b7a521e08e4e6180600666c61677b646563727970745f3235367d0a
INT (big endian) : 4618144028027957675862906963888332345633248954043303780331531906089123082
INT (little endian) : 4744358497414744401850218354568232353073084770228403473305939615805528146432
utf-16 : Ȁ₝穻Ṓᣦ汦条摻捥祲瑰㉟㘵੽
STR : b'\x00\x02\x9d {zR\x1e\x08\xe4\xe6\x18\x06\x00flag{decrypt_256}\n'
1
flag{decrypt_256}

6. CrackRTF

查看一下CryptCreateHash函数原型:

1
2
3
4
5
6
7
BOOL CRYPTFUNC CryptCreateHash( 
HCRYPTPROV hProv, //句柄
ALG_ID Algid, //算法id
HCRYPTKEY hKey, //必须为0
DWORD dwFlags, //标志值
HCRYPTHASH* phHash //存储哈希值的地址
);

Algid在这里查:https://docs.microsoft.com/en-us/windows/win32/seccrypto/alg-id ,发现0x8004是SHA1加密算法。

此时可以通过Python的hashlib包对前6位进行爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib
flag2 = "@DBApp"
# 字符串转换为整型大于100000,长度为6,所以最大为999999
# atoi只有字符串全为数字才能转换
for i in range(100000,999999):
h2 = hashlib.sha1((str(i)+flag2).encode())
flags = h2.hexdigest()
# hashlib解密后是小写,所以要转换为小写
if "6E32D0943418C2C33385BC35A1470250DD8923A9".lower() == flags:
print (str(i)+flag2)
'''
123321@DBApp
'''

所以前6位为123321。继续分析密码2。

进入401019,又是加密函数,0x8003是MD5加密。但这时不能用上面方法解决了,因为它的范围太大了,不只是数字组合还有字母组合还有数字字母组合,所以爆破这方法不可行。

我们看到还调用了一个40100F的函数,可以进去看看。

既然需要知道资源数据,我们在吾爱破解培训第三课里就用过很多查看或修改资源的工具,比如:Restorator、ResEdit、ResourceHacker等。

我们用ResourceHacker打开exe文件,找到AAA类型的资源数据。

整个字符串一共18位,但后12位我们已经知道了,所以只要异或前6位即可。资源数据前6位为05 7D 41 15 26 01。而rtf文件的前6位是什么呢?我们已经知道CreateFileA新建一个0字节的文件,也就是说rtf的文件头也是我们写入的,而头文件是文件最开始的数据,所以我们需要rtf文件头的前6位。

创建一个.rtf文件,在里面写点东西。拖进WinHex查看文件头。(不写直接拖进WinHex为空,任何文件任何格式创建了不写都是如此)

那么现在可以编写逆向脚本了:

1
2
3
4
5
6
7
8
9
rtf = [0x7b,0x5c,0x72,0x74,0x66,0x31]
res = [0x05,0x7d,0x41,0x15,0x26,0x01]
ans = ""
for i in range(len(rtf)):
ans += chr(rtf[i] ^ res[i])
print(ans)
'''
~!3a@0
'''

所以这6位应该为~!3a@0。运行程序,依次输入密码1和密码2,会在当前目录生成一个dbapp.rtf文件,打开就是flag。

1
2
Flag{N0_M0re_Free_Bugs}
flag{N0_M0re_Free_Bugs}

7. login

这道题应该算是Web逆向吧?

1
2
3
4
5
6
7
8
9
if c <= 'Z':
tmp = 90
else:
tmp = 122
c2 = ord(c) + 13
if tmp >= c2:
result = c2
else:
result = c2 - 26

不知不觉,get到遍历可打印字符的香了。

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
import string
flag = list("PyvragFvqrYbtvafNerRnfl@syner-ba.pbz")
ans = flag
input = string.printable
for i in range(len(flag)):
for j in input:
if (j <= 'Z' and j >= 'A') or (j <= 'z' and j >= 'a'):
if j <= 'Z':
tmp = 90
else:
tmp = 122
j2 = ord(j) + 13
if tmp >= j2:
result = j2
else:
result = j2 - 26
if chr(result) == flag[i]:
ans[i] = j
break
for i in range(len(ans)):
print(ans[i],end="")
print()
'''
ClientSideLoginsAreEasy@flare-on.com
'''
1
flag{ClientSideLoginsAreEasy@flare-on.com}

8. easyRE

下载下来是ELF文件,直接载入IDA看看吧。

太多函数的话直接找字符串,大家都会了吧?不会寄几拣生,懒得写了。

我们算出v18后再加上3个0字符也进行base64算法10次试试,发现与off_6CC090内存中的数据不一样!!

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
import base64
flag = list("Iodl>Qnb(ocy")
flag.append(127)
flag.extend("y.i")
flag.append(127)
flag.extend("d`3w}wek9{iy=~yL@EC")
v18 = flag
for i in range(len(flag)):
if isinstance(flag[i],int):
v18[i] = chr(flag[i] ^ i)
else:
v18[i] = chr(ord(flag[i]) ^ i)
print(v18[i],end="")
print()
v18.append('0')
v18.append('0')
v18.append('0')
str=''.join(v18)
print(str)
bs = base64.b64encode(str.encode("utf-8"))
for i in range(9):
bs = base64.b64encode(bs)
print(bs)
'''
Info:The first four chars are `flag`
Info:The first four chars are `flag`000
b'Vm0wd2QyVkZOVWRXV0doVVYwZDRWRmx0ZEhkVU1WcDBUVmM1VjFadGVIbFhhMk0xVmpGYWRHVkdiR0ZXVjJoeVdWZDRZV014WkhGUmJVWlhWakZLU1ZkV1dsWmxSbGw0V2toV2FGSnNjSEJXTUdSdlpWWmtWMWR0ZEZSTlZUVklWbTAxUjFWdFNrZFhiR2hhWWtkU2RsWldXbXRYUjFKSVVteG9hVlpyV1hwV1JscGhWakZrU0ZOcmFGWmlSMmhoV1d0YWQxUkdjRmRYYkhCc1VtMVNNRnBGV2xOVWJGcDFVV3h3VjFZemFIWmFSRXBIVWpGT2RWWnNTbWhsYlhob1YxZDBhMkl5VW5OV2JrNVlZbGhTV0ZSV1pEQk9iR3hXVjJzNVZXSkdjRlpXYlhSelZqRmFSbUV6YUZkaGEzQklWbXBHVDFkWFRrZFRiV3hvWld4YVdsWXhaREJaVm14V1RVaG9hbEpYYUhOVmFrNVRWMFphZEdONlJsaGlSM2hYVmpJeE1HRkdXbkppZWtwYVYwaENSRll3V2xwbGJGWnpZVVp3YUdFeGNIbFdWRUpoVkRKU1YxUnVTbEJXYlZKUFZXMDFRMWRzV1hoWGJYUk9VakZHTkZZeWRHdGhWazVIVTI1T1ZtSllUWGhXTUZwelkyeGtkRkp0ZUZkaVJsa3hWa1phVTFFeVJrWk5XRTVZWW0xb1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=='
'''

好吧思路错了,v21与v18根本没关系,那我们将内存中的数据解码10次看看是什么。

1
2
3
4
5
6
7
8
import base64
bs = 'Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=='
for i in range(10):
bs = str(base64.b64decode(bs), "utf-8")
print(bs)
'''
https://bbs.pediy.com/thread-254172.htm
'''

竟然是一个网址。写主动防御。听君一席话,胜读十年书。所以怎么找呢?这么多函数坑死我了。找有运算操作的,最后在4009C6下面一个函数400D35找到了有fg字样的东西,进行某些运算。

1
2
3
4
5
6
7
8
9
10
11
v1 = list("flag")
b6cc = [0x40,0x35,0x20,0x56,0x5d,0x18,0x22,0x45,0x17,0x2f,0x24,0x6e,0x62,0x3c,0x27,0x54,0x48,0x6c,0x24,0x6e,0x72,0x3c,0x32,0x45,0x5b]
ans = ""
for i in range(4):
v1[i] = ord(v1[i]) ^ b6cc[i]
for i in range(len(b6cc)):
ans += chr(b6cc[i] ^ v1[i%4])
print(ans)
'''
flag{Act1ve_Defen5e_Test}
'''

9. [GUET-CTF2019]re

ELF文件,有UPX壳。ELF脱UPX壳在Linux下执行脱壳命令与Windows一致。

1
2
3
4
5
6
7
8
9
10
v5le0n9@ubuntu:~/Desktop$ upx -d re
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013

File size Ratio Format Name
-------------------- ------ ----------- -----------
841042 <- 304524 36.21% linux/ElfAMD re

Unpacked 1 file.
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
ans = ""
ans += chr(166163712//1629056)
ans += chr(731332800//6771600)
ans += chr(357245568//3682944)
ans += chr(1074393000//10431000)
ans += chr(489211344//3977328)
ans += chr(518971936//5138336)
ans += " "
ans += chr(406741500//7532250)
ans += chr(294236496//5551632)
ans += chr(177305856//3409728)
ans += chr(650683500//13013670)
ans += chr(298351053//6088797)
ans += chr(386348487//7884663)
ans += chr(438258597//8944053)
ans += chr(249527520//5198490)
ans += chr(445362764//4544518)
ans += chr(981182160//10115280)
ans += chr(174988800//3645600)
ans += chr(493042704//9667504)
ans += chr(257493600//5364450)
ans += chr(767478780//13464540)
ans += chr(312840624//5488432)
ans += chr(1404511500//14479500)
ans += chr(316139670//6451830)
ans += chr(619005024//6252576)
ans += chr(372641472//7763364)
ans += chr(373693320//7327320)
ans += chr(498266640//8741520)
ans += chr(452465676//8871876)
ans += chr(208422720//4086720)
ans += chr(515592000//9374400)
ans += chr(719890500//5759124)
print(ans)
'''
flag{e 65421110ba03099a1c039337}
'''

牛牛们说缺的那个是字符1,我在想难道是IDA优化问题导致的?还是说出题人就想我们爆破?

1
flag{e165421110ba03099a1c039337}

10. [SUCTF2019]SignIn

ELF文件,载入IDA。

发现了什么姐妹们,有幂模运算,也有RSA标志性公钥65537,这还不是RSA加密算法??RSA算法描述与攻击方法

1
2
3
n = v4 = 103461035900816914121390101299049044413950405173712170434161686539878160984549
e = v5 = 65537
c = v7 = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35

将n拿去 http://factordb.com 分解一下,发现可以分解,得出p和q的值。

1
2
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419

然后用RSA解密脚本直接得出私钥d,进而得到明文m。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import gmpy2
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
e = 65537

d = gmpy2.invert(e,(p-1)*(q-1))
print ("d = " + str(d))
#d为私钥
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
m = gmpy2.powmod(c,d,n)
print("m = " + str(m))
'''
d = 91646299298871237857836940212608056141193465208586711901499120163393577626813
m = 185534734614696481020381637136165435809958101675798337848243069
'''

下一步就是进入sub_96A函数查看算法。

所以这个函数很有可能是字符串转换成十六进制,因为我们输入的就是字符串形式,返回值v9是以十六进制的形式存入v6的。将m转换为字符串:

1
2
3
4
5
6
7
import binascii
m = 185534734614696481020381637136165435809958101675798337848243069
# 先将它转化为16进制再去掉开头的0x,unhexlify与a2b_hex功能一致,作用是从16进制字符串中返回二进制数据,再用utf-8解码
print(binascii.unhexlify(hex(m)[2:]).decode(encoding="utf-8"))
'''
suctf{Pwn_@_hundred_years}
'''

11. Transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
flag = [0x67,0x79,0x7b,0x7f,0x75,0x2b,0x3c,0x52,0x53,0x79,0x57,0x5e,0x5d,0x42,0x7b,0x2d,0x2a,0x66,0x42,0x7e,0x4c,0x57,0x79,0x41,0x6b,0x7e,0x65,0x3c,0x5c,0x45,0x6f,0x62,0x4d]
qiao = [9,0xa,0xf,0x17,7,0x18,0x0c,6,1,0x10,3,0x11,0x20,0x1d,0x0b,0x1e,0x1b,0x16,4,0x0d,0x13,0x14,0x15,2,0x19,5,0x1f,8,0x12,0x1a,0x1c,0x0e,0]
ans = [0 for x in range(0,33)] #定义列表长度并初始化为0
st = [0 for x in range(0,33)]
for i in range(len(flag)):
ans[i] = flag[i] ^ qiao[i]
print(chr(ans[i]),end="")
print()
for i in range(len(ans)):
# qiao的元素是st的下标,其实就是IDA那一行换过来赋值,我可能是困了在这里绕了好久!!
st[qiao[i]] = ans[i]
for i in range(len(st)):
print(chr(st[i]),end="")
print()
'''
nsthr30TRiTO}_p31pFs_ClCr{z4N_slM
MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}
'''
1
flag{Tr4nsp0sltiON_Clph3r_1s_3z}

12. usualCrypt

进去sub_401080看看。又有一个初始化函数,然后进行base64加密操作,最后的返回值也调用了一个函数。

进去初始化函数,发现是将base64索引表的某些元素调换位置。

进去返回值调用的函数。

分析牛牛的脚本,我看懂了。

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
import base64
# 大小写转换
secret = 'zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9'.swapcase()
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
dict = {}
offset = 10
flag = ''

# 初始化字典的键值对都是它本身
for i in range(len(a)):
dict[a[i]] = a[i]

# 构造键不变,值变的字典
for i in range(6,15):
b = dict[a[i]]
dict[a[i]] = dict[a[i+offset]]
dict[a[i+offset]] = b

# 用secret的每个元素作为字典的键取字典的值,将secret转换为正常的base64加密字符串
for i in range(len(secret)):
flag += dict[secret[i]]

flag = base64.b64decode(flag)
print(flag)
'''
flag{bAse64_h2s_a_Surprise}
'''

13. Youngter-drive

运行发现打不开,缺少msvcr100d.dll,从网上下载一个放在同目录下就好,现在可以打开了。有UPX壳,直接脱壳机脱掉。脱壳后程序又打不开了。

不管,我们直接载入IDA分析代码。

先看第一个线程。

再看第二个。

查看411190。

整理下思路,可以确定输入字符串的长度为30,这两个线程是同步的,所以进行一次41112C函数要自减2,也就是输入的字符是间隔调用41112C的。但不知道哪个线程先执行,所以都要试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
result = "TOiZiZtOrYaToUwPnToBsOaOapsyS"
flag = ''
f418 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"

# 下标为双数进行操作
for i in range(len(result)):
if i % 2 == 1:
flag += result[i]
continue

# 如果是大写字母,一定是小写字母转换来的
if(result[i].isupper()):
flag += chr(f418.find(result[i]) + 96)
else:
flag += chr(f418.find(result[i]) + 38)
print (flag)
'''
dOGZGZDOCYJTHUAPXTHBKOJOJpKyk
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
result = "TOiZiZtOrYaToUwPnToBsOaOapsyS"
flag = ''
f418 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"

# 下标为单数进行操作
for i in range(len(result)):
if i % 2 == 0:
flag += result[i]
continue
if(result[i].isupper()):
flag += chr(f418.find(result[i]) + 96)
else:
flag += chr(f418.find(result[i]) + 38)
print (flag)
'''
ThisisthreadofwindowshahaIsES
'''

第二个flag是有意义的,所以应该是第二个。但长度为29,最后一位只能爆破,爆破出来是E

1
flag{ThisisthreadofwindowshahaIsESE}

14. [HDCTF2019]Maze

有UPX壳,脱壳后运行,正常。载入IDA,发现main函数部分不能反编译。那拿去OD爆破一下看看,发现我们输入的字符串就是flag。

在OD查找关键字符串,发现有一个可有可无的跳转。(其实在IDA中main函数那里的jnz附近有标红,也可以知道这附近是有问题的)

将修改好的程序保存,载入IDA发现main函数可以反编译了。这里其实是用到了花指令,我们刚才所做的就是去除花指令。

题目名字,迷宫,很熟悉啦,大家应该都玩过类似的题。玩过小游戏的就知道,wsad表示上下左右。

但是迷宫地图在哪呢?去到字符串窗口发现,一串*号,但没有哪个函数调用它,我们只能猜测它的行数和列数。最后发现它7行10列时看起来比较像迷宫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flag = "*******+********* ******    ****   ******* **F******    **************"
print(len(flag))
for i in range(0,70,10):
ans = ""
for j in range(10):
ans += flag[i+j]
print(ans)
'''
70
*******+**
******* **
**** **
** *****
** **F****
** ****
**********
'''

按照+为起点,F为终点,可以得到ssaaasaassdddw

1
flag{ssaaasaassdddw}

15. [MRCTF2020]Xor

但我找了一下,IDA就没有401095啊?随便点进几个函数后发现又可以F5了,这里不知道是什么原因。

如果实在不能F5,那就老老实实读汇编吧。

剩下就很简单了,直接写脚本。这里只是想记录一下为什么会出现这个错?求解惑。

1
2
3
4
5
6
7
8
flag = list('MSAWB~FXZ:J:`tQJ"N@ bpdd}8g')
ans = ''
for i in range(len(flag)):
ans += chr(ord(flag[i]) ^ i)
print(ans)
'''
MRCTF{@_R3@1ly_E2_R3verse!}
'''
1
flag{@_R3@1ly_E2_R3verse!}

16. [GWCTF 2019]xxor

1
2
3
4
5
6
7
# 求解三元一次方程组
from sympy import *
x,y,z = symbols('x,y,z')
print(solve([x-y-2225223423,y+z-4201428739,x-z-1121399208],[x,y,z]))
'''
{x: 3774025685, y: 1548802262, z: 2652626477}
'''

那么现在v7数组中的所有元素都已知了。

1
2
3
4
5
6
v7[0] = 3746099070	# 将负数转化为16进制再转为10进制变正数
v7[1] = 550153460
v7[2] = 3374025685
v7[3] = 1548802262
v7[4] = 2652626477
v7[5] = 2230518816

回看sub_400686,将它的算法逆回来,循环中它是连加,那我们就连减。

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
a2 = [2,2,3,4]
a1 = [3746099070,550153460,3374025685,1548802262,2652626477,2230518816]

for j in range(0,5,2):
v3 = a1[j]
v4 = a1[j+1]
v5 = 1166789954 * 64
for i in range(64):
v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10
v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + a2[0]) ^ ((v4 >> 9) + a2[1]) ^ 0x20
v5 -= 1166789954
a1[j] = v3
a1[j+1] = v4

for i in range(6):
print(a1[i])
print()
'''
4094640321151510381430114527348791340818061486368325081105118858029441421392562115899927051354373143479918602379843527326422590896702063205097735983800064780954056157519958676446188144354842905191184000047384119685662681315026347071556102162

-64846407269888040321797137717063369418274550862165842691382879936447707289304081311831415851335266644244860582121374108524030247661708208453235916273041741260734859775013491470888710031344340472282583536018181016610582990270536331206408833

3626154157263275744924041982703855211085590162096727318779868232410757794884022528128551059600370555282067352751792979104390903963503421941488321742737873865096405783997624394295188802763521349460701601867796734712493771114831851042253589698

-55775144830965060265952775552624430801605611614179956213691056963158136715255700275463473676424802814041829568498889655173123370495054229651227352476227137023578229841739490925121109830438369699146048932715237138347095053098321988593808508

5587372600709315909307412143935466921910496550659392214787792118052869573959065029961759081338383226145709070679113041271660083414962147047524219462376386239236674131323547659492256250592476561092722517194615155328289506636964049781303051329

-86739435205112991582038868144090858872150758616294322376667291065375318440446215889691830034827200180020765008148649882034652663397413325576631432190050276236816651479339637604954866146349956713033299076850370554519685898575638020305197992
'''

救命这什么东西!!回看400686函数,发现v3和v4是无符号整型,但Python没有无符号整型。所以这个逆向算法最好用C写。

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
#include <stdio.h>
int main()
{
unsigned int a2[4] = { 2, 2, 3, 4 };
unsigned int a1[6] = { 3746099070,550153460,3374025685,1548802262,2652626477,2230518816 };
int i, j;
unsigned int v3, v4;
for (j = 0; j <= 4; j += 2)
{
v3 = a1[j];
v4 = a1[j + 1];
int v5 = 1166789954 * 64;
for (i = 0;i < 64;i++)
{
v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + a2[0]) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
v5 -= 1166789954;
}
a1[j] = v3;
a1[j + 1] = v4;

}
for (i = 0;i < 6;i++)
printf("%x", a1[i]);
}
/**
666c61677b72c2106b1c38d08e1772656174217d
*/
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>
int main()
{
unsigned int xorm[6];
xorm[0] = 3746099070;
xorm[1] = 550153460;
xorm[2] = 3774025685;
xorm[3] = 1548802262;
xorm[4] = 2652626477;
xorm[5] = 2230518816;
int i = 0, j = 0, sum;
unsigned int temp[2] = { 0 };
unsigned int data[4] = { 2,2,3,4 };
for (i = 0; i < 5; i += 2)
{
temp[0] = xorm[i];
temp[1] = xorm[i + 1];

sum = 0x458BCD42 * 64;
for (j = 0; j < 64; j++)
{
temp[1] -= (temp[0] + sum + 20) ^ ((temp[0] << 6) + 3) ^ ((temp[0] >> 9) + 4) ^ 0x10;
temp[0] -= (temp[1] + sum + 11) ^ ((temp[1] << 6) + 2) ^ ((temp[1] >> 9) + 2) ^ 0x20;
sum -= 0x458BCD42;
}
xorm[i] = temp[0];
xorm[i + 1] = temp[1];
}
for (i = 0; i < 6; i++)
printf("%x", xorm[i]);
}
/**
666c61677b72655f69735f6772656174217d
*/

我真是日了狗了,上面的是我写的,下面有别人写的,究竟哪里不一样了?!为什么第二次结果就不同呢?!!!最后拿去在线16进制转文本的网站得到flag。

Python我看到有位牛牛写出来了:

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
from Crypto.Util.number import long_to_bytes

# ctypes包调用C语言的数据类型
from ctypes import *
v6=[3746099070,550153460,3774025685,1548802262,2652626477,2230518816]
unk=[2,2,3,4]
for i in range(0,5,2):

# v5是int型,v3和v4是unsigned int型
v5 = c_int(1166789954*0x40)
v3 = c_uint(v6[i])
v4 = c_uint(v6[i+1])
for j in range(0x40):

# 一定要加.value,因为它们类型不一样不能相加
v4.value -= ((v3.value+v5.value+20)^((v3.value<<6)+unk[2])^((v3.value>>9)+unk[3])^0x10)
v3.value -= ((v4.value+v5.value+11)^((v4.value<<6)+unk[0])^((v4.value>>9)+unk[1])^0x20)
v5.value -= 1166789954
v6[i] = v3.value
v6[i+1] = v4.value

for k in range(6):
# 进行long转byte的转换
print(long_to_bytes(v6[k]).decode(),end='')
print()
'''
flag{re_is_great!}
'''

17. hello_world_go

这题告诉我们一定要有耐心,尤其是我做完第16题在发疯的边缘完全是不能思考问题的。载入IDA,看到非常乱的代码,非常多函数,不要慌。找main函数,将main函数调用的内存先看了,如果特别的,再看main函数调用的函数。

找呀找,最终找到这个内存数据,进去看看。

发现就是flag。

1
flag{hello_world_gogogo}

18. level3

载入IDA,查看源码。

但发现它确实是变种的,一定是哪个函数给索引表的某些元素调换位置了。

那在IDA找找,发现这个是个多么显眼的函数。

按照它的算法将base64索引表替换。可利用第12题脚本进行解密。

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
import base64
secret = 'd2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD=='
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
dict = {}
offset = 10
flag = ''

# 初始化字典的键值对都是它本身
for i in range(len(a)):
dict[a[i]] = a[i]

# 构造键不变,值变的字典
for i in range(10):
b = dict[a[i]]
dict[a[i]] = dict[a[19-i]]
dict[a[19-i]] = b

# 用secret的每个元素作为字典的键取字典的值,将secret转换为正常的base64加密字符串
for i in range(len(secret)):
flag += dict[secret[i]]

flag = base64.b64decode(flag).decode()
print(flag)
'''
wctf2020{Base64_is_the_start_of_reverse}
'''
1
flag{Base64_is_the_start_of_reverse}

19. IgniteMe

无壳,载入IDA看看。

那v4的初始值是多少呢?小凉第一次尝试IDA动态调试Windows程序啦!

Debugger -> Select debugger -> Local Windows debugger,F9开启调试,在这里下断点,在程序里输入随意,回车,发现IDA停在断点处。此时,eax的值为4。

也就是说v4的初始值为4。我太low了,思路理不清结果要画图才理得清。

那么现在可以把逆向脚本写出来了:

1
2
3
4
5
6
7
8
9
10
11
12
b403 = [0x0d,0x26,0x49,0x45,0x2a,0x17,0x78,0x44,0x2b,0x6c,0x5d,0x5e,0x45,0x12,0x2f,0x17,0x2b,0x44,0x6f,0x6e,0x56,0x9,0x5f,0x45,0x47,0x73,0x26,0x0a,0x0d,0x13,0x17,0x48,0x42,0x1,0x40,0x4d,0x0c,0x2,0x69]
v4 = 4
for i in range(len(b403)-1,0,-1):
if i == len(b403)-1:
b403[i] = b403[i] ^ v4
b403[i-1] = b403[i] ^ b403[i-1]
for i in range(len(b403)):
print(chr(b403[i]),end="")
print()
'''
R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com
'''
1
flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}

20. Overlong

运行程序,只有一个提示框。载入IDA,查看只有几个函数。

进入内存数据发现它从08开始一直到b7,一共AF个字节。我们载入OD,将它的长度从1C修改为AF。

但我们会发现修改后会覆盖下面的指令,所以我们要用到内嵌补丁。内嵌补丁的知识可以看调试器使用教程 内嵌补丁部分

哎嘿!答案不就出来了?

1
flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}

21. BJD hamburger competition

恕我打开一脸懵逼。最后我选择看wp。这个是Unity游戏,没错,一眼看出。原来Unity游戏有个很大的漏洞啊喂!Unity游戏逆向及破解方法介绍

看完上面那篇文章,我们都知道应该找Assembly-CSharp.dll文件。

找到它拿去dnSpy反编译,不会用dnSpy?这不就来教程了。.Net逆向教程

搜索游戏中的关键词,找到Spawn方法。

真的非常简单,将那一串字符串用SHA1解密后再用MD5加密,转换为大写字母取前20位即可得到flag。

1
flag{B8C37E33DEFDE51CF91E}

22. Oruga

去到Hex窗口查看。

所以输入应为MEWEMEWJMEWJM

1
flag{MEWEMEWJMEWJM}

23. easy strcmp

构造逆向脚本:

1
2
3
4
5
6
7
8
9
b201 = [0x42,9,0x4a,0x49,0x35,0x43,0x0a,0x41,0xf0,0x19,0xe6,0x0b,0xf5,0xf2,0x0e,0x0b,0x2b,0x28,0x35,0x4a,6,0x3a,0x0a,0x4f]
flag = list("********CENSORED********")
ans = ''
for i in range(len(flag)):
ans += chr(b201[i] + ord(flag[i]))
print(ans)
'''
l3ts_m4kij^Ĵ^ńńSOUR_t0d4y
'''

发现前一段和后一段都是正常的、有意义的字符串,但中间那段乱码了。尝试用汇编代码分析一下。

这也太难看了吧,而且也只有中间那段代码出现了问题,难道我脚本错了?利用binascii模块来写脚本。

有关binascii模块的主要函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
import binascii
data = 'consumer'

# 把数据换成二进制数据然后再用十六进制字符串表示
b = binascii.b2a_hex(data.encode()) #bin to ascii hex
print(b)
#结果:
#b'636f6e73756d6572'

#十六进制字符格式换成原数据
print(binascii.a2b_hex(b)) #ascii hex to bin
#结果:
#b'consumer'

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import binascii

enc = "********CENSORED********"
key = [0x410A4335494A0942, 0x0B0EF2F50BE619F0, 0x4F0A3A064A35282B]
flag = b'' #创建一个字节型变量

for i in range(len(key)):
p = enc[i * 8:(i + 1) * 8][::-1] # 因为key是小端存储,所以enc要逆序过来进行运算
a = binascii.b2a_hex(p.encode('ascii')) # 将字符串转换成二进制形式再用十六进制字符串表示
b = binascii.a2b_hex(hex(int(a, 16) + key[i])[2:])[::-1] # 将a转换成十进制与key相加后再转换成十六进制形式,去掉前缀0x,转换成字节数据,再逆序输出
flag += b
print(flag.decode())
'''
l3ts_m4k3_4_DETOUR_t0d4y
'''

所以我上面那个脚本为什么错了啊?我搞不懂,牛牛救命!