0%

buuctf-reverse write-ups (1)

buu001-easyre

直接用IDA打开,main函数里面就是。

buu002-reverse1

用IDA打开通过输出字符串定位到输入部分,flag在字符串中直接就有。

buu003-reverse2

用IDA打开,这是一个Linux ELF文件,在main函数中首先把flag里面的i和r替换成1就行了。

buu004-内涵的软件

用IDA打开,把DBAPP改成flag就行了。

buu005-新年快乐

这道题是使用了UPX进行了压缩加壳,只需要用UPX工具解压即可得到flag。

UPX使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS E:\...\upx-4.0.2-win64> .\upx.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2023
UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023

Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file..

Commands:
-1 compress faster -9 compress better
-d decompress -l list compressed file
-t test compressed file -V display version number
-h give more help -L display software license
Options:
-q be quiet -v be verbose
-oFILE write output to 'FILE'
-f force compression of suspicious files
-k keep backup files
file.. executables to (de)compress

Type 'upx --help' for more detailed help.

UPX comes with ABSOLUTELY NO WARRANTY; for details visit https://upx.github.io

buu006-xor

这道题进行了一个简单的异或操作,将前一个字符与后一个字符异或。

解密脚本:

1
2
3
4
5
6
7
cipher = '\x66\x0A\x6B\x0C\x77\x26\x4F\x2E\x40\x11\x78\x0D\x5A\x3B\x55\x11\x70\x19\x46\x1F\x76\x22\x4D\x23\x44\x0E\x67\x06\x68\x0F\x47\x32\x4F'
plaintext = ['f']

for i in range(len(cipher) - 1):
plaintext.append(chr(ord(cipher[i]) ^ ord(cipher[i+1])))

print(''.join(plaintext))

buu007-helloword

这道题是一个apk文件的逆向,可以使用Jadx的Intellij插件进行反编译,直接获得flag。

buu008-reverse3

这道题是一个base64编码程序,实际上不需要对代码进行全部逆向分析,只需要通过动态调试即可得知。在编码之后,程序还进行了另一种处理:对索引为x的字节的ASCII码加x,然后与事先保存的字符串比较。可写出下列解密程序:

1
2
3
4
5
6
7
8
9
10
11
import base64

cipher = 'e3nifIH9b_C@n@dH'
dec_1 = []

for i in range(len(cipher)):
dec_1.append(chr(ord(cipher[i]) - i))

dec_1 = ''.join(dec_1)

print(base64.b64decode(dec_1))

buu009-不一样的flag

走迷宫。按照0的路线走即可。

1
2
3
4
5
#1111
01000
01010
00010
1111#

buu010-SimpleRev

一个简单的转换。python脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
key = 'adsfkndcls'
text = 'killshadow'
answer = [] * 10
k = 10
for i in range(10):
for j in range(128):
if not (0x40 < j <= 0x40 + 26 or 0x60 < j <= 0x60 + 26):
continue
if chr((j - 39 - ord(key[k % 10]) + 97) % 26 + 97) == text[i] and (0x40 < j <= 0x40+26):
answer.append(chr(j))
k += 1
break
print(''.join(answer))

buu011-Java逆向解密

用Intellij反编译class文件,一个简单的字节加密。

buu012-[GXYCTF2019]luck_guy

脚本:

1
2
3
4
5
6
7
8
9
former = 'GXY{do_not_'
x = 'icug`of\x7F'
t = [] * 8
for i in range(len(x)):
if(i % 2 == 1):
t.append(chr(ord(x[i]) - 2))
else:
t.append(chr(ord(x[i]) - 1))
print(former + ''.join(t))

buu013-[BJDCTF2020]JustRE

要点19999次才能获得flag,用Shift+F12直接找到flag。

buu014-刮开有奖

这道题的DialogFunc函数里面有一个sub_4010F0函数,里面对一个长度为10的数组进行了处理,直接把伪代码拷到Dev里面执行得到结果,下面还有一个是base64编码,然后可以根据判断条件把flag拼出来。

