攻防世界

NO.GFSJ0166

先用die检查,发现是安卓,放到模拟器里面运行,要求一个密码

在mainactivity里面看到

image-20231209203012868

把这个字符串比较了,尝试输入这个字符串,得到

image-20231209203053644

但是flag输入不对,在main中看到有flagactivity这个类,进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FlagActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C0074R.layout.activity_flag);
String flag = "";
int[] d = {102, 108, 97, 103, 123, 119, 52, 110, 110, 52, 95, 106, 52, 114, 95, 109, 121, 95, 100, 51, 120, 125};
for (int i = 0; i < 22; i++) {
flag = flag.concat(String.valueOf((char) d[i]));
}
TextView flagText = (TextView) findViewById(C0074R.C0076id.flagText);
flagText.setText(flag);
}

感觉也没问题……最后发现flag不能加flag,直接w4nn4_j4r_my_d3x即可。

变换检验而不变换输入


BUUCTF

newstarctf2023reweek2:AndroDbgMe

用jadx打开:

image-20231210130908057

这个字符串真是太抽象了!

用模拟器打开apk:

image-20231210123339930

发现它只是根据输入做变换然后输出,并没有校验???第一次遇到这种题

同时输出是以toast消息显示的,所以看看有没有和toast有关的:搜索后发现

image-20231210123657945

这个时候我发现静态分析太难了,看到题目名字:dbgme想到会不会要动态调试

(请教大佬安卓调试ing)

然后整理安卓调试流程如下:

前置流程:

  1. 先下载adb(部分模拟器自带adb,在模拟器根目录)
  2. 打开模拟器的root和开发者模式,在开发者模式里面开usb调试

然后:

  1. 用adb连接模拟器:adb connect 127.0.0.1:xxxxx(xxxxx根据模拟器而定),不要关闭
  2. 打开jadx的debug功能,顶上的image-20231210140413425,然后应该能看到下面的设备出现了各种线程
  3. 找到对应进程,开始调试

然后来到这个界面,需要smali语法。。

image-20231210140637472

这时无论输入什么都会得到正确flag,原来是个教学题

image-20231210140705572

安卓调试


sctf2019 who is he

打开压缩包,发现是UNITY游戏,根据网上教程下载ILSpy分析assembly-CSharp.dll

找到image-20231213165705074,打开函数查看:

image-20231213165841784

发现有一个初始字符串,一个加密函数,一个解密函数

start是空函数并且最后的onclick函数是检测输入是否正确:

image-20231213170122579

所以分析加密和解密的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private string Encrypt(string str)
{
try
{
byte[] bytes = Encoding.Unicode.GetBytes(encryptKey);
byte[] bytes2 = Encoding.Unicode.GetBytes(str);
DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateEncryptor(bytes, bytes), CryptoStreamMode.Write);
cryptoStream.Write(bytes2, 0, bytes2.Length);
cryptoStream.FlushFinalBlock();
byte[] inArray = memoryStream.ToArray();
cryptoStream.Close();
memoryStream.Close();
return Convert.ToBase64String(inArray);
}
catch
{
return str;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private string Decrypt(string str)
{
try
{
byte[] bytes = Encoding.Unicode.GetBytes(encryptKey);
byte[] array = Convert.FromBase64String(str);
DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Write);
cryptoStream.Write(array, 0, array.Length);
cryptoStream.FlushFinalBlock();
byte[] bytes2 = memoryStream.ToArray();
cryptoStream.Close();
memoryStream.Close();
return Encoding.Unicode.GetString(bytes2);
}
catch
{
return str;
}
}

所以加密和解密流程对应,加密是先DES然后base64,解密相反,那么根据观察str是加密后的字符,因此把解密函数和字符串,密钥放在一个类里面计算:

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
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;


public class MyClass
{
private static string encryptKey = "1234";
private string EncryptData = "1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA==";

private string Decrypt(string str)
{
try
{
byte[] bytes = Encoding.Unicode.GetBytes(encryptKey);
byte[] array = Convert.FromBase64String(str);
DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Write);
cryptoStream.Write(array, 0, array.Length);
cryptoStream.FlushFinalBlock();
byte[] bytes2 = memoryStream.ToArray();
cryptoStream.Close();
memoryStream.Close();
return Encoding.Unicode.GetString(bytes2);
}
catch
{
return str;
}
}

