2025 XAUTCTF新生赛 rev&algo部分WP

debu8ger Lv3

algo

ezzz_base


题目描述:欸,这个base怎么有点不一样??


打开附件,

dict:{0: 'J', 1: 'K', 2: 'L', 3: 'M', 4: 'N', 5: 'O', 6: 'x', 7: 'y', 8: 'U', 9: 'V', 10: 'z', 11: 'A', 12: 'B', 13: 'C', 14: 'D', 15: 'E', 16: 'F', 17: 'G', 18: 'H', 19: '7', 20: '8', 21: '9', 22: 'P', 23: 'Q', 24: 'I', 25: 'a', 26: 'b', 27: 'c', 28: 'd', 29: 'e', 30: 'f', 31: 'g', 32: 'h', 33: 'i', 34: 'j', 35: 'k', 36: 'l', 37: 'm', 38: 'W', 39: 'X', 40: 'Y', 41: 'Z', 42: '0', 43: '1', 44: '2', 45: '3', 46: '4', 47: '5', 48: '6', 49: 'R', 50: 'S', 51: 'T', 52: 'n', 53: 'o', 54: 'p', 55: 'q', 56: 'r', 57: 's', 58: 't', 59: 'u', 60: 'v', 61: 'w', 62: '+', 63: '/', 64: '='}

chipertext:
PNO99NC8GX3nHMCgc8KT9x9SQTKxQpUndn9w

通过“=”和64个字符,得知是base64编码的一个换表,

重新换表后输入密文解码得到flag:XAUTCTF{tH3_m@sTer_0F_b4sE}

e咋不是65537???


题目描述:完了,e不是65537,咋办呢?


关于RSA的前置知识,建议食用关于CTF-RSA题目类型解题思路

打开附件:

from Crypto.Util.number import *
m=bytes_to_long(b'xxxxxx')
p=getPrime(256)
q=getPrime(256)
e=74
n=p*q
c=pow(m,e,n)
print("p=",p)
print("q=",q)
print("c=",c)
#p = 86053582917386343422567174764040471033234388106968488834872953625339458483149
#q = 72031998384560188060716696553519973198388628004850270102102972862328770104493
#c = 331902287503397442396418773991759527842718419738370800180657653090960908650804
# 1781233603918453326670975069669017566120311964861589659101428987255146018427

简单的RSA,但与一般的RSA不同的是e为74。

e=74=2*37,不与phi(n)互素,得使用最大公约数(GCD)来解答,明文m0,密文c,c=m0^e (mod n)

t = gcd(e,phi(n)),使e = t*e',从而得到gcd(e',phi(n))=1,重新互素。

计算步骤:

  1. 计算t = gcd(e,phi(n))
  2. e' = e/t,求d = (e')^-1 (mod phi(n))
  3. 最后计算m = c^d mod n,也就为最终的明文

EXP:

import gmpy2
from Crypto.Util.number import *