buu015-简单注册器

直接用jadx打开,把flag生成的代码直接复制到Java执行即可。

buu016-[GWCTF 2019]pyre

这是一个pyc的python字节码逆向,安装undecompyle进行反编译。
undecompyle x.pyc > x.py
然后逆向解密即可。

buu017-[ACTF新生赛2020]easyre

又是一个UPX加壳,直接脱壳逆向。

1
2
3
4
5
6
7
8
9
x = "*F'\"N,\"(I?+@"
data = '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$# !"'
answer = ['A', 'C', 'T', 'F', '{']
for i in range(12):
for j in range(1, len(data) + 1):
if x[i] == data[j - 1]:
answer.append(chr(j))
answer.append('}')
print(''.join(answer))

buu018-findit

jadx反编译。

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
public class main {  
public static void main(String[] args) {
final char[] a = {'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e'};
final char[] b = {'p', 'v', 'k', 'q', '{', 'm', '1', '6', '4', '6', '7', '5', '2', '6', '2', '0', '3', '3', 'l', '4', 'm', '4', '9', 'l', 'n', 'p', '7', 'p', '9', 'm', 'n', 'k', '2', '8', 'k', '7', '5', '}'};
char[] x = new char[17];
char[] y = new char[38];
for (int i = 0; i < 17; i++) {
if ((a[i] < 'I' && a[i] >= 'A') || (a[i] < 'i' && a[i] >= 'a')) {
x[i] = (char) (a[i] + 18);
} else if ((a[i] < 'A' || a[i] > 'Z') && (a[i] < 'a' || a[i] > 'z')) {
x[i] = a[i];
} else {
x[i] = (char) (a[i] - '\b');
}
}
for (int i2 = 0; i2 < 38; i2++) {
if ((b[i2] < 'A' || b[i2] > 'Z') && (b[i2] < 'a' || b[i2] > 'z')) {
y[i2] = b[i2];
} else {
y[i2] = (char) (b[i2] + 16);
if ((y[i2] > 'Z' && y[i2] < 'a') || y[i2] >= 'z') {
y[i2] = (char) (y[i2] - 26);
}
}
}
System.out.println(y);
}
}

buu019-rsa

一道crypto的题,用yafu可以分解他给的公钥,然后解密。

buu020-[ACTF新生赛2020]rome

首先用UPX脱壳,然后逆向就行了,就是一个凯撒加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tar = 'Qsw3sj_lz4_Ujw@l'
answer = []
for i in range(16):
for j in range(128):
k = 0
if 64 < j <= 90:
k = (j - 51) % 26 + 65
elif 96 < j <= 122:
k = (j - 79) % 26 + 97
else:
k = j
if k == ord(tar[i]):
answer.append(chr(j))
break
print(''.join(answer))

buu021-[FlareOn4]login

还是凯撒加密,不过是JS代码审计。位移13位。

buu022-CrackRTF

这题进行了2次哈希,第1次是SHA哈希,第2次是MD5哈希。第1次只能输入整数,第2次输入不限,对第2次输入的暴力可能需要的时间比较长。

buu023-[WUSTCTF2020]level1

直接逆,不解释了。

1
2
3
4
5
6
7
output = [198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570, 92160, 1200, 565248, 756, 1474560, 800, 6291456, 1782, 65536000]
for i in range(len(output)):
if i & 1 == 0:
output[i] >>= i+1
else:
output[i] //= i+1
print(''.join([chr(o) for o in output]))

buu024-[GUET-CTF2019]re

这题可以使用Detect It Easy工具检测到UPX加壳,然后脱壳逆向。
不知道为什么flag里面有一个字节不给,还得爆破。
flag{e165421110ba03099a1c039337}

buu025-[2019红帽杯]easyRE

这题是一个Linux静态编译的文件,关键就是要理清楚一些库函数的功能。