public static void Main(string[] args)
{
MyClass myClass = new MyClass();
string decryptedData = myClass.Decrypt(myClass.EncryptData);
Console.WriteLine(decryptedData);
}
}
#He_P1ay_Basketball_Very_We11!Hahahahaha!

问题来了,结果不对,不是很能理解,去网上看wping

最后跟着教程做,一直都做不出来,在大佬指点之下,发现是因为电脑报毒把某个文件删了还不提醒,找到那个文件:,打开发现是二进制文件,放进die分析,是pe文件,加了UPX壳,用upx的官方程序脱壳

打开之后

image-20231213231111411

大致是加载了一个dll

终于点按钮有提示了:image-20231213231322058

根据网上博客,CE分析mono

image-20231213231845210

发现和之前那个很类似,并且这个dll在恢复报毒文件之后才出现

用mono分析功能把main放到窗口下方,按按钮

image-20231213233408953

image-20231213233422155

得到密钥test,然后勾选上UTF-16搜Emmmm提示词,发现出现了两次,第二次的位置打开内存窗口:

image-20231215103737650

这是第二组密文,下面也有密钥test,复制下来,解密

1
785A57445A614B4568574E4D43626947595042496C59332B61726F7A4F397A6F6E7772594C69564C346E6A53657A3252594D32577773476E736E6A43446E4873374E34336146764E4535346E6F53616450394638654570765473355150472B4B4C305444452F34306E62553D

转为

1
xZWDZaKEhWNMCbiGYPBIlY3+arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40nbU=

用密钥test和刚刚的算法得

1
She_P1ay_Black_Hole_Very_Wel1!LOL!XD!

c#,假答案,DES,base64,CE,mono


NSSCTF

wordy

打开ida后发现没有main函数,start里面标红,怀疑有花指令,到汇编中查找main,用alt+t键,找到后:

image-20231215105547630

发现确实是花指令,先u,再跳一个数据c,多来几次,就会发现垃圾数据是0EBh,同时每两个eb之间都有putchar指令输出一个字符,由于0XEB是不可见的所以,打开hex窗口只会留下putchar的字符:

image-20231215110118700

得flag:GFCTF{u_are2wordy}

花指令,hex,脚本


ganctf 2023

py?python!

打开文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def encrypt(data, key):
encrypted_data = []
key_len = len(key)
for i in range(len(data)):
encrypted_data.append(ord(data[i]) ^ ord(key[i % key_len]))
return encrypted_data[::-1]


data = [19, 78,6 , 68, 17, 36, 23,63, 55, 0, 57, 15, 42, 32, 88, 51, 38, 41, 95, 3, 40, 71, 43, 15, 93, 29, 40, 43, 12, 96
, 55, 20, 15, 21, 21,54]
key = "Python"
flag = input("请输入flag")
encrypted_data = encrypt(flag, key)
if encrypted_data == data:
print("Good!You find it")
else:
print("Sorry,Try again")

很简单的加密算法,直接逆向即可

1
2
3
4
5
6
7
8
9
last = [19, 78,6 , 68, 17, 36, 23,63, 55, 0, 57, 15, 42, 32, 88, 51, 38, 41, 95, 3, 40, 71, 43, 15, 93, 29, 40, 43, 12, 96
, 55, 20, 15, 21, 21,54]
key = "Python"
last =last[::-1]
be = ''
for i in range(len(last)):
be += (chr((last[i]) ^ ord(key[i % len(key)])))
print(be)
#flag{Y0u_@r3_R3@l1y_G0OD_@t_Pyth0n!}

Ezmaze

