强网杯_mailbox2

强网杯

Crypto300:

这题其实是改了2017年国赛mailbox的,相当mailbox2吧,只是绕过ElGamal的方式不一样吧,mailbox i春秋上有复现,官方也有WP

首先分析程序:

class HandleCheckin(SocketServer.StreamRequestHandler):
    def handle(self):
        Random.atfork()
        req = self.request
        proof = b64.b64encode(os.urandom(12))  #产生12位的随机字符

        req.sendall(
            "Please provide your proof of work, a sha1 sum ending in 16 bit's set to 0, it must be of length %d bytes, starting with %s\n" % (
            len(proof) + 5, proof))

        test = req.recv(21)  #输入的字符
        ha = hashlib.sha1()
        ha.update(test)

        if (test[0:16] != proof or ord(ha.digest()[-1]) != 0 or ord(ha.digest()[-2]) != 0): # or ord(ha.digest()[-3]) != 0 or ord(ha.digest()[-4]) != 0):
            req.sendall("Check failed")
            req.close()
            return
        req.sendall('''=== Welcome to Overwatch Mailbox Login Portal v2.0 ===
                        [Notice] As pointed out recently by Dr. Winston, username/password style authentication apparently becomes old-fashioned.
                        We've introduced new signature-based auth system. To login in, please input your username and your signature.

                        [Notice] You have only one chance to log-in.\n\n''')

我们需要生成一个以proof开头的长度为proof长度加5的字符串,并且其sha1的值以16比特的0结束。

这里我们直接使用如下的方式来绕过。

def f(x):
    return sha1(prefix + x).digest()[-2:] == '\0\0'


sh = remote('117.50.6.36', 20014)
# bypass proof
sh.recvuntil('starting with ')
prefix = sh.recvuntil('\n', drop=True)
print string.ascii_letters
s = util.iters.mbruteforce(f, string.ascii_letters + string.digits, 5, 'fixed')
test = prefix + s
sh.send(test)

这里使用了pwntools中的util.iters.mbruteforce,这是一个利用给定字符集合以及指定长度进行多线程爆破的函数。其中,第一个参数为爆破函数,这里是sha1,第二个参数是字符集,第三个参数是字节数,第四个参数指的是我们只尝试字节数为第三个参数指定字节数的排列,即长度是固定的。更加具体的信息请参考pwntools。

绕过之后,我们继续分析程序,简单看下generate_keys函数,可以知道该函数是ElGamal生成公钥的过程,然后看了看verify函数,就是验证签名的过程。

继续分析

        req.sendall("Generating keys...\nDispatching keys to corresponding owners...\n")
        pk, sk, g, p = generate_keys()
        req.sendall("Current PK we are using: %s\n" % repr([p, g, pk]))
        print sk, pk, g, p

        for it in range(1):
            req.sendall("Username:")
            msg = self.rfile.readline().strip().decode('base64')
            print 'we got', repr(msg), digitalize(msg)

            if len(msg) < 6 or digitalize(msg) < 1e5 or len(msg) > MSGLENGTH:
                req.sendall("what r u do'in?")
                req.close()
                return
            req.sendall("Signature:")
            sig = self.rfile.readline().strip() #签名
            print 'we got', repr(sig)
            if len(sig) > MSGLENGTH:
                    req.sendall("what r u do'in?")
                    req.close()
                    return
            sig_rs = sig.split(",")
            if len(sig_rs) < 2:
                    req.sendall("yo what?")
                    req.close()
                    return

            # print "Got sig", sig_rs
            if verify(digitalize(msg), int(sig_rs[0]), int(sig_rs[1]), pk, p, g):
                    req.sendall("Login Success.\nDr. Ziegler has a message for you: " + FLAG)
                    print "shipped flag"
                    req.close()
                    return
             else:
                    req.sendall("You are not the Genji I knew!\n")

这里大概的意思就是generate_keys会自动生成p,g,pk,我们需要输入一串base64加密的信息,然后再输入数字签名,程序通过验证函数verify判断是否满足条件,如果满足的话就输出flag,不满足就不行
根据条件可以知道的是:

  • 自己提供msg和数字签名
  • 输入的msg需要先用base64编码
  • msg的长度大于6,msg的比特位<10的5次方,小于MSGLENGTH = 40000
  • 数字签名需要用,隔开

