EasyCTF 2017 Cryptography Write Up
0x04 Cryptography
Flip My Letters
problem
I dropped my alphabet on its head, can you help me reassemble it? easyctf{r_wlmg_vevm_mvvw_zm_zhxrr_gzyov}
solution
使用了简单的替换的加密方法,每个字母被对应另一个字母替换,使用基于字典的词频分析在线网站quipqiup解出flag。
Clear and Concise Commentary on Caesar Cipher
problem
The flag is in Commentary.pdf. Use lowercase.
solution
凯撒密码,给出的文档值得一看,找到文档中RNFLPGS{LBHTBGVG}
,凯撒密码在线解密
最终flag为easyctf{yougotit}
RSA1
problem
I found somebody’s notes on their private RSA! Help me crack this.
solution
import libnum
p = 33499881069427614105926941260008415630190853527846401734073924527104092366847259
q = 34311544767652906613104559081988349779622789386528780506962212898921316785995851
e = 65537
c = 43465248299278658712013216049003172427898782261990372316282214376041873514481386908793943532363461126240609464283533882761307749486816342864113338277082746552
n = p * q
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) #easyctf{wh3n_y0u_h4ve_p&q_RSA_iz_ez_7829d89f}
Let Me Be Frank
problem
I was talking to one of my friends but I couldn’t quite understand what he was saying. I think it might be important so here it is: Nwh whdjwh qm uepen, T tjb fsmt tixgi jsrsh sigm gs mpzp xwqf iahxpv iw fslkt. pehgpxf{qtextz_glacz_elt_neinrw_qsg_bums_dcp}
solution
维吉尼亚密码解密
RSA2
problem
Some more RSA! This time, there’s no P and Q… this.
solution
yafu- Automated integer factorization
yafu-x64.exe factor(266965481915457805187702917726550329693157)
...snip...
***factors found***
P21 = 458070420083487550883
P21 = 582804455845022449879
import libnum
n = 266965481915457805187702917726550329693157
p = 458070420083487550883
q = 582804455845022449879
e = 65537
c = 78670065603555615007383828728708393504251
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) #flag{l0w_n_0eb6}
Decode Me
problem
Someone I met today told me that they had a perfect encryption method. To prove that there is no such thing, I want you to decrypt this encrypted flag he gave me.
solution
import base64
with open('encrypted_flag.txt') as file:
data = file.read()
while True:
if 'easyctf' not in data:
data = base64.b64decode(data)
else:
break
print(data) # easyctf{what_1s_l0v3_bby_don7_hurt_m3}
Hash on Hash
problem
There’s a lot of hex strings here. Maybe they’re hiding a message? hexstrings
solution
import hashlib
md5 = {}
string = ''
for x in xrange(0, 256):
char = chr(x)
md5_obj = hashlib.md5()
md5_obj.update(char)
md5_str = md5_obj.hexdigest()
md5[md5_str] = char
with open('hexstrings.txt') as file:
while True:
line = file.readline().replace('\n', '')
if len(line) == 0:
break
string += md5[line]
print string # easyctf{1_h0p3_y0u_d1dn7_d0_7h47_by_h4nd}
RSA 3
problem
We came across another message that follows the same cryptographic schema as those other RSA messages. Take a look and see if you can crack it.
solution
λ yafu-x64.exe factor(1172115235823606152023154654763747932660073385647694278339165126116107393252225152886841069176950866458540922555424233427042989493814327572078420 5110462278896087432260439082955593874915727686637956899)
fac: factoring 11721152358236061520231546547637479326600733856476942783391651261161073932522251528868410691769508664585409225554242334270429894938143275720784205110462278896087432260439082955593874915727686637956899
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
Total factoring time = 0.3556 seconds
***factors found***
P100 = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780871
P100 = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780869
ans = 1
import libnum
n = 11721152358236061520231546547637479326600733856476942783391651261161073932522251528868410691769508664585409225554242334270429894938143275720784205110462278896087432260439082955593874915727686637956899
p = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780871
q = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780869
e = 65537
c = 2907995727224121244474109148869412603986746589983095760953378907471772983106265015352351411281256847387789301815094608746590512178894738862276459859204020010443067567963450732279228357933677075986407
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) # easyctf{tw0_v3ry_merrry_tw1n_pr1m35!!_417c0d}
推荐文章:CTF中RSA的常见攻击方法
Diffie-cult
problem
I just intercepted some odd messages.txt. It appears to be a Diffie-hellman protocol, but my math isn’t good enough to figure out what the final shared key is. Help! (The answer is a number. There is no easyctf{}
)
Hint: Wikipedia explains Diffie-hellman pretty well.
solution
g^a mod p = 421049228295820
g^b mod p = 105262307073955
p = 442101689710611
如果不知道Diffie-Hellman密钥交换算法可以先看:
题目的意思大概就是想让我们求出密钥K,Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度(知乎-离散对数为什么是难题?),这里p较小且这题给的g^a mod p
和g^b mod p
以及p
都比较巧(g^a mod p
即使A发送给B的值,而g^b mod p
是B发送给A的值),为什么比较巧呢,我们在素数库找到他们的标准分解式。
g^a mod p = 421049228295820 = 2^2 · 5 · 17 · 19^3 · 37 · 47^4
g^b mod p = 105262307073955 = 5 · 17 · 19^3 · 37 · 47^4
p = 442101689710611 = 3 · 7 · 17 · 19^3 · 37 · 47^4
现在令n = 17 · 19^3 · 37 · 47^4
,那么就有:
g^a mod 21n = 20n
g^b mod 21n = 5n
我们把g^a mod 21n = 20n
化简一下即:g^a mod 21n = -n
,因为有20n mod 21n = -n
,至于原理可以看一下密码学基础。
根据Diffie-Hellman密钥交换协议:
我们求的密钥K等于:K = ((g ^ a)mod p)^b mod p
也就是:K = ((g ^ a)mod 21n)^b mod 21n
,而前面已求过g^a mod 21n = -n
,所以K = (-n)^b mod 21n
,现在只有b的值不知道,不过我们可以以b自变量的值为变量研究因变量K的变化情况:
n = 17 * (19 ** 3) * 37 * (47 ** 4)
p = 21 * n
for b in xrange(1,10):
k = (-n) ** b % p
print('k = (-n)^%d mod p = %d' %(b, k))
K = (-n)^b mod 21n
k = (-n)^1 mod p = 421049228295820
k = (-n)^2 mod p = 42104922829582
k = (-n)^3 mod p = 357891844051447
k = (-n)^4 mod p = 168419691318328
k = (-n)^5 mod p = 105262307073955
k = (-n)^6 mod p = 231577075562701
k = (-n)^7 mod p = 421049228295820
k = (-n)^8 mod p = 42104922829582
k = (-n)^9 mod p = 357891844051447
可以看到当b = 7又开始循环,所以K的可能值有6个,最后提交验证K为421049228295820
。
Security Through Obscurity
problem
I’ve never seen such a cryptosystem before! It looks like a public key cryptosystem, though… Could you help me crack it?
encrypt.sage
publickey and ciphertext.txt
solution
SageMath 是一个基于GPL协议的开源数学软件。它使用Python作为通用接口,将现有的许多开源软件包整合在一起,构建一个统一的计算平台。
我们的目标:创建一个有活力的自由开源软件以替代Magma,Maple,Mathematica和Matlab。
encrypt.sage:
p = 196732205348849427366498732223276547339
primelist = [2,3,5,7,11,13,17,19,23,29,31,37,43,47,53,59]
secret = REDACTED
def calc_root(num, mod, n):
f = GF(mod)
temp = f(num)
return temp.nth_root(n)
def gen_v_list(primelist, p, secret):
a = []
for prime in primelist:
a.append(calc_root(prime, p, secret))
return a
def decodeInt(i, primelist):
pl = sorted(primelist)[::-1]
out = ''
for j in pl:
if i%j == 0:
out += '1'
else:
out += '0'
return out
def bin2asc(b):
return hex(int(b,2)).replace('0x','').decode('hex')
message = REDACTED
chunks = []
for i in range(0,len(message),2):
chunks += [message[i:i+2]]
vlist = gen_v_list(primelist,p,secret)
print(vlist)
for chunk in chunks:
binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1] #lsb first
enc = 1
for bit in range(len(binarized)):
enc *= vlist[bit]**int(binarized[bit])
enc = enc%p
print(enc)
publickey and ciphertext.txt:
p = 196732205348849427366498732223276547339
vlist = [186290890175539004453897585557650819247, 75402298316736094226532182518108134406, 125495142022496378270547998225256386407, 97774267687164931514953833940936099082, 101991197227908059637463567354647370660, 153833851791059142883915934225837717549, 57404874013093467650483424580890463792, 21385179362692238453302681296928238570, 73119997627509808412069264512026243174, 187307466063352771786747395191866088255, 99696708971915885525739992181010504930, 35400960589917132410614021764179554582, 165004028169785856134522269878963539096, 23921651712221317415895203722083962980, 101282552285744196401422074083408273639, 36527324251768098978171373433957274016]
ciphertext = [10804437392992369932709952388461430442, 176193785024128365464527424154073333243, 149270645998191619421663334736314262928, 84083279828403258970202482839973583723, 105542809657403162156368566034837560781, 170535468317794277192003839288646533914, 1709561989051017137832962458645802494, 30208132812353075834728747743616689590, 179552149608863037880916374596103803214, 146319871444551859531557724256502213689, 94266034977624098660397183255753485858, 59624105602644297614582310044425417646, 150207980679551836987813576795479579005, 47189940152625174480564945084004798024, 60923399917552243674613186036841652885, 56060552313063913798237738953734149992, 153365453785043472981157196787373992079, 97439800863356756323659264743487719966, 105572255903480949865247928773026019148, 47189940152625174480564945084004798024, 32547907449246015626932936731350157592, 97471053149217334376536988401195572824, 156999991149661497460742185971412527182, 97705058765750947378422286408948780428, 56123764944636237849915747435965967337, 180380146745295930385428990214293723238, 178014626944341285289827069179285260436, 99504741454750536629756505680249931430]
大致看一下程序,理一下代码逻辑,message就是我们要求的明文,然后被分成每2个字符为一组添加到chunks列表:
message = REDACTED
chunks = []
for i in range(0,len(message),2):
chunks += [message[i:i+2]]
vlist已经给了,所以不用再去求secret
,接下来就是每2个字符简单的二值化处理再倒序,之后就是16次循环加密处理。
vlist = gen_v_list(primelist,p,secret)
print(vlist)
for chunk in chunks:
binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1] #lsb first
enc = 1
for bit in range(len(binarized)):
enc *= vlist[bit]**int(binarized[bit])
enc = enc%p
print(enc)
加密结果已经给了,一共28组,所以能推出明文长度为56,我们知道ASCII字符的范围0-255,再从上面的加密代码分析可知,chunk最多就只有256*256=65536
种组合,再做16次循环加密处理,也就是说最多1048576(256*256*16
)次就能把一组明文穷举出来,计算机对1048576这个次数简直毫无压力,所以上代码:
p = 196732205348849427366498732223276547339
vlist = [186290890175539004453897585557650819247, 75402298316736094226532182518108134406, 125495142022496378270547998225256386407, 97774267687164931514953833940936099082, 101991197227908059637463567354647370660, 153833851791059142883915934225837717549, 57404874013093467650483424580890463792, 21385179362692238453302681296928238570, 73119997627509808412069264512026243174, 187307466063352771786747395191866088255, 99696708971915885525739992181010504930, 35400960589917132410614021764179554582, 165004028169785856134522269878963539096, 23921651712221317415895203722083962980, 101282552285744196401422074083408273639, 36527324251768098978171373433957274016]
ciphertext = [10804437392992369932709952388461430442, 176193785024128365464527424154073333243, 149270645998191619421663334736314262928, 84083279828403258970202482839973583723, 105542809657403162156368566034837560781, 170535468317794277192003839288646533914, 1709561989051017137832962458645802494, 30208132812353075834728747743616689590, 179552149608863037880916374596103803214, 146319871444551859531557724256502213689, 94266034977624098660397183255753485858, 59624105602644297614582310044425417646, 150207980679551836987813576795479579005, 47189940152625174480564945084004798024, 60923399917552243674613186036841652885, 56060552313063913798237738953734149992, 153365453785043472981157196787373992079, 97439800863356756323659264743487719966, 105572255903480949865247928773026019148, 47189940152625174480564945084004798024, 32547907449246015626932936731350157592, 97471053149217334376536988401195572824, 156999991149661497460742185971412527182, 97705058765750947378422286408948780428, 56123764944636237849915747435965967337, 180380146745295930385428990214293723238, 178014626944341285289827069179285260436, 99504741454750536629756505680249931430]
plaintext = ''
for i in ciphertext:
find = False
for j in xrange(256):
if find:
break
for k in xrange(256):
chunk = chr(j) + chr(k)
binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1]
enc = 1
for bit in range(len(binarized)):
enc *= vlist[bit]**int(binarized[bit])
enc = enc%p
if enc == i:
find = True
plaintext += chunk
break
print(plaintext)
flag{i_actu4lly_d0nt_know_th3_name_of_th15_crypt0sy5tem}
used time:18.040904s
Lost Seed
problem
Every time I encrypt a flag with this program, it gives me something different.
solution
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // bl@2
int result; // eax@4
__int64 v5; // rcx@4
char v6; // [sp+Bh] [bp-85h]@2
int v7; // [sp+Ch] [bp-84h]@1
int v8; // [sp+10h] [bp-80h]@1
int v9; // [sp+14h] [bp-7Ch]@2
FILE *stream; // [sp+18h] [bp-78h]@1
char ptr[88]; // [sp+20h] [bp-70h]@1
__int64 v12; // [sp+78h] [bp-18h]@1
v12 = *MK_FP(__FS__, 40LL);
stream = fopen("flag.in", "r");
fread(ptr, 1uLL, 0x50uLL, stream);
fclose(stream);
stream = fopen("flag.out", "wb");
seed = realrand("flag.out", "wb");
v7 = 0;
v8 = strlen(ptr);
while ( v7 < v8 )
{
v3 = ptr[v7];
v9 = pseudorand();
v6 = v3 ^ v9;
fwrite(&v6, 1uLL, 1uLL, stream);
++v7;
}
fclose(stream);
result = 0;
v5 = *MK_FP(__FS__, 40LL) ^ v12;
return result;
}
Listen Closely
problem
We intercepted a secret message, but we can’t tell what it’s saying. Maybe you can help? super secret message
hint: 1, 16, 8000. After you use those, the problem is strictly crypto.
solution
Genius
problem
Your boss told you that this team has come up with the cryptographic hash of the future, but something about their operation just seems a little fishy.
solution
8a7fca9234d2f19c8abfcd812971a26c8c510dcaefd5061b191ad41d8b57d0ce631f5074f94b32730d0c025f1d7aacd7
be1ab1632e4285edc3733b142935c60b90383bad42309f7f6850d2b4250a713d0b2d7a97350465a02554d29d92bfefaf
d64ddd0de1b187cd670783f5e28d681dd401ed3009d05ce4ef600d364a2c953e4cc801b880dddef59829a5ad08bd8a63
73d559bc117f816333174e918d0587de5cca214701dbe9f7f42da7bccf074b811292b9d4dc398866ef95869b22b3941e
78635bc95eaa7662a2ddf3e3d45cf1084f4233d6c396e8a0e6fbf597d07b88178d03f3f7757bdbdaaed60729d08bb180
b42dad5453b2128a32f6612b13ea5d9fef843bee79633652a6d6ae08e964609f00e883ab809346226dff6887080fb68b
给了6组哈希值,每组96个字符,还提示到了MD5,于是丢crackstation跑一下:
简单验证一下:
md5(like) = be1ab1632e4285edc3733b142935c60b
md5(ly_s) = d64ddd0de1b187cd670783f5e28d681d
md5(ng_2) = 73d559bc117f816333174e918d0587de
md5(have) = b42dad5453b2128a32f6612b13ea5d9f
推测每组就是三个MD5组合的,于是将所有的MD5换行拆分再进行一次查找:
还有7个没有破解,从上面的都是4位简单的消息,于是把未找出的7个扔MD5库:
按顺序组合一下:OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34
提交给了flag:easyctf{OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d}
py优雅解决方式:
import hashlib
hashs = '8a7fca9234d2f19c8abfcd812971a26c8c510dcaefd5061b191ad41d8b57d0ce631f5074f94b32730d0c025f1d7aacd7be1ab1632e4285edc3733b142935c60b90383bad42309f7f6850d2b4250a713d0b2d7a97350465a02554d29d92bfefafd64ddd0de1b187cd670783f5e28d681dd401ed3009d05ce4ef600d364a2c953e4cc801b880dddef59829a5ad08bd8a6373d559bc117f816333174e918d0587de5cca214701dbe9f7f42da7bccf074b811292b9d4dc398866ef95869b22b3941e78635bc95eaa7662a2ddf3e3d45cf1084f4233d6c396e8a0e6fbf597d07b88178d03f3f7757bdbdaaed60729d08bb180b42dad5453b2128a32f6612b13ea5d9fef843bee79633652a6d6ae08e964609f00e883ab809346226dff6887080fb68b'
def get_md5_list(hashs):
md5_list = []
for x in xrange(0, len(hashs), 32):
md5_list.append(hashs[x:x+32])
return md5_list
def gen_char_list():
char_list = []
for x in xrange(48, 58): # 0 ~ 9
char_list.append(chr(x))
for x in xrange(65, 91): # A ~ Z
char_list.append(chr(x))
for x in xrange(97, 123): # a ~ z
char_list.append(chr(x))
char_list.append('_') # _
return char_list
def brute_force_md5(char_list, md5_list):
plain_dict = {}
for c1 in char_list:
for c2 in char_list:
for c3 in char_list:
for c4 in char_list:
chars = c1 + c2 + c3 + c4
md5_obj = hashlib.md5()
md5_obj.update(chars)
md5_str = md5_obj.hexdigest()
if md5_str in md5_list:
plain_dict[md5_str] = chars
return plain_dict
def get_plain(md5_list, plain_dict):
plain = ''
for md5 in md5_list:
plain += plain_dict[md5]
return plain
def main():
md5_list = get_md5_list(hashs)
char_list = gen_char_list()
plain_dict = brute_force_md5(char_list, md5_list)
plain = get_plain(md5_list, plain_dict)
print(plain)
if __name__ == '__main__':
main()
RSA 4
problem
After doing so much RSA, I finally messed up…. pls help. I encrypted my secret message but the decryption isn’t working!!
solution
Premium RSA
problem
My RSA is the greatest. It’s so strong, in fact, that I’ll even give you d! file
hint: You thought it’d be that simple?
solution
Paillier Service
problem
My friend made some sort of encryption service using the Paillier cryptosystem. Can you get him to encrypt the string easyctf{3ncrypt_m3!} for me? Your flag will be a base 10 integer.
Access his encryption service at paillier.tcp.easyctf.com 8570
solution
# Paillier.py
import binascii
#Gathered from connecting manually
# m = 1, r = 1
g = 76148136246979412868353192826161253341403849263254887278017187958514513340458179944731332795505616407225022188597713956679924138156737337560391522285190471306102238935856085554943425316921717217530405444795878376547349107664015741971592178799088766898531556269231518219697725522509132047243753064371633643298
# m = 2, r = 1
g2 = 152296272493958825736706385652322506682807698526509774556034375917029026680916359889462665591011232814450044377195427913359848276313474675120783044570380942612204477871712171109886850633843434435060810889591756753094698215328031483943184357598177533797063112538463036439395451045018264094487506128743267286595
expectedG2 = g*g
#Using Factordb, we find that expectedG2-g2 is a perfect square of a prime, which is below
#http://factordb.com/index.php?id=1100000000882961502
n = 76148136246979412868353192826161253341403849263254887278017187958514513340458179944731332795505616407225022188597713956679924138156737337560391522285190471306102238935856085554943425316921717217530405444795878376547349107664015741971592178799088766898531556269231518219697725522509132047243753064371633643297
n2 = n*n
m2r2 = 642704871773304452155778596282877892451871980828477596157415930594972102473171707034871466334408214634990379265334519095544245651795310239071984348465353456082430791507322024283077057140015173791209040404351064470318177893091562745760770981747716308255111472933684059218100124906239297276402113587510274467857526915676715307055889593001002210535184406398178516901311847346979934161946287183599368736554797730366291587740218078384204696550286009123986874335424671114430592617561047352470044247529967986001239137580719442869043114141323570567593427242451750466586033713111304296116982128148631354597378733690535403149
#check for no errors
assert (pow(g,2,n2)*pow(2,n,n2))%n2 == m2r2
assert (pow(g,2,n2))%n2 == g2
# get int of string easyctf{3ncrypt_m3!}
goal = b'easyctf{3ncrypt_m3!}'
hexGoal = str(binascii.hexlify(goal),'utf-8')
goal = int(hexGoal,16)
#print(goal)
#goal is divisible by 5, so use that for Homomorphic property
m5r1 = pow(g,5,n2)
goalDiv5 = goal // 5
# Now use the Homomorphic property :)
flagInt = pow(m5r1,goalDiv5,n2)
print(flagInt)