打开ida,检查main,化简后大概是:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int c; // eax
char path[112]; // [rsp+20h] [rbp-B0h] BYREF
__int64 tab[5]; // [rsp+90h] [rbp-40h] BYREF
int len; // [rsp+B8h] [rbp-18h]
__int64 check; // [rsp+BCh] [rbp-14h]
int b; // [rsp+C4h] [rbp-Ch]
__int64 a; // [rsp+C8h] [rbp-8h]
int ch1,ch2;
_main(argc, argv, envp);
qmemcpy(tab, "@01111010110011110011110100#01", 30);
a = 0i64;
//puts(&Buffer); // 可怜的猫猫在迷宫里迷路了,你能找到它在哪吗?
//puts(&byte_40402C); // 请输入路径,找到猫猫
gets(path);
len = strlen(path);
b = 0;
while ( 1 )
{
check = a;
c = path[b];
if ( c == 100 )
{
ch2++;
}
else if ( c > 100 )
{
if ( c == 115 )
{
ch1++;
}
else if ( c == 119 )
{
ch1--;
}
}
else if ( c == 97 )
{
ch2--;
}
if ( ++b > len
|| ch1 > 5
|| (unsigned int)check > 4
|| (tab[5*ch1+ch2]) != '0'
&& (tab[5*ch1+ch2]) != '@'
&& (tab[5*ch1+ch2]) != '#' )
{
break;
}
a = check;
}
if ( *((_BYTE *)tab + 5 * HIDWORD(a) + (int)a) == '#' )
{
//puts(&byte_404040);
//system("Pause"); // 成功
}
else
{
//printf(&Format); // 失败
}
//system("Pause");
return 0;
}

分析check的值可知它的高32位储存纵方向数值,低32位储存横方向数值,a,w为减sd为加。跳出循环条件表明至少s5次,d4次,把tab改成迷宫:

@0111

10101

10011

11001

11101

00#01

可得@为开始位置,#为末位置

dssdsdssa即可(但是d也没有4次呀?奇怪)

flag{dssdsdssa}

逆向分析,正向求解


easy_xor

这个题也很简单,ida分析后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_main();
*(_QWORD *)enc = 0x6E6E717F64636D66i64;
strcpy(&enc[8], "{V;xSLQi|pun");
printf("Enter the flag: ");
scanf("%s", flag);
if ( strlen(flag) != 20 )
{
puts("Invalid flag length! Exiting...");
exit(0);
}
for ( j = 0; j < strlen(flag); ++j )
flag[j] ^= j;
if ( !strcmp(flag, enc) )
puts("Good!");
else
puts("No!");
return 0;

直接动态调试找enc的地址异或

1
2
print(''.join([chr(get_wide_byte(0x62FDF0+i)^i) for i in range(20)] ))
#flag{this_1s_A_flag}

easy_upx

这个在upx官方的脱壳软件脱壳后打开main转换字符串即可,或直接打开hex视图:

flag{UXP_yyds!}

UPX,hex


try_reverse_it

有一个upx壳,先脱掉,然后进入main查看:

发现是rc4加密,之前没有学过,所以现学:

整个加密分为三个阶段:

  • 初始化,S盒初始化为顺序线性列表,用已知的key填满T盒如果key长度小于T盒大小,就一直重复直到填满
  • 混乱化:在循环中,把第i位的S和一个由这个S和T盒生成的算法计算出的S[j]交换
  • 用混乱化之后的S盒和flag进行加密i次每次取1位,由于rc4加密是对称的所以一般用异或,同时每次加密会同时交换由S[i]变换的来的S[j]交换,所以每次加密都是在上一次基础上完成的

而这道题也只是把每一位flag和那一位计算的S异或,所以完全可以顺着先把S盒求出来,根据异或的性质直接把最后的值异或得到答案

补充一个细节:之前我写的脚本:

1
2
3
4
t = ''
for i in range(32):
t.join(chr(flag[i]))
print(t)
1
2
3
for i in range(32):
t=''.join(chr(flag[i]))
print(t)

这两个都是错误的,第一个的t.join(chr(flag[i]))意思是返回把后方传入的列表变成一个字符串间隔字符是t的字符串,但是我并没有用字符串接收这个返回值,导致没有改变

第二个是因为’’.join每次都会初始化t,改成+=就好了

exp:

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
key = [0x6C, 0x65, 0x74, 0x27, 0x73, 0x5F, 0x72, 0x65, 
0x76, 0x65, 0x72, 0x73, 0x5F, 0x69, 0x74]

