0x00 几句废话
总体来说,suctf的crypto部分的难度没有想象中的那么高(所以难度就全部集中到misc和web中去了吧可能),结合su-team分享的出题思路以及网上各路大神的解题脚本,学习了许多新姿势。
0x01 题目概览
此次比赛共出了四道密码学相关题目.
- 传统的DSA题目——重复使用r进行签名.
- 传统的RSA题目——LSB-Oracle攻击.
- 新型RSA题目——
e == n
情况下使用四个素数构造n的情况. - 随机数发生器——考察mt19937-32位可逆随机比特发生器的应用.
0x02 解题新姿势
pwntools用于C/S交互
简介
pwntools是一个研究者开发的用于pwn的CTF框架,这里主要讨论其在用户端/客户端交互过程中的使用,从而对解决服务器交互类型的Crypto问题起到作用.
安装
- Linux虚拟机中安装pwntools只需要命令
pip install pwntools
. - Mac系统中使用
brew install pwntools
,该命令的安装效果要强于使用pip命令.安装好之后,进入python2所在的包路径,在其中放入*.pth
文件,在该文件中写入pwntools所在路径即可完成安装. - 安装参考链接
交互功能
- 导入方法:
from pwn import *
- 连接
- 本地:
sh = process("./level10")
- 远程:
sh = remote("127.0.0.1",10001)
- 关闭连接:
sh.close()
- 本地:
- I/O模块
- 发送数据:
sh.send(data)
- 发送一行数据,相当于在数据后面加”\n”(许多服务器交互题目必须模拟提交后的回车键,故需要在提交信息后加上”\n”):
sh.sendline(data)
- 接收数据,numb指定接收的字节,timeout指定超时:
sh.recv(numb=2048, timeout=default
- 接收一行数据,keepends为是否保留行尾的\n:
sh.recvline(keepends=True)
- 接收数据直到出现设置的标志(避免了使用正则表达式):
sh.recvutil("Hello,World\n",drop=false)
- 一直接收直到EOF:
recvall()
- 持续接收直到EOF或timeout:
recvrepeat(timeout=default)
- 直接进行交互,相当于回到shell的模式,在取得shell之后使用:
sh.interactive()
- 发送数据:
- pwntools常用函数
python-z3库
Z3约束求解器是针对Satisfiability modulo theories Problem 的一种通用求解器.所谓SMT问题,在 Z3环境下是指关于算术、位运算、数组等背景理论的一阶逻辑组合决定性问题.能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行Fuzzing模糊测试等.
安装
- python3可以直接使用
pip3 install z3-solver
来安装z3库. - python2可以使用
pip2 install z3-solver
来获得安装包,再使用上文提到的创建*.pth
文件将该包加入到python2包路径里 - 基础用法
- 创建约束求解器:
solver = Solver()
- 添加约束条件:
solver.add()
- 判断解是否存在:
solver.check()
,当该函数返回sat时说明有解,返回unsat说明无解 - 求解:
solver.model()
- 数据类型:
Int()
: 有符号数.BitVec(variable, bits_number)
: 其中bits_number表示该变量的最大比特位数.只有BitVec变量可以进行异或,故可以用来解决位运算问题.BitVecVal值之间不能进行>或<比较,只能转换成python认识的类型才可以比较.as_long()
: 该函数用于将BitVec()类型的数据转换成long类型数据从而输出.
- 创建约束求解器:
- z3详细介绍
- z3在CTF中的应用-长亭技术专栏
0x03 题目分析
DSA
基本原理
签名方案:
- 选定公共参数p,q,g,其中$ g^q mod p = 1 $,即g的阶为p;
- 签名方随机生成私钥x,满足0 < x < q,计算并公开公钥
y = g^x mod q
; - 针对消息m,计算其哈希值
h = H(m)
,并生成随机数k,满足0 < k < q; - 计算
r = (g^k mod p)mod q
(相当于时间戳,防止重放攻击); - 计算
s = k^(-1)(H(m)+x*r) mod q
; - 以
为数字签名进行发布.
验证方案:
接收方在已知公共参数p,q,g和接收到消息m与签名r = (g^(s^(-1)*H(m)) + y^(s^(-1)*r) mod p) mod q
其中$ s^{-1} $指s在模q时的乘法逆元.
利用方法
一旦发现两条消息m1,m2的数字签名
1 | k*s1 = H(m1) + x*r mod q |
因此有x*r*(s2-s1) = H(m2)*s1 - H(m1)*s2 (mod q)
,所以x = (r*(s2-s1))^(-1)*(H(m2)*s1 - H(m1)*s2) mod q
。求得私钥x后,自然可以根据DSA数字签名方案对任意消息进行签名.
解题过程
题目为典型的与服务器交互的类型.直接nc目标端口,发现给出了p,q,g以及明文m,明文压缩之后的哈希值H(m)以及签名对
1 | import gmpy2 |
mt
基本原理
随机数发生器MT19937在从状态提取32bits随机数时进行了四步平移和异或运算,但该四步运算均为可逆运算,从而导致可从32bits随机数还原状态.随机数生成原始过程如下:
1 | def extract_number(self): |
解题过程
这是一倒较为基础的逆向随机数生成器的题目,没有什么特殊的技巧.两个基础的思路是爆破和逆向算法.参考大佬们的解决方案:官方给出的wp实现了左移和右移异或的逆运算函数,然后调用两个函数对密文进行解密;de1ta战队则选择了爆破;还有师傅使用python-z3库实现了算法的逆向.
首先来看官方的解题过程:
1 | from Crypto.Util import number |
对于左移和右移异或,写出其逆运算函数.基于mt随机数发生器使用32bits为一轮,故进行32/blocksize
次数的异或还原,而blocksize则为左移或右移的位数,还原循环中的函数与加密过程相同.先挖一个坑,具体原理等研究完mt19937后再来填.
使用z3-solver解决的过程中,实际上也是利用了爆破的原理,以题目给出的关系式convert(init) == cipher
为约束条件进行处理.
采用直接爆破方法的关键代码为:
1 | def decode(c): |
经过实验,爆破脚本的解题效率极高,以后碰到的随机数生成问题建议首选爆破脚本.该爆破能够成功主要由于伪随机数生成器得到的数字具有周期性,经测试该伪随机数生成器的周期为61320.
Prime
基本原理
该题目使用了RSA加密中e == n
这一特殊情况构造模数n.
对任意两个不同素数p,q和整数n=p*q,对任意整数m,0 < m < p且m < q,若c = m^n mod n
,则c^(q') mod q = m
,其中q’满足q1*p mod (q-1) = 1.予以证明如下:
根据费马小定理: $ m^{q-1} mod q = 1 $,所以$ m^{k’*(q-1)+1} mod q = m $.同理$ c^{q’} mod p = m $.
该题以此为原理,进行了扩展,将素数数量由2个扩大到了4个,并将m的范围扩大到0 < m < n.
解题过程
nc地址之后,首先进行通过爆破的方式验证哈希值,从而通过proof_of_work()
函数.爆破代码如下:
1 | from random import choice |
然后返回c0-c3,n0-n3共四对(n,s)对.对给出的n0,n1,n2,n3进行最大公因子分析,可分别得出他们的四个素因子.一般的,对于n = p1*p2*p3*p4
和c = m^n mod n
,有$ c^{p{i}^{‘}} (mod p_i) = m (mod p_i) $,其中$p{i}^{‘}$满足$p{i}^{‘}(n(p_i))mod (p_i-1) = 1 $,即$ p{i}^{‘} $是$ (n/p_i) $在$(p_i-1)$下的乘法逆元.然后可以利用中国剩余定理求解.官方解题脚本如下:
1 | import numpy as np |
RSA
基本原理
通读server2.py发现大致的交互过程如下:随机生成1024bits的n及加密密钥并公布.在0-(1024bits-1)范围内生成明文.用户可以不断向服务器发送加密过后的明文,服务器返回解密后明文的奇偶性.用户利用RSA的parity oracle漏洞得到随机生成的明文并上传给服务器.重复上述过程三次,当用户三次全部猜测正确时返回flag.
解题过程
拿到脚本之后,根据返回odd和even,可以想到是利用了LSB oracle漏洞.该漏洞原理较为简单,但一般情况下最终还原出的明文和真实明文会存在一定偏差,这主要是因为max和min是整数类型,其表示的上界和下界不够精确.官方给出了精确还原m的脚本:
1 | def crack(n, e, c): |
解题脚本如下:
1 | from pwn import * |
完成与服务器的多次交互并正确猜测三次后即可得到flag.
0x04 再说几句废话
本次题目的crypto部分还是可以学到很多东西的.与nc类题目以前经常使用socket编程进行,但匹配到需要的数据需要同时使用正则表达式和eval()函数,较为繁琐,解题速度慢,这次比赛学到了pwntools这一集成工具的使用,大大加快了解题速度.另外使用python-z3库可以更快更明确地完成爆破,只需要提供约束条件即可.