Sky's blog

inndy-crypto-writeup

字数统计: 2,441阅读时长: 13 min
2018/08/16 Share
1
文章首发在 https://xz.aliyun.com/t/2581

前言

休假在家,不如做点数学题?
今天心血来潮打开

1
https://hackme.inndy.tw/scoreboard/

既然刷完了web,今天就把crypto也刷完XD

easy

题目给了一串16进制

1
526b78425233745561476c7a49476c7a4947566863336b7349484a705a3268305033303d

解了之后发现是Base64,写个脚本即可拿到flag

1
2
3
import base64
c = "526b78425233745561476c7a49476c7a4947566863336b7349484a705a3268305033303d"
print base64.b64decode(c.decode('hex'))

flag:FLAG{This is easy, right?}

r u kidding

题目:

1
EKZF{Hs'r snnn dzrx, itrs bzdrzq bhogdq}

简单的凯撒加密

得到flag:FLAG{It's tooo easy, just caesar cipher}

not hard

题目信息:

1
2
Nm@rmLsBy{Nm5u-K{iZKPgPMzS2I*lPc%_SMOjQ#O;uV{MM*?PPFhk|Hd;hVPFhq{HaAH<
Tips: pydoc3 base64

随机想到py3的base85
于是尝试

最后发现只是base85+base32
即可获得flag

1
FLAG{Do you know base32 encoding?}

classic cipher 1

题目如下

1
2
MTHJ{CWTNXRJCUBCGXGUGXWREXIPOYAOEYFIGXWRXCHTKHFCOHCFDUCGTXZOHIXOEOWMEHZO}
Solve this substitution cipher

直接凯撒遍历不行,于是直接使用工具

可以得到flag

1
FLAG{SOLVING SUBSTITUTION CIPHER DECRYPTION IS ALWAYS EASY JUST LIKE A PIECE OF CAKE}

注:交的时候去掉空格

classic cipher 2

题目给了一个很长的vigenere cipher

在线工具解密

1
https://www.guballa.de/vigenere-solver

得到

搜索flag

得到答案

1
FLAG{VIGENERE CIPHER CAN BE CRACKED BY FREQUENCY ANALYSIS ATTACK}

easy AES

下载后发现是.xz结尾
于是

1
xz -d 1.py.xz

即可得到1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES # pip3 install pycrypto

def main(data):
c = AES.new(b'Hello, World...!')
plain_text = bytes.fromhex(input('What is your plain text? '))
if c.encrypt(plain_text) != b'Good Plain Text!':
print('Bad plain text')
exit()

c2 = AES.new(plain_text[::-1], mode=AES.MODE_CBC, IV=b'1234567887654321')

decrypted = c2.decrypt(data)

with open('output.jpg', 'wb') as fout:
fout.write(decrypted)

main(base64.b64decode('.......'))

思路相当清晰:
1.第一轮密钥为b'Hello, World...!'
2.第一轮密文为b'Good Plain Text!'
3.解密即可得到plain_text
4.第二轮密钥为plain_text
5.直接解密输出图片即可
代码如下

1
2
3
4
5
6
7
8
9
import base64
from Crypto.Cipher import AES
c = AES.new(b'Hello, World...!')
plain_text = c.decrypt(b'Good Plain Text!')
c2 = AES.new(plain_text[::-1], mode=AES.MODE_CBC, IV=b'1234567887654321')
data = base64.b64decode('.......')
decrypted = c2.decrypt(data)
with open('output.jpg', 'wb') as fout:
fout.write(decrypted)

得到图片

即可获得flag:FLAG{I can encrypt AES}

one time padding

看到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

/*
* one time padding encryption system
*
* we generate {$r = random_bytes()} which {strlen($r) == strlen($plaintext)}
* and encrypt it with {$r ^ $plaintext}, so no body can break our encryption!
*/

// return $len bytes random data without null byte
function random_bytes_not_null($len)
{
$result = '';
for($i = 0; $i < $len; $i++)
$result .= chr(random_int(1, 255));
return $result;
}

if(empty($_GET['issue_otp'])) {
highlight_file(__file__);
exit;
}

require('flag.php');

header('Content-Type: text/plain');

for($i = 0; $i < 20; $i++) {
// X ^ 0 = X, so we want to avoid null byte to keep your secret safe :)
$padding = random_bytes_not_null(strlen($flag));
echo bin2hex($padding ^ $flag)."\n";
}

注意到每次加密都是使用random_bytes_not_null生成随机的key,然后与flag进行异或,正面突破显然无望
但是我们注意到一段注释

1
// X ^ 0 = X, so we want to avoid null byte to keep your secret safe :)

题目意思为随机key中不会存在0,那么意味着不会出现flag中的原字母
那么我们反过来想,只要爆破每一位,每一位从未出现过的,即flag
所以写出脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests
import re
from bs4 import BeautifulSoup

url = "https://hackme.inndy.tw/otp/?issue_otp=a"

res_list = [[True] * 256 for i in range(50)]

for i in range(300):
print i,res_list
r = requests.get(url)
soup = BeautifulSoup(r.text, "html.parser")
text = str(soup)
c_list = re.findall("[^\n]*\n", text)
for j in c_list:
j = j.replace('\n','')
for k in range(1, len(j)/2+1):
char_hex = "0x" + j[k * 2 - 2: k * 2]
char_int = int(char_hex, 16)
res_list[k - 1][char_int] = False

flag = ""
for i in range(50):
for j in range(256):
if res_list[i][j]:
flag += chr(j)

print flag

其实最外层循环100次左右就够了,怕有人很非,所以写了300次= =
要是二维数组True不止50个。。对不起,你是大非酋。。。写1000吧
最后得到flag

1
FLAG{otp is most secure way to encrypt data....?!}

shuffle

拿到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import random
import string

characters = ''.join(map(chr, range(0x20, 0x7f)))

with open('plain.txt', 'r') as fin:
plaintext = fin.read()

mapping = list(characters)
random.shuffle(mapping)
mapping = ''.join(mapping)

T = str.maketrans(characters, mapping)

with open('crypted.txt', 'w') as fout:
fout.write(plaintext.translate(T))

plain = list(plaintext)
random.shuffle(plain)
suffled_plaintext = ''.join(plain)

with open('plain.txt', 'w') as frandom:
frandom.write(suffled_plaintext)

代码很清晰:
1.将明文随机替换加密,保存为crypted.txt
2.将明文打乱,保存为plain.txt
故此我们只要根据plain.txt和crypted.txt计算出字频和对应的字符
然后替换一遍即可
类似于:

随机写了个冗余的代码(想到哪里写到哪里= =别介意,没优化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
f1 = open('./crypted.txt')
cry_list=[0 for i in range(300)]
cry_content = f1.read()
list1=[]
for i in cry_content:
cry_list[ord(i)]+=1
for i in range(len(cry_list)):
if cry_list[i]!=0:
list1.append((cry_list[i],i))
list2=[]
f2 = open('./plain.txt')
plain_list=[0 for i in range(300)]
plain_content = f2.read()
for i in plain_content:
plain_list[ord(i)]+=1
for i in range(len(plain_list)):
if plain_list[i]!=0:
list2.append((plain_list[i],i))
res1 = sorted(list1)
res2 = sorted(list2)
res = []
for i in range(len(res1)):
cry_chr = chr(int(res1[i][1]))
plain_chr = chr(int(res2[i][1]))
res.append((cry_chr,plain_chr))
f3 = open('./crypted.txt')
flag_content = f3.read()
res_content = ""
for i in flag_content:
flag = False
for j in range(len(res)):
if i == res[j][0]:
res_content+=res[j][1]
flag = True
break
if flag == False:
res_content+=i
print res_content

运行即可替换回正确的文本,个别符号需要微调,因为出现频率相同

故此得到flag

1
FLAG{C01d n3v3r b0th3r3d m3 @nyw@y}

login as admin 2

拿到源码分析一下,看到关键函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function load_user()
{
global $secret, $error;

if(empty($_COOKIE['user'])) {
return null;
}

list($sig, $serialized) = explode('#', base64_decode($_COOKIE['user']), 2);

if(md5(md5($secret).$serialized) !== $sig) {
$error = 'Invalid session';
return false;
}

parse_str($serialized, $user);
return $user;
}

发现需要

1
md5(md5($secret).$serialized) === $sig


1
2
3
4
$serialized = http_build_query($user);
$sig = md5(md5($secret).$serialized);
$all = base64_encode("{$sig}#{$serialized}");
setcookie('user', $all, time()+3600);

现在我们的cookie中,user为

1
NmJjYjljOTE1NTk3NWE1M2U5NTFiMGI1MGYxMzc0ODAjbmFtZT1ndWVzdCZhZG1pbj0w

解码

1
6bcb9c9155975a53e951b0b50f137480#name=guest&admin=0

如此一来:
1.我们知道md5(salt.data)的值即sig
2.我们可以控制data
3.哈希长度拓展攻击即可
于是构造脚本

1
2
3
4
5
6
7
8
9
10
11
12
import hashpumpy
import base64
import requests

url = 'https://hackme.inndy.tw/login2/'
tmp = hashpumpy.hashpump('6bcb9c9155975a53e951b0b50f137480', 'name=guest&admin=0', 'name=guest&admin=1', 32)
payload = base64.b64encode(tmp[0]+'#'+tmp[1])
cookie = {
'user':payload
}
r =requests.get(url=url,cookies=cookie)
print r.content

运行得到

即可获得flag

1
FLAG{H3110, 4dm1n1576a70r... 1f y0u kn0w my 53cR37}

login as admin 5

我们看到关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
function set_session($user)
{
global $cipher;
$cookie = base64_encode($cipher->encrypt(json_encode($user)));
setcookie('user5', $cookie, time() + 60 * 60, '/', 'hackme.inndy.tw', true, true);
}
function restore_session()
{
global $cipher;
global $user;
$data = $cipher->decrypt(base64_decode($_COOKIE['user5']));
$user = json_decode($data, true);
}

发现加解密都用的rc4,然后明文直接使用了json
而我们可以知道json,又知道密文,那么可以反推rc4生成的流密钥
然后利用生成的流密钥,即可伪造消息
脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import base64
import urllib
import requests

c = base64.b64decode('U/osUbnY8nSrWz4WPwKSwWPzKq9tOIQ9eCWnN5E+')
plain = '{"name":"guest","admin":false}'
res = ''
for i in range(len(c)):
res += chr(ord(c[i])^ord(plain[i]))
need = '{"name":"guest","admin":true}'
payload = ''
for i in range(len(need)):
payload += chr(ord(need[i])^ord(res[i]))
payload = urllib.quote(base64.b64encode(payload))
cookie = {
'user5':payload
}
url = "https://hackme.inndy.tw/login5/"
r = requests.get(url=url,cookies=cookie)
print r.content

得到结果

flag:

1
FLAG{Every hacker should know RC4 cipher}

xor

运行github开源的xortool脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
G:\python2.7\Scripts>python xortool -c 20 xor
The most probable key lengths:
1: 8.6%
3: 10.6%
6: 9.4%
9: 21.8%
12: 7.1%
15: 6.2%
18: 14.1%
27: 9.7%
36: 7.1%
45: 5.4%
Key-length can be 3*n
1 possible key(s) of length 9:
hackmepls
Found 1 plaintexts with 95.0%+ printable characters
See files filename-key.csv, filename-char_used-perc_printable.csv

得到key:hackmepls
运行脚本解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
f1 = open("xor","rb")
key = "hackmepls"
f3 = open("flagtest.txt","wb")
# key = f2.read().replace(" ", "")
# key = "47 6F 6F 64 4C 75 63 6B 54 6F 59 6F 75".replace(" ", "").decode("hex")
flag = f1.read()
flag_length = len(flag)
key_length = len(key)
flag_res = ""
for i in range(0,flag_length):
xor_str = chr(ord(flag[i])^ord(key[i%key_length]))
flag_res += xor_str
f3.write(flag_res)
f3.close()

即可在解密后的明文中找到flag

得到flag:FLAG{Exclusive oR 15 y0ur fr1end}

emoji

拿到题目后丢进

1
https://tool.lu/js/

解密,得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log((function() {
if (typeof(require) == 'undefined') return '(´・ω・`)';
var code = require('process').argv[2];
if (!code) return '(´・ω・`)';
String.prototype.zpad = function(l) {
return this.length < l ? '0' + this.zpad(l - 1) : this
};

function encrypt(data) {
return '"' + (Array.prototype.slice.call(data).map((e) = > e.charCodeAt(0)).map((e) = > (e * 0xb1 + 0x1b) & 0xff).map((e) = > '\\u' + e.toString(16).zpad(4))).join('') + '"'
}
var crypted = ".......";
if (JSON.parse(encrypt(code)) != crypted) return '(´・ω・`)';
try {
eval(code)
} catch (e) {
return '(´・ω・`)'
}
return '(*´∀`)~♥'
})())

观察到关键代码

1
2
3
4
5
6
7
var crypted = ".......";
if (JSON.parse(encrypt(code)) != crypted) return '(´・ω・`)';
try {
eval(code)
} catch (e) {
return '(´・ω・`)'
}

关键点应该是解密crypted去得到code
跟到加密函数,发现直接爆破即可,于是写出脚本

1
2
3
4
5
6
7
8
9
10
11
def crack(n):
for i in range(256):
if (i * 0xb1 + 0x1b) & 0xff == n:
return i

crypted=u'......'
res = [crack(ord(i)) for i in crypted]
code = ''
for j in res:
code += chr(j)
print code

得到代码

1
$$$=~[];$$$={___:++$$$,$$$$:(![]+"")[$$$],__$:++$$$,$_$_:(![]+"")[$$$],_$_:++$$$,$_$$:({}+"")[$$$],$$_$:($$$[$$$]+"")[$$$],_$$:++$$$,$$$_:(!""+"")[$$$],$__:++$$$,$_$:++$$$,$$__:({}+"")[$$$],$$_:++$$$,$$$:++$$$,$___:++$$$,$__$:++$$$};$$$.$_=($$$.$_=$$$+"")[$$$.$_$]+($$$._$=$$$.$_[$$$.__$])+($$$.$$=($$$.$+"")[$$$.__$])+((!$$$)+"")[$$$._$$]+($$$.__=$$$.$_[$$$.$$_])+($$$.$=(!""+"")[$$$.__$])+($$$._=(!""+"")[$$$._$_])+$$$.$_[$$$.$_$]+$$$.__+$$$._$+$$$.$;$$$.$$=$$$.$+(!""+"")[$$$._$$]+$$$.__+$$$._+$$$.$+$$$.$$;$$$.$=($$$.___)[$$$.$_][$$$.$_];$$$.$($$$.$($$$.$$+"\""+$$$.$$__+$$$._$+"\\"+$$$.__$+$$$.$_$+$$$.$$_+"\\"+$$$.__$+$$$.$$_+$$$._$$+$$$._$+(![]+"")[$$$._$_]+$$$.$$$_+"."+(![]+"")[$$$._$_]+$$$._$+"\\"+$$$.__$+$$$.$__+$$$.$$$+"(\\\"\\"+$$$.__$+$$$.___+$$$.$$_+"\\"+$$$.__$+$$$.__$+$$$.$__+"\\"+$$$.__$+$$$.___+$$$.__$+"\\"+$$$.__$+$$$.___+$$$.$$$+"{\\"+$$$.__$+$$$.__$+$$$._$_+"\\"+$$$.__$+$$$._$_+$$$._$$+"\\"+$$$.$__+$$$.___+"\\"+$$$.__$+$$$.___+$$$.$_$+"\\"+$$$.__$+$$$.$_$+$$$.$$_+$$$.$$__+$$$._$+$$$.$$_$+$$$.$$$_+"\\"+$$$.__$+$$$.$$_+$$$._$_+"\\"+$$$.$__+$$$.___+"\\"+$$$.__$+$$$._$_+$$$._$$+$$$._+$$$.$$__+"\\"+$$$.__$+$$$.$_$+$$$._$$+"\\"+$$$.__$+$$$.$$_+$$$._$$+"}\\\");"+"\"")())();

丢进控制台

即可得到flag

1
FLAG{JS Encoder Sucks}

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. easy
  3. 3. r u kidding
  4. 4. not hard
  5. 5. classic cipher 1
  6. 6. classic cipher 2
  7. 7. easy AES
  8. 8. one time padding
  9. 9. shuffle
  10. 10. login as admin 2
  11. 11. login as admin 5
  12. 12. xor
  13. 13. emoji