last = [0x6C, 0xF8, 0x90, 0xFF, 0x8B, 0x18,
0xE5, 0x76, 0xB5, 0xFB, 0x50, 0xF5,
0x2C, 0x5F, 0xF3, 0x10, 0xCB, 0x3E,
0xEA, 0x9A, 0xC8, 0x37, 0x2D, 0x4F,
0xCC, 0x8F, 0x21, 0x29, 0x17, 0x06,
0xCA, 0x67]
#初始化T,S
S = [0]*256
for i in range (256):
S[255-i] = i
T = [0]*256
for i in range(256):
T[i] = key[i % len(key)]

#打乱S
a = b = 0
for i in range(256):
a = S[i]
b = (a+T[i]+b) % 256
S[i] = S[b]
S[b] = a
#S盒初始化成功,异或算法相同,所以相当于再来一次
m = n = 0
flag = [0]*32
for i in range (32):
m = (m + 1) % 256
k = S[m]
n = (k+n) % 256
S[m] = S[n]
S[n] = k
flag[i] = last[i] ^ S[(k + S[m]) % 256]
t =''
for i in range(32):
t +=''.join(chr(flag[i]))
print(t)
#flag{Y0u_5eem_1ike4pro_revers3r}

UPX,RC4

BrownFox

ida直接打开,先动态调试把improvefox函数之后的fox拿出来,再把最后的检测函数拿出来,变换全在tofox里面所以详细看tofox,化简之后:

1
2
3
4
5
6
7
8
for ( i = 0; i != num;i++ )
{
k = flag[i] >> 3;//k最大15得前4位
check[2 * i] = fox[flag[i] & 7];//最大7得后三位
check[2 * i+ 1] = fox[k]
}
check += 2 * num;
*check = 0;

这里的变换是非连续性的所以k<<3+check[2*i]在fox中的位置可得flag

注:fox一开始是有重复字符的,但是在improvefox之后没有了

最后又被坑了,注意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
fox = [0x41, 0x71, 0x75, 0x69, 0x63, 0x6B, 0x62, 0x72, 
0x6F, 0x77, 0x6E, 0x66, 0x30, 0x78, 0x6A, 0x31,
0x6D, 0x70, 0x73, 0x32, 0x76, 0x65, 0x33, 0x74,
0x68, 0x34, 0x6C, 0x61, 0x7A, 0x79, 0x64, 0x35,
0x67]
sng = [0x62, 0x30, 0x63, 0x78, 0x71, 0x30, 0x72, 0x30,
0x69, 0x31, 0x71, 0x66, 0x41, 0x62, 0x6B, 0x6A,
0x72, 0x66, 0x69, 0x78, 0x62, 0x78, 0x72, 0x78,
0x72, 0x6A, 0x72, 0x66, 0x75, 0x6F, 0x41, 0x6F,
0x69, 0x6A, 0x6B, 0x30, 0x72, 0x66, 0x62, 0x6A,
0x6B, 0x30, 0x75, 0x6A, 0x71, 0x31, 0x72, 0x66,
0x72, 0x6A, 0x6B, 0x30, 0x71, 0x62, 0x71, 0x62,
0x6B, 0x31, 0x41, 0x41] #60个值
def up(x):
for i in range (len(fox)):
if (fox[i] == x):
return i

flag = ''
for i in range (0,60,2):
t = up(sng[i])+((up(sng[i+1]))<<3)
flag += ''.join(chr(t))

print(flag)
#flag{Y0u_know_B@se_very_we11}

算法,c语言


find_flag

先看一下程序:发现会输入两次flag,不知道什么用意

还是用ida打开,在判断函数之前看到了加密函数根据1640531527可知这是一个TAE加密,但是由于知道有输入了两个flag先看看之前的flag1怎么样

