Reverse-WP记录9
前言
之前写的,一直没发,留个记录吧,万一哪天记录掉了起码在csdn有个念想
1.easyre1
32位无壳elf文件
shift+F12进入字符串,发现一串数字,双击进入
进入main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{__isoc99_scanf("%s", flag);enkey();reduce();check();return 0;
}
enkey函数,把flag的每一项和key的对应项异或,因为这里的0x804A0A0代表地址,正好是key的地址
int enkey()
{int i; // [esp+Ch] [ebp-4h]for ( i = 0; i <= 31; ++i )*(_BYTE *)(i + 134520992) ^= *(_BYTE *)(i + 134520896);return 0;
}
reduce函数,这里是把flag的每一项的ascll码减一,因为这里的0x804A0A0就是flag的地址
int reduce()
{int i; // [esp+Ch] [ebp-Ch]for ( i = 0; i <= 30; ++i )--*(_BYTE *)(i + 134520992);putchar(10);return 0;
check函数,验证加密后的flag
int check()
{if ( !strcmp(flag, "d^XSAozQPU^WOBU[VQOATZSE@AZZVOF") )return puts("you are right");elsereturn puts("no no no");
}
然后根据逻辑写出解密脚本
enc = 'd^XSAozQPU^WOBU[VQOATZSE@AZZVOF'
key = '5055045045055045055045055045055'
flag=''
for i,j in zip(enc,key):i = ord(i) + 1flag += chr(i ^ ord(j))
print(flag)
flag{PolarDNbecomesbiggerandstronger}
2.BabyRE
64位无壳exe文件
ida进入main函数,c++写的程序,if判断flag
int __cdecl main(int argc, const char **argv, const char **envp)
{std::ostream *v3; // raxchar v5[48]; // [rsp+20h] [rbp-20h] BYREF_main();endoce();std::string::basic_string(v5);std::operator>><char>(refptr__ZSt3cin);if ( (unsigned __int8)std::operator==<char>(v5, &flag[abi:cxx11]) )v3 = (std::ostream *)std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Ok");elsev3 = (std::ostream *)std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Err");refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(v3);std::string::~string(v5);return 0;
}
endoce函数是把flag每一位加2
__int64 endoce(void)
{__int64 result; // rax__int64 v1; // [rsp+20h] [rbp-20h] BYREF__int64 v2; // [rsp+28h] [rbp-18h] BYREF_BYTE *v3; // [rsp+30h] [rbp-10h]void *v4; // [rsp+38h] [rbp-8h]v4 = &flag[abi:cxx11];v2 = std::string::begin(&flag[abi:cxx11]);v1 = std::string::end(&flag[abi:cxx11]);while ( 1 ){result = __gnu_cxx::operator!=<char *,std::string>(&v2, &v1);if ( !(_BYTE)result )break;v3 = (_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&v2);*v3 += 2;__gnu_cxx::__normal_iterator<char *,std::string>::operator++(&v2);}return result;
}
但是看不出来flag被初始化成了什么,也不知道flag有几位,这里选择动调,在这里下断点,等运行到这里F7步入,因为在这之前没有看到对flag的初始化,然后随便输入
size函数计算flag的长度,size对应地址加8就是flag的长度
__int64 __fastcall std::string::size(__int64 a1)
{return *(a1 + 8);
}
运行到这里时,双击a2
长度为0x10即16位
重新开启动调,输入16个1,运行到这里可以查看v4的值,运行到下一步可以看到v5的值
这里的逻辑显然是比较v4和v5的值,v3是长度,类似C语言的strncmp(v4,v5,v3),由于到这一步我们的输入已经过了加密步骤,所以不需要对flag解密
flag{cufhiexdpolivnqr}
3.C^
查壳,无壳,发现32位
int __cdecl main(int argc, const char **argv, const char **envp)
{int v4; // [esp-Ah] [ebp-60h]int v5; // [esp-6h] [ebp-5Ch]_BYTE v6[48]; // [esp-2h] [ebp-58h] BYREFint v7; // [esp+2Eh] [ebp-28h]size_t v8; // [esp+32h] [ebp-24h]int v9; // [esp+36h] [ebp-20h]int v10; // [esp+3Ah] [ebp-1Ch]int *p_argc; // [esp+4Ah] [ebp-Ch]p_argc = &argc;init();*(_DWORD *)&v6[2] = 0;v7 = 0;memset(&v6[4], 0, 4 * (((&v6[2] - &v6[4] + 50) & 0xFFFFFFFC) >> 2));v10 = 0;v9 = 0;puts("Please enter flag\n");((void (__stdcall *)(const char *, _BYTE *, int, int, _DWORD))__isoc99_scanf)("%s", &v6[2], v4, v5, *(_DWORD *)v6);v8 = strlen(&v6[2]);fun1(&v6[2], v8);if ( check(&v6[2], v8) )puts("RIGHT");elseprintf("Try again");return 0;
}
通过代码审计可知
- 调用函数:
- 调用
fun1(&v6[2], v8);
对输入字符串进行某种变换。 - 调用
check(&v6[2], v8);
检查变换后的字符串是否符合预期。 - 输出结果:
- 如果
check
返回非零值,输出"RIGHT"
;否则输出"Try again"
然后点进去
fun1函数对v6加密,异或1
int __cdecl fun1(int a1, int a2)
{int i; // [esp+Ch] [ebp-4h]for ( i = 0; i < a2; ++i )*(i + a1) ^= 1u;return 0;
}
check函数验证s与加密后的v6是否相等
int __cdecl check(int a1, int a2)
{char s[10]; // [esp+8h] [ebp-20h] BYREF__int16 v4; // [esp+12h] [ebp-16h]int v5; // [esp+14h] [ebp-14h]int v6; // [esp+18h] [ebp-10h]int i; // [esp+1Ch] [ebp-Ch]strcpy(s, "shfiu777");s[9] = 0;v4 = 0;v5 = 0;v6 = 0;if ( strlen(s) != a2 )return 0;for ( i = 0; i < a2; ++i ){if ( *(i + a1) != s[i] )return 0;}return 1;
}
然后直接写脚本解密即可
def decrypt(processed_flag):# 逆向 fun1 操作(每个字符与 1 异或)original_flag = ''.join(chr(ord(c) ^ 1) for c in processed_flag)return original_flagdef encrypt(original_flag):# 正向 fun1 操作(每个字符与 1 异或)processed_flag = ''.join(chr(ord(c) ^ 1) for c in original_flag)return processed_flagdef check(processed_flag):# 预期值为 "shfiu777"expected_value = "shfiu777"return processed_flag == expected_value# 主程序
if __name__ == "__main__":# 根据 check 函数的逻辑,processed_flag 必须等于 "shfiu777"processed_flag = "shfiu777"# 解密original_flag = decrypt(processed_flag)print("Original flag:", original_flag)# 验证解密结果reconstructed_flag = encrypt(original_flag)if check(reconstructed_flag):print("Verification successful!")else:print("Verification failed!")
flag{f9239748ca798af5d838ac8699bb5d3d}
4.crc
打开ida
int __fastcall main(int argc, const char **argv, const char **envp)
{const char *v3; // raxconst char *v4; // raxconst char *v5; // raxconst char *v6; // raxconst char *v7; // raxconst char *v8; // raxchar v10[16]; // [rsp+0h] [rbp-30h] BYREFchar v11[8]; // [rsp+10h] [rbp-20h] BYREF__int64 v12; // [rsp+18h] [rbp-18h]int v13; // [rsp+20h] [rbp-10h]unsigned __int64 v14; // [rsp+28h] [rbp-8h]v14 = __readfsqword(0x28u);*(_QWORD *)v11 = 0LL;v12 = 0LL;v13 = 0;scanf("%s", v11);strmncpy(v11, 0, 4, v10);v3 = (const char *)magic(v10);if ( !strcmp(v3, "d1f4eb9a") ){strmncpy(v11, 4, 1, v10);v4 = (const char *)magic(v10);if ( !strcmp(v4, "15d54739") ){strmncpy(v11, 5, 4, v10);v5 = (const char *)magic(v10);if ( !strcmp(v5, "540bbb08") ){strmncpy(v11, 9, 2, v10);v6 = (const char *)magic(v10);if ( !strcmp(v6, "3fcbd242") ){strmncpy(v11, 11, 4, v10);v7 = (const char *)magic(v10);if ( !strcmp(v7, "2479c623") ){strmncpy(v11, 15, 1, v10);v8 = (const char *)magic(v10);if ( !strcmp(v8, "fcb6e20c") )printf("Very nice!");}}}}}return 0;
}
分析可知是一系列的递归计算,这时候还看不出什么
打开strmncpy函数,是从输入的内容中进行字段选择的过程
char *__fastcall strmncpy(const char *a1, int a2, int a3, char *a4)
{char *v4; // raxchar *v5; // rdxchar *result; // raxint i; // [rsp+24h] [rbp-14h]const char *v9; // [rsp+28h] [rbp-10h]v9 = &a1[a2];for ( i = 0; i < a3; ++i ){v4 = a4++;v5 = (char *)v9++;*v4 = *v5;}result = a4;*a4 = 0;return result;
}
再打开magic函数,是CRC32计算
再回到main函数进行综合分析,这几个8位十六进制数其实就是从flag中取出1-4个字段进行crc计算后的结果
然后写了个通用的爆破脚本
import binascii
import stringprint('-------------Start Crack CRC-------------')def crack_crc_1(target):comment = ''chars = string.printablefor crc_value in target:for char1 in chars:char_crc = binascii.crc32(char1.encode()) # 获取遍历字符的CRC32值calc_crc = char_crc & 0xffffffff # 将获取到的字符的CRC32值与0xffffffff进行与运算if calc_crc == crc_value: # 将每个字符的CRC32值与每个文件的CRC32值进行匹配comment += char1print('{}'.format(comment))def crack_crc_2(target):comment = ''chars = string.printablefor crc_value in target:for char1 in chars:for char2 in chars:res_char = char1 + char2 # 获取遍历的任意2Byte字符char_crc = binascii.crc32(res_char.encode()) # 获取遍历字符的CRC32值calc_crc = char_crc & 0xffffffff # 将获取到的字符的CRC32值与0xffffffff进行与运算if calc_crc == crc_value: # 将获取字符的CRC32值与每个文件的CRC32值进行匹配comment += res_charprint('{}'.format(comment))def crack_crc_3(target):comment = ''chars = string.printablefor crc_value in target:for char1 in chars:for char2 in chars:for char3 in chars:res_char = char1 + char2 + char3 # 获取遍历的任意3Byte字符char_crc = binascii.crc32(res_char.encode()) # 获取遍历字符的CRC32值calc_crc = char_crc & 0xffffffff # 将遍历的字符的CRC32值与0xffffffff进行与运算if calc_crc == crc_value: # 将获取字符的CRC32值与每个文件的CRC32值进行匹配comment += res_charprint('{}'.format(comment))def crack_crc_4(target):comment = ''chars = string.printablefor crc_value in target:for char1 in chars:for char2 in chars:for char3 in chars:for char4 in chars:res_char = char1 + char2 + char3 + char4 # 获取遍历的任意4Byte字符char_crc = binascii.crc32(res_char.encode()) # 获取遍历字符的CRC32值calc_crc = char_crc & 0xffffffff # 将遍历的字符的CRC32值与0xffffffff进行与运算if calc_crc == crc_value: # 将获取字符的CRC32值与每个文件的CRC32值进行匹配comment += res_charprint('{}'.format(comment))if __name__ == '__main__':crack_crc_4([0xd1f4eb9a])crack_crc_1([0x15d54739])crack_crc_4([0x540bbb08])crack_crc_2([0x3fcbd242])crack_crc_4([0x2479c623])crack_crc_1([0xfcb6e20c])print('-----------CRC Crack Completed-----------')
然后大概爆破了有1分钟吧,然后就出来答案了
flag{ezrebyzhsh}
5.JunkCode
一道花指令的题,IDA打开后通过内存可以大概做出题目,但按照题目的本意应该是要进行花指令去除的
发现反编译不了,然后在后面看到了爆红的字段,然后就看到了这个花指令
z就是判断条件为1就跳转,但00411AC4为花指令,后面的内容无法被正常识别和解析,导致反编译失败
具体的操作方法是:选中内存地址00411AC4,按U键进行undefine
选中00411AC5处按C键MakeCode,再将00411AC4处的字节内容改为0x90
再选中00411AC5处按C键MakeCode
最后到函数头位置按P构建函数
再进行反编译
和刚开始看内存时的猜测一样,就是进行一次异或
写出解密脚本
a = 'dnceyhwli]amfg]kq]dwll{'
b = ''
for i in range(0,len(a)):b += chr(ord(a[i]) ^ 2)
print(b)
6.[HGAME 2023 week3]patchme
打开ida
从旁边看其他函数,发现有一个SMC自解密的函数,先利用IDC脚本还原看一下逻辑
for i in range(0x00000000000014C6,0x00000000000014C6+961):patch_byte(i,get_wide_byte(i)^0x66)
print("yes")
然后F5选择强制分析,但是可能canary赋值的时候会出错,先U转未定义然后选择分析就能识别正常
下面这里就是输出flag的地方,直接在函数在canary赋值完后直接jmp到flag操作的段上
但是这个init_array会执行两个函数,第一个不影响,第二个会调用SMC自解密的函数会影响我们原本解密的函数
这里有两个干扰,一个是exit退出直接nop掉,还有一个是异或干扰也直接nop掉
或者直接在这个函数前面直接调用我们的flag那个函数就可以完成操作,这里call open我们直接call flag
但是需要下面去call一个退出,不然会出现段错误
解密脚本
flag=[0xFA, 0x28, 0x8A, 0x80, 0x99, 0xD9, 0x16, 0x54, 0x63, 0xB5, 0x53, 0x49, 0x09, 0x05, 0x85, 0x58, 0x97, 0x90, 0x66, 0xDC, 0xA0, 0xF3, 0x8C, 0xCE, 0xBD, 0x4C, 0xF4, 0x54, 0xE8, 0xF3, 0x5C, 0x4C, 0x31, 0x83, 0x67, 0x16, 0x99, 0xE4, 0x44, 0xD1,0xAC, 0x6B, 0x61, 0xDA, 0xD0, 0xBB, 0x55]
key=[0x92, 0x4F, 0xEB, 0xED, 0xFC, 0xA2, 0x4F, 0x3B, 0x16, 0xEA, 0x67, 0x3B, 0x6C, 0x5A, 0xE4, 0x07, 0xE7, 0xD0, 0x12, 0xBF, 0xC8, 0xAC, 0xE1, 0xAF, 0xCE, 0x38, 0x91, 0x26, 0xB7, 0xC3, 0x2E, 0x13, 0x43, 0xE6, 0x11, 0x73, 0xEB, 0x97, 0x21, 0x8E, 0xC1, 0x0A, 0x54, 0xAE, 0xB5, 0xC9, 0x28]
for i in range(len(flag)):print(chr(flag[i]^key[i]),end="")
7.猜猜我在哪
拖入IDA,main函数伪代码如下:
可以看到这里对输入字符串的变换是随机的,会生成0-4的随机数,使用这个随机数作为key进行变换,最后与程序中存储的字符串进行比较判断flag是否正确。
encrypt函数伪代码如下:
这里实现了一个类似凯撒密码的加密,使用key对输入的字符串进行偏移,只会处理大写字母和小写字母(可参考ASCII码表了解其判断大小写字母的原理),其他字符不被偏移。逆运算则是减去key。
了解了加密原理之后,获取到加密后文本,则可尝试对其解密,观察main函数中第27行,其中用于比较的全局变量IDA给予其名为src,双击观察到其内容为khb i0dj lv qrw khuh.
编写一个脚本尝试对其进行如上面凯撒密码的解密
参考脚本:
flag = list("khb iodj lv qrw khuh.")for i in range(len(flag)):ord_ = ord(flag[i])if ord_ >= 0x60 and ord_ <=0x7a:flag[i] = chr(ord_ - 3)print("".join(flag))
由于已知传进来的字符串只有小写字母,就只判断了小写字母,第6行减去的值为试出来的key,当其值为3时可观察到输出结果内存在有意义的单词
输出结果:
he_ flag is not here.
可以发现这里的脚本写的并不完美,本应在26个英文字母之间循环的字符出现了一个下划线,需要手动修复一下
这里加密后的字符为b,减去偏移3后为y(abcdefghijklmnopqrstuvwxyz
字母表中进行循环,b减1之后则到字母表的首项,再从字母表的尾项继续减掉剩下的2,得到字母y)
最终明文为:
hey flag is not here.
输入到程序中:
由于key是随机的,需要多试几次才能得到key值为3,最后会提示You are get it!
flag为flag{hey flag is not here.}
8.c2
可根据运行提示,结合IDA的反编译代码分析。运行如下图:
拖入ida中
分析一波
首先,对用户输入字符串进行了逐字节与10异或。随后,对异或后的字符串每字节ASCII码-3,得到最终变换的字符串。最后,将变换后的字符串与程序中检查的字符串比较,判断用户输入的flag是否正确。
检查字符串如图所示:
然后写出解密脚本
str1 = list("hefklijcda")
for i in range(len(str1)):str1[i] = chr(ord(str1[i]) + 3)str1[i] = chr(ord(str1[i]) ^ 10)print("".join(str1))
9.语言不通禁止入内
打开是反汇编代码,可用放ai里,也可以手搓
这里数据太长了就不放进ai里了,直接全局搜索main函数
放进vscode进行寻找
发现这里将字符串传给了processString
函数,找到该函数:
该函数对传入的字符串逐字节进行了与6异或后减1的操作
提取出来字符串
str = [0x7E, 0x60, 0x62, 0x64, 0x69, 0x75, 0x60, 0x64, 0x63, 0x64, 0x72, 0x72, 0x60, 0x68, 0x65, 0x6B, 0x7C]
flag = ""
for i in range(len(str)):flag += chr((str[i] ^ 6) - 1)
print(flag)
10.左右为难
一看就是迷宫题,简单分析一下
可以看到,16个字符为一行,把地图拿出来划分好,碰到$为成功,@为目前所在位置,碰到0失败
0000000000000000
0@00000111000000
0100111101011100
0100100001010100
0100111001110110
0100001000000010
01111110000000$0
0000000000000000
然后可以手搓数一下路径,或者拿bfs或者dfs算法优先算一下搞个脚本
from collections import deque# 定义迷宫
maze = ["0000000000000000","0@00000111000000","0100111101011100","0100100001010100","0100111001110110","0100001000000010","01111110000000$0","0000000000000000"
]# 调整方向优先级:下(s) > 右(d) > 上(w) > 左(a)
directions = {'s': (1, 0),'d': (0, 1),'w': (-1, 0),'a': (0, -1)
}def bfs(maze):rows, cols = len(maze), len(maze[0])# 找到起点 @ 和终点 $for r in range(rows):for c in range(cols):if maze[r][c] == '@':start = (r, c)if maze[r][c] == '$':end = (r, c)# 初始化队列和访问集合queue = deque([(start, "")]) # 队列元素为 (当前位置, 当前路径)visited = set([start]) # 已访问的格子while queue:(r, c), path = queue.popleft()# 如果到达终点,返回路径if (r, c) == end:return path# 尝试四个方向(按优先级顺序)for move, (dr, dc) in directions.items():nr, nc = r + dr, c + dc# 检查是否越界或访问过if 0 <= nr < rows and 0 <= nc < cols and (nr, nc) not in visited:if maze[nr][nc] == '1' or maze[nr][nc] == '$': # 只能走 1 或 $visited.add((nr, nc))queue.append(((nr, nc), path + move))# 如果队列为空且未找到路径return "No path found"# 调用 BFS 并输出结果
result = bfs(maze)
print("迷宫路径:", result)
sssssdddddwwaawwdddwddsssddwwddssdss
然后md5加密一下
f787a0b786068936636c1e10e246a297
11. android2
拖进jadx中,看到加密密文
定位关键函数encode
但是发现这个key值不太对,所以有问题
然后可以看见key已经修改了
然后写个脚本
enc="棿棢棢棲棥棷棊棐棁棚棨棨棵棢棌"
print(hex(ord(enc[0])))
key=987654321
flag=""
for i in range(len(enc)):flag+=(chr(ord(enc[i])^key & 0xFFFF))
print(flag)
12. [FSCTF 2023]ezcode
打开附件,看样子还是python字节码
让ai帮我们给转换成一下python代码然后看看代码运行逻辑是咋样的
def func1(key, message):s_box = func2(key)crypt = str(func3(message, s_box))return cryptdef func2(key):s_box = list(range(256))j = 0for i in range(256):j = (j + s_box[i] + ord(key[i % len(key)])) % 256s_box[i], s_box[j] = s_box[j], s_box[i]return s_boxdef func3(plain, box):res = []y = 'FSCTF'i = j = 0for s in plain:i = (i + 1) % 256j = (j + box[i]) % 256box[i], box[j] = box[j], box[i]t = (box[i] + box[j]) % 256k = box[t]res.append(chr(ord(s) ^ k ^ ord(y[i % len(y)])))cipher = ''.join(res)return cipherdef encode(inputs):s = 'vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/'bin_str = []for i in inputs:x = str(bin(ord(i))).replace('0b', '')bin_str.append('{:0>8}'.format(x))outputs = ''nums = 0while bin_str:temp_list = bin_str[:3]if len(temp_list) != 3:nums = 3 - len(temp_list)while len(temp_list) < 3:temp_list.append('00000000')temp_str = ''.join(temp_list)temp_str_list = []for i in range(0, 4):temp_str_list.append(int(temp_str[i*6:(i+1)*6], 2))if nums:temp_str_list = temp_str_list[:4-nums]for i in temp_str_list:outputs += s[i]bin_str = bin_str[3:]outputs += '=' * numsreturn outputs# Main program
print('Please input your flag:')
m = input()
k = 'XFFTnT'
c = func1(k, m)
outputs = encode(c)if outputs == 'ADkopgjJFP+28RYgXUxU2Oej':print('Success!!')
else:print('think again')
然后来分析一下这个代码
4个函数
函数1
def func1(key, message):s_box = func2(key) # 用 key 生成 s_box(类似RC4的KSA)crypt = str(func3(message, s_box)) # 用 s_box 对 message 加密(类似RC4的PRGA)return crypt
函数2 (有点像rsa)
def func2(key):s_box = list(range(256)) # 初始化 s_box = [0, 1, 2, ..., 255]j = 0for i in range(256):j = (j + s_box[i] + ord(key[i % len(key)])) % 256 # 用 key 打乱 s_boxs_box[i], s_box[j] = s_box[j], s_box[i] # 交换 s_box[i] 和 s_box[j]return s_box
函数3 (PRGA + XOR加密)
def func3(plain, box):res = []y = 'FSCTF' # 额外的XOR密钥i = j = 0for s in plain:i = (i + 1) % 256j = (j + box[i]) % 256box[i], box[j] = box[j], box[i] # 更新 s_boxt = (box[i] + box[j]) % 256k = box[t] # 生成密钥流字节 k# 加密:s ^ k ^ y[i % len(y)]res.append(chr(ord(s) ^ k ^ ord(y[i % len(y)])))cipher = ''.join(res)return cipher
函数4 (自定义base64编码)
def encode(inputs):s = 'vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/'bin_str = []for i in inputs:x = bin(ord(i))[2:].zfill(8) # 转8位二进制bin_str.append(x)outputs = ''nums = 0while bin_str:temp_list = bin_str[:3] # 每次取3字节(24位)if len(temp_list) != 3:nums = 3 - len(temp_list) # 补0的字节数temp_list += ['00000000'] * nums # 补全3字节temp_str = ''.join(temp_list) # 24位二进制temp_str_list = []for i in range(4): # 分成4个6位组chunk = temp_str[i*6 : (i+1)*6]temp_str_list.append(int(chunk, 2)) # 转成0-63的数字if nums: # 如果有补0,去掉最后`nums`个字符temp_str_list = temp_str_list[:4 - nums]for num in temp_str_list:outputs += s[num] # 查表映射bin_str = bin_str[3:] # 处理下一组outputs += '=' * nums # 补'='return outputs
整个逻辑已经分析的很清楚了,只要把encode
得到 func1
的输出 b'=.\x07#M...'
。逆向 func3
:重新计算 s_box = func2('XFFTnT')
。用 cipher_byte ^ k ^ y_byte
恢复 plain
即可
def func2(key):s_box = list(range(256))j = 0for i in range(256):j = (j + s_box[i] + ord(key[i % len(key)])) % 256s_box[i], s_box[j] = s_box[j], s_box[i]return s_boxdef decrypt_func3(cipher, box):res = []y = 'FSCTF'i = j = 0for s in cipher:i = (i + 1) % 256j = (j + box[i]) % 256box[i], box[j] = box[j], box[i]t = (box[i] + box[j]) % 256k = box[t]# 解密:plain_byte = cipher_byte ^ k ^ y_byteplain_byte = ord(s) ^ k ^ ord(y[i % len(y)])res.append(chr(plain_byte))return ''.join(res)# 1. 计算 s_box
key = 'XFFTnT'
s_box = func2(key)# 2. 解密 cipher
cipher = '=.\x07#M\xd8Q\xef\x9d\xf2\x0ct\xc2\xd0\xadv|\xb7'
plain = decrypt_func3(cipher, s_box.copy()) # 避免修改原 s_box
print("Decrypted plaintext:", plain)
13.MYapk
挺有意思的,显示的数字是经过运算得出的,在内存中不存在这个数,导致了gg修改器修改不了这个数字
// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.import java.util.Timer;
import java.util.TimerTask;public class Main {private Timer timer;private TimerTask timerTask;private Boolean started = false;private int success = 0;String getit(String input) {int i;byte[] message;int messageLength;int messageLength2;int f;int g;int[] T = new int[64];for (int i2 = 0; i2 < 64; i2++) {T[i2] = (int) (Math.abs(Math.sin(i2 + 1)) * 4.294967296E9d);}byte[] message2 = input.getBytes();int d = message2.length;int numBlocks = ((d + 8) >>> 6) + 1;int totalLength = numBlocks << 6;byte[] paddedMessage = new byte[totalLength];System.arraycopy(message2, 0, paddedMessage, 0, d);paddedMessage[d] = Byte.MIN_VALUE;long messageBits = d * 8;int i3 = 0;while (true) {i = 8;if (i3 >= 8) {break;}paddedMessage[(totalLength - 8) + i3] = (byte) (messageBits >>> (i3 * 8));i3++;}int[] state = {-1732584194, -271733879, 271733878, 1732584193};int i4 = 0;while (i4 < numBlocks) {int[] block = new int[16];for (int j = 0; j < 16; j++) {int index = (i4 << 6) + (j << 2);block[j] = (paddedMessage[index] & 255) | ((paddedMessage[index + 1] & 255) << i) | ((paddedMessage[index + 2] & 255) << 16) | ((paddedMessage[index + 3] & 255) << 24);}int a = state[0];int b = state[1];int c = state[2];int j2 = 0;int d2 = state[3];while (j2 < 64) {if (j2 < 16) {message = message2;messageLength = d;messageLength2 = d2;f = ((~b) & messageLength2) | (b & c);g = j2;} else {message = message2;messageLength = d;messageLength2 = d2;if (j2 < 32) {f = (messageLength2 & b) | ((~messageLength2) & c);g = ((j2 * 5) + 1) % 16;} else if (j2 < 48) {f = (b ^ c) ^ messageLength2;g = ((j2 * 3) + 5) % 16;} else {f = ((~messageLength2) | b) ^ c;g = (j2 * 7) % 16;}}int temp = messageLength2;int d3 = c;c = b;b += Integer.rotateLeft(a + f + block[g] + T[j2], 7);a = temp;j2++;d2 = d3;message2 = message;d = messageLength;T = T;block = block;}int messageLength3 = d;int messageLength4 = d2;state[0] = state[0] + a;state[1] = state[1] + b;state[2] = state[2] + c;state[3] = state[3] + messageLength4;i4++;d = messageLength3;T = T;i = 8;}byte[] hash = new byte[16];for (int i5 = 0; i5 < 4; i5++) {hash[i5 * 4] = (byte) (state[i5] & 255);hash[(i5 * 4) + 1] = (byte) ((state[i5] >>> 8) & 255);hash[(i5 * 4) + 2] = (byte) ((state[i5] >>> 16) & 255);hash[(i5 * 4) + 3] = (byte) ((state[i5] >>> 24) & 255);}StringBuilder sb = new StringBuilder();for (int i6 = 0; i6 < hash.length; i6++) {sb.append(String.format("%02x", Integer.valueOf(hash[i6] & 255)));}return sb.toString();}public static void main(String[] args) {Main ma=new Main();System.out.println( ma.getit("66.666s"));// Press Shift+F10 or click the green arrow button in the gutter to run the code.}
}
d1be6793ed8569ee43950ef21f991095
采用frida来做
setImmediate(function() { //prevent timeoutconsole.log("[*] Starting script");// //com.moible.r15.main
// Java.perform(function () {
// // var FridaActivity2 = Java.use(
// // "com.moible.r15.main");
// // console.log("static_bool_var:", FridaActivity2.static_bool_var.value);
// // FridaActivity2.setStatic_bool_var(); //调用静态函数
// // console.log("static_bool_var:", FridaActivity2.static_bool_var.value);// //调用非静态函数
// Java.choose("com.moible.r15.main", {
// onMatch : function(instance) {
// console.log("bool_var:", instance.bool_var.value);
// instance.setBool_var();
// console.log("bool_var:", instance.bool_var.value);
// }, onComplete : function() {
// }
// })
// });Java.perform(function() {// 获取目标类的引用var main = Java.use('com.moible.r15.main');var mains=main.$new();// 调用目标函数var result = mains.getit("66.666s");// 在这里处理返回值console.log("Result: " + result);
});})
//frida -U -l exp.js -f com.moible.r15 com.moible.r15.main
//getText
NSSCTF{d1be6793ed8569ee43950ef21f991095}
调用静态函数可以直接调用,不用implementation
再去定义。调用非静态函数需要用choose
去搜索实例,再从结果实例里调用非静态函数,无需手动触发了。
package com.github.androiddemo.Activity;import android.content.Intent;public class FridaActivity2 extends BaseFridaActivity {private static boolean static_bool_var = false;private boolean bool_var = false;private static void setStatic_bool_var() {static_bool_var = true;}private void setBool_var() {this.bool_var = true;}
}
function call_var() {Java.perform(function () {var FridaActivity2 = Java.use("com.github.androiddemo.Activity.FridaActivity2");console.log("static_bool_var:", FridaActivity2.static_bool_var.value);FridaActivity2.setStatic_bool_var(); //调用静态函数console.log("static_bool_var:", FridaActivity2.static_bool_var.value);//调用非静态函数Java.choose("com.github.androiddemo.Activity.FridaActivity2", {onMatch : function(instance) {console.log("bool_var:", instance.bool_var.value);instance.setBool_var();console.log("bool_var:", instance.bool_var.value);}, onComplete : function() {}})});
}
最后的脚本:
setImmediate(function() { //prevent timeoutconsole.log("[*] Starting script");
// //com.moible.r15.main
// Java.perform(function () {
// // var FridaActivity2 = Java.use(
// // "com.moible.r15.main");
// // console.log("static_bool_var:", FridaActivity2.static_bool_var.value);
// // FridaActivity2.setStatic_bool_var(); //调用静态函数
// // console.log("static_bool_var:", FridaActivity2.static_bool_var.value);// //调用非静态函数
// Java.choose("com.moible.r15.main", {
// onMatch : function(instance) {
// console.log("bool_var:", instance.bool_var.value);
// instance.setBool_var();
// console.log("bool_var:", instance.bool_var.value);
// }, onComplete : function() {
// }
// })
// });Java.perform(function() {// 获取目标类的引用var main = Java.use('com.moible.r15.main');var mains=main.$new();// 调用目标函数var result = mains.getit("66.666s");// 在这里处理返回值console.log("Result: " + result);
});
})
//frida -U -l exp.js -f com.moible.r15 com.moible.r15.main
//getText
计算过程解析
- 输入字符串:
"66.666s"
(即计时器显示的值) - 哈希算法:
getit()
函数实现了一个类似 MD5 的自定义哈希算法:
-
- 初始化 4 个状态变量(类似 MD5 的 A/B/C/D)
- 对输入进行 填充(Padding)和 分块(Block Processing)
- 进行 64 轮位运算(涉及
AND
、OR
、XOR
、NOT
和 循环左移) - 最终生成 16 字节(128 位)哈希值,并转为 32 位十六进制字符串
- 计算步骤:
-
- 输入:
"66.666s"
(字节形式:[0x36, 0x36, 0x2e, 0x36, 0x36, 0x36, 0x73]
) - 填充:
- 输入:
-
-
- 在末尾追加
0x80
(10000000
) - 填充
0x00
直到总长度为56 mod 64
(MD5 标准填充) - 最后 8 字节存储输入长度(
7 * 8 = 56
,即0x38
)
- 在末尾追加
-
-
- 分块处理:
-
-
- 分成 512-bit(64 字节) 的块
- 每块再分成 16 个 32-bit 整数
-
-
- 64 轮位运算:
-
-
- 每轮使用不同的 非线性函数(
F
,G
,H
,I
类似 MD5) - 每轮使用不同的 位移量 和 常数(基于
sin
函数)
- 每轮使用不同的 非线性函数(
-
-
- 最终哈希:
-
-
- 4 个状态变量
A, B, C, D
拼接成 16 字节 - 转为 小端序十六进制 字符串
- 4 个状态变量
-
- 最终结果:
-
- 计算
getit("66.666s")
得到1a74ee530fafa690dcddd0ce38260755
- 计算
NSSCTF{1a74ee530fafa690dcddd0ce38260755}
14. [MoeCTF 2021]clothes
1.查壳,发现是ASPack壳
发现需要用工具脱壳,当然也可以用onlydebug进行手动脱壳然后dump下来
这里直接用工具进行脱壳就行
打开ida
发现这里的逻辑异常清晰,是一个加密文一和密文二的异或就行
enc1=[0x1E, 0x2A, 0x4E, 0x24, 0x23, 0x0F, 0x28, 0x39, 0x71, 0x3C, 0x4F, 0x4C, 0x6E, 0x35, 0x22, 0x3E, 0x08, 0x02, 0x31, 0x7D, 0x2C, 0x36, 0x16, 0x04, 0x22, 0x1A, 0x53, 0x07, 0x73, 0x38, 0x00, 0x00]
enc2=[0x73, 0x45, 0x2B, 0x47, 0x57, 0x69, 0x53, 0x0D, 0x44, 0x4C, 0x2E, 0x2F, 0x05, 0x6A, 0x13, 0x4D, 0x57, 0x31, 0x4B, 0x22, 0x58, 0x06, 0x49, 0x71, 0x4C, 0x6A, 0x32, 0x64, 0x18, 0x45, 0x00, 0x00]
flag=""
for i in range(30):flag+=chr(enc1[i]^enc2[i])
print(flag)
15. [MTCTF 2021 final]pyc
发现是pyc字节码题目,这里直接用uncompyle6或者用pycdc.exe进行反编译即可
import hashlib
s = input()
if len(s) != 72:print('wrong')
a1 = set()
a2 = set()
a3 = set()
a4 = [0x9E3779B9L,0x9E3779B9L]
for d in '012345678':a3.add(s.count(d))
for i in range(0, len(s), 9):for l in range(0, 15, 2):a2.add(sum((lambda .0: Unsupported opcode: GEN_START
pass# WARNING: Decompyle incomplete
)((lambda .0: [ int(v) for v in .0 ])(str(a4[1] ^ 0xE4172600000000L ^ 0xCD70877AL)[l:l + 3]))))if int(s[i:i + 9]) >= a4[0]:passelse:a4[0] = int(s[i:i + 9])a1.add(s[i:i + 9])if len(a1) == 8 and len(a2) == 1 and len(a3) == 1 and s.count('9') == 0:print(f'''flag{{{hashlib.md5(s.encode('ascii')).hexdigest()}}}''')return NoneNone(print)return None
进行分析
import hashlib
s = input()
if len(s) != 72:print('wrong')
a1 = set()
a2 = set()
a3 = set()
a4 = [0x9E3779B9,0x9E3779B9]
for d in '012345678':a3.add(s.count(d))for i in range(0, len(s), 9): # 8组for l in range(0, 15, 2):a2.add(sum(int(s[i+j:i+j+1]) for j in ([ int(v) for v in (str(a4[1] ^ 0xE4172600000000 ^ 0xCD70877A)[l:l + 3])])))# 642 201 174 480 063 345 528 867# 三行 三列 两对角线"""|0|1|2||3|4|5||6|7|8|"""print(a2)if int(s[i:i + 9]) >= a4[0]: # 每一组都要比前一组小passelse:a4[0] = int(s[i:i + 9])a1.add(s[i:i + 9])if len(a1) == 8 and len(a2) == 1 and len(a3) == 1 and s.count('9') == 0:# '0'~'8'出现次数一样 没有'9' 求出的sum和相同 9个字符一组 每组9个字符都一样print(f'''flag{{{hashlib.md5(s.encode('ascii')).hexdigest()}}}''')# 分析发现是3x3幻方 用0~8来填 刚好3x3也只有8个
结合set a1,a2,a3 以及一些sum的check 可以分析出这是8个3x3幻方
网上找到
-1过后降序排列md5即可
然后输入
723048561705246381561048723507642183381246705327840165183642507165840327