接下来看验证函数:

    def verify(m, r, s, pk, p, g):
        if r < 1 or r >= p or s < 1 or s >= p-1: return False
        if (r + s) % (p-1) == 0: return False  # Simple forgery won't work!
        if (pow(pk, r, p) * pow(r, s, p)) % p == pow(g, m, p):
            return True
        return False

这里我后来查了一下,是一种ElGamal签名的验证方法

ElGamal签名

验证算法

可以看到这里的验证方法跟题目中的verify函数几乎是一致的,给出的p,g,pk也就是上图中的p,g,y,验证(pow(pk, r, p) * pow(r, s, p)) % p == pow(g, m, p)也就是上面的验证方法

那么知道题目的验证是什么方法,我们应该怎么绕过呢,这里与国赛的mailbox就不一样了,国赛的是给了签名的,所以是选择签名伪造,但是这里是自己提供message和签名的,有一种攻击ElGamal的方法叫做通用伪造签名

大概意思就是自己通过伪造能通过验证的message和签名,那么根据上面写脚本(图中j的-1这里表示求j关于p-1的乘法逆元)

def mul_inv(a,b):  #用扩展欧几里得求乘法逆元
    b0 = b
    x0,x1=0,1
    if b ==1:return 1
    while a> 1:
        q =a / b
        a,b = b, a% b
        x0,x1 = x1 - q * x0,x0
    if x1 < 0 : x1 += b0
    return x1

def makeodd(m):   #使m长度为偶数,不然编码十六进制的时候会出现错误
    return len(m) % 2 == 1 and '0' + m or m

e = getRandomRange(2,p-1)  #获得2,p-1之间的随机整数
v = getRandomRange(2,p-1)
while gmpy2.gcd(v,p-1) != 1 :
    v = getRandomRange(2,p-1)
r = gmpy2.powmod(g,e,p) * gmpy2.powmod(pk,v,p) % p
s = (-r * mul_inv(v,p-1)) % (p-1)
if s <0:
    s += p-1
msg=e * s % (p-1)
msg_base64=base64.b64encode(makeodd(hex(msg)[2:].rstrip('L').decode('hex')))

这样我们就得到了能绕过验证的message和签名

最终脚本:

from pwn import *
from hashlib import sha1
import string
from Crypto import Random
from Crypto.Util.number import *
import gmpy2
import base64
def f(x):
    return sha1(prefix + x).digest()[-2:] == '\0\0'


sh = remote('117.50.6.36', 20014)
# bypass proof
sh.recvuntil('starting with ')
prefix = sh.recvuntil('\n', drop=True)
print string.ascii_letters
s = util.iters.mbruteforce(f, string.ascii_letters + string.digits, 5, 'fixed')
test = prefix + s
sh.send(test)  #这里用sendline会出现错误,使得username无法输入

print sh.recv()
print sh.recv()
PK= sh.recv() #PK
#print PK
p=int(PK.split()[5][1:-2])
g=int(PK.split()[6][:-2])
pk=int(PK.split()[7][:-2])


def mul_inv(a,b):
    b0 = b
    x0,x1=0,1
    if b ==1:return 1
    while a> 1:
        q =a / b
        a,b = b, a% b
        x0,x1 = x1 - q * x0,x0
    if x1 < 0 : x1 += b0
    return x1

def makeodd(m):
    return len(m) % 2 == 1 and '0' + m or m
e = getRandomRange(2,p-1)
v = getRandomRange(2,p-1)
while gmpy2.gcd(v,p-1) != 1 :
    v = getRandomRange(2,p-1)
r = gmpy2.powmod(g,e,p) * gmpy2.powmod(pk,v,p) % p
s = (-r * mul_inv(v,p-1)) % (p-1)
if s <0:
    s += p-1
msg=e * s % (p-1)
#print "msg : ",msg
#print makeodd(hex(msg)[2:].rstrip('L').decode('hex'))
msg_base64=base64.b64encode(makeodd(hex(msg)[2:].rstrip('L').decode('hex')))


print sh.recvuntil('Username:')
sh.sendline(msg_base64)
print sh.recvuntil('Signature:')
#print str(r)+","+str(s)
sh.sendline(str(r)+","+str(s))
print sh.recv()
# sh.close()

得到flag

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容