1
2
3
4
5
6
7
8
9
while ( v3 < v5 - 2 )
{
*(_BYTE *)(v7 + v3) = alphabet[(unsigned __int8)a1[v4] >> 2];
*(_BYTE *)(v7 + v3 + 1LL) = alphabet[(16 * (a1[v4] & 3)) | ((unsigned __int8)a1[v4 + 1] >> 4)];
*(_BYTE *)(v7 + v3 + 2LL) = alphabet[(4 * (a1[v4 + 1] & 0xF)) | ((unsigned __int8)a1[v4 + 2] >> 6)];
*(_BYTE *)(v7 + v3 + 3LL) = alphabet[a1[v4 + 2] & 0x3F];
v4 += 3;
v3 += 4;
}

sub_0x400E44函数里面找到上面这块代码,明显能看出是用来base64编码的,因此改名为base64_encode

然后在main函数里面找到这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 v18 = __readfsqword(0x28u);
qmemcpy(secret, "Iodl>Qnb(ocy", 12);
secret[12] = 0x7F;
qmemcpy(&secret[13], "y.i", 3);
secret[16] = 0x7F;
qmemcpy(&secret[17], "d`3w}wek9{iy=~yL@EC", 19);
memset(input, 0, sizeof(input));
read(0LL, input, 0x25uLL);
input[36] = 0;
LODWORD(v0) = strlen(input);
if ( v0 == 36 )
{
for ( i = 0; ; ++i )
{
LODWORD(v1) = strlen(input);
if ( i >= v1 )
break;
if ( (unsigned __int8)(input[i] ^ i) != secret[i] )
goto LABEL_9;
}
...
}

就是一个字符串解密,用下面的脚本进行解密:

1
2
3
4
5
6
7
secret = 'Iodl>Qnb(ocy\x7fy.i\x7fd`3w}wek9{iy=~yL@EC'
plaintext = ''

for i in range(len(secret)):
plaintext += chr(ord(secret[i]) ^ i)

print(plaintext)

得到字符串为:Info:The first four chars are flag

这个是第一步,然后还有第二步,第二步看似是一个base64,编码编了10层之后出来一个网址,但这个网址是骗人的,flag在其他的地方。

https://bbs.kanxue.com/thread-254172.htm

在0x6CC0A0处还发现了一个可疑的东西,可能与flag有关系。这个东西在sub_400D35里面被引用了。

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
unsigned __int64 sub_400D35()
{
unsigned __int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-24h]
int i; // [rsp+10h] [rbp-20h]
int j; // [rsp+14h] [rbp-1Ch]
unsigned int v4; // [rsp+24h] [rbp-Ch]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v1 = time(0LL) - qword_6CEE38;
for ( i = 0; i <= 1233; ++i )
{
sub_40F790(v1);
sub_40FE60();
sub_40FE60();
v1 = sub_40FE60() ^ 0x98765432;
}
v4 = v1;
if ( ((unsigned __int8)v1 ^ real_secret[0]) == 'f' && (HIBYTE(v4) ^ real_secret[3]) == 'g' )
{
for ( j = 0; j <= 24; ++j )
sub_410E90((unsigned __int8)(real_secret[j] ^ *((_BYTE *)&v4 + j % 4)));
}
result = __readfsqword(0x28u) ^ v5;
if ( result )
sub_444020();
return result;
}

这里面判断了第一个字母和第四个字母,由此可以发现这个加密的原理。

1
2
3
4
5
6
7
8
__int64 sub_4009AE()
{
__int64 result; // rax

result = time(0LL);
qword_6CEE38 = result;
return result;
}

qword_6CEE38这个变量只在两个地方被引用,发现其值就是time(0),因此v1的值应该是0才对。实际上我们根本就不用管上面的逻辑,看if语句就可以知道v1和v4的值应该是什么。

v1=0x26,HIBYTE(v4)=0x31。

后面有个for循环,以4个字节为循环,将real_secret中的内容与v4异或。因为前4个字节是flag,所以v4的值实际上可以获取到,为0x31415926,还是个挺吉利的数字。然后我们就可以写脚本拿到真正的flag了:

