利用通用伪造签名绕过ElGamal
利用通用伪造签名绕过ElGamal
ElGamal签名加密国赛mailbox有出过,国赛的绕过方法是选择签名伪造绕过,而我们所讲的是利用通用伪造签名来绕过,以强网杯的mailbox2为例子
首先分析程序:
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 %sn" % (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.nn''')
我们需要生成一个以proof开头的长度为proof长度加5的字符串,并且其sha1的值以16比特的0结束。
这里我们直接使用如下的方式来绕过。
def f(x):
return sha1(prefix + x).digest()[-2:] == ' '
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: %sn" % 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:] == ' '
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
- 海量数据切分抽取的实践场景(r11笔记第43天)
- 使用shell自动化诊断性能问题(一)(r11笔记第41天)
- Data Guard实现故障自动切换(二)(r11笔记第40天)
- Oracle Data Guard延迟的原因(r11笔记第69天)
- 一个细小的空间问题触发的报警(r11笔记第68天)
- MySQL误操作数据恢复的简单实践(r11笔记第67天)
- Oracle 12c中JOB运行失败的简单处理(r11笔记第66天)
- MySQL中的半同步复制(r11笔记第65天)
- Linux系统LVM逻辑卷创建过程以及自动化脚本
- 一个闪回区报警的数据恢复(r11笔记第62天)
- 利用腾讯云COS云对象存储定时远程备份网站
- 分享一个自写的Python远程命令和文件(夹)传输类
- Oracle数据误操作全面恢复实战(r11笔记第78天)
- 远程协助解决异常宕库的问题(r11笔记第75天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 动态规划的楼层算法
- 58.Vue 使用render方法渲染组件
- xinetd被动服务唤醒
- Bash特殊变量:$0, $#, $*, $@, $?, $$实战
- 0799-1.8-CDSW1.8的新功能
- socket.io实践干货
- 0800-5.16.2-如何禁用Hue中Oozie的部分Action
- linux ulimit 调优
- 初识ABP vNext(3):vue对接ABP基本思路
- 0801-什么是Apache Ranger - 4 - Resource vs Tag Based Policies
- IDA-3D技术细节分析
- 0802-Cloudera Data Center7.1.3正式GA
- 2017,科学使用strace神器(附代码,举栗子)
- kubernete编排技术四:Job和CronJob
- Go 视图模板篇(二):模板指令