WeChall
本文为We Chall题解,仍在更新。
Training: Get Sourced
直接查看源代码。
答案为:html_sourcecode
Training: Stegno I
下载下来,打开二进制文件即可看到。
答案就是:steganoI
Training: Crypto - Caesar I
凯撒密码,移动 13 位,得到
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG OF CAESAR AND YOUR UNIQUE SOLUTION IS FDLDGDFSHERH
答案为fdldgdfsherh
Training: WWW-Robots
这题没有提交按钮。
先访问 http://www.wechall.net/robots.txt 查看一下内容:
User-agent: *
Disallow: /challenge/training/www/robots/T0PS3CR3T
User-agent: Yandex
Disallow: *
/challenge/training/www/robots/T0PS3CR3T
是禁止爬虫爬取的,那么一定有问题。
访问网址即可:http://www.wechall.net/challenge/training/www/robots/T0PS3CR3T/
Training: ASCII
将ASCII转换为字符
结果为:The solution is: lpfnosrhcbrl
提交lpfnosrhcbrl即可。
Encodings: URL
URLDecode,得到:
Yippeh! Your URL is challenge/training/encodings/url/saw_lotion.php?p=dnserhciprci&cid=52#password=fibre_optics Very well done!
2021 Christmas Hippety
Prime Factory
枚举判断素数,懒得写了。结果是10000331000037
Training: Encodings I
JPK
题目提供了一个软件JPK,那就用它来解决。
1.看到0和1,猜测是二进制acsii转换。 ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符。
标准ASCII码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。
2.用给定的JPK进行Binary Format转换,默认bitsperblock是8,转换后发现多出1位会乱码,换用7
3.最后转成ascii码,用给定的JPK进行Binary to ASCII转换即可。 This text is 7-bit encoded ascii. Your password is easystarter.
提交easystarter即可.
Python3
a='101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110'
for i in range(0,len(a),7):
print(chr(int(a[i:i+7],2)),end="")
JavaScript
var a = '101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110'
var text = ''
for (var i = 0; i < a.length; i+=7) {
text += String.fromCharCode(parseInt(a.substr(i, 7), 2))
}
console.log(text)
PHP
<?php
function bin2ascii($texto){
$tmp = '';
$texto = preg_replace("/[^01]*/", '', $texto);
for($i=0; $i<strlen($texto); $i += 7){
$tmp .= chr(bindec(substr($texto, $i, 7)));
}
return $tmp;
}
$txt = '101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110';
echo bin2ascii($txt);
?>
C
首先需要实现二进制转十进制函数,这里参考了C语言二进制转化为十进制源码,之后用了strncpy函数提取7个字符。
#include <math.h>
#include <stdio.h>
#include <string.h>
int bin2dec(char a[])
{
int n, sum = 0, i = 0;
n = strlen(a);
for (i = n - 1; i >= 0; i--)
sum += (a[i] - '0') * ((int)pow(2, n - 1 - i));
return sum;
}
int main()
{
int sum = 0;
char txt[1024] = "101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110";
char temp[1024];
for (int i = 0; i < strlen(txt); i+=7)
{
strncpy(temp, txt+i, 7);
sum = bin2dec(temp);
printf("%c", sum);
}
}
Bash
这个方法来自[WeChall] Solution in Bash
其中$((2#$a))
的意思是: 将2进制转成10进制 printf \\
是转义反斜杠,来显示八进制数的ascii码。
ascii=$(echo "101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110" | tr -d '\n' | sed -r 's/(.{7})/\1\n/g')
for a in $ascii; do printf \\$(printf "%o" $(echo $((2#$a)))); done; echo
Training: Programming 1
Python
import requests
url1 = "http://www.wechall.net/challenge/training/programming1/index.php?action=request"
url2 = "http://www.wechall.net/challenge/training/programming1/index.php?answer="
c = {"WC": "你的cookie"}
key = requests.get(url1, cookies=c).text
requests.get(url2+key, cookies=c)
Curl
answer=$(curl -H 'Cookie:WC=你的cookie' http://www.wechall.net/challenge/training/programming1/index.php?action=request)
curl -H 'Cookie:WC=你的cookie' http://www.wechall.net/challenge/training/programming1/index.php?answer=$answer
Training: Regex
Level 1
匹配一个空字符串,学习匹配匹配字符串开头结尾的两个符号:/^$/
^ 匹配字符串的开始
$ 匹配字符串的结束
Level 2
匹配”wechall”,/^wechall$/
Level 3
匹配以wechall或wechall4为文件名,并以.jpg/.gif/.tiff/.bmp/.png
为后缀的图像,
/^wechall4?\.(?:jpg|gif|tiff|bmp|png)$/
? 重复零次或一次
所以4?表示重复0次或1次4,也就是wechall和wechall4都可以匹配。
\. 转义
使用\
来取消.
字符的特殊意义,来显示.
字符本身
(?:jpg|gif|tiff|bmp|png)\
(?:exp)
表示非捕获分组,匹配exp,不捕获匹配的文本,也不给此分组分配组号。
为什么要用(?:exp)
,而不用(exp)
呢?因为直接提交/^wechall4?\.(jpg|gif|tiff|bmp|png)$/
会报错:
Your pattern would capture a string, but this is not wanted. Please use a non capturing group. 您的模式将捕获一个字符串,但这是不需要的。请使用非捕获组。
所以需要使用(?:exp)
非捕获分组。
至于(?:jpg|gif|tiff|bmp|png)\
中的|
表示分枝条件
Level 4
捕获文件名,需要对文件名添加捕获分组:
/^(wechall4?)\.(?:jpg|gif|tiff|bmp|png)$/
(wechall4?)
用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作。
Training: PHP LFI
这是关于利用文件包含漏洞的题目。 目标是执行../solution.php
文件。
题目的网址是:http://www.wechall.net/challenge/training/php/lfi/up/index.php
复制一下../solution.php
的地址, 可以看到是http://www.wechall.net/challenge/training/php/lfi/solution.php
所以应该是../../
而不是 ../
, 因为从index.php跳转到solution.php需要经过两个父目录。
但是直接提交?file=../../solution.php
会出错,提示找不到文件pages/../../solution.php.html
可以看到后面多了个.html
,可以利用空字节%00
来过滤掉后面的.html
最终只要浏览器访问下面地址,即可解题成功
http://www.wechall.net/challenge/training/php/lfi/up/index.php?file=../../solution.php%00
PHP 0817
当一个非数字开头的字符串与数字0进行==
比较时,结果总是true.因此可以直接提交solution作为which
变量的值,"solution"
相当于0,必然会执行require_once
命令。
更多知识可以参考字符串与数字0比较要注意
因此,答案也就出来了。浏览器访问下面链接即可。
http://www.wechall.net/challenge/php0817/index.php?which=solution
Training: Crypto - Transposition I
置换密码就是对明文重新排序以形成密文。
大致过程为:加密,先分组(最后不足补齐);分别按组进行置换(置换矩阵)。
对待置换密码,首先需要根据其长度特征进行判断分组大小。分组大小是密文长度的因子。
该文本的长度为148,对148求因数是2,2,37。 也就是说分组大小可能是2,2,37。就是说是有4 x 37, 37 x 4, 2 x 74, 74 x 2这么几种情况。
可以用Transposition Cipher Solver来将密码转成矩阵形式。
可以看到:将每两个字符(矩阵的每行)调换一下顺序,就可以还原成明文。例如oWdnreuf.l
就是Wonderful.
python
def decrypto(crypto):
for i in range(0, len(crypto),2):
print(crypto[i+1], end="")
print(crypto[i], end="")
print()
decrypto("oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r")
运行结果:
Wonderful. You can read the message way better when the letters are in correct order. I think you would like to see your password now: onnbimhsfoor.
提交onnbimhsfoor
即可。
Bash
创建ciphertext
文件,内容为
oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r
接着在terminal输入下面命令
sed -r -e 's/(.{2})/\1\n/g' ciphertext | sed -r -e 's/(.)(.)/\2\1/g' | tr -d '\n'; echo
PHP
<?php
function crypto_trans1_encrypt($pt)
{
$len = strlen($pt);
if (($len % 2) == 1) {
$pt .= 'X';
$len++;
}
$i = 0;
$ct = '';
while ($i < $len) {
$ct .= $pt{$i + 1};
$ct .= $pt{$i};
$i += 2;
}
return $ct;
}
$ciphertext = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r";
$plaintext = crypto_trans1_encrypt($ciphertext);
print($plaintext);
?>
JavaScript
var answer = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo lgnhipasmi.m"
.match(/[\s\S]{1,2}/g)
.map(function(val){
return val[1]+val[0];
})
.join('');
console.log(answer)
C
如果用在其他地方,malloc(200);
中的200可以改大点,以容纳更多字符。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char* decrypt(char cryptotext[]){
int i = 0;
char *value = malloc(200);
while (i < strlen(cryptotext)){
value[i] = cryptotext[i+1];
value[i+1] = cryptotext[i];
i += 2;
}
value[i] = '\0';
return value;
}
int main(void)
{
char cryptotext[] = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r";
printf("%s\n", decrypt(cryptotext));
return 0;
}
Training: Crypto - Substitution I
题目中提到了 simple substitution 中文叫单表替代密码
中文原理可以参考这篇文章如何破解单表替换密码
这里使用在线工具来解决,quipquip。粘贴密文进去,点击Solve即可。
破解后的文本是
BY THE ALMIGHTY GOD YOU CAN READ THIS MY FRIEND I AM IMPRESSED VERY WELL DONE YOUR SOLUTION KEY IS FALFONNFAOFN THIS LITTLE CHALLENGE WAS NOT TOO HARD WAS IT
提交FALFONNFAOFN
即可。
Training: Crypto - Caesar II
根据题目可以看到,明文不再是简单的26个字母,而是数字、字符组成的ascii序列。
从给的ascii码值中可以看到是16位进制数值。
首先把题目给的acsii码中换行符换成空格,在Linux中可以使用命令
$ echo "37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F
5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58
51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20
5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E
55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63
69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E
17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20
59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C
20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64
20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20
64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F
5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58
59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C
20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65
64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51
53 54 60 52 1E" | tr '\n' ' '
从而得到:
37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F 5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58 51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20 5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E 55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63 69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E 17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20 59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C 20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64 20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20 64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F 5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58 59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C 20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65 64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51 53 54 60 52 1E
构造python3代码:
cipher = "37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F 5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58 51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20 5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E 55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63 69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E 17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20 59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C 20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64 20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20 64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F 5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58 59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C 20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65 64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51 53 54 60 52 1E"
cipher = cipher.split()
#对整个字符串循环
for shift in range(127):
#遍历字符串的每个字符
for every in cipher:
current = int(every, 16)+shift+1
print(chr(current % 128), end='')
print()
print(shift+1)
在16的时候,得到flag
Good0job,0you0solved0one0more0challenge0in0your0journey.0This0one0was0fairly0easy0to0crack.0Wasn't0it?01280keys0is0a0quite0small0keyspace,0so0it0shouldn't0have0taken0you0too0long0to0decrypt0this0message.0Well0done,0your0solution0is0egahopoacdpb.
16
把0换成空格,来看下
Good job, you solved one more challenge in your journey. This one was fairly easy to crack. Wasn't it? 128 keys is a quite small keyspace, so it shouldn't have taken you too long to decrypt this message. Well done, your solution is egahopoacdpb.
提交egahopoacdpb即可。
Training: Crypto - Digraphs
Training: MySQL I
题目已给判断登陆成功代码,第42行定义了查询语句。
验证代码
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
方法一
username输入 admin'#
即可登录。
原理:用Mysql单行注释符号#
将后半句AND语句注释掉。
$query = "SELECT * FROM users WHERE username='admin'#' AND password='$password'";
构成SQL语句
SELECT * FROM users WHERE username='admin'
故而登录成功。 补充:之所以不是php注释,是因为#
在双引号中,只会显示它的字面量。
同样可以用--
–的后面要加空格,详情参考MySQL的注释 - 简书
方法二
username输入 admin' or '1
即可登录。
构成SQL语句
SELECT * FROM users WHERE username='admin' or '1' AND password='$password'
这里的1
可以换成其他非空字符。
因为or
在and
的前面,所以可以理解成username='admin'
和 '1' AND password='$password'
进行或运算, 因此即使第二部分是假,整条语句也是真的。
Training: MySQL II
从代码可以看到username password分开来验证。通常的利用方法是使用union构造已知MD5值的查询。
查询代码:
$query = "SELECT * FROM users WHERE username='$username'";
username一栏填写123' union select 1,'admin',md5('password');#
构成sql语句:
SELECT * FROM users WHERE username='123' union select 1,'admin',md5('password');#'
由于最后有个注释符号,所以相当于:
SELECT * FROM users WHERE username='123' union select 1,'admin',md5('password');
这句话首先通过username=123将原语句报错。因此返回的将会是第二条语句产生的信息。
而我们union select的是直接构造了用户名为admin
,密码为password
的md5值。这样就可以让程序误认为我们构造的信息就是它从数据库里面提取得到的信息。
验证密码正确是通过判断: $result['password']
和 $password
是否一致。
$result['password']
是用union构造的,因此password一栏填写password
即可登录成功。
username填写 123' union select 1,'admin',md5('password');#
password填写 password
Training: WWW-Basics
Training: Register Globals
这题和mysql I那题很类似,但是由于加了一个判断:
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
判断中的$login[0]
在28行已经被赋值为$_POST['username']
。
所以不能通过将username的值提交成admin'#
来完成这题。
从32行开始,有下面这段代码:
if (isset($login))
{
echo GWF_HTML::message('Register Globals', $chall->lang('msg_welcome_back', array(htmlspecialchars($login[0]), htmlspecialchars($login[1]))));
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
}
意思是:如果$login[0] === 'admin'
,那么解题成功。
由于在28行已经有下面代码:
$login = array($_POST['username'], (int)$row['level']);
因而,$login[0]
已经被赋值为$_POST['username']
,但是可以通过全局变量来覆盖它。
在这里,程序会将GET等语句得到的变量注册成为全局变量,就可以覆盖掉源代码中的变量值。
因此在地址栏输入下面链接,回车即可成功解题
http://www.wechall.net/challenge/training/php/globals/globals.php?login[0]=admin
Training: Math Pyramid
题目要求: 求出方锥体积公式,公式所用字符不得超过9。
上图就是方锥(square-based pyramid)
方锥体积是:
\[V=\frac{1}{3}SH=\frac{1}{3}a^2 H\]有红线和蓝线所表示的两种求法。
根据蓝线,有下面等式:
\[H^2+{\left(\frac{a}{2}\right)}^2=a^2-{\left(\frac{a}{2}\right)}^2\]进而得到
\[H=\sqrt{a^2 - 2\times{\left(\frac{a}{2}\right)}^2}=\sqrt{\frac{a^2}{2}}=\frac{a}{\sqrt{2}}=\frac{\sqrt{2}a}{2}\]带入体积计算公式:
\[V=\frac{1}{3}a^2H=\frac{a^3}{3\sqrt{2}}\]可以写成a^3/3/sqrt(2)
,但是这样就超过了9个字符限制,所以可以写成a^3/18^.5
,提交即可。
Training: Baconian
这题是解密培根密码,培根密码有两种密码表,并使用’A’、’B’代替0,1进行编码。 第一种密码表:
第二种密码表:
假如我要加密‘hello’,按照第一种方法加密的结果为:aabbb,aabaa,ababa,ababa,abbab;第二种为:aabbb,aabaa,ababb,ababb,abbba。
假如要解密‘WOrld…’,把整个字符串的大小写代表着‘A’、‘B’编码,所以这里有两个编码可能性,一是大写代表‘A’,小写代表‘B’,第二种相反。同时这里又有两种密码表,所以这里一共有2*2=4种可能性。
大写代表‘A’,小写代表‘B’,用第一种密码表可得:‘h’,第二种为:‘h’,这里刚好一样。 大写代表‘B’,小写代表‘A’,用第一种密码表没有结果,第二种为:‘y’。
方法一
首先将密文转换为A,B的集合,然后利用在线工具进行解密
利用以下Python代码转换密文:
ciper = "BaCoN's cIphEr or THE bacOnIAN CiPHer iS a meThOD oF sTEGaNOGrapHY (a METhoD Of HidIng A sECRet MeSsaGe as OpPOsEd TO a TRUe CiPHeR) dEVIseD BY francis bAcoN. a MessAge Is coNCeALED in THe pRESenTatIoN OF TexT, ratHer thaN iTs coNteNt. tO enCODe A MEsSaGe, eaCh lETter Of THe pLAInText Is rePLAcED By A groUp oF fIvE OF the LeTterS 'a' OR 'B'. thiS REpLACEmenT Is donE accORding To The alpHAbeT OF tHe BACOnIAN cIpHeR, sHoWn bElOw. NoTe: A SeCoNd vErSiOn oF BaCoN'S CiPhEr uSeS A UnIqUe cOdE FoR EaCh lEtTeR. iN OtHeR WoRdS, i aNd j eAcH HaS ItS OwN PaTtErN. tHe wRiTeR MuSt mAkE UsE Of tWo dIfFeReNt tYpEfAcEs fOr tHiS CiPhEr. AfTeR PrEpArInG A FaLsE MeSsAgE WiTh tHe sAmE NuMbEr oF LeTtErS As aLl oF ThE As aNd bS In tHe rEaL, sEcReT MeSsAgE, tWo tYpEfAcEs aRe cHoSeN, oNe tO RePrEsEnT As aNd tHe oThEr bS. tHeN EaCh lEtTeR Of tHe fAlSe mEsSaGe mUsT Be pReSeNtEd iN ThE ApPrOpRiAtE TyPeFaCe, AcCoRdInG To wHeThEr iT StAnDs fOr aN A Or a b. To dEcOdE ThE MeSsAgE, tHe rEvErSe mEtHoD Is aPpLiEd. EaCh 'TyPeFaCe 1' LeTtEr iN ThE FaLsE MeSsAgE Is rEpLaCeD WiTh aN A AnD EaCh 'TyPeFaCe 2' LeTtEr iS RePlAcEd wItH A B. tHe bAcOnIaN AlPhAbEt iS ThEn uSeD To rEcOvEr tHe oRiGiNaL MeSsAgE. aNy mEtHoD Of wRiTiNg tHe mEsSaGe tHaT AlLoWs tWo dIsTiNcT RePrEsEnTaTiOnS FoR EaCh cHaRaCtEr cAn bE UsEd fOr tHe bAcOn cIpHeR. bAcOn hImSeLf pRePaReD A BiLiTeRaL AlPhAbEt[2] FoR HaNdWrItTeN CaPiTaL AnD SmAlL LeTtErS WiTh eAcH HaViNg tWo aLtErNaTiVe fOrMs, OnE To bE UsEd aS A AnD ThE OtHeR As b. ThIs wAs pUbLiShEd aS An iLlUsTrAtEd pLaTe iN HiS De aUgMeNtIs sCiEnTiArUm (ThE AdVaNcEmEnT Of lEaRnInG). BeCaUsE AnY MeSsAgE Of tHe rIgHt lEnGtH CaN Be uSeD To cArRy tHe eNcOdInG, tHe sEcReT MeSsAgE Is eFfEcTiVeLy hIdDeN In pLaIn sIgHt. ThE FaLsE MeSsAgE CaN Be oN AnY ToPiC AnD ThUs cAn dIsTrAcT A PeRsOn sEeKiNg tO FiNd tHe rEaL MeSsAgE."
result = ""
for i in ciper:
if i.islower():
result += 'A'
elif i.isupper():
result+='B'
print(result)
将打印输出的结果粘贴到在线工具中:
结果如下:
VERYXWELLXDONEXFELLOWXHACKERXTHEXSECRETXKEYWORDXISXCROHPDBDBIGOXXKVFKSUJOUWKWWURNWVFNFWJKSVEWVLKXLKJNJVMTMTEVLKUVJFKNKZEUVUVSKKSZKTNKWVKVSUSOEVWVJKKZKVKVJWWVSVUVKVJVJOSVVJUWKSKWVJLFJFJNJFLKVLNFKJUSKKVFJKKVNKWVWWVUWUSVJKZUWWKJKTFKSTMVJKVNKWKWVWVSKKFSSKVFNLFKSWKKWWVWNVWSKXKKTJFV
可以看到将X
换成空格,即可得到有意义的英文句子。
用python替换下:
plain = "VERYXWELLXDONEXFELLOWXHACKERXTHEXSECRETXKEYWORDXISXCROHPDBDBIGOXXKVFKSUJOUWKWWURNWVFNFWJKSVEWVLKXLKJNJVMTMTEVLKUVJFKNKZEUVUVSKKSZKTNKWVKVSUSOEVWVJKKZKVKVJWWVSVUVKVJVJOSVVJUWKSKWVJLFJFJNJFLKVLNFKJUSKKVFJKKVNKWVWWVUWUSVJKZUWWKJKTFKSTMVJKVNKWKWVWVSKKFSSKVFNLFKSWKKWWVWNVWSKXKKTJFV"
print(plain.replace('X',' '))
结果:
VERY WELL DONE FELLOW HACKER THE SECRET KEYWORD IS CROHPDBDBIGO KVFKSUJOUWKWWURNWVFNFWJKSVEWVLK LKJNJVMTMTEVLKUVJFKNKZEUVUVSKKSZKTNKWVKVSUSOEVWVJKKZKVKVJWWVSVUVKVJVJOSVVJUWKSKWVJLFJFJNJFLKVLNFKJUSKKVFJKKVNKWVWWVUWUSVJKZUWWKJKTFKSTMVJKVNKWKWVWVSKKFSSKVFNLFKSWKKWWVWNVWSK KKTJFV
flag已经得到,是CROHPDBDBIGO
,提示:每次刷新flag都会变。所以我们的不一样。
方法二
这种方法不用工具,直接用python来做。
代码来自:ctf-wechall-Crypto - zhang14916的博客 - CSDN博客
s="BaCoN's cIphEr or THE bacOnIAN CiPHer iS a meThOD oF sTEGaNOGrapHY (a METhoD Of HidIng A sECRet MeSsaGe as OpPOsEd TO a TRUe CiPHeR) dEVIseD BY francis bAcoN. a MessAge Is coNCeALED in THe pRESenTatIoN OF TexT, ratHer thaN iTs coNteNt. tO enCODe A MEsSaGe, eaCh lETter Of THe pLAInText Is rePLAcED By A groUp oF fIvE OF thE LettERS 'a' Or 'b'. thIS REPlacEMenT is dOnE aCcORdiNG To The alPhABet oF THe BACOnIAN cIpHeR, sHoWn bElOw. NoTe: A SeCoNd vErSiOn oF BaCoN'S CiPhEr uSeS A UnIqUe cOdE FoR EaCh lEtTeR. iN OtHeR WoRdS, i aNd j eAcH HaS ItS OwN PaTtErN. tHe wRiTeR MuSt mAkE UsE Of tWo dIfFeReNt tYpEfAcEs fOr tHiS CiPhEr. AfTeR PrEpArInG A FaLsE MeSsAgE WiTh tHe sAmE NuMbEr oF LeTtErS As aLl oF ThE As aNd bS In tHe rEaL, sEcReT MeSsAgE, tWo tYpEfAcEs aRe cHoSeN, oNe tO RePrEsEnT As aNd tHe oThEr bS. tHeN EaCh lEtTeR Of tHe fAlSe mEsSaGe mUsT Be pReSeNtEd iN ThE ApPrOpRiAtE TyPeFaCe, AcCoRdInG To wHeThEr iT StAnDs fOr aN A Or a b. To dEcOdE ThE MeSsAgE, tHe rEvErSe mEtHoD Is aPpLiEd. EaCh 'TyPeFaCe 1' LeTtEr iN ThE FaLsE MeSsAgE Is rEpLaCeD WiTh aN A AnD EaCh 'TyPeFaCe 2' LeTtEr iS RePlAcEd wItH A B. tHe bAcOnIaN AlPhAbEt iS ThEn uSeD To rEcOvEr tHe oRiGiNaL MeSsAgE. aNy mEtHoD Of wRiTiNg tHe mEsSaGe tHaT AlLoWs tWo dIsTiNcT RePrEsEnTaTiOnS FoR EaCh cHaRaCtEr cAn bE UsEd fOr tHe bAcOn cIpHeR. bAcOn hImSeLf pRePaReD A BiLiTeRaL AlPhAbEt[2] FoR HaNdWrItTeN CaPiTaL AnD SmAlL LeTtErS WiTh eAcH HaViNg tWo aLtErNaTiVe fOrMs, OnE To bE UsEd aS A AnD ThE OtHeR As b. ThIs wAs pUbLiShEd aS An iLlUsTrAtEd pLaTe iN HiS De aUgMeNtIs sCiEnTiArUm (ThE AdVaNcEmEnT Of lEaRnInG). BeCaUsE AnY MeSsAgE Of tHe rIgHt lEnGtH CaN Be uSeD To cArRy tHe eNcOdInG, tHe sEcReT MeSsAgE Is eFfEcTiVeLy hIdDeN In pLaIn sIgHt. ThE FaLsE MeSsAgE CaN Be oN AnY ToPiC AnD ThUs cAn dIsTrAcT A PeRsOn sEeKiNg tO FiNd tHe rEaL MeSsAgE."
codebook1 = {
'A':"aaaaa",
'B':"aaaab",
'C':"aaaba",
'D':"aaabb",
'E':"aabaa",
'F':"aabab",
'G':"aabba",
'H':"aabbb",
'I':"abaaa",
'J':"abaab",
'K':"ababa",
'L':"ababb",
'M':"abbaa",
'N':"abbab",
'O':"abbba",
'P':"abbbb",
'Q':"baaaa",
'R':"baaab",
'S':"baaba",
'T':"baabb",
'U':"babaa",
'V':"babab",
'W':"babba",
'X':"babbb",
'Y':"bbaaa",
'Z':"bbaab",
}
def zhuanhua(s):
str1=""
j=0
for i in s:
if ord(i)>64 and ord(i)<91:
str1=str1+"b"
j=j+1
elif ord(i)>96 and ord(i)<123:
str1=str1+'a'
j=j+1
if j==5:
str1+=" "
j=0
return str1
def decode(s):
cipher=""
ss = s.split(" ")
for c in ss:
sign=True
for k in codebook1.keys():
if codebook1[k] == c:
cipher+=k
sign=False
break
if sign:
#cipher+=c
pass
return(cipher)
a=zhuanhua(s)
b=decode(a)
print b
mingwen=""
for i in b:
if i=="X":
mingwen+=" "
else:
mingwen+=i
print mingwen.lower()
Training: LSB
WeChall 网站使用了 cookie 和 session 来产生动态的答案,所以不同的人在每一次登录时,答案是不同的。我在这里贴出我的答案,你复制过去一般就是错误的答案,这样可以提防不动脑子只粘贴答案的人。
这道题目有链接提示,链接的颜色是 #EEE,接近背景色白色 #FFF,还是很明显看得出来的,看来作者没有有意隐藏这一提示。
家里网没能打开这个链接,原因还没查出,公司网可以打开。steganabara 原来是一个 .jar 包,这里下载 steganabara.jar,并运行程序 java -jar steganabara-1.1.1.jar
,将隐藏信息的图片拖进来。似乎是很正常的一张图片,LED 数码管显示了作者的大名 Gizmore。查了一下,gizmor 英文是小发明的意思。当然,这只是作者创作题目的署名,与解题没有任何关系。解题偶尔能有额外的收获。
这张图片是 RGB 格式,没有 alpha 通道,随便勾选 RGB 某一通道的某一位,共有 3*8=24 种单项选择(复合选择暂未考虑进来,从简单到复杂嘛~)。点击菜单 Filter -> Bit Mask,从低位到高位一个一个勾选盲试的话,很容易试出答案来。思路是这样的,你的答案不一定是这样的。注意勾上 Amplify 选项,否则很难辨认。
借助事实分析和判断,点击菜单 Analyse -> Histogram 给出 RGB 各分量的柱状图,范围是 0~255,有 3 个连续分布图和 1 个离散分布(斑马带)图,可以怀疑出是哪个通道有问题。相邻柱子的间距是周期,可以推断出是哪个比特位被篡改,导致出现重复的模式。
LSB (least significant bit) 最低有效位,权重最小的位,也是修改最不容易引起注意的位;与之对应的是 MSB (most significant bit) 最高有效位,修改后很容易被察觉。上面之所以需要增强信号(Amplify) 就是因为隐藏的信息在 LSB 上不易被发觉。这个是图片与图片的操作,我想到了一种字符串与图片混合的办法。
将字符串信息(也就是需要隐藏的信息 ASCII 码值)拆开成比特位,分别藏在图像中的连续像素中的最低位。思路是读取承载信息的图片,然后把需要隐藏的信息以比特流的形式连续覆盖图片 RGB 值的最低位,为了进一步增加难度,可以只覆盖其中某一个分量,可以不用放最低位,如果比特位用完了,信息可以从头循环再覆盖,直到覆盖所有。例如此题采用的是 Blue 通道的第 4 位。
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "libpng16/png.h"
int main()
{
png_image image;
memset(&image, 0, sizeof image);
image.version = PNG_IMAGE_VERSION;
image.format = PNG_FORMAT_RGB; // no alpha channel
const char* message = "No 300 taels of silver buried here.";
const int BITS = strlen(message) * CHAR_BIT;
const char* original = "g2YMC634NSqoEhXe.png";
const char* stegano = "stegano.png";
if(png_image_begin_read_from_file(&image, original))
{
size_t size = PNG_IMAGE_SIZE(image);
png_bytep buffer = (png_bytep)malloc(size);
if(buffer != NULL)
{
// blend text bits to image's least significant bit
int bit = 0;
for(size_t i = 0; i < size; ++i)
{
char test = (original[bit/8] & (1<<(bit%8))) ?1:0;
buffer[i] = (buffer[i]&0xFE) | test;
++bit;
if(bit >= BITS)
bit = 0; // repeat message
}
if(png_image_finish_read(&image, NULL/*background*/, buffer,
0/*row_stride*/, NULL/*colormap for PNG_FORMAT_FLAG_COLORMAP*/))
{
if(png_image_write_to_file(&image, stegano, 0/*convert_to_8bit*/,
buffer, 0/*row_stride*/, NULL/*no colormap*/))
fprintf(stdout, "write to file %s successfully.\n", stegano);
else
fprintf(stderr, "write %s: %s\n", stegano, image.message);
free(buffer);
}
else
{
fprintf(stderr, "read %s: %s\n", original, image.message);
/* This is the only place where a 'free' is required; libpng does
* the cleanup on error and success, but in this case we couldn't
* complete the read because of running out of memory.
*/
png_image_free(&image);
}
}
else
fprintf(stderr, "out of memory: %lu bytes\n",
(unsigned long)PNG_IMAGE_SIZE(image));
}
else
fprintf(stderr, "%s: %s\n", original, image.message);
return 0;
}
Training: GPG
Limited Access
PHP
<?php
$cookie="WC=xxxxxxxxxxxxxxxxxxxxx";
$url="http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php";
$post="";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$res = curl_exec ($ch);
curl_close($ch);
echo $res;
?>
HTML
新建html文件,填写以下内容。通过模拟表单提交来提交post请求。
<form method="POST" action="https://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php">
<input type="submit" value="submit">
</form>
Wget
wget --post-data=limited --header "Cookie: WC=*" http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php
Curl
curl -v -H "Cookie: [...]" -X POST http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php
Limited Access Too
Shadowlamb - Chapter I
Training: Warchall - The Beginning
Repeating History
打开题目给的第一个地址,找不到什么东西。有用的都在第二个超链接里。打开是一个GitHub的目录。
然后你发现文件夹层层叠叠,文件浩如烟海,你不知道从何找起。至于为什么文件那么多,显然这个是整个WeChall的工程目录,所以你觉得与这个题目相关的flag应该藏在哪里?当然是本题的目录下。看看URL?
注意这个单词,subversive
,题目中也提到了。
于是就应该知道去哪里找了吧。进入到本题的目录下,很清爽。文件夹只有两个,下面的文件数也不多,可以慢慢找。
很容易看到的是part one的flag
然后可以找到另一个比较隐秘的flag
这是一个md5,所以可以拿去解码一下:https://www.dcode.fr/hash-function
结果是wrong。这个不是程序报错…这说明这串md5就是wrong哈希过来的。什么意思?找错了呗。当然,我们也应该试一下InDaxInwrong这样对不对。不过应该就是错的。
下面就需要想想题目是什么意思了。Repeating History历史重演?作者的意思可能是以前solution是以明文形式存在.php文件里面的,这是一个极其致命 (subversive) 的漏洞,后来作者意识到了这一点,就把solution哈希了。
所以历史 (History) 里面应该能找到原来明文形式的solution。
发现History只有3个版本,非常好办。要么都看看,要么就看看comment。发现最新的版本就是修复Repeating History这道题目的(刚刚所说的明文存solution的问题),所以我们可以选择上一个版本查看。
flag的第二部分就能找到了。
最后提交InDaxInNothingHereMoveAlong
即可解决问题。
PHP My Admin
这道题目需要我们找一下PHPMyAdmin的登陆页面。
首先,如果做过前面的WeChall题目,就会知道看到题目有空行一定是要小心的。
藏起来的这句话的告诉我们——这道题肯定有hint。
首先当然是猜这个页面的路径了。我们可以网上查一下一般PHPMyAdmin会放在哪个路径下。
会发现 http://www.wechall.net/phpmyadmin 最多,另外 http://www.wechall.net/pma 也是有人用的,因为通常就把PHPMyAdmin简称为pma。另外,想不出来的时候就去看看那道题的讨论:http://www.wechall.net/forum-t710/Evil.html
可以看到作者本人就是将其称呼为pma的。
尝试上面两个路径,就能找到在 http://www.wechall.net/pma 下的hint。
提示告诉我们子域不对,也就是说不在www域下。也就是说我们要找的并不是路径,而是子域名。
这时我们可以尝试一下 http://pma.wechall.net 可以发现什么都没有发生。但是尝试 https://pma.wechall.net 就可以发现拿到flag了。
当然,如果你真的没找到这个hint,你还是可以从讨论中找到一些hint,如下。
作者说想象一下自己设置Apache的vhost(虚拟主机)。在一个Apache服务器上可以配置多个虚拟主机,实现一个服务器提供多站点服务,其实就是可以通过不同子域名访问同一个服务器上的不同目录。
这句话实际上也就是提示了找子域名。
至于在 http://pma.wechall.net 不行的时候,怎么想到要换协议 https://pma.wechall.net 呢?第一是直觉。
第二是,我们可以扫描一下服务器的开启端口:http://tool.chinaz.com/port/
开启端口对应的协议分别为:
80-HTTP
25-SMTP
110-POP3
443-HTTPS
所以HTTPS是开启的。
至于为什么 http://pma.wechall.net 什么都没有发生,而 https://pma.wechall.net 就可以呢?
拿到Flag之后看了一下solution,发现作者只允许https的方式访问pma子域名,这样可以阻止一些scanner发现这个页面。
Training: Caterpillar
很明显flag就在那条虫子的几个球的颜色表示数值上呀。所谓颜色的表示数值,正规一点应该叫色彩空间。色彩空间是用来描述一个颜色的。一个色彩空间是一套表示颜色的体系,很类似与用几个维度的坐标表示空间内的一个点(某个颜色)。我们最常用的色彩空间有RGB,CMYK等。
说到这里其实也不用怎么说下去了,一个个常见的色彩空间去试试就好了!当然,怎样算是可能是对的色彩空间?很简单,数值在字母的ASCII码范围内不就行了~
最后的提示,Photoshop这个工具很好用。
好吧,其实正确的色彩空间是HSB,flag如下。
提交COLOR-SHEMES
即可。
AUTH me
Interesting
Wanda
Railsbin
Factor 2
Connect the Dots
hi
从下面可以看到这是个等差数列,d是1,首项是2
求和公式是:
题目需要求,0xfffbadc0ded
也就是十进制的17591026060781
分钟后的发送信息数量。 故而尾项是17591026060782
使用[Wolfram | Alpha: Computational Intelligence](https://www.wolframalpha.com/)来计算,(2+17591026060782)*17591026060781/2 的结果: |
最后提交154722098935564539692256152
完成。
Stegano Woman
这道题目与图片内数据的隐写有一点点不同,它与 Training: Stegano I (Training, Stegano) 倒是比较像。实际上都是隐藏在二进制数据中的隐写。
把题目的附件下载下来,直接解压会发现有两张图片。其实有两张图片的隐写还有别的方法,比如合并两张图片之类的。这道题的坑爹地方就是想把人往这个方向误导。
但是实际上如果不解压,直接打开压缩文件会有怎样的效果呢?
下方选中的部分实际上是压缩文件的注释部分。
看图里面我选中的注释,Stegano后面是不是还有一大串不可见的字符?原来猫腻就在这里。马上用010 Editor打开拉到最后看一下。
原来那一堆不可见的字符是两种字符组成的,09(tab),20(space)。所以很容易想到这是使用两种不同字符的编码方式。
思考一下有什么编码方式是这样子的:二进制ASCII、Morse码、Bacon码……我大概就想到这么多。
下面我们来分析一下为什么后面两者是几乎不可能的。
Morse码,大家应该都知道它是由.-.-…—–这种东西组成的。而且要搞清楚Morse码、Huffman编码、前缀编码的概念。Morse码和Huffman编码都是通过二叉树进行构造的。但是它们有一个很重要的区别——学过相关课程的人应该知道,Huffman编码全部采用了叶子节点。这样有什么好处?这样使得Huffman编码是前缀编码,即对字符集进行编码时,要求字符集中任一字符的编码都不是其它字符的编码的前缀。
再简单点,举个例子:如果001代表a,那么不存在一个字符会编码为0010或者0011。这就使得译码的时候,从头到尾看01串,看到到符合编码表中某一个字符的01子串就可以直接将其翻译为对应字符,而不需要顾虑有没有别的字符对应的子串的前缀与这个01子串相同。这样的编码方式虽然不同字符编码为不同长度的码,但是不需要分隔符就可以译码,而不会产生二义。
刚刚说Morse不一样,没有全部使用叶子节点,也就是说它肯定不是前缀编码。而且又因为它每个字符编码为不同长度的“嘀嗒串”,因此它必须要分隔符才能无二义地译码。看看密文,不像有分隔符的影子,因此这样直接去按Morse译码肯定有N种情况,基本不可能。
第二个Bacon码不是基本不可能,是根本不可能。为什么呢?Bacon比较多是用不同字体大小,不同字体等比较难发现的方式去加密的,这样使用比较不符合Bacon的使用场景。不过这只是一个“基本不可能”的因素。根本不可能是因为Bacon一个字符用长度为5的AB串表示。但是上面那一串东西长度是264,不是5的倍数,所以否定Bacon。
所以肯定是ASCII了。ASCII可以用7位也可以用8位的。算一下264不是7的倍数而是8的倍数(264=33*8),所以只能是8位编码了。又因为8位编码的时候第一位肯定肯定是0,所以显然“09”代表的是0,“20”代表的是1.
所以只要替换一下,转为ASCII码,再转为字符就可以得到Flag。
提示:16进制文本中Stegano
后面的0d0a
代表回车换行。所以不用管。
将其转换为二进制,并转换为ascii字符的Python代码:
with open('stegano_woman.zip','rb') as f:
text = f.read()
index = text.find(bytes('Stegano','ascii'))
## 上面这个获取的是 S 的下标
text=text[index+9:]
#['S', 't', 'e', 'g', 'a', 'n', 'o', '\r', '\n']
## 所以这里要从 9 开始
text = ''.join('1' if x==32 else '0' for x in text)
s=""
for i in range(len(text)//8):
s+= chr(int(text[i*8:i*8+8],2))
print (s)
The solution is “dangerous life”. 提交dangerous life
即可。
Comments