FZNCTF Writeup

Crypto

sign

解密

打开得到这一串密文,一眼Base64了;

flag{w@rm_up_m@n_wh@t_c@n_i_s@y}

babyRSA

[CTF Crypto RSA合集(新生赛难度)_swpuctf 2021 新生赛]crypto1-CSDN博客

可以看看这个博客,关于RSA;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import gmpy2
from Crypto.Util.number import bytes_to_long, getStrongPrime

flag = b"FZNCTF{xxxxxxxxxxxxxxxxxxxxxx}"
m = bytes_to_long(flag)
p = getStrongPrime(1024)
q = getStrongPrime(1024)
n = p * q
e1 = 65537
e2 = 2333
assert gmpy2.gcd(e1, e2) == 1
c1 = pow(m, e1, n)
c2 = pow(m, e2, n)
print("n = ", n)
print("c1 = ", c1)
print("c2 = ", c2)
'''
n = 20116181115185256137151544962617461969127485079539874123483790179691451026659727409952002522890170053136432251484598758202749921581090222404128778286934496528266389586853622784941814905724374659722595150921699219297623333597701057604954651692729250881800409786303147914726282180533059864870515367790648247781809163302813093152120079755029826297153011886828297246071460547641705486357297417233767122432498694486964352342801912061641754894572829465334992075139630449930115173945624459850685926333265959551792326024379566586598102224813413893610109613076229994854338779713676554568790353735720642800095499489845542499243
c1 = 12574615186232508658058781239986357997690221455470707882708919538344867889046756940392586425215083828833917124513057649780713973391884806015516720789553246206689136824212927709627728801288304629797527956697243414600762350309939098109707454770146886759387428373061131102235552303687641823437812335805429238591967849935403611629054866383093303269886760991083623181420555220699498615783586898665752705379697907365417049724321589193519522361497873285356095894318360385694468646045503420079559869794438944147268173957051158766280963203638656844473553699835099487100606197891044561669390594778573658401240732422863142027023
c2 = 8662571349644155432847824828393121776098409938437916669251107156874603954233944520039184762114790042807930511195272715449923236367967421315595229284040395158368328305331059953339506075385510377512416235056474493145376149687125036012499178684540639274797034307771820863938251633851132423882652884158338847776108707048056678532767030904154141724336547346190841924329986431350894721191694788035732072169271856487733779585742838903331792853668246043440328934976370767441620664696662437609635592559825455438053522844176591659902503216608366261175995953153271651775486985191394510128456927543378761783994086684673696547021
'''

两c两e,一个n;直接调用博客里面的代码了,修改数据即可;

1
2
3
4
5
6
7
8
9
10
from gmpy2 import *
from Crypto.Util.number import *
c1 = 100156221476910922393504870369139942732039899485715044553913743347065883159136513788649486841774544271396690778274591792200052614669235485675534653358596366535073802301361391007325520975043321423979924560272762579823233787671688669418622502663507796640233829689484044539829008058686075845762979657345727814280
c2 = 86203582128388484129915298832227259690596162850520078142152482846864345432564143608324463705492416009896246993950991615005717737886323630334871790740288140033046061512799892371429864110237909925611745163785768204802056985016447086450491884472899152778839120484475953828199840871689380584162839244393022471075
e1 = 3247473589
e2 = 3698409173
n = 103606706829811720151309965777670519601112877713318435398103278099344725459597221064867089950867125892545997503531556048610968847926307322033117328614701432100084574953706259773711412853364463950703468142791390129671097834871371125741564434710151190962389213898270025272913761067078391308880995594218009110313
_ ,s1 ,s2 = gcdext(e1,e2)
m = pow(c1,s1,n) * pow(c2,s2,n) % n
print(long_to_bytes(m))

运行即可得到flagFZNCTF{D0_U_kn0w_c0mm0n_m0du1u5_4tt4ck?}

Reverse