1
2
3
4
5
6
7
8
secret = '@5 V]\x18"E\x17/$nb<\x27THl$nr<2E['
mask = '\x26\x59\x41\x31'
plaintext = ''

for i in range(len(secret)):
plaintext += chr(ord(secret[i]) ^ ord(mask[i%4]))

print(plaintext)

拿到flag:flag{Act1ve_Defen5e_Test}

buu026-[MRCTF2020]Transform

这题逻辑挺简单,就是一个简单的加密:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Str[104]; // [rsp+20h] [rbp-70h] BYREF
int j; // [rsp+88h] [rbp-8h]
int i; // [rsp+8Ch] [rbp-4h]

sub_402230();
printf("Give me your code:\n");
scanf("%s", Str);
if ( strlen(Str) != 33 )
{
printf("Wrong!\n");
system("pause");
exit(0);
}
for ( i = 0; i <= 32; ++i )
{
input[i] = Str[mask[i]];
input[i] ^= LOBYTE(mask[i]);
}
for ( j = 0; j <= 32; ++j )
{
if ( cipher[j] != input[j] )
{
printf("Wrong!\n");
system("pause");
exit(0);
}
}
printf("Right!Good Job!\n");
printf("Here is your flag: %s\n", Str);
system("pause");
return 0;
}

直接上脚本:

1
2
3
4
5
6
7
8
9
10
11
mask = [9, 0x0a, 0x0f, 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]

cipher = 'gy{\x7fu+<RSyW^]B{-*fB~LWyAk~e<\\EobM'
plaintext = ['a'] * len(mask)

for i in range(len(mask)):
c = ord(cipher[i])
c ^= mask[i]
plaintext[mask[i]] = chr(c)

print(''.join(plaintext))

结果MRCTF{TrEnsp0sltiON_Clph3r_1s_3z},buu平台要改成flag开头。

buu027-[WUSTCTF2020]level2

这题用detect it easy扫出来也是upx加壳的,脱一下就拿到了。

buu028-[SUCTF2019]SignIn

这题就是纯纯的密码题,把输入转成大数i,给e、m、r,求i使得(i^e)%m=r。复习一下残缺不全的密码学知识…

根据欧拉定理:xφ(y)1(mody)x^{\varphi(y)}\equiv 1\pmod y

模数的两个因数分别为282164587459512124844245113950593348271366669102002966856876605669837014229419

ed1(mod(u1)(v1))ed\equiv 1\pmod {(u-1)(v-1)},用扩展欧几里得来算。

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

if __name__ == '__main__':
m = 103461035900816914121390101299049044413950405173712170434161686539878160984549
u = 282164587459512124844245113950593348271
v = 366669102002966856876605669837014229419
e = 65537
cipher = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35

d = gmpy2.invert(e, (u-1)*(v-1))
print(d)
print(bytes.fromhex(hex(pow(cipher, d, m))[2:]).decode())

suctf{Pwn_@_hundred_years}

FSSID:HIGDDEN_HOHTPOT PPASS:L0GIC_ANA1YSIS_CAN_FOR_FUN FSSID:HIGDDEN_HOHTPOT PPASS:L0QGIC_ANAR1YSIS_CSAN_FOR_TFUN

buu029-[ACTF新生赛2020]usualCrypt

这题实现了一个类似于base64的加密算法,3个字节输入,4个字节输出。从输出可知一共有27字节输入。进行类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
28
29
30
31
32
33
34
35
36
37
38
cipher = 'zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9'
alphabet = 'ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/'
plaintext = ''

def find_char(c):
for i in range(len(alphabet)):
if c == alphabet[i]:
return i
return -1

