版权声明:欢迎评论交流,转载请注明原作者。 https://blog.csdn.net/m0_37809890/article/details/86584741
提交题解阶段写的题解,因为当时仍属于比赛就发了私密文章,直到3月6日才想起来取消解密。
0w1_CTF_Writeup by LittleFall
0. 前言
- 只写正确的思路,错误的思路以及探索的过程因为太多就不写了orz;
0.1 SIGN-签到
- 关注公众号
0w1网络安全俱乐部
; - 发送
0w1NB!
; - 收到flag:
flag{0w1_nb_!_hohoho}
1. Reverse
1.1 小试牛刀
工具
Ollydbg
流程
- 使用OD打开程序;
- 中文搜索引擎->智能搜索,列出全部字符串;
- 其中
XJbest{We1c0meMyFriend}
长得很像flag;
- 提交,成功。
1.2 有丶意思
工具
IDA
C++
流程
- 使用IDA打开程序,F5大法列出反编译代码;
- 读main函数,大致为:
- 读入字符串str;
- str经过某些操作(sub_411AB0函数)后得到一个字符串Dest;
- 处理Dest,方法是:
for(j=0;j<strlen(Dest);j++) Dest[j]+=j;
- 拿Dest和字符串Str2比较,如果在Dest的长度内都相等,输出“rigth flag”(right?),否则输出"wrong flag"。
- 读sub_411AB0函数,函数接受字符串str和它的size(第三个指针参数没有用过,可以用来传递生成的Dest的长度),返回字符串Dest,内容大致为:
- 通过一些操作由str的size得到计算Dest的size,至少是 ;
- 把str的每3个字符(字节)的24位拼到一起,然后每6位一组,作为4个下标从数组aAbcdefghijklmn里取值放到Dest里。 当str位数不够时,下标默认为64;
- 给Dest补0(字符串结束符),返回Dest。
- 读数组aAbcdefghijklmn,内容是
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
,第64位是=
; - 读Str2,内容为:
0x57, 0x46, 0x72, 0x6C, 0x5E, 0x5D, 0x54, 0x37, 0x6D, 0x39, 0x58, 0x7A, 0x66, 0x64, 0x64, 0x88, 0x73, 0x42, 0x4B, 0x56, 0x77
- 坑点:Str2只有21字节,此时跑不出正确的结果。想到Dest的字节数是4的倍数,Str2后面必然还有东西,查看内存得到:
再后面全是0,应该不会有坑了;0x7F, 0x58, 0x50
- 逆向:把str2逐字节减去0到23,然后在数组aAbcdefghijklmn中找到对应于这个字符的下标位置,一共24个。把这24个数字每4个拼接起来再分成3份,就得到了结果。
- 运行如下代码,得到结果
XJbest{Cheers_Br0}
,提交成功。
代码
gen是生成函数(省略了不满3的倍数的情况),inv是逆向函数。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std;
using byte = unsigned char;
const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
const int N = 24;
const byte target[N] = {
0x57, 0x46, 0x72, 0x6C, 0x5E, 0x5D, 0x54, 0x37,
0x6D, 0x39, 0x58, 0x7A, 0x66, 0x64, 0x64, 0x88,
0x73, 0x42, 0x4B, 0x56, 0x77, 0x7F, 0x58, 0x50
};
string gen(string src)
{
string des;
for(int i = 0; src[i]; ++i)
{
char t[3] = {}, j;
for(j = 0; j < 3 && src[i]; ++j)
t[(int)j] = src[i++];
des.push_back(base[t[0] >> 2]);
des.push_back(base[((t[1] & 0xF0) >> 4) | (t[0] & 0x03) << 4]);
des.push_back(base[((t[2] & 0xC0) >> 6) | (t[1] & 0x0F) << 4]);
des.push_back(base[t[2] & 0x3F]);
}
for(int i = 0; des[i]; ++i)
des[i] += i;
return des;
}
string inv(const byte* des)
{
byte id[N]; //index
for(int i=0;i<N;++i)
{
for(int j=0;j<=64;++j) //search
if(des[i]-i==base[j])
id[i]=j, j=64;
}
string src;
for(int i=0;i<N/4;++i)
{
int mask = 0;
for(int j=0;j<4;++j)
mask |= id[i*4+j] << 6*(3-j);
src.push_back(mask>>16);
src.push_back(byte(mask>>8));
src.push_back(byte(mask));
}
return src;
}
int main(void)
{
cout << inv(target) << endl;
return 0;
}
1.3 CrackMe
工具
FileAnalysis
jadx-gui
流程
- 用FileAnalysis分析CrackMe.jpg发现可能是APK;
- 用jadx-gui打开,导出源代码;
- 打开
sources/com/tay/crackme/MainActivity.java
,逻辑返回this_stupid_challenge
;
- 提交
flag{this_stupid_challenge}
,通过。
1.4 难以自拔
工具
FileAnalysis
IDA64
C++
流程
- 用FileAnalysis分析re3,发现可能是linux下可执行的elf文件;
- 使用IDA64打开,找到main,F5大法反编译;
- 读main函数,大致为:
- 读入字符串str;
- 调用函数
sub_40111A(str)
; - 调用函数
sub_401332(str)
; - 释放str。
- 读函数sub_40111A,函数接受一个参数str,可以认为无返回值,内容大致为:
- 定义了char变量s1,紧随其后定义了一堆char变量并赋值,且后文中出现了对s1的字符串操作,猜测可能是将s1当作字符串的首部。
- 遍历str,执行如下操作:
for(int i=0; str[i]; ++i) { char c1 = char(str[i])*4; byte b1 = byte(str[i])>>6; byte b2 = (c1|b1)^i; str[i] = char(b2); }
- 如果str与s1相等,输出
g00d j0b but .....
- 读函数sub_401332,函数接受一个参数str,可以认为无返回值,内容大致为:
- 定义char数组s[8],逐个赋值,紧随其后定义三个__int64变量(64位)并赋值,由上一步的经验,猜测可能是把这些变量按字符接在了s数组后面,这样的话s共占
个字节;
下面是我把大整数-1920493130842480203变成char型的过程,较为繁琐,也可以直接用memcpy:- 先将大整数变为无符号类型:16526250942867071413;
- 变为16进制,0xe55909ac368e61b5;
- 每两位取值,注意:内存按小端存储,即应当按字节从低到高读。
0xb5,0x61,0x8e,0x36,0xac,0x09,0x59,0xe5 - 大于0x80的,要将其减去0x100,因为char类型的取值是-0x80到0x7f:
- 变为10进制(可选):
-75,97,-114,54,-84,9,89,-27
- 对str数组进行如下操作:
用逻辑计算或真值表可以发现,里面的操作与异或等价,相当于for(int i=0; i<4; ++i) for(int j=1; j<strlen(s); ++j) str[j] = (str[j]&str[j-1]) & ~(str[j-1]&str[j]);
str[j]^=str[j-1];
连续执行四次,用二进制模拟之后发现,这个操作相当于每一个数异或了所有在自己之前且位置和自己相差为4的整倍的数,如下图所示:
第12个数本来是1,0000,0000,0000,它与第8个数,第4个数,第0个数异或后变成了1,0001,0001,0001. - 如果str和s相等,输出
you g0t it!
。
- 定义char数组s[8],逐个赋值,紧随其后定义三个__int64变量(64位)并赋值,由上一步的经验,猜测可能是把这些变量按字符接在了s数组后面,这样的话s共占
个字节;
- 在读代码的过程中,手动复现了C++程序(见代码1),更好地理解了原程序,这里将两个子函数简记为deal1和deal2。注意:deal1的处理过程是独立的,但deal1会更改原字符串,导致影响deal2的结果;
- 逆向deal1(记为inv1):deal1的本质是对每个字符的独立处理,暴力检索即可,也可打表:运算更快,但编码更多;
- 逆向deal2(记为inv2):记操作之前的字符串叫做src,操作之后的字符串叫做des。deal2相当于
des[i]=str[i]^des[i-4]
,所以逆向时,src[i]=des[i]^des[i-4]
; - 分别记deal1,deal2中的数组叫做s1,s2。对s1跑一遍inv1会得到一个假flag:
flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
,对s2跑一遍inv2,再跑一遍inv1会得到真正的flag:flag{W0w_y0u_m4st3r_C_p1us_p1us}
,提交通过;(见代码2)
实际上,在linux中运行原程序是无法得到第一个提示信息的,因为输入的假flag带有空格,而输入程序遇到空格就停止了,原程序复现版可以读入。
代码
- 原程序复现版:
/* LittleFall : Hello! */
#include<bits/stdc++.h>
using namespace std;
using byte = unsigned char;
void deal1(string &);
void deal2(string &);
int main(void)
{
string str;
cout << "input flag:";
getline(cin,str);
deal1(str); //flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
deal2(str); //flag{W0w_y0u_m4st3r_C_p1us_p1us}
return 0;
}
void deal2(string &str)
{
const string s2 = //little endian
{
-103, -80, -121, -98, 112, -24, 65, 68,
5, 4, -117, -102, 116, -68, 85, 88,
-75, 97, -114, 54, -84, 9, 89, -27,
97, -35, 62, 63, -71, 21, -19, -43
};
for(int i=0; i<4; ++i)
for(int j=1; j<(int)s2.size(); ++j)
str[j] ^= str[j-1];
if(str==s2)
cout << "you g0t it!" << endl;
}
void deal1(string &str)
{
const string s1 = {
-103,-80,-121,-98,-124,-96,-53,-17,-120,-112,-69,-114,
-111,-32,-46,-82,-44,-59,111,-41,-64,104,-58,106,-127,-55,
-73,-41,97,4,-38,-49,61,92,-42,-17,-48,88,-17,-14,-83,-83,-33
};
for(int i=0; str[i]; ++i)
{
char c1 = char(str[i])*4;
byte b1 = byte(str[i])>>6;
byte b2 = (c1|b1)^i;
str[i] = char(b2);
}
if(str==s1)
{
cout << "g00d j0b" << endl << "but ....." << endl;
exit(0);
}
}
- 逆向程序:
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std;
using byte = unsigned char;
const string s1 =
{
-103,-80,-121,-98,-124,-96,-53,-17,-120,-112,-69,-114,
-111,-32,-46,-82,-44,-59,111,-41,-64,104,-58,106,-127,-55,
-73,-41,97,4,-38,-49,61,92,-42,-17,-48,88,-17,-14,-83,-83,-33
};
const string s2 = //little endian
{
-103, -80, -121, -98, 112, -24, 65, 68,
5, 4, -117, -102, 116, -68, 85, 88,
-75, 97, -114, 54, -84, 9, 89, -27,
97, -35, 62, 63, -71, 21, -19, -43
};
string inv1(const string &des)
{
string src;
for(int i=0;des[i];++i)
{
bool flag = 0;
for(int ch=0;ch<256;++ch)
{
char v2 = char(ch)*4;
byte v1 = (byte(ch)>>6|v2)^i;
if(char(v1)==des[i])
{
src.push_back(ch);
flag = 1;
break;
}
}
if(flag==0)
{
printf("?\n");
}
}
return src;
}
string inv2(const string &des)
{
string src = des;
for(int i=0;des[i];++i)
if(i>=4)
src[i]^=des[i-4];
return src;
}
int main(void)
{
cout << inv1(s1) <<endl;
cout << inv1(inv2(s2)) << endl;
return 0;
}
2. Web
2.1 god is a flag
- 打开网页;
- 查看源代码,看到flag:
flag{love&peace}
; - 提交,成功。
2.2 快问我flag在哪
工具
BurpSuite
流程
- 打开网页发现有一个超链接,指向
flag.html
; - 打开这个超链接,发现网页变成了
flaghere.html
,名字对不上; - 打开burpsuite抓包,发现flag.html的HTTPresponse头中有一项叫flag,值为
flag{more_love&peace}
- 提交,成功。
2.3 哈哈哈希
工具
BurpSuite
hashpump
原理
哈希长度拓展攻击:
- 简介:如果已知md5(secret)和secret的长度,则可以构造出一个attack_data和hash_value,使得md5(secret.attack_data)=hash_value,其中
.
表示php的字符串连接运算. - 应用场景:某些网页内部有一个secret,要求输入data和hash,当md5(secret.data)===hash时认为用户合法。
当我们已经知道secret的长度,且有一组data和hash时,就可以构建出其它合法的data和hash,且旧data是新data的一个前缀。 - 应用方式:linux下安装hashpump。
输入hashpump -s hash -d data -k secret_len -a add
其中-s之后跟原hash值(签名signature);-d之后跟原data;-k之后跟secret的长度;-a之后跟需要追加的内容。
会输出新的n_data和新的签名n_hash,此时md5(secret.n_data)===n_hash
流程
- 打开网页,发现是一个登陆框,尝试登陆无果,查看源码无果,下载图片破解无果,搜索路径无果(有一个log但是并不会用);
- 登陆,用burpsuite抓包,发现cookie中有一项source=0比较奇怪,将其改成1之后发现网页返回了源码。
- 分析逻辑如下:
- 有名为flag的变量保存flag,secret_key保存密文,secret_key的长度为17。在head中添加了hash_key;
- if嵌套,条件1:cookie有非空的getflag项;条件2:用户名是0w1且密码不是0w1;条件3:cookie的getflag项和md5(secret_key.username.password)全等。当条件123全部满足时,以html注释形式返回flag;
- 在cookie中设置了名为sample-hash的一项,值为md5(secret_key.“0w1”.“0w1”);
- 当cookie的source项不为空时,返回
<source_code>
- 使用hashpump进行哈希长度拓展攻击:sample-hash(hash_key?)的值作为原签名,"0w10w1"作为原数据,11作为secret长度,"lf"作为添加的值。
返回了新的签名和数据,如图所示。
- 用户名仍为0w1,密码变为
0w1%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%88%00%00%00%00%00%00%00lf
(将\x
换成了%
),在cookie中添加getflag=e53277fa10674f83a2bd850f8843a2e3
,提交。(因为各种bug失败了若干次,每次满怀希望看到“滚犊子”简直心态爆炸) - 网页返回陈独秀先生,在源码中看到flag:
flag{h4sh_1s_1nteresting}
,提交,成功。
2.4 逃亡计划
工具
BurpSuite
在线工具:16进制与文本转换
7-zip
python
流程
- 打开网页,发现是个游戏,玩了一会计算每次的期望收益= 是负的,小赌怡情大赌输光,不能玩了。
- 氪分买hint,提示是robot,打开robots.txt->robots.php,得到一个二进制串,转成文本串提示fangzhou.zip.使用7-zip解压得到caipiao6文件夹,是程序的源码。
- 核心逻辑是随机产生7个数,与输入的数字使用
==
比较,如下:
numbers使用json字符串传值,number初始是一个字符串,如下:for($i=0; $i<7; $i++){ if($numbers[$i] == $win_numbers[$i]){ $same_count++; } }
data: JSON.stringify({ action: "buy", numbers: numbers })
- 在php中,0==false,(1->9)==true,true可以匹配除0之外的所有值。抓包后将numbers改成
"numbers":[true,true,true,true,true,true,true]
,现在获奖的期望就变成了 (我感觉还要高一些,可能随机数不是平均的),可以随便玩了。 - 钱够1e7后买票,提示需要计算RSA中
p=223398606,q=221141,e=23
时的d,而 即d为e模(p-1)(q-1)的逆元,由扩展欧几里得算法(见代码)可以计算得d=17183432177287,加上flag格式后通过。
代码
python代码
p = 223398606
q = 221141
e = 23
def exeuclid(a, b):
if b == 0:
return 1, 0
else:
k = a // b
x1, y1 = exeuclid(b, a % b)
x, y = y1, x1 - k * y1
return x, y
def inv(a, n):
x = exeuclid(e,n)[0]
x = (x%n+n)%n
return x
d = inv(e,(p-1)*(q-1));
print(d);
3. misc
3.1 再也不用担心我1T的资源被妈妈发现啦[滑稽]
工具
7-zip
winhex
流程
- 下载final.jpg,猜测可能有文件藏在里面,使用7-zip打开得到interim2.jpg;
- 不能再做为压缩文件打开,使用winhex打开,发现最后(jepg文件结尾FFD9之后)还有一段二进制码,翻译为:flag=0w1_{D_hidden_picture};
- 提交,成功。
3.2 找到女神的位置
工具
7-zip
百度识图
流程
- 下载zip文件,使用7-zip解压得到若干图片;
- 一头雾水,拿去百度识图,找到一篇帖子:谁说IT男没有春天!!黑客思维轻松追女神!!!;
- 拜读完之后表示十分佩服,然后提交了flag:
flag{青田皇家风尚宾馆}
,通过。
3.3 和咩咩的PY交♂易
参考资料
工具
FileAnalysis
Wireshark
7-zip
winhex
python
流程
- 下载文件pcapng,拿fileanalysis分析的得到可能是pcapng文件;
- 使用wireshark将其打开,文件->导出对象->HTTP,得到三个文件:一个html,一个json,一个未知格式的文件multi;
- html和json里的信息表面同目录下应该有一个zip文件,于是拿7-zip打开multi,里面有无格式的flag1和flag2;
- 使用fileanalysis分析flag1,100%是png,打开后发现是一张下面全黑的图片。而flag是未知文件;
- 使用winhex打开flag1,发现找不到文件结尾(IEND,通常为0000 0000 4945 4E44 AE42 6082)。而在flag2中发现了png文件结尾;
- 把flag1和flag2拼接起来,得到全图如下:
- 猜测可能用其它方式隐藏数据,用winhex打开这张图,运算发现,文件头部分的CRC校验不通过;
- 暴力检索可能出现的修改方式(修改宽度或高度),发现可以将高度修改为0x2a7从而通过校验(见代码);
- 修改后打开图片,得到flag:
flag=0w1_{D_Bug_2333}
,提交后通过。
代码
import os
import binascii
import struct
misc = bytes([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, # PNG
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, # IHDR
0x00, 0x00, 0x02, 0x7d, 0x00, 0x00, 0x01, 0xfd, # 宽度, 高度
0x08, 0x02, 0x00, 0x00, 0x00, 0x7c, 0xb4, 0x6c, # CRC(从0x7c开始)
0xd3
])
print("width:")
for i in range(1024):
data = misc[12:20] + struct.pack('>i',i) + misc[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
if crc32 == 0x7cb46cd3:
print(hex(i));
print("height:")
for i in range(1024):
data = misc[12:16] + struct.pack('>i',i) + misc[20:29]
crc32 = binascii.crc32(data) & 0xffffffff
if crc32 == 0x7cb46cd3:
print(hex(i));
4. Crypto
4.1 easyRSA
参考资料
工具
在线工具:大数分解
在线工具:16进制与文本转换
python
原理
RSA是一种非对称加密算法,名字来源于三个提出人的首字母简写。
- RSA密钥的产生
- 选择两个大质数 和 ,令 ,则 的欧拉函数值 ;
- 选择一个整数 ,满足 ;
- 由 计算出 ;
- 为公钥, 为私钥。
- RSA加密解密操作
- 设明文为 ,密文为 ;
- 加密: ;
- 解密: 。
操作流程
- 下载文件,得到n,c,e;
- 分解n得到p和q;
- 计算phi = (p-1)*(q-1);
- 计算d,d为e模phi的逆元,由扩展欧几里得算法计算,代码如下;
- 计算m,m=c^d mod n;
- 将m换成16进制,在线转换字符串后得到答案。
代码
python代码
n = 966808932627497190635859236054960349099463975227350564265384373280336699853387254070662881265937565163000758606154308757944030571837175048514574473061401566330836334647176655282619268592560172726526643074499534129878217409046045533656897050117438496357231575999185527675071002803951800635220029015932007465117818739948903750200830856115668691007706836952244842719419452946259275251773298338162389930518838272704908887016474007051397194588396039111216708866214614779627566959335170676055025850932631053641576566165694121420546081043285806783239296799795655191121966377590175780618944910532816988143056757054052679968538901460893571204904394975714081055455240523895653305315517745729334114549756695334171142876080477105070409544777981602152762154610738540163796164295222810243309051503090866674634440359226192530724635477051576515179864461174911975667162597286769079380660782647952944808596310476973939156187472076952935728249061137481887589103973591082872988641958270285169650803792395556363304056290077801453980822097583574309682935697260204862756923865556397686696854239564541407185709940107806536773160263764483443859425726953142964148216209968437587044617613518058779287167853349364533716458676066734216877566181514607693882375533
p = 31093551302922880999883020803665536616272147022877428745314830867519351013248914244880101094365815998050115415308439610066700139164376274980650005150267949853671653233491784289493988946869396093730966325659249796545878080119206283512342980854475734097108975670778836003822789405498941374798016753689377992355122774401780930185598458240894362246194248623911382284169677595864501475308194644140602272961699230282993020507668939980205079239221924230430230318076991507619960330144745307022538024878444458717587446601559546292026245318907293584609320115374632235270795633933755350928537598242214216674496409625928797450473
q = 31093551302922880999883020803665536616272147022877428745314830867519351013248914244880101094365815998050115415308439610066700139164376274980650005150267949853671653233491784289493988946869396093730966325659249796545878080119206283512342980854475734097108975670778836003822789405498941374798016753689377992355122774401780930185598458240894362246194248623911382284169677595864501475308194644140602272961699230282993020507668939980205079239221924230430230318076991507619960330144745307022538024878444458717587446601559546292026245318907293584609320115374632235270795633933755350928537598242214216674496409625928997877221
c = 168502910088858295634315070244377409556567637139736308082186369003227771936407321783557795624279162162305200436446903976385948677897665466290852769877562167487142385308027341639816401055081820497002018908896202860342391029082581621987305533097386652183849657065952062433988387640990383623264405525144003500286531262674315900537001845043225363148359766771033899680111076181672797077410584747509581932045540801777738548872747597899965366950827505529432483779821158152928899947837196391555666165486441878183288008753561108995715961920472927844877569855940505148843530998878113722830427807926679324241141182238903567682042410145345551889442158895157875798990903715105782682083886461661307063583447696168828687126956147955886493383805513557604179029050981678755054945607866353195793654108403939242723861651919152369923904002966873994811826391080318146260416978499377182540684409790357257490816203138499369634490897553227763563553981246891677613446390134477832143175248992161641698011195968792105201847976082322786623390242470226740685822218140263182024226228692159380557661591633072091945077334191987860262448385123599459647228562137369178069072804498049463136233856337817385977990145571042231795332995523988174895432819872832170029690848
e = 65537
def exeuclid(a, b):
if b == 0:
return 1, 0
else:
k = a // b
x1, y1 = exeuclid(b, a % b)
x, y = y1, x1 - k * y1
return x, y
def inv(a, n):
x = exeuclid(e,n)[0]
x = (x%n+n)%n
return x
d = inv(e,(p-1)*(q-1))
m = pow(c,d,n)
# print(pow(m,e,n)==c)
print(hex(m))
4.2 未知的短信
工具
百度一下orz
流程
- 数据33 53 21 41 43 74 74 43 61 71 53 32;
- 打开手机9键键盘,每两个数字分为一组,第一个表示按哪个键,第二个表示取这个键上的第几个字母;
- 得到F L A G I S S M L E
- 提交flag{flagissimple},通过