诸神的三试炼

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
// local variable allocation has failed, the output may be wrong!
int __fastcall main(int argc, const char **argv, const char **envp)
{
Stream *Stream; // rax
Stream *Stream_1; // rax
char Buffer[108]; // [rsp+20h] [rbp-70h] BYREF
int n2; // [rsp+8Ch] [rbp-4h] BYREF

_main(*&argc, argv, envp);
show_menu();
_mingw_scanf("%d", &n2);
getchar();
if ( n2 == 2 )
{
puts("Well, it looks like you don't like the god challenge!!!");
return 0;
}
else if ( n2 == 1 )
{
puts("\nWelcome to the god Triple Challenge!");
puts("Find all three parts of the flag to complete the challenge.\n");
puts("=== Challenge 1: Find the first part of the flag ===");
puts("Hint: Look for strings in the binary (try Shift+F12 in IDA)...");
_mingw_printf("Enter flag part 1: ");
Stream = __acrt_iob_func(0);
fgets(Buffer, 100, Stream);
Buffer[strcspn(Buffer, "\n")] = 0;
if ( flag1(Buffer) )
{
puts("Correct! Moving to the next challenge...\n");
puts("=== Challenge 2: Find the second part of the flag ===");
puts("Hint: Do you know debugger?");
_mingw_printf("Enter flag part 2: ");
Stream_1 = __acrt_iob_func(0);
fgets(Buffer, 100, Stream_1);
Buffer[strcspn(Buffer, "\n")] = 0;
if ( flag2(Buffer) )
{
puts("Correct! Moving to the final challenge...\n");
puts("=== Challenge 3: Find the third part of the flag ===");
puts("Hint: There's two hidden functions that can help you decrypt the data...");
flag3_fake();
}
}
puts("Incorrect! Try again.");
return 0;
}
else
{
puts("Invalid choice. Exiting...");
return 0;
}
}

全是字符,有点多,动调一下;

跟进得到第一段flag;

第二段;

得到一个假的第三段;

shift+f12看到一个,按任意键退出,跟进看看,目前我们还没到这里;

一个随机种子的异或,跟进提取密文,直接写脚本就行;

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
int main()
{
srand(0x1234);
int enc[] = {0xCD, 0xCE, 0xD0, 0x58, 0x5D, 0x92, 0x42, 0xB1, 0x7C, 0xA9, 0xFF, 0x0};
for (int i = 0; i < 12; i++)
{
printf("%c", enc[i] ^ (rand() % 256));
}
return 0;
}
//Use_IDA!!!}

三段连起来加上头就是flag;FZNCTF{G00d!!!_N0w_y0U_c@n_Use_IDA!!!}

LZM爱恋日记

下载得到这么个东西,.exe文件是c++写的,放入IDA里面依托,太冗杂了,没找到;直接运行.exe看看;

迷宫题?这个还不是正常的键位…强行爆破10000次了;

1
eeeessssseeessA

上面txt文件;

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

time.sleep(5) # 等待5秒

file_path = r"D:\Monster\keyboard\key.txt"
try:
with open(file_path, "r", encoding="utf-8") as file:
for i in range(24):
for line in file:
keyboard.write(line.strip()) # 去掉换行符
keyboard.press_and_release("enter") # 如果需要换行seeessA手动输入回车
file.seek(0) # 重置文件指针到开头
time.sleep(0.01) # 可选:每次循环后稍作等待
except FileNotFoundError:
print(f"文件未找到: {file_path}")
except Exception as e:
print(f"发生错误: {e}")

这是之前用过的脚本;可恶的出题人,爆破这么久给我说是错误的flag???

醉了…看看另外一个;C#写的,用dnspy打开看看


可以找到这么个玩意,逐字节异或;写解密脚本;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdint.h>
int main()
{
int enc[] = {70, 28, 82, 17, 69, 3, 120, 73, 51, 94, 1, 103, 14, 96, 4, 91, 51, 2, 113, 46, 23, 126, 12, 61, 10, 120, 17, 116, 26, 126, 3};
char num[32];
for (int i = 32; i > 1; i--)
{
num[i] = enc[i] ^ enc[i - 1];
}
for (int i = 0; i < 32; i++)
{
printf("%c", num[i]);
}

return 0;
}
//NCTF{1zm_find_h1s_9ir17riend}

少了个头,懒得纠结了,手动添加;FNZCTF{1zm_find_h1s_9ir17riend}

lzm出逃日记

嗯…少复制了几个密文,导致解密的时候一直差几位明文…4*8=24?我也不知道自己怎么算的;

