CTF-做题记录
Write-up:
几道比较基础的ctf题,题目来源:西安电子科技大学网安竞赛暑期培训
Reverse:
1.Click:
题目描述:题目来源: 2023 52pojie 新春 第3题
解题方法:题目附件下载下来之后是一个apk的文件,把它安装到手机上,是一个程序,进去界面就是一直Click点击,当点击次数等于999时就可以得到flag
这里我们把它放进jadx-gui里面,去查看一下他的源码,找到他的main函数来分析它的源码
截取里面关键的两段源码并且将注释加到他的源码上,便于分析:
/**
* Lambda表达式,用于处理MainActivity的onCreate方法中的特定逻辑。
*
* @param this$0 MainActivity的实例,表示当前上下文。
* @param key 用于显示文本的TextView。
* @param view 视图对象,可能是某个用户界面元素。
*/
public static final void m19onCreate$lambda0(MainActivity this$0, TextView key, View view) {
// 检查参数不为空,如果为空,则抛出NullPointerException。
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(key, "$key");
// 获取MainActivity的实例,并调用jntm方法。
MainActivity mainActivity = this$0;
this$0.jntm(mainActivity);
// 将TextView的文本设置为MainActivity实例的num值的字符串表示。
key.setText(String.valueOf(this$0.num));
// 检查MainActivity实例的check方法的返回值是否为999。
if (this$0.check() == 999) {
// 如果是,显示Toast提示信息。
Toast.makeText(mainActivity, "快去论坛领CB吧!", 1).show();
// 将TextView的文本设置为使用decrypt方法解密的特定字符串。
key.setText(this$0.decrypt("hnci}|jwfclkczkppkcpmwckng\u007f", 2));
}
}
/**
* 解密方法,将加密的文本还原为原始文本。
*
* @param encryptTxt 要解密的文本。
* @param i 解密时使用的偏移量。
* @return 解密后的原始文本。
*/
public final String decrypt(String encryptTxt, int i) {
// 检查参数不为空,如果为空,则抛出NullPointerException。
Intrinsics.checkNotNullParameter(encryptTxt, "encryptTxt");
// 将加密文本转换为字符数组。
char[] charArray = encryptTxt.toCharArray();
// 检查字符数组不为空,如果为空,则抛出NullPointerException。
Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
// 创建StringBuilder对象,用于存储解密后的文本。
StringBuilder sb = new StringBuilder();
for (char c : charArray) {
// 将每个字符减去偏移量,并追加到StringBuilder中。
sb.append((char) (c - i));
}
// 将StringBuilder转换为字符串并返回。
String sb2 = sb.toString();
// 检查字符串不为空,如果为空,则抛出NullPointerException。
Intrinsics.checkNotNullExpressionValue(sb2, "with(StringBuilder()) {\n… toString()\n }");
return sb2;
}
这里的逻辑很简单,就是当我们点击到check==999时:
// 将TextView的文本设置为使用decrypt方法解密的特定字符串。
key.setText(this$0.decrypt("hnci}|jwfclkczkppkcpmwckng\u007f", 2));
然后在解密函数里面,它将传下来的字符串减去2个偏移量,就可以返回解密后的原始文本即flag
现在我们编写python脚本就可以得到flag:
str = 'hnci}|jwfclkczkppkcpmwckng\u007f'
flag = ''
for i in range(len(str)):
flag += chr(ord(str[i])-2)
print(flag)
运行得到flag:flag{zhudajiaxinniankuaile}
2.T1:
题目描述:来自buu
解题方法:题目附件下载下来之后,发现是一个exe文件
把它放进exeinfope.exe里面查看一下它的属性:
这里显示它是一个64位的无壳的exe,现在把它放进IDA里面,F5反汇编一下,查看它的伪代码:
#include <stdio.h>
#include <string.h>
void sub_1400111D1(const char *message);
void sub_14001128F(const char *format, ...);
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char *v3;
__int64 i;
size_t v5;
char v7;
int j;
char Str1[224];
__int64 v10;
v3 = &v7;
for (i = 82i64; i; --i)
{
*(_DWORD *)v3 = -858993460;
v3 += 4;
}
// 遍历字符串 Str2 中的字符,将所有 'o' 替换为 '0'
for (j = 0; ; ++j)
{
v10 = j;
if (j > j_strlen(Str2))
break;
if (Str2[j] == 111)
Str2[j] = 48;
}
// 输出提示信息,要求用户输入标志
sub_1400111D1("input the flag:");
// 接受用户输入的字符串,最多 20 个字符
sub_14001128F("%20s", Str1);
// 获取字符串 Str2 的长度
v5 = j_strlen(Str2);
// 比较用户输入的字符串和 Str2 是否相同
if (!strncmp(Str1, Str2, v5))
sub_1400111D1("this is the right flag!\n");
else
sub_1400111D1("wrong flag\n");
return 0;
}
这里的逻辑很清楚,我们的输入为Str1,然后将我们的输入Str1和Str2进行比较,相同就输出这是正确的flag,所以这里我们的思路很清楚,Str2里面的值就是我们的flag,但是这里有一个条件,在上面对Str2进行了转换,遍历字符串 Str2 中的字符,将所有 'o' 替换为 '0'
我们先看Str2里面的值:
flag{hello~~}
转换一下,将所有 'o' 替换为 '0',就是我们正确的flag:
flag{hell0~~}
3.T2:
题目描述:easy algorithm
解题方法:题目附件下载下来之后,发现是一个exe文件
把它放进exeinfope.exe里面查看一下它的属性:
这里显示它是一个64位的无壳的exe,现在把它放进IDA里面,F5反汇编一下,查看它的伪代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+20h] [rbp-40h]
__int64 v5[2]; // [rsp+40h] [rbp-20h] BYREF
int v6; // [rsp+50h] [rbp-10h]
int i; // [rsp+5Ch] [rbp-4h]
_main(argc, argv, envp);
v5[0] = 0i64;
v5[1] = 0i64;
v6 = 0;
v4[0] = 110;
v4[1] = -108;
v4[2] = 97;
v4[3] = 111;
v4[4] = -101;
v4[5] = 114;
v4[6] = 109;
v4[7] = -98;
v4[8] = 109;
v4[9] = -110;
v4[10] = -109;
v4[11] = 109;
v4[12] = 103;
v4[13] = 81;
v4[14] = -109;
v4[15] = 103;
v4[16] = 110;
v4[17] = -99;
v4[18] = -106;
v4[19] = 0x85;
puts("Hi!CTFer!please input your flag:");
scanf("%20s", v5); //这里是我们的输入v5
for ( i = 0; i <= 19; ++i )
{ //对我们的输入v5进行一系列操作,遍历v5将所有字符的偏移量加上20,再和0x14进行异或
*((_BYTE *)v5 + i) += 20;
*((_BYTE *)v5 + i) ^= 0x14u;
}
for ( i = 0; i <= 19; ++i )
{
if ( *((_BYTE *)v5 + i) != v4[i] ) //这里将处理过后的v5和v4进行比较,相同就输出你是对的
{
puts("qwq~ something worng!");
exit(0);
}
}
puts("you are right!");
return 0;
}
分析到这里,我们的思路就很清晰,我们的输入v5进行一系列的操作之后和v4进行比较,那我们就逆向思维过来,我们shift+E提取出v4的值,然后将v4的值进行逆向回去,就可以得到我们的输入的正确的flag:
编写脚本:
str = [0x6E,0x94,0x61,0x6F,0x9B,0x72,0x6D,0x9E,0x6D,0x92,0x93,0x6D,0x67,0x51,
0x93,0x67,0x6E,0x9D,0x96,0x85]
flag = ''
for i in range(len(str)):
flag += chr((str[i]^0x14)-20)
print(flag)
得到flag:flag{Reverse_1s_fun}
4.简单注册机:
题目描述:来自buuctf re区
解题方法:附件下载下来发现是一个apk文件
这里我们把它放进jadx-gui里面,去查看一下他的源码,找到他的main函数来分析它的源码:
这里我们分析它生成注册码的这段核心代码:
public void onClick(View v) {
int flag = 1;
String xx = editview.getText().toString();
if (xx.length() != 32 || xx.charAt(31) != 'a' || xx.charAt(1) != 'b' || (xx.charAt(0) + xx.charAt(2)) - 48 != 56) {
flag = 0;
}
if (flag == 1) {
char[] x = "dd2940c04462b4dd7c450528835cca15".toCharArray();
x[2] = (char) ((x[2] + x[3]) - 50);
x[4] = (char) ((x[2] + x[5]) - 48);
x[30] = (char) ((x[31] + x[9]) - 48);
x[14] = (char) ((x[27] + x[28]) - 97);
for (int i = 0; i < 16; i++) {
char a = x[31 - i];
x[31 - i] = x[i];
x[i] = a;
}
String bbb = String.valueOf(x);
textview.setText("flag{" + bbb + "}");
return;
}
textview.setText("输入注册码错误");
}
});
}
这里注册码的生成算法很清晰明了,现在我们顺着它的算法来编写一个python脚本来生成注册码及我们flag:
def manipulate_string():
x = list("dd2940c04462b4dd7c450528835cca15")
x[2] = chr(ord(x[2]) + ord(x[3]) - 50)
x[4] = chr(ord(x[2]) + ord(x[5]) - 48)
x[30] = chr(ord(x[31]) + ord(x[9]) - 48)
x[14] = chr(ord(x[27]) + ord(x[28]) - 97)
for i in range(16):
a = x[31 - i]
x[31 - i] = x[i]
x[i] = a
bbb = ''.join(x)
return "flag{" + bbb + "}"
result = manipulate_string()
print(result)
得到注册码(flag):
flag{59acc538825054c7de4b26440c0999dd}
Pwn:
1.ez_ret:
题目描述:最简单的栈溢出
解题方法:附件下载下来是一个ELF文件,在linux里面checksec一下,查看一下它开启了哪些保护:
这里发现是一个64位的ELF文件,而且它的保护机制就开启了NX保护,其他的栈保护,地址随机化都没有开启,说明难度不大
我们把它放进IDA里面,分析一下它的反汇编代码:
这里我们发现一个栈溢出的漏洞:
这里的buf定义的大小是128位,但是read函数可读取0x200位,说明可能存在栈溢出漏洞
查看一下它的栈空间:
这里满足栈溢出漏洞的条件,可以发现buf就只有0x80字节的大小,输入超过0x80个字节以后,你就会溢出,你会覆盖返回地址。所以如果你先填充字节,然后修改掉返回地址就可以调转到你想要跳转的地方。
存在溢出条件,接下来就是要找后门函数地址了。(为什么/bin/sh在前面,因为64位先传参数,其次找函数的地址就是找system函数以及它的参数)
发现system(“/bin/sh")函数地址,只要我们能控制程序返回到0x40059A,就可以得到系统的shell了
现在我们就编写exp:
from pwn import *
p = remote('192.168.0.113',57522)
payload = b'a'*(0x80+8)+p64(0x40059A)
p.sendline(payload)
p.interactive()
进行攻击就可以拿到它的shell来获取flag:
flag{ret2text_so_ez}
Crypto:
1.Caesar_Cipher:
题目描述:简单的凯撒加密
解题方法:题目下载下来得到一串编码
yetz{rhn_atox_extKg_ahP_Mh_wxvKriM_Max_vtxltK_vbiaxK_n9byqb9b!CZhyFC3}
放进凯撒解密中枚举一下就可以得到flag:
flag{you_have_leaRn_hoW_To_decRypT_The_caesaR_cipheR_u9ifxi9i!JGofMJ3}
2.ez_operation:
题目描述:扣1恢复flag,hint:你听说过xor吗
解题方法:附件下载下来得到一串编码:
V11QVkpIXmRuVVhCUl5HVENUVW5FWVRuV11QdhBufGJdfHZYYgJ7dV0IAUNoYlhee1JYV2IHB1hddXxnAnVSWGhXEF1hTA==
还没有思路,待解
3.ez_Rsa:
题目描述:difficulty: moe (dbt version)
解题方法:将题目附件下载下来之后,查看它的加密源码:
from Crypto.Util.number import *
p = getPrime(1024)
q = getPrime(1024)
with open("flag.txt","r") as f:
flag = f.read().strip().encode()
m = bytes_to_long(flag)
n = p * q
e1 = 0x114514
e2 = 19198101
c1 = pow(m,e1,n)
c2 = pow(m,e2,n)
print(c1) # 11243012629649045184257669052855431188426619870857851755340357869962705251529124481978887404032231030311954715759443327334649701826838249497627422162723529080187074305359133800382818757112524981361511272720347671484879744564726772477690953799909596578674760833743895999872098342933776047006084882941079225529272699832145082693784520673369701708282010624156314804803765740561724885216012240803579559575196257659976536322512513863415544971227511774584604634702835852394154465564382214825073417512177133660720176845005417197321877972124248232888049633785306052049643162715767407206695527961925841675825471402231322773302
print(c2) # 13908838404665714525469872111009048142275774516574817012612303360182929186976326418845853832220462041187678521684854412144556576290260102114111686997756058286551332862979054266959478356427295041005818617274634710655405047308232376569501906325318765681777539302233123140590830201288731581246481265419559455962387111544368410947264170181057169070947670779846942329795518253622039238288373931184324649505146866669830975544230836334469713021340098330110095926531355748694050179572044376525329427421760700641904670475366316321600442973851120434246304778738771046543700225744237743249815624721738402920323747409352615724417
print(n) # 19988202562091079609089308705614482303944446998739929311122975838789540619557621657313791322188360240322479546623228234498987716237291487911715123662141533437516467656819081087822927677844909809145858009790123612288587521757088942729679608789475060418234226845941528147374848444623701986375339203883428139471506212500029711951340175962051544266932829183904033759349848307144227177119955562243057882815461375557809109473763829649791991995312572272515672264651244155391860835180863961730834954937594945747086499622858587453845632609942765805190071473098082158621841666644919898891980108801709943506274123300448153887451
分析发现是RSA中的两组e和两组c的情况,编写脚本一把梭哈:
import gmpy2
from Crypto.Util.number import long_to_bytes
#p =
#q =
#n = p * q
n = 19988202562091079609089308705614482303944446998739929311122975838789540619557621657313791322188360240322479546623228234498987716237291487911715123662141533437516467656819081087822927677844909809145858009790123612288587521757088942729679608789475060418234226845941528147374848444623701986375339203883428139471506212500029711951340175962051544266932829183904033759349848307144227177119955562243057882815461375557809109473763829649791991995312572272515672264651244155391860835180863961730834954937594945747086499622858587453845632609942765805190071473098082158621841666644919898891980108801709943506274123300448153887451
e1 = 0x114514
c1 = 11243012629649045184257669052855431188426619870857851755340357869962705251529124481978887404032231030311954715759443327334649701826838249497627422162723529080187074305359133800382818757112524981361511272720347671484879744564726772477690953799909596578674760833743895999872098342933776047006084882941079225529272699832145082693784520673369701708282010624156314804803765740561724885216012240803579559575196257659976536322512513863415544971227511774584604634702835852394154465564382214825073417512177133660720176845005417197321877972124248232888049633785306052049643162715767407206695527961925841675825471402231322773302
e2 = 19198101
c2 = 13908838404665714525469872111009048142275774516574817012612303360182929186976326418845853832220462041187678521684854412144556576290260102114111686997756058286551332862979054266959478356427295041005818617274634710655405047308232376569501906325318765681777539302233123140590830201288731581246481265419559455962387111544368410947264170181057169070947670779846942329795518253622039238288373931184324649505146866669830975544230836334469713021340098330110095926531355748694050179572044376525329427421760700641904670475366316321600442973851120434246304778738771046543700225744237743249815624721738402920323747409352615724417
s = gmpy2.gcdext(e1, e2)
s1 = s[1]
s2 = s[2]
if s1 < 0:
s1 = - s1
c1 = gmpy2.invert(c1, n)
elif s2 < 0:
s2 = - s2
c2 = gmpy2.invert(c2, n)
m = pow(c1, s1, n) * pow(c2, s2, n) % n
print(long_to_bytes(m))
得到flag:flag{deebaTo_is_God!_6!M6oSMuliPPcA36DxPlMluS3lJfGcArGuJV3l06DcYlM!xM}
Misc:
1.GIF:
题目描述:附件下载下来后发现是一张.gif
的动图
解题方法:仔细观察发现这张动图里面有黑色的字母在闪烁,猜测它的flag信息可能就隐藏在里面:
编写python脚本来将它逐帧分离:
from PIL import Image
im = Image.open(r'D:\桌面/flag.gif') #读入一个GIF文件
try:
im.save(r'D:\桌面\逐帧图片\{}.png'.format(im.tell()))
while True:
im.seek(im.tell()+1)
im.save(r'D:\桌面\逐帧图片\{}.png'.format(im.tell()))
except:
print('处理结束')
分离后可以看到有201张图片:
查看了每一张图片的属性,发现他们的宽度都为2像素,这里就可以想到要将这些图片进行排序组合成一张宽度为201x2
像素的一张大图,编写python脚本:
from PIL import Image
# 设置大图的宽度和高度
big_image_width = 2 * 201 # 每张照片宽度为2像素,共201张照片
big_image_height = 600
# 创建一张空白的大图
big_image = Image.new('RGB', (big_image_width, big_image_height))
# 将所有照片按顺序拼接到大图上
for i in range(201):
# 读取每张照片
filename = f"D:\桌面\逐帧图片/{i}.png" # 更改为你的照片文件路径和命名规则
img = Image.open(filename)
# 将照片缩放到与大图一样的高度并粘贴到大图上,依次排列
img_resized = img.resize((2, 600))
big_image.paste(img_resized, (i * 2, 0)) # 每张照片的宽度为2像素,依次排列
# 保存拼接完成的大图
big_image.save("D:\桌面\逐帧图片/big_image_sorted.png") # 更改为你想保存的大图路径和文件名
最后就组合成一张大图:
这里我们就可以得到它的flag
2.其他隐写:
题目描述:压缩包
解题方法:题目附件下载下来之后,是一个压缩包,解压时发现有密码
把它放进010_editor里面,查看一下它的十六进制:
这里我们发现是压缩包的伪加密,它的标志位是偶数说明没有加密,但是flag.txt哪里的标志位是奇数,说明是伪加密,修改一下:
保存后解压一下就得到一个flag.txt,打开就得到我们的flag:
flag{I_am_here}
3.图片隐写1:
题目描述:png隐写
解题方法:把图片下载下来
把图片放进010_editor里面,查看一下它的十六进制:
在底部发现了一个隐藏压缩包的,压缩包里面有个flag.txt文档:
现在我们的思路很清晰,用foremost把图片分离一下:
分离出一个压缩包,解压后得到一个flag.txt文档,打开后就得到flag:
flag{Yoimiya_is_so_cute}
4.文档隐写:
题目描述:根据课上所学
解题方法:题目附件下载下来后发现是一个doc的Word文件
猜测是word隐写,打开后全选更改颜色,显示隐藏信息,也没有发现flag
把它放进010_editor里面查看一下它的十六进制:
发现里面有好多PK压缩包文件,将doc的word文档后缀改成.zip,然后解压出许多文件
最后在里面找到了flag:
flag: flag{1t_w@s_d1sc0vere6!}
5.流量分析:
题目描述:结合课上所教,不只是考察wireshark的用法
题目来源于buuctf
公安机关近期截获到某网络犯罪团伙在线交流的数据包,但无法分析出具体的交流内容,聪明的你能帮公安机关找到线索吗?
解题方法:题目附件下载下来是一个pcapng文件
用wireshark打开分析一下里面的数据流量包:
我们追踪它的http流,发现了一段类似base64的编码,但是它的编码特别多,猜测一下是base64转图片
将这段编码放进base64转图片里面,就得到了一张图片:
这里我们就得到了flag
6.编码:
题目描述:编码
解题方法:附件下载下来得到一串编码:
RzVBVE1OUlhHVTNEQ04yQ0dRWlRLUlJXR1kzREdOUlZHNDRUS1JSV0lVM0RTTktHR1pBVE1OUlhHVTNUR05LR0c0M0RPTkpXSVEzVFNOMkU=
一眼看是base64编码,进行base64解码得到:
G5ATMNRXGU3DCN2CGQZTKRRWGY3DGNRVG44TKRRWIU3DSNKGGZATMNRXGU3TGNKGG43DONJWIQ3TSN2E
这里全是大写字母和数字组成,可能是base32编码,进行base32解码得到:
7A6675617B435F666365795F6E695F6A6675735F76756D797D
得到一串由数字和字母组成,但是字母的范围是A-F,说明是hex编码,进行hex解码:
zfua{C_fcey_ni_jfus_vumy}
一眼看是凯撒编码,进行凯撒枚举解码得到flag:
flag{I_like_to_play_base}
总结:
还是特别基础的几道ctf题,对新手很友好
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通