# 当e约去公约数t后与phi(n)互素
def decrypt(p, q, e, c):
n = p * q
phi = (p - 1) * (q - 1)
t = gmpy2.gcd(e, phi)
d = gmpy2.invert(e // t, phi)
m = pow(c, d, n)
print(m)
msg = gmpy2.iroot(m, t)
print(msg)
if msg[1]:
print(long_to_bytes(msg[0]))
e=74
p= 86053582917386343422567174764040471033234388106968488834872953625339458483149
q= 72031998384560188060716696553519973198388628004850270102102972862328770104493
c= 3319022875033974423964187739917595278427184197383708001806576530909609086508041781233603918453326670975069669017566120311964861589659101428987255146018427

decrypt(p, q, e, c)

# XAUTCTF{e_1s_n0t_@_Prime}

Close Enough


题目描述:听说 RSA 非常安全?可是如果两个质数选得“太接近”,结果会怎样呢?


打开附件,

from Crypto.Util.number import getPrime, bytes_to_long, isPrime, inverse

//找到大于n的最大素数
def nextprime(n):
n += 1
while not isPrime(n):
n += 1
return n

flag = ""
e = 65537

p = getPrime(512)
q = nextprime(p + (1 << 20))

n = p * q
phi = (p - 1) * (q - 1)
d = inverse(e, phi)

m = bytes_to_long(bytes(flag, 'utf-8'))
c = pow(m, e, n)

print("n =", n)
print("c =", c)

'''
n = 123396213393166669967180741417142386608199293295343396860771048265983027294499309946576382614888097841439905355747919662299668639065387197060901118151079928153661471067906790612624750455011912757452786783406975664690965235505528837643347037179762435944987875469138529309017524600020070268892228090521628748157
c = 96164959972807254618417630680358223130932461911993510788732180904733021127322517962027522173599694137945712716717847174536035583857007099675639087774330478493529755676338936283880541666682835088571888431839407259147158612358623749706985446040831405827991266588402528874606153834653456725906949141238839683080
'''

我们知道了n、c和e,通过nextprime函数和p、q的设定得知q是p+2^20之后的一个大素数,鉴于生成的素数都很大,所以该题中p和q相差不大。

在这里,我们有两种思路:

  1. 费马分解:我们知道了n = p*q后,设:

    a = (p+q)/2
    b = (q-p)/2
    得 n = (a+b)(a-b) = a^2-b^2

    本题,pq接近,可以从ceil(sqrt(n))将n开方后尝试a,直至a^2-n为完全平方数

  2. 基于偏移量的暴力搜索(逼近)

    我们知道了q = p +2^20,则n = p*q = p*(p +2^20) = p^2 + 2^20 * p

    p^2 + 2^20 * p - n也就约等于0了,解方程式得p。

    知道了p,也就知道了q,之后就是RSA的常规操作了。

所以,我们解密该RSA的核心思路就是通过p^2 + 2^20 * p - n解方程得到p的大概值,从其前后各试多个数,从是不是素数q_ca = p+2^20大的下一个素数p*q_ca是不是为n几个方面猜测,循环直至得到找到pq。通过数学估算+暴力逼近解密RSA。

EXP:

from math import isqrt
from Crypto.Util.number import isPrime, inverse, long_to_bytes

n = 123396213393166669967180741417142386608199293295343396860771048265983027294499309946576382614888097841439905355747919662299668639065387197060901118151079928153661471067906790612624750455011912757452786783406975664690965235505528837643347037179762435944987875469138529309017524600020070268892228090521628748157
c = 96164959972807254618417630680358223130932461911993510788732180904733021127322517962027522173599694137945712716717847174536035583857007099675639087774330478493529755676338936283880541666682835088571888431839407259147158612358623749706985446040831405827991266588402528874606153834653456725906949141238839683080
e = 65537

def nextprime(n):
n += 1
while not isPrime(n):
n += 1
return x

//估算p
delta = 1 << 20
p_guess = (isqrt(4 * n + delta * delta) - delta) // 2

//得到大概值后前后数猜测
for diff in range(-50000, 50000):
p = p_guess + diff
if p <= 1: continue
if not isPrime(p): continue
q = nextprime(p + delta)
if p * q == n:
break
else:
print("Failed to find p, q")
exit()

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode('utf-8', errors='ignore')

print("Flag:", flag)

#flag: XAUTCTF{Y0u_c@n_d0_3zzz_rs4!!!}

rev

SignIn


题目描述:来签个到吧! 什么?你想直接得到flag?


下载附件,用IDA打开,看到全是密密麻麻的函数肯定不想看下去了吧

直接Ctrl+F12看所有的字符串,得到flag:XAUTCTF{F|rsT_5t3P_}

Welcome to Reverse World


题目描述:XAUT网络安全小组的新实验室刚刚上线,传说每一个想要进入实验室的人,必须在入口处输入正确的口令。

然而,这个口令被小组的成员编译进了一段神秘程序中。

想要成为实验室的新成员,你必须通过逆向分析,找出正确的通关口令!

程序已经为你准备好,快来试试吧!


下载附件,用IDA打开,查看字符串,很遗憾,你被骗了 T_T

本题有两种解法:直接用strings看/将十六进制转换

第一种转换:进入main函数,re1

v6数组存储的字符串就是加密后的flag,即 xmmword_2080xmmword_2090xmmword_20A0

单击进入,得到小端序的flag十六进制字节:

re2

我们把这些数值反转,再转换为ASCII字符,

得到flag:XAUTCTF{W3c0me_T0_tH3_w0r1d_oF_R3verSe_3nGineErin9!!!}

第二种直接strings命令:在命令行输入strings welcome_rev,即可得到flag。

re3

你喜欢贝斯吗


题目描述:听说贝斯蛮好听的,一起来听吧!


下载附件。在IDA中打开,

一样的,查看字符串,看到一串奇怪的字符串WEFVVENURntTM2MwbkRfOUhhczNffQ==

后面有两个=的一定是base64编码,一个=的一般是base32编码,

我们放到解码模块里得到flag:XAUTCTF{S3c0nD_9Has3_}

base_revenge


题目描述:base reunion


IDA中打开,查看main函数,代码逻辑为用base58字母表加密base64表然后再用解密后的base64表去加密flag,得到密文。

知道了逻辑,只需找到对应的字符串即可,

re4

先用base58标准字母表解密base64加密表(注意,上下两个字符串都是表,base64表为64个字符)得ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

再用解密后的表解密密文得到flag:XAUTCTF{|_|nu5U@L_b@sE}

Eeeeazy


题目描述:can u find me??? 得到的flag用XAUTCTF{}包裹提交

​ hint:涉及动态调试,可参考IDA使用技巧之动态调试


IDA打开,定位_main函数,打开:

 __main();
time(&Time);
v5 = localtime(&Time);
puts("Can you find me?\n");
system("pause");
return 0;
}