这个位置有个显而易见的SMC自异或;动调修改一下;重新反编译可以得到如下的伪代码:

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
unsigned int sub_401550()
{
int char; // eax
Stream *Stream; // rax
size_t v2; // rax
char *p_Buffer; // rdx
int v4; // ecx
unsigned int n49; // eax
__int64 v6; // rdx
__int64 v7; // rsi
char *p_Buffer_1; // r8
signed int v9; // ecx
int v10; // ebp
__int64 i; // rdi
_QWORD v12[4]; // [rsp+20h] [rbp-A8h] BYREF
char n34; // [rsp+40h] [rbp-88h]
char Buffer[120]; // [rsp+50h] [rbp-78h] BYREF

v12[0] = 0x6324323A03390577LL;
v12[1] = 0x166F001F5A1E1F07LL;
v12[2] = 0x185D28282D01002BLL;
v12[3] = 0x143A002F3801062FLL;
n34 = 34;
do
char = getchar();
while ( char != -1 && char != 10 );
Stream = psub_403290();
fgets(Buffer, 50, Stream);
v2 = strcspn(Buffer, "\n");
p_Buffer = Buffer;
Buffer[v2] = 0;
do
{
v4 = *p_Buffer;
p_Buffer += 4;
n49 = ~v4 & (v4 - 16843009) & 0x80808080;
}
while ( !n49 );
if ( (~v4 & (v4 - 16843009) & 0x8080) == 0 )
{
n49 >>= 16;
p_Buffer += 2;
}
v6 = &p_Buffer[-__CFADD__(n49, n49) - 3] - Buffer;
if ( v6 )
{
v7 = (v6 - 1);
p_Buffer_1 = Buffer;
for ( n49 = 49; ; n49 = a1WNt7oP1YCtf[v9 % 18] )
{
*p_Buffer_1 ^= n49;
v9 = 1 - Buffer + p_Buffer_1++;
if ( p_Buffer_1 == &Buffer[v7 + 1] )
break;
}
if ( Buffer[0] == 119 )
{
v10 = v6 - 1;
for ( i = 0; n49 == *(v12 + i); ++i )
{
if ( v10 == i )
{
puts("A valid reason is the essence of asking for leave.");
puts("lzm says big thanks!\n");
n49 = printf(".....");
if ( i == v7 )
return n49;
}
else if ( i == v7 )
{
return n49;
}
n49 = Buffer[i + 1];
}
}
return puts("lzm was rejected.");
}
return n49;
}

这里就只有一个简单的异或…多复制点,4*8=24我真没招了;

这里有个异或,密钥也给了;

没招了...FZNCTF{Th@nk_y0u_f0r_h3lp1ng_1zm}

GoTea

Go语言,就一个反调试,也是脑子抽了,没做出来;

四个函数,Input、Debug、TEAdecrypt、main;实则就只有一个反调试,过了就行…TM以为这个是TEA加密;

在调试的地方下断点找到在哪调试的,直接nop掉(这也行?我还准备进去看看怎么绕过呢)…

然后回到TEAdecrypt函数的返回点,查看:

这里看出来v23是解密的块,v23是从v1来的,跟进查看:FZNCTF{R3ver5e_G0g0Go_H4v3_4_G00D_D4y}

babyvm

Frida

爆破条件:
  • 单字节比较–>如果明文和密文之间是通过数组一位一位的比较的就可以使用此方法,我们可以通过不断的变换明文每一个位置的字符,直至加密后的明文和密文相匹配,证明我们得到了这个位置的正确明文,循环上述操作直至得到所有明文;通过此思想可以判断,多字节比较也是可行的,将多字节作为一个整体就行,不过,鉴于可见字符集是100位,如果当两位字节为一组的时候爆破时间和难度就成指数级上升了,单字节需要爆破100位,双字节就需要爆破100^2位,以此类推,所以当解密块的字节大于4字节时,不考虑爆破;
  • 可以同过输入明文并且微调明文的字符对比加密后的密文可以知道密文是不是单字节加密的,如果每个明文间不是独立加密也不可用Frida爆破;
爆破理论:
  • 由于是通过数组来比较明文和密文的,所以相关数组必然会有一个索引自增的位置,我们就可以通过数组的自增知道这个位置爆破的字符是等于明文的;
  • 如果此时爆破的字符不等于明文,必然会有一个条件判断,使得程序跳转到错误指令的地方,我们可以通过此条件的变化知道此时爆破的字符不等于明文,于是尝试下一个字符的爆破;