if __name__ == '__main__':
'''
input[0~2], output[0~3]
(input[0]>>2) & 0x3F -> output[0]
16 * (input[0] & 3) + (input[1] >> 4) & 0xF -> output[1]
4 * (input[1] & 0xF) + (input[2] >> 6) & 3 -> output[2]
input[2] & 0x3F -> output[3]

input[0] = output[0] << 2 + output[1] & 3
0 1 2 3 4 5
0 0.2 0.3 0.4 0.5 0.6 0.7
1 1.4 1.5 1.6 1.7 0.0 0.1
2 2.6 2.7 1.0 1.1 1.2 1.3
3 2.0 2.1 2.2 2.3 2.4 2.5
'''
cipher2 = ''
for i in cipher:
if i.isalpha():
cipher2 += chr(ord(i) ^ 0x20)
else:
cipher2 += i
cipher = cipher2
print(cipher2)
for i in range(len(cipher) // 4):
plaintext += chr((find_char(cipher[i * 4]) << 2) + (find_char(cipher[i * 4 + 1]) >> 4))
plaintext += chr(((find_char(cipher[i * 4 + 1]) & 0xF) << 4) + (find_char(cipher[i * 4 + 2]) >> 2))
plaintext += chr(((find_char(cipher[i * 4 + 2]) & 0x3) << 6) + find_char(cipher[i * 4 + 3]))
print(plaintext)

flag{bAse64_h2s_a_Surprise}

buu030-[HDCTF2019]Maze

这道题首先用upx脱壳,然后发现操作很简单,就是wasd走迷宫,但是不要忽略程序里面有一个70字节的地图,如果不给地图直接走迷宫有很多种走法能到终点,但flag只有一个,所以必须按照迷宫的走法来走。

1
2
3
4
5
6
7
*******+**
******* **
**** **
** *****
** **F****
** ****
**********

flag{ssaaasaassdddw}

buu031-[MRCTF2020]Xor

一个简单的异或。

1
2
3
4
5
cipher = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g'
plaintext = ''
for i in range(0x1B):
plaintext += chr(ord(cipher[i]) ^ i)
print(plaintext)

MRCTF{@_R3@1ly_E2_R3verse!}

buu032-[MRCTF2020]hello_world_go

开盖即送,但这是一个go写的程序,也值得进行分析。这里保留这个程序待后续分析。

flag{hello_world_gogogo}

buu033-Youngter-drive

这个程序首先一开始打不开,说缺少MSVCR100D.dll,这个在网上下一个就行了。然后双击一打开就死掉,用命令行打开发现会输出两个WARNING字符串,然后进入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
BOOL sub_411460()
{
size_t v0; // eax
BOOL i; // [esp+D0h] [ebp-24Ch]
HANDLE hSnapshot; // [esp+DCh] [ebp-240h]
PROCESSENTRY32W pe; // [esp+E8h] [ebp-234h] BYREF

pe.dwSize = 556;
hSnapshot = j_CreateToolhelp32Snapshot(2u, 0);
for ( i = j_Process32FirstW(hSnapshot, &pe); i; i = j_Process32NextW(hSnapshot, &pe) )
{
v0 = wcslen(pe.szExeFile);
wcslwr_s(pe.szExeFile, v0 + 1);
if ( !wcscmp(pe.szExeFile, L"ollyice.exe") )
{
printf("///////WARNING///////\n");
exit(0);
}
if ( !wcscmp(pe.szExeFile, L"ollydbg.exe") )
{
printf("///////\nWARNING\n///////\n");
exit(0);
}
if ( !wcscmp(pe.szExeFile, L"peid.exe") )
{
printf("///////\nWARNING\n///////\n");
exit(0);
}
if ( !wcscmp(pe.szExeFile, L"ida.exe") )
{
printf("///////\nWARNING\n///////\n");
exit(0);
}
if ( !wcscmp(pe.szExeFile, L"idaq.exe") )
{
printf("///////\nWARNING\n///////\n");
exit(0);
}
}
return CloseHandle(hSnapshot);
}

在这个函数里面可以发现这个程序有反调试的机制,CreateToolhelp32Snapshot这个WinAPI用于获取指定进程以及这些进程使用的堆、模块和线程的快照,参数传2表示TH32CS_SNAPPROCESS,即系统中快照中的所有进程,他现在拿到了所有进程的快照,然后就一个个遍历。Process32FirstW是返回系统快照中遇到的第一个进程的信息,Process32NextW就是获取下一个进程信息。wcslen是宽字符个数,wcslwr_s是把字符串大写转小写,是wchar_string_lower的缩写。然后下面判断如果进程名是ollyice等5个用于程序分析和调试的程序就会输出WARNING然后退出。一开始程序中一共打印出了2个WARNING,这里只打印出来一个,首先把这个地方的反调试给禁用了再看另外一个在哪。禁用的最简单方式就是全改成nop指令。框选这个函数里面除了retn指令之外的所有指令,然后用keypatch的fill range功能就能一键修改成nop

然后程序就能够正常跑起来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS D:\CTF-reverse\buu033-Youngter-drive> .\upxout.exe
1111111111111111111111111111111111111111111111111111111111111111111111111111111
*******************************************************************************
************** ****************************************************
************** ******** ********************* *************
************** ********* ********************* ***************************
************** ********* ********************* ***************************
************** ********* ********************* ***************************
************** ******* ********************** ***************************
************** **** ************************* ***************************
************** * *************************** **************
************** *** ************************* ***************************
************** ****** *********************** ***************************
************** ******** ********************* ***************************
************** ********** ******************* ***************************
************** *********** ***************** *************
*******************************************************************************
1111111111111111111111111111111111111111111111111111111111111111111111111111111
input flag:

找到打印这个banner的函数在sub_411BD0,这个函数只有一个scanf把用户输入保存到了一个全局变量Source里面。这个函数是由main函数直接调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
HANDLE Thread; // [esp+D0h] [ebp-14h]
HANDLE hObject; // [esp+DCh] [ebp-8h]

j_banner();
::hObject = CreateMutexW(0, 0, 0);
j_strcpy(Destination, &Source);
hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);
Thread = CreateThread(0, 0, sub_41119F, 0, 0, 0);
CloseHandle(hObject);
CloseHandle(Thread);
while ( dword_418008 != -1 )
;
sub_411190();
CloseHandle(::hObject);
return 0;
}