没啥好看的,继续看可疑函数,找到了_ques函数

  v3 = 2147122737;
v4[0] = 140540;
v4[1] = -2008399303;
v4[2] = 141956;
v4[3] = 139457077;
v4[4] = 262023;
v4[5] = -2008923597;
v4[6] = 143749;
v4[7] = 2118271985;
v4[8] = 143868;
for ( i = 0; i <= 4; ++i )
{
memset(v2, 0, sizeof(v2));
v8 = 0;
v7 = 0;
v0 = v4[2 * i];
LODWORD(v6) = v4[2 * i - 1];
HIDWORD(v6) = v0;
while ( v6 > 0 )
{
v2[v8++] = v6 % 2;
v6 /= 2LL;
}
for ( j = 50; j >= 0; --j )
{
if ( v2[j] )
{
if ( v2[j] == 1 )
{
putchar(42);
++v7;
}
}
else
{
putchar(32);
++v7;
}
if ( !(v7 % 5) )
putchar(32);
}
result = putchar(10);
}
return result;
}

很明显是一个加密函数,你可以选择解密这一段代码,逻辑是将v4数组的奇数索引的值作为低位,偶数为高位,一起组成一个64位的整数v6,以二进制位图打印出图形

v4 = [140540,-2008399303,141956,139457077,262023,-2008923597,143749,2118271985]
for i in range(4):
n = ((v4[i*2] & 0xFFFFFFFF) << 32) | (v4[i*2+1] & 0xFFFFFFFF)
print(''.join(' *'[((n >> (49-j)) & 1)] + ' '*(j%5==4) for j in range(50)))

运行看到flag为 HACKIT4FUN :re5

但本题真正考察IDA的动态调试。

通过看该附件的_main函数汇编代码,re6,在0x401773下断点,让我们能在main函数执行完成前暂停程序;

然后,再看_ques函数,先记下_ques开始的位置0x401520,这里汇编语言中pop ebp即为将把基址指针寄存器弹出堆栈,也就是将我们之前所说的 v6 所包含的值剥开来,呈现在命令行里。所以,我们要想在运行时得到位图,就得在pop这儿下断点,即0x401723,方便查看最终的flag。

我们运行程序,来到该界面:re8

General registers0x0401773修改EIP为刚刚我们记下的_ques开始的位置0x0401520,然后点击继续运行,即可得到二进制位图flag XAUTCTF{HACKIT4FUN} :re10

糖衣炸弹


题目描述:听说每一位新成员都会得到一颗SS-Team定制糖果吗?

​ 真的有那么善良吗?

​ “不要随便吃糖,有毒咋办?”妈妈说。

​ hint:U konw Packed Xanadu?(用UPX脱壳)


下载附件,先查查有无加壳,放到Exeinfo里查壳,re11,得到有UPX的壳。

于是,我们到upx指定目录下,运行./upx -d candy.exe给其脱壳(关于UPX相关介绍),

re12

然后再IDA打开,定位到主函数(这里不是main):

v7 = __readfsqword(0x28u);
sub_405450("Welcome to XAUT simple XOR crackme!");
sub_405450("Enter the secret code to reveal the flag:");
if ( sub_4050B0(v6, 64LL, off_4B06D8) )
{
v6[sub_401140(v6, "\r\n")] = 0;
if ( (unsigned int)sub_401A90(v6) )
{
*(__m128i *)v5 = _mm_load_si128((const __m128i *)&xmmword_48AA40);
*(__m128i *)&v5[9] = _mm_load_si128((const __m128i *)&xmmword_48AA50);
v0 = sub_4108A0(26LL);
if ( v0 )
{
for ( i = 0LL; i != 25; ++i )
*(_BYTE *)(v0 + i) = v5[i] ^ 0x5A;
*(_BYTE *)(v0 + 25) = 0;
sub_41BFA0(2, (unsigned int)"Good job! The flag is:\n%s\n", v0, (unsigned int)v5, v1, v2, v5[0]);
sub_410FB0(v0);
}
}
else
{
sub_405450("Wrong secret. Try reversing the binary!");
}
}
if ( v7 != __readfsqword(0x28u) )
sub_41C070();
return 0LL;
}

xmmword_48AA40xmmword_48AA50里的数据存入v5里,再进行异或操作得到密文,我们要做的是把密文异或回去 明文 ^ key = 密文,即 密文 ^ key = 明文,这里key0x5A

最后的EXP:

enc_flag = [2, 27, 15, 14, 25, 14, 28, 33, 3, 106, 47, 5, 61, 106, 14, 5, 105, 44, 51, 22, 5, 47, 10, 34, 39]
key = 0x5A

plain = ''.join(chr(i ^ key) for i in enc_flag)
print("Decrypted flag:", plain)

#flag:XAUTCTF{Y0u_g0T_3viL_uPx}
Comments