爆破方法:
  • 基于条件断点的理论,等同于在程序的固定位置增加函数,因此我们可以在索引自增的地方增加一个函数,功能为:输出此时爆破的字符,即明文;并且尝试下一位明文的爆破;
  • 在程序进入错误函数的条件的位置增加一个函数,功能为输出下一位爆破字符,并且再次判断是否触发函数的判断条件;

BUUCTF-[MRCTF2020]Xor

frida学习了,试试之前没有得到的题,直接修改脚本,自增的地方地址为10E0,判断错误的地方为10FF,退出的地址为4B7E(地址110E处调用的函数);flag长度为0x1B;光速套用脚本修改即可;

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
//Frida脚本
var num = 0//爆破的位置索引,也就是明文的索引
function main()
{
// var base = Module.findBaseAddress("babyvm.exe")
var base = Process.getModuleByName('xor.exe').base;//获取名为"babyvm.exe"的进程,.date是这个进程在内存中加载的起始地址

var wrong_addr = 0x10FF//判断错误的分支地址
var i_addr = 0x10E0//明文和密文对比索引自增的位置
var exit_addr = 0x4B7E//调用退出的函数地址

if(base){//地址不为零,表示加载成功
Interceptor.attach(base.add(wrong_addr), {//hook判断错误的位置,基地址+相对偏移,执行到这个位置就执行js后续程序
onEnter: function(args) {
send(num);//发送num,也就是当前爆破的位置---明文索引
var tmp = 0;

for(var i = 0 ; i < 999999 ; i ++ )tmp += 1;//浪费时间,没啥用,尝试注释掉,发现后续的程序回闪退,查询得知:浪费一点 CPU 时间,人为延迟、方便 attach 后看状态/防止程序秒退等

var exit = new NativeFunction(base.add(exit_addr),'void',['int']);

exit(0)//结合上面,封装并调用程序本身的退出函数
}
});

Interceptor.attach(base.add(i_addr), {
onEnter: function(args) {
num += 1;
}

});//程序运行到这个位置,证明第num位比较成功,程序里面比较的索引要+1,所以我们也将爆破的索引+1,为下一次爆破做准备
}
}
setImmediate(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
#python脚本
import subprocess
import frida
import sys
import win32api
import win32con
import itertools
import string


file_path = fr"D:\Browser\xor\xor.exe"#引用文件绝对路径
filename = file_path.split('\\')[-1]#从文件路径里面提取出进程名字,'\'分割,取最后一部分,即'babyvm.exe'

js_code = open("./star_hook.js", "rb").read().decode()#以二进制rb方式读取Frida脚本并且将bytes解码为Python字符串

result = 0

def brute(F):
def on_message(message, data):
global result
if message['type'] == 'send': # send(num)
result = message['payload']#接收Frida传回来的num,否则打印错误
else:
print(message)

process = subprocess.Popen(file_path, stdin=subprocess.PIPE,#启动'babyvm.exe'程序,并且运行python向其中输入-->flag
stdout=subprocess.PIPE,#接收输出
stderr=subprocess.PIPE,#接收报错
universal_newlines=True)#将输入和输出从bytes处理为字符串

session = frida.attach(filename)#将Frida注入到进程,返回到session对象
script = session.create_script(js_code)#读入js后创建一个Frida脚本对象
script.on('message', on_message)#更新返回的输出和报错
script.load()#脚本开始注入

print(f"idx:{result}\t" + F.decode('latin1'), end='\r')#接收返回的消息(result,也就是上一次的num)并且打印为latin1编码,用回车覆盖当前行,不换行(end='\r')

output, error = process.communicate(input=F.decode('latin1'),timeout=2)#输入flag,并且设置了运行超时的时间

process.terminate()#关闭进程,避免进程并未关闭
return result#返回本次的num


flag_len = 0x1B#flag长度
flag = bytearray(b'!' * flag_len)#定义flag字符,'!'的ASCII码为33,在常见的可见字符前,故用此作为第一位字符爆破

idx = 0#明文的索引
flag_idx = 0#字符索引

while idx < flag_len:
num = brute(flag)
if num > idx:#num增加证明找到明文,大于当前明文的索引,进入
idx = num#更新爆破出来的明文长度
flag_idx += 1#更新一下明文索引,为下一次爆破做准备
print(flag.decode())
else:
flag[flag_idx] += 1#爆破错误,更新字符

**MRCTF{@_R3@1ly_E2_R3verse!!3verse!}**修改头即可;

babyvm

鉴于需要动调,需要先绕过一下反调试,30行开始调试的检测,63行才检测完调试开始正常的主函数,因此直接从29行的函数执行完时跳到63行正常主函数即可;

将这里的跳转函数指令修改即可,记得保存;

这是两次不同输入对比的密文,可以看出来这里只有第一位密文不一样,所以推断是单字节加密;各位可以再试试,鉴于是复现并且之前已经试过,故不再尝试;

跟踪输出”Correct!“可以很容易的找到加密过程;

这里可以动调发现algn_7FF674005664这个东西是传入的明文flag,但是在加密函数外交叉引用str会发现找不到引用,原因是因为”数组越界访问(不知道叫这个名字合不合适,但是个人是这样理解的)“;在这里可以看到明文的地址应该是7FF674005664,但是真正查看明文的时候会发现,明文的地址是7FF674005670,查看附近的汇编看看,可以发现它将5665h作为了基地址,然后使用rdi+rax的变化来访问数据,究其原因理解是:”数据段在内存中是连续的,假设我们定义两个数组a[10]和b[10],在正常情况下,我们应该是无法访问和编译a[11]的,但鉴于数据段内存的连续性,我们可以通过a[11]来间接访问b[0],a[11]是从a的地址之后的第11个数据,但恰好a首地址往后推的第11个数据的地址就是b[0]的地址,此时我们访问到了b[0],但是我们想要引用b的话,程序自然无法找到在哪,因为对于程序来说,它本身是没有调用b这个数组的,但它确实使用到了b这个数组的数据“;

回到加密逻辑,显然IDA反编译得到的伪代码很难看,所以这里在单字节加密的前提下,考虑爆破;向下查看一下明文和密文的比较逻辑;

随便看看伪代码,就可以去对照着汇编寻找我们爆破需要的东西(地址)了;v25是程序是否退出的条件,也就是明文和密文比较成功与否的标志;

大致了解后,我们就可以去套用Frida脚本了;

脚本一
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
//Frida脚本
var num = 0//爆破的位置索引,也就是明文的索引
function main()
{
// var base = Module.findBaseAddress("babyvm.exe")
var base = Process.getModuleByName('babyvm.exe').base;//获取名为"babyvm.exe"的进程,.date是这个进程在内存中加载的起始地址

var wrong_addr = 0x19FB//判断错误的分支地址
var i_addr = 0x19F0//明文和密文对比索引自增的位置
var exit_addr = 0x2C68//调用退出的函数地址

if(base){//地址不为零,表示加载成功
Interceptor.attach(base.add(wrong_addr), {//hook判断错误的位置,基地址+相对偏移,执行到这个位置就执行js后续程序
onEnter: function(args) {
send(num);//发送num,也就是当前爆破的位置---明文索引
var tmp = 0;

for(var i = 0 ; i < 999999 ; i ++ )tmp += 1;//浪费时间,没啥用,尝试注释掉,发现后续的程序回闪退,查询得知:浪费一点 CPU 时间,人为延迟、方便 attach 后看状态/防止程序秒退等

var exit = new NativeFunction(base.add(exit_addr),'void',['int']);

exit(0)//结合上面,封装并调用程序本身的退出函数
}
});

Interceptor.attach(base.add(i_addr), {
onEnter: function(args) {
num += 1;
}

});//程序运行到这个位置,证明第num位比较成功,程序里面比较的索引要+1,所以我们也将爆破的索引+1,为下一次爆破做准备
}
}
setImmediate(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
#python脚本
import subprocess
import frida
import sys
import win32api
import win32con
import itertools
import string


file_path = fr"C:\Users\Lenovo\Desktop\FNZCTF\babyvm\babyvm.exe"#引用文件绝对路径
filename = file_path.split('\\')[-1]#从文件路径里面提取出进程名字,'\'分割,取最后一部分,即'babyvm.exe'

js_code = open("./star_hook.js", "rb").read().decode()#以二进制rb方式读取Frida脚本并且将bytes解码为Python字符串

result = 0

def brute(F):
def on_message(message, data):
global result
if message['type'] == 'send': # send(num)
result = message['payload']#接收Frida传回来的num,否则打印错误
else:
print(message)

process = subprocess.Popen(file_path, stdin=subprocess.PIPE,#启动'babyvm.exe'程序,并且运行python向其中输入-->flag
stdout=subprocess.PIPE,#接收输出
stderr=subprocess.PIPE,#接收报错
universal_newlines=True)#将输入和输出从bytes处理为字符串

session = frida.attach(filename)#将Frida注入到进程,返回到session对象
script = session.create_script(js_code)#读入js后创建一个Frida脚本对象
script.on('message', on_message)#更新返回的输出和报错
script.load()#脚本开始注入

print(f"idx:{result}\t" + F.decode('latin1'), end='\r')#接收返回的消息(result,也就是上一次的num)并且打印为latin1编码,用回车覆盖当前行,不换行(end='\r')

output, error = process.communicate(input=F.decode(),timeout=2)#输入flag,并且设置了运行超时的时间

process.terminate()#关闭进程,避免进程并未关闭
return result#返回本次的num


flag_len = 0x18#flag长度
flag = bytearray(b'!' * flag_len)#定义flag字符,'!'的ASCII码为33,在常见的可见字符前,故用此作为第一位字符爆破

idx = 0#明文的索引
flag_idx = 0#字符索引

while idx < flag_len:
num = brute(flag)
if num > idx:#num增加证明找到明文,大于当前明文的索引,进入
idx = num#更新爆破出来的明文长度
flag_idx += 1#更新一下明文索引,为下一次爆破做准备
print(flag.decode())
else:
flag[flag_idx] += 1#爆破错误,更新字符

脚本二

在前面绕过动调的基础上,将程序结束跳回main函数,实现死循环,避免程序退出,方便持续爆破;

main函数的最后,这里有防止栈溢出的检测,也就是call指令这里,所以需要给这个地方修改一下,从lea这个指令开始,打开Assemble复制指令,从xor这个指令接着打开Assemble覆盖,重复直至retn;最后就算修改完成了;

试过了,不该报错了;按照上面修完就可以了;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Frida脚本
var a = [];
function main() {
Interceptor.attach(Module.findBaseAddress("babyvmfix.exe").add(0x19d7), {//附加进程并且寻找地址:基地址+偏移量
onEnter: function (args) {
console.log('[' + new Uint8Array(ptr(this.context.rbx).readByteArray(24)) + '], ');//调用回调函数,把rbx这个寄存器的值的前24字节包装为Uint8Array并输出到终端
},
onLeave: function (retval) {},
});
}

setImmediate(main);//加载完脚本执行frida


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#python脚本
from pwn import *

test = [0x46,0x5a,0x4e,0x43,0x54,0x46,0x7b,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x7d]#预定的字符串
p = process('./babyvmfix.exe')#附加进程

table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/_-+="#字符集
print(chr(table[54]), chr(table[46]), chr(table[65]))#打印进程ID端口号
print(p.pid)
pause()#等待回车

for j in range(len(table)):#循环字符集
for i in range(7, 23):#循环明文的7-22位
test[i] = table[j]#一个字符爆破多个位置
p.sendlineafter(b"starting...", bytes(test))#把整数列表转为bytes对象,等待出现"starting..."就输入
print(str(bytes(test)))#打印发送的字符
sleep(0.1)

p.interactive()#交互
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
#checkflag脚本
a = [[158,0,139,106,254,80,36,215,253,40,236,49,174,220,78,140,176,53,200,241,126,223,43,125],
[158,0,139,106,254,80,36,41,18,146,69,64,160,153,41,145,248,76,13,76,122,155,15,125],
[158,0,139,106,254,80,36,240,81,106,152,249,115,63,183,7,161,216,201,223,156,198,84,125],
[158,0,139,106,254,80,36,40,197,85,221,136,219,2,163,73,59,155,135,98,16,55,253,125],
[158,0,139,106,254,80,36,7,91,220,71,175,216,166,155,165,38,99,144,238,246,61,72,125],
[158,0,139,106,254,80,36,33,222,177,163,132,3,130,130,93,152,134,241,110,184,135,252,125],
[158,0,139,106,254,80,36,71,145,136,225,48,74,122,121,108,170,45,5,171,101,1,39,125],
[158,0,139,106,254,80,36,131,210,183,44,152,163,158,20,83,207,98,203,54,99,195,243,125],
[158,0,139,106,254,80,36,243,219,139,167,141,241,210,91,46,73,38,100,101,225,15,186,125],
[158,0,139,106,254,80,36,230,39,115,93,62,167,186,48,40,186,35,99,19,174,239,54,125],
[158,0,139,106,254,80,36,83,151,189,58,191,213,112,157,105,208,101,235,161,171,107,116,125],
[158,0,139,106,254,80,36,51,184,92,245,65,196,165,120,126,183,92,169,82,37,121,63,125],
[158,0,139,106,254,80,36,134,205,11,39,27,71,227,118,144,251,164,77,226,76,152,51,125],
[158,0,139,106,254,80,36,70,53,104,200,113,92,201,179,163,255,119,28,153,219,6,106,125],
[158,0,139,106,254,80,36,124,203,250,219,124,22,11,143,250,118,149,188,169,59,95,10,125],
[158,0,139,106,254,80,36,58,4,98,231,161,1,145,185,121,111,187,132,94,104,190,11,125],
[158,0,139,106,254,80,36,156,141,238,43,10,88,246,144,137,56,128,233,210,123,83,44,125],
[158,0,139,106,254,80,36,76,83,173,75,15,69,143,244,16,120,120,0,39,255,145,30,125],
[158,0,139,106,254,80,36,2,227,76,122,40,61,222,193,233,103,10,89,180,168,226,19,125],
[158,0,139,106,254,80,36,133,50,23,30,177,238,243,93,88,14,126,32,196,11,204,21,125],
[158,0,139,106,254,80,36,22,70,124,159,176,175,12,129,23,195,122,103,142,19,122,139,125],
[158,0,139,106,254,80,36,27,92,221,189,97,179,46,22,217,121,112,226,158,53,245,108,125],
[158,0,139,106,254,80,36,199,147,209,155,26,212,237,53,26,239,96,255,189,39,169,228,125],
[158,0,139,106,254,80,36,137,153,169,118,72,23,33,232,2,143,51,94,12,196,62,26,125],
[158,0,139,106,254,80,36,179,61,224,15,219,224,127,89,190,237,188,124,121,17,208,40,125],
[158,0,139,106,254,80,36,126,215,90,227,126,43,215,107,252,233,168,86,14,118,87,17,125],
[158,0,139,106,254,80,36,160,245,65,3,183,181,0,50,79,68,36,218,73,67,206,147,125],
[158,0,139,106,254,80,36,181,109,116,141,131,150,86,174,242,107,181,195,187,150,105,37,125],
[158,0,139,106,254,80,36,109,240,254,97,189,132,213,158,251,137,249,12,147,71,212,184,125],
[158,0,139,106,254,80,36,159,108,155,148,221,28,182,136,205,181,158,22,177,51,250,123,125],
[158,0,139,106,254,80,36,94,65,211,27,70,44,136,23,81,250,230,148,8,138,18,141,125],
[158,0,139,106,254,80,36,84,212,181,92,151,155,99,40,102,100,116,122,193,161,63,153,125],
[158,0,139,106,254,80,36,246,57,1,131,155,207,66,56,219,134,41,58,159,65,76,32,125],
[158,0,139,106,254,80,36,90,3,35,210,182,96,70,52,155,222,152,227,146,131,134,202,125],
[158,0,139,106,254,80,36,214,133,114,31,43,67,191,9,206,10,213,119,160,119,159,236,125],
[158,0,139,106,254,80,36,234,127,219,204,157,182,238,94,235,71,85,71,127,215,138,45,125],
[158,0,139,106,254,80,36,86,154,73,190,154,102,209,64,101,2,3,97,114,135,28,76,125],
[158,0,139,106,254,80,36,132,106,79,47,172,244,218,203,249,247,180,48,248,227,21,185,125],
[158,0,139,106,254,80,36,81,186,21,246,12,45,17,142,141,128,125,164,205,160,84,162,125],
[158,0,139,106,254,80,36,174,93,16,218,8,148,245,100,214,34,192,171,51,239,156,96,125],
[158,0,139,106,254,80,36,202,102,212,130,95,40,74,5,237,220,240,143,152,253,216,13,125],
[158,0,139,106,254,80,36,39,223,242,249,243,185,206,236,90,29,196,231,184,34,31,97,125],
[158,0,139,106,254,80,36,79,135,140,129,112,78,178,190,71,102,114,109,201,191,172,41,125],
[158,0,139,106,254,80,36,116,170,63,24,47,65,50,28,220,48,90,146,99,224,199,117,125],
[158,0,139,106,254,80,36,32,80,67,252,227,41,192,220,114,230,247,238,197,254,243,187,125],
[158,0,139,106,254,80,36,129,198,78,57,173,204,79,240,196,124,236,29,40,62,57,225,125],
[158,0,139,106,254,80,36,138,94,176,41,164,209,144,116,177,214,95,161,231,31,26,69,125],
[158,0,139,106,254,80,36,152,247,175,110,78,202,208,239,231,215,220,225,120,60,167,156,125],
[158,0,139,106,254,80,36,14,98,109,247,116,80,189,213,72,51,12,50,129,117,127,148,125],
[158,0,139,106,254,80,36,219,156,166,20,159,233,146,87,244,185,111,85,217,87,186,94,125],
[158,0,139,106,254,80,36,223,234,130,208,240,27,253,79,139,203,252,112,0,205,164,62,125],
[158,0,139,106,254,80,36,57,224,70,126,18,197,137,246,198,17,7,186,185,89,13,121,125],
[158,0,139,106,254,80,36,45,72,17,147,111,245,31,212,142,165,171,179,216,98,196,99,125],
[158,0,139,106,254,80,36,252,40,143,85,4,158,141,242,78,225,150,165,41,248,64,166,125],
[158,0,139,106,254,80,36,52,160,137,83,68,236,252,114,34,4,169,110,219,24,174,188,125],
[158,0,139,106,254,80,36,63,63,201,157,53,133,123,74,236,197,200,16,118,20,35,221,125],
[158,0,139,106,254,80,36,158,255,7,153,213,217,114,98,186,55,88,174,23,185,14,224,125],
[158,0,139,106,254,80,36,145,192,253,123,76,168,27,62,243,136,208,91,188,2,43,55,125],
[158,0,139,106,254,80,36,29,176,93,161,254,16,59,19,246,98,107,64,162,234,193,193,125],
[158,0,139,106,254,80,36,237,124,210,21,143,140,199,178,68,89,186,92,243,238,170,14,125],
[158,0,139,106,254,80,36,187,55,32,36,34,55,82,216,70,204,173,49,60,241,49,240,125],
[158,0,139,106,254,80,36,21,216,48,125,195,117,91,172,168,90,106,224,24,211,228,118,125],
[158,0,139,106,254,80,36,251,37,89,209,103,87,128,192,10,12,228,33,135,28,202,47,125],
[158,0,139,106,254,80,36,155,241,191,156,237,208,202,197,100,91,237,177,212,0,24,211,125],
[158,0,139,106,254,80,36,10,27,237,196,110,8,204,32,69,252,229,182,104,244,4,1,125],
[158,0,139,106,254,80,36,188,225,246,45,209,215,102,231,32,206,81,213,95,151,165,174,125],
[158,0,139,106,254,80,36,251,37,89,209,103,87,128,192,10,12,228,33,135,28,202,47,125],
[158,0,139,106,254,80,36,150,162,144,2,38,64,108,96,80,110,62,116,56,88,58,60,125]]


table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/_-+="

tar = [0x9E,0x0,0x8B,0x6A,0xFE,0x50,0x24,0xFC,0xC6,0xED,0x1F,0xE3,0x8,0x0,0x64,0x45,0xFA,0x58,0xEE,0x0,0xF4,0xA7,0xA2,0x7D]


print('Flag: FNZCTF{', end='')
for i in range(7, 23):
for j in range(len(table)):
if(a[j][i] == tar[i]):
print(chr(table[j]), end='')
break
print('}', end='')

感觉原理都差不多,解毕 FNZCTF{1t_is_an_e4sy_vm}

Misc

Misc-有趣的emoji

进去第一个压缩包密码,看文件名得到压缩包密码FZNCTF,跟进得到txt文件和一个asc文件,以及一个压缩包;


???对这句话RSA加密???,当时比赛理解的是对这串英文字符进行RSA加密…最后发现RSA加密的私钥每次分解出来的私钥对是随机的啊…这怎么处理?

我以为这串是私钥…没看出来PGP是一种加密;后面比完赛了才知道,才疏学浅了;直接给这串丢到CyberChef上了;

前十二个字符就是压缩包密码“wcBMA2Uwnbca”

比赛前一天才聊到emoji是Base100加密…结果困在压缩包密码了…

FZNCTF{M1sc_15_s0_f45cin4ting!}