CreateMutexW创建一个互斥锁,CreateThread创建一个线程。这里查资料发现CreateThread返回的是一个线程句柄,之后是可以立即通过CloseHandle关闭句柄的,因为线程句柄和线程本身的生命周期不同,线程句柄被关闭并不意味着线程立即结束,所以如果一个线程不需要任何干预,在创建之后就关闭句柄即可。第一个线程的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __stdcall __noreturn StartAddress_0(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( bytes_remained > -1 )
{
sub_41112C(&Source, bytes_remained);
--bytes_remained;
Sleep(0x64u);
}
ReleaseMutex(hObject);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// positive sp value has been detected, the output may be wrong!
void __cdecl encrypter(char *buffer, int arg)
{
char byte; // [esp+D3h] [ebp-5h]

byte = buffer[arg];
if ( (byte < 'a' || byte > 'z') && (byte < 'A' || byte > 'Z') )
exit(0);
if ( byte < 'a' || byte > 'z' )
buffer[arg] = Palphabet[0][buffer[arg] - 0x26];
else
buffer[arg] = Palphabet[0][buffer[arg] - 0x60];
}

其中sub_41112C中有这一段处理,规定所有输入只能为字母,然后进行了处理。而另一个线程的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void __stdcall __noreturn sub_411B10(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( bytes_remained > -1 )
{
Sleep(100u);
--bytes_remained;
}
ReleaseMutex(hObject);
}
}

这两个线程执行完之后,后面进行一次字符串比较,将输入处理后的字符串与TOiZiZtOrYaToUwPnToBsOaOapsyS进行非直接比较。

1
2
3
4
5
6
7
8
9
10
11
int sub_411880()
{
int i; // [esp+D0h] [ebp-8h]

for ( i = 0; i < 29; ++i )
{
if ( *(&Source + i) != off_418004[i] )
exit(0);
}
return printf("\nflag{%s}\n\n", Destination);
}

直接上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
alphabet = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
cipher = 'TOiZiZtOrYaToUwPnToBsOaOapsyS'
cipher1 = ''