1
2
3
4
5
6
7
8
void __fastcall sub_401AB0()
{
if ( !strnum )
{
strnum = 1;
check1();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall check1()
{
void (**pfunc)(void); // rbx
__int64 *v1; // rsi
unsigned int i; // eax

for ( i = 0; bigq[i + 1]; ++i )
;
if ( i )
{
pfunc = (void (**)(void))&bigq[i];
v1 = &bigq[i - (unsigned __int64)(i - 1) - 1];//就是首元素
do
(*pfunc--)();
while ( pfunc != (void (**)(void))v1 );
}
sub_401510((int (__cdecl *)())sub_401A00);
}

可以看到check1中有一个指向函数指针的指针这个指针指向了bigq的地址,而打开可以看到bigq的地址后面跟着两个函数的地址,所以当pfunc这个指针指向这个地址的时候,它解引用就是这个函数地址在(*pfunc–)();处调用并减小,可以把bigq当作一个函数指针数组

注意for后面有;所以只增加了i,后面不在循环里,所以相当于从后向前执行那两个函数,那么先看那两个函数,第一个调用的函数(这个是func2)是一个退出函数??暂时不管。

打开func1:

看到很多数组,在读取时动态调试下断点,发现这里是真的检查处

真的检查函数是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  for ( k = 0; k <= 4; ++k )
sub_401550(&flag1[2 * k]);
if ( !memcmp(flag1, &v3[3], 0x28ui64) )
void __fastcall sub_401550(unsigned int *a1)
{
unsigned int i; // [rsp+30h] [rbp-10h]
int e; // [rsp+34h] [rbp-Ch]
unsigned int b2; // [rsp+38h] [rbp-8h]
unsigned int b1; // [rsp+3Ch] [rbp-4h]

b1 = *a1;
b2 = a1[1];
e = 0;
for ( i = 0; i <= 0x1F; ++i )
{
e -= 1640531527;
b1 += (b2 + e) ^ (16 * b2 + 137) ^ ((b2 >> 5) + 118);
b2 += (b1 + e) ^ (16 * b1 + 84) ^ ((b1 >> 5) + 50);
}
*a1 = b1;
a1[1] = b2;
}

也是一个TEA

注意此处读取前40个值且取test数组的第四位开始,而flag是char类型,后面转成unint类型,鉴于不好python和c大数计算有差别,所以用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
#include <stdio.h>
void dec(unsigned int *a) {
unsigned int b2;
unsigned int b1;
b1 = *a;
b2 = a[1];
int e = 0;
for (int i = 0; i <= 0x1F; ++i)
{
e -= 1640531527;
}
for (int i = 0; i <= 0x1f; ++i) {
b2 -= (b1 + e) ^ (16 * b1 + 84) ^ ((b1 >> 5) + 50);
b1 -= (b2 + e) ^ (16 * b2 + 137) ^ ((b2 >> 5) + 118);
e += 1640531527;
}
*a = b1;
a[1] = b2;
}



int main(void) {
unsigned int data[10] = {
0x9F1F796B, 0xE4AF7378, 0x72FCDFD5, 0x4A57FFE0, 0x1672BA07, 0xAA057BE6, 0x9E171F8E, 0xDD153E4F,
0x41079E59, 0x807578DD
};
for (int k = 0; k < 5; k++)
{
dec(&data[k*2]);
}

for (int i = 0; i < 10; i++)
printf("%#X", data[i]);
}
//0X67616C660X7730577B0X756F59210X6E31465F0X456D5F640X6F635F210X4072674E0X416C75740X6E6F31740X7D2173

然后字符串转换:

666C61677B57307721596F755F46316E645F6D45215F636F4E67724074756C4174316F6E73217D

转string:flag{W0w!You_F1nd_mE!_coNgr@tulAt1ons!}

c语言,函数指针数组和指向函数指针数组的指针,TEA

总结

RC4加密

详见专栏:RC4

TAE加密

详见专栏:TEA

安卓调试流程

前置流程:

  1. 先下载adb(部分模拟器自带adb,在模拟器根目录)
  2. 打开模拟器的root和开发者模式,在开发者模式里面开usb调试

然后:

  1. 用adb连接模拟器:adb connect 127.0.0.1:xxxxx(xxxxx根据模拟器而定),不要关闭
  2. 打开jadx的debug功能,顶上的image-20231210140413425,然后应该能看到下面的设备出现了各种线程
  3. 找到对应进程,开始调试

CE的mono功能

mono是Novell开发的跨平台.NET运行环境,而Csharp是这个环境上的主要编程语言,Unity的使用的就是Csharp,所以CE的mono功能可以对大部分unity游戏使用

开启mono后,Ctrl+D打开反汇编程序窗口后可以直接看到类名和函数名,也可以在mono窗口查看DLL内容,相应类和方法,变量等,不仅可以方便写CElua脚本还能方便反编译器lispy或dnspy找DLL和方法名