前言

休假在家,竟然早上6点自然醒,不如做点数学题?
于是心血来潮打开

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

既然刷完了web,今天也尽量把crypto也刷完XD
做的过程中发现网上很难搜到题解,于是有了这篇文章
注:web全题解

http://skysec.top/2018/01/07/hackme%E7%BD%91%E7%AB%99%E8%BE%B9%E5%81%9A%E8%BE%B9%E8%AE%B0%E5%BD%95/
https://www.anquanke.com/post/id/156377

easy

题目给了一串16进制

526b78425233745561476c7a49476c7a4947566863336b7349484a705a3268305033303d

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

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

r u kidding

题目:

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

简单的凯撒加密

not hard

题目信息:

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

随机想到py3的base85
于是尝试

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

classic cipher 1

题目如下

MTHJ{CWTNXRJCUBCGXGUGXWREXIPOYAOEYFIGXWRXCHTKHFCOHCFDUCGTXZOHIXOEOWMEHZO}
Solve this substitution cipher

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

可以得到flag
注:交的时候去掉空格

classic cipher 2

题目给了一个很长的vigenere cipher

在线工具解密

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

得到

搜索flag

得到答案

easy AES

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

xz -d 1.py.xz

即可得到1.py

#!/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.直接解密输出图片即可
代码如下

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

one time padding

看到代码

<?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进行异或,正面突破显然无望
但是我们注意到一段注释

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

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

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

shuffle

拿到代码

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计算出字频和对应的字符
然后替换一遍即可
类似于:

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

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

login as admin 2

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

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;
}

发现需要

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

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

现在我们的cookie中,user为

NmJjYjljOTE1NTk3NWE1M2U5NTFiMGI1MGYxMzc0ODAjbmFtZT1ndWVzdCZhZG1pbj0w

解码

6bcb9c9155975a53e951b0b50f137480#name=guest&admin=0

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

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

login as admin 5

我们看到关键代码

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生成的流密钥
然后利用生成的流密钥,即可伪造消息
脚本如下

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

得到结果

xor

运行github开源的xortool脚本

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
运行脚本解密

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

emoji

拿到题目后丢进

https://tool.lu/js/

解密,得到

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 '(*´∀`)~♥'
})())

观察到关键代码

var crypted = ".......";
    if (JSON.parse(encrypt(code)) != crypted) return '(´・ω・`)';
    try {
        eval(code)
    } catch (e) {
        return '(´・ω・`)'
    }

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

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

得到代码

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

丢进控制台

即可得到flag

multilayer

解题脚本

import base64
from Crypto.Util import number
n=0x80dd2dec6684d43bd8f2115c88717386b2053bdb554a12d52840380af48088b7f1f71c3d3840ef4615af318bbe261d2d2d90616c0d2dcb6414e05c706f2b6d700ed98128048a2b79f57d2c6476add369ec96fb0fed936506d9aee4da5d36aaa97f117b082924c0638923e4367f250cc6cd23918702d98c5359bbb6bad2bef741c65362ad40355fd2edb35248256413d0ee576e7a351f17b9a5a3a7eebbbb2b22f27c342ef6dcaf1396085a105cf5e8b9bbf80e002053347fd9db6e83dc63599b1e1e5a81f7f2e4e2473bc2d14d040c9c6e6f62b9027853c7550a10df49c3a786962c9e9d5b95551a95077d0bd354b88ef31c5625e21edf98f721504f73e1b867
e=0xcf98d5
lines = open('encrypted').readlines()
data = base64.b64decode(lines[3].strip())
def xor(a, b):
    res=''
    for i in range(len(a)):
        res+=chr(ord(a[i])^ord(b[i]))
    return res
dec = {}
for i in range(0x10000):
    x = b'%.4x' % i
    v = number.bytes_to_long(x)
    dec[pow(v, e, n)] = x
raw = b''
for i in range(256, len(data), 256):
    prev = data[i-256:i]
    curr = int(xor(prev, data[i:i+256]).encode('hex'), 16)
    raw += dec[curr]
data = raw.decode('hex')
r = number.inverse(17, 251)
for key in range(0,256):
    output=''
    res=''
    for i in data:
        key = (key * 0xc8763 + 9487) % 0x10000000000000000
        output+=chr((ord(i) ^ key) & 0xff)
    for i in output:
        res += chr((ord(i)*r)%251)
    if res[4:5]=='{' and res[-2:] == '}\n':
        print res
        break

详细题解:
https://xz.aliyun.com/t/2627

ffa

解题脚本

from z3 import *
from primefac import *
import libnum
M=349579051431173103963525574908108980776346966102045838681986112083541754544269
p=240670121804208978394996710730839069728700956824706945984819015371493837551238
q=63385828825643452682833619835670889340533854879683013984056508942989973395315
z=213932962252915797768584248464896200082707350140827098890648372492180142394587
m=282832747915637398142431587525135167098126503327259369230840635687863475396299
x=254732859357467931957861825273244795556693016657393159194417526480484204095858
y=261877836792399836452074575192123520294695871579540257591169122727176542734080

a, b, c = BitVecs('a b c', 262)
s = Solver()
s.add(UGT(a, pow(2, 256, m)))
s.add(ULT(a, pow(2, 257, m)))
s.add(UGT(b, pow(2, 256, m)))
s.add(ULT(b, pow(2, 257, m)))
s.add(UGT(c, pow(2, 256, m)))
s.add(ULT(c, pow(2, 257, m)))
s.add(x == (a + b * 3) % m)
s.add(y == (b - c * 5) % m)
s.add(z == (a + c * 8) % m)
while s.check() == sat:
    A,B= s.model()[a].as_long(),s.model()[b].as_long()
    if gcd(A,B) == 1:
        break
s1,s2,tmp = libnum.xgcd(A, B)
if s1<0:
    s1 = - s1
    p = modinv(p, M)
    if p<0:
        p+=M
elif s2<0:
    s2 = - s2
    q = modinv(q, M)
    if q<0:
        q+=M
m=(pow(p,s1,M)*pow(q,s2,M)) % M
print libnum.n2s(m)

详细题解:
https://www.anquanke.com/post/id/156915

后记

欢迎师傅们讨论,菜鸡献丑了!

源链接

Hacking more

...