def find_char(c):
for i in range(len(alphabet)):
if c == alphabet[i]:
return i
return -1

for i in range(29):
if i % 2 == 0:
cipher1 += cipher[i]
continue
if find_char(cipher[i]) <= 26:
cipher1 += chr(find_char(cipher[i]) + 0x60)
else:
cipher1 += chr(find_char(cipher[i]) + 0x26)

print(cipher1)

求出来一个高度疑似flag,但是交上去不对:flag{ThisisthreadofwindowshahaIsES}。于是彻底禁了反调试之后开调。发现输入的字符串处理完之后是这样:

1
2
ThisisthreadofwindowshahaIsES
xhPsPsXhLeWdHfBiGdHwZhWhWIZEz

可见,第2、4、6、…个字符不变,那到底是什么地方出了问题呢?我换了一下处理的奇偶数,然后程序能输出flag,但是那个东西就纯粹拼不出来什么单词了,后来查wp才发现最后还有一位,那这就不是我的问题了。flag{ThisisthreadofwindowshahaIsESE}

buu034-[WUSTCTF2020]level3

一个换了表的base64,看到程序里面有个O_OLookAtYou函数获取换表操作,换完直接解码。

wctf2020{Base64_is_the_start_of_reverse}

buu035-相册

一道Android逆向,用jadx打开后首先看AndroidMenifest.xml。

1
2
3
4
5
6
<activity android:label="@string/app_name" android:icon="@drawable/iocn" android:name="cn.baidujiayuan.ver5304.C1">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>

找到上面这个项,这里的action是android.intent.action.MAIN,因此确定了apk的入口是cn.baidujiayuan.ver5304.C1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPackageManager().setComponentEnabledSetting(getComponentName(), 2, 1);
A2.log("安装后执行这个");
startService(new Intent(this, M2.class));
readContacts();
SmsManager.getDefault();
((TelephonyManager) getSystemService("phone")).getLine1Number();
A2.sendMsg(C2.phoneNumber, A2.getInstallFlag(this, ""));
try {
new SmsTas("", this).execute(new Integer[0]);
} catch (Exception e) {
A2.log("邮件发送错误");
}
try {
new MailTask("", this).execute(new Integer[0]);
} catch (Exception e2) {
A2.log("邮件发送错误");
}
check();
}

这里面有一个sendMsg方法,疑似是发送邮件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static int sendMailByJavaMail(String mailto, String title, String mailmsg) {
if (!debug) {
Mail m = new Mail(C2.MAILUSER, C2.MAILPASS);
m.set_host(C2.MAILHOST);
m.set_port(C2.PORT);
m.set_debuggable(true);
m.set_to(new String[]{mailto});
m.set_from(C2.MAILFROME);
m.set_subject(title);
m.setBody(mailmsg);
try {
if (m.send()) {
Log.i("IcetestActivity", "Email was sent successfully.");
} else {
Log.i("IcetestActivity", "Email was sent failed.");
}
} catch (Exception e) {
Log.e("MailApp", "Could not send email", e);
}
}
return 1;
}

这是在A2中发现的发送邮件的方法,其中有个MAILSERVER,定位一下:

1
public static final String MAILSERVER = Base64.decode(NativeMethod.m());

是个Base64解码,不过解码的东西是NativeMethod.m()

在lib目录里面的libcore.so里面找到了东西。能找到Java_com_net_cn_NativeMethod_m这个函数,然后返回了一个base64值,解码即可。

buu036-[FlareOn4]IgniteMe

简单的windows x86程序,一个加密

1
2
3
4
5
6
7
8
9
10
11
12
13
cipher = [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, 0x0]

crc = 4
plaintext = [0] * 40
idx = 39

while idx >= 0:
plaintext[idx] = crc ^ cipher[idx]
crc = plaintext[idx]
idx -= 1

for i in plaintext[:-1]:
print(chr(i), end="")

flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}

buu037-[WUSTCTF2020]Cr0ssfun

wctf2020{cpp_@nd_r3verse_@re_fun}