GoogleCTF18-MITM

类型 Crypto
题目连接:https://github.com/google/google-ctf/tree/master/2018/quals/crypto-mitm
参考:https://github.com/p4-team/ctf/tree/master/2018-06-23-google-ctf/crypto_mitm
https://mhackeroni.it/assets/docs/mitm_mhackeroni.pdf
考察知识点:ECDH,ECC,Curve25519

这道题目只给了一个服务器端的脚本,脚本中有一些有趣的函数:
1.Challenge
2.Client
3.Server
4.Handshake

此外还用了一个第三方模块curve25519
下面简单分析一下这些函数

Challenge

def Challenge(password, flag, reader, writer):
  try:
    server_or_client = ReadLine(reader)
    is_server = server_or_client[0] in b'sS'
    is_client = server_or_client[0] in b'cC'

    if is_server:
      return Server(password, flag, reader, writer)
    elif is_client:
      return Client(password, reader, writer)
    else:
      WriteLine(writer, b'Error: Select if you want to speak to the (s)erver or (c)lient.')
      return 1
  except Exception as e:
    WriteLine(writer, b'Error')
    return 1

可以看到,每次连接,我们可以选择和一个服务端通信,也可以选择和一个客户端通信。服务端和客户端共享一个password,flag在服务端。当然,我们可以把服务端的数据转发给客户端,把客户端数据转发给服务端,而且能任意修改这些数据再转发,相当于一个中间人。

Client

def Client(password, reader, writer):
  sharedKey = Handshake(password, reader, writer)
  if sharedKey is None:
    WriteLine(writer, b'Error: nope.')
    return 1

  mySecretBox = nacl.secret.SecretBox(sharedKey)
  line = mySecretBox.decrypt(ReadBin(reader))
  if line != b"AUTHENTICATED":
    WriteLine(writer, b'Error: nope.')
    return 1

  WriteBin(writer, mySecretBox.encrypt(b"whoami"))
  line = mySecretBox.decrypt(ReadBin(reader))

  if line != b'root':
    return 1

  WriteBin(writer, mySecretBox.encrypt(b"exit"))
  return 0

可以看到如果我们和客户端通信,必须先正确完成握手部分,并生成一个共享密钥,后续通信都需使用该密钥加密。

Server

def Server(password, flag, reader, writer):
  sharedKey = Handshake(password, reader, writer)
  if sharedKey is None:
    WriteLine(writer, b'Error: nope.')
    return 1

  mySecretBox = nacl.secret.SecretBox(sharedKey)
  WriteBin(writer, mySecretBox.encrypt(b"AUTHENTICATED"))

  while 1:
    cmd = mySecretBox.decrypt(ReadBin(reader))
    if cmd == b'help':
      rsp = b'help|exit|whoami|getflag'
    elif cmd == b'exit':
      return 0
    elif cmd == b'whoami':
      rsp = b'root'
    elif cmd == b'getflag':
      rsp = flag
    else:
      return 1
    WriteBin(writer, mySecretBox.encrypt(rsp))

服务端和客户端的逻辑很像,也必须先完成握手部分,生成共享密钥,后续通信都需用该密钥 加密。我们需要向其发送加密的‘getflag’才能获得flag。

Handshake

def Handshake(password, reader, writer):
  myPrivateKey = Private()
  myNonce = os.urandom(32)

  WriteBin(writer, myPrivateKey.get_public().serialize())
  WriteBin(writer, myNonce)

  theirPublicKey = ReadBin(reader)
  theirNonce = ReadBin(reader)

  if myNonce == theirNonce:
    return None
  if theirPublicKey in (b'\x00'*32, b'\x01' + (b'\x00' * 31)):
    return None

  theirPublicKey = Public(theirPublicKey)

  sharedKey = myPrivateKey.get_shared_key(theirPublicKey)
  myProof = ComputeProof(sharedKey, theirNonce + password)

  WriteBin(writer, myProof)
  theirProof = ReadBin(reader)

  if not VerifyProof(sharedKey, myNonce + password, theirProof):
    return None

  return sharedKey

握手部分是该密钥协商过程的关键,包含以下部分:

  1. 随机生成公私钥对
  2. 随机生成的nonce(Number once)
  3. 发送自己的公钥
  4. 发送自己的nonce
  5. 接受对方的 公钥 和nonce
  6. 如果对方的nonce和自己一样,或者对方的公钥是一些特殊值就 握手失败
  7. 根据自己的私钥和对方的公钥生成共享密钥 。
  8. 根据 password和对方的nonce,用共享密钥 通过哈希算法计算proof
  9. 发送proof
  10. 读取对方的proof
  11. 验证两个proof是否相同
  12. 如果相同则握手成功,返回共享密钥

显然,由于哈希算法的不可逆性,在不知道password的情况下,我们不可能伪造出一个通过检查的proof。如果我们想要通过握手,只能转发我们从服务端和客户端得到的proof,而且必须保证他们使用的是相同的sharedkey。值得注意的是因为我们是分别同服务端和客户端建立通信的,他们的privatekey是不同的,即使我们作为中间人发送相同的Publickey,生成的sharedkey也是不同的 。那是否存在这样的Publickey,能让不同的Privatekey生成出相同的sharedkey呢?我们注意到,在第六步,会判断公钥是否是两个特殊的值 b'\x00'*32, b'\x01' + (b'\x00' * 31),这其实就是一个提示,说明当key是某些特殊值时,会危害该密钥协商算法的安全性。
通过搜索Curve25519 的信息,可以找到一些资料:
https://cr.yp.to/ecdh.html
在这里有提到

Unusual keys

果然0,1 以及 一些其他值会破坏密钥协商过程的安全性。所以攻击过程就是在握手过程中,使用一个特殊的公钥,使客户端和服务端生成了同样的sharedkey,然后我们只需要转发proof,就能通过验证,然后我们用sharedkey加密getflag发给服务端,即可。
这里的另一个问题是我们最好使用和题目一样的第三方模块,来确保能生成同样的sharedkey。可能题目用的是这个库?https://github.com/Muterra/donna25519
最终的代码:

from curve25519 import Private, Public
from crypto_commons.generic import long_to_bytes

def riggedHandshake(server, client):
    zeroPublicKey = long_to_bytes(39382357235489614581723060781553021112529911719440698176882885853963445705823)[::-1]

    print("Rigging handshake")
    clientPublicKey = Public(readBin(client))
    print("Client key = "+str(clientPublicKey))
    clientNonce = readBin(client)
    print("Client nonce = " + clientNonce.encode("hex"))

    serverPublicKey = Public(readBin(server))
    print("Server key = " + str(serverPublicKey))
    serverNonce = readBin(server)
    print("Server nonce = " + serverNonce.encode("hex"))

    print("Sending to server")
    writeBin(server, zeroPublicKey)
    writeBin(server, clientNonce)

    print("Sending to client")
    writeBin(client, zeroPublicKey)
    writeBin(client, serverNonce)

    serverProof = readBin(server)
    clientProof = readBin(client)
    print("Server proof = "+serverProof.encode("hex"))
    print("Client proof = "+clientProof.encode("hex"))

    print("Forwarding proofs")
    writeBin(server, clientProof)
    writeBin(client, serverProof)

    return Private().get_shared_key(Public(zeroPublicKey))
from binascii import hexlify
from binascii import unhexlify

import nacl.secret
from crypto_commons.netcat.netcat_commons import nc, receive_until, send

def readBin(socket):
    try:
        data = receive_until(socket, "\n")[:-1]
        return unhexlify(data)
    except:
        print('error', data)


def writeBin(socket, data):
    send(socket, hexlify(data))


def main():
    url = "mitm.ctfcompetition.com"
    port = 1337
    server = nc(url, port)
    client = nc(url, port)
    send(server, "sS")
    send(client, "cC")
    sharedKey = riggedHandshake(server, client)
    mySecretBox = nacl.secret.SecretBox(sharedKey)

    print(mySecretBox.decrypt(readBin(server)))
    writeBin(server, mySecretBox.encrypt("getflag"))
    print(mySecretBox.decrypt(readBin(server)))

main()

Output:

Rigging handshake
Client key = <curve25519.keys.Public instance at 0x7f9a019ecea8>
Client nonce = bfaf0c97e8030e0bc59f6371438ea348bd51ad60127302d5ccd9a08add8190d3
Server key = <curve25519.keys.Public instance at 0x7f9a019f42d8>
Server nonce = 1b50d096795799effc8e1dbdf5a45e71a612aa0f3ccb60d5d09e94639c128f73
Sending to server
Sending to client
Server proof = 30e2f5990849450ca851bd99cc7b15569167e04c7068fa4383d25469e32916ef
Client proof = 3ae7e589e04fec309f5c6a235c35d70ff2cd031d4bc9e1104a3fdbc13e0bd567
Forwarding proofs
AUTHENTICATED
CTF{********}

那么如果没有这个第三方模块,我们能不能不通过调用get_shared_key(),直接获得sharedkey呢?
如果进一步了解Curve25519,可以知道:

  • Curve25519(any scalar,0x0000...0000)=0x0000...0000
  • Curve25519(any scalar,0x0000...0001)=0x0000...0000
  • 椭圆曲线是在有限域上的,Curve25519 是定义在模(2^255-19)上的。

由此可知,如果我们输入的公钥是(2^255-19),会被规约为0,最终生成的公钥也为0。这样就可以在不知道get_shared_key()具体实现的情况下,得到sharedkey
具体代码如下:

#!/usr/bin/env python3
from binascii  import  hexlify
from binascii  import unhexlify
import logging,sys,os
import  nacl.secret
import hmac,hashlib,pwn
pwn.context.log_level=logging.DEBUG
from hashlib import sha256,sha512
def ReadLine(reader):
  data=b''
  while not data.endswith(b'\n'):
    cur=reader.recv(1)
    data+=cur
    if cur ==b'':
      return data
    return data[:-1]
def WriteLine(writer,msg):
  writer.send(msg+b'\n')
def ReadBin(reader):
  return unhexlify(ReadLine(reader))
def WriteBin(writer,data):
  WriteLine(writer,hexlify(data))
def main():
  pk=long_to_bytes(57896044618658097711785492504343953926634992332820282019728792003956564819949).rjust(32,'\0')[::-1]
  pk1=long_to_bytes(57896044618658097711785492504343953926634992332820282019728792003956564819950).rjust(32,'\0')[::-1]
with pwn.remote('mitm.ctfcompetition.com'.1337) as c:
  c.sendline('c')
  ckey=ReadBin(c)
  cnonce=ReadBin(c)
  WriteBin(c,pk1)
with pwn.remote('mitm.ctfcompetition.com'.1337) as s:
  c.sendline('s')
  skey=ReadBin(s)
  snonce=ReadBin(s)
  WriteBin(s,pk)
  WriteBin(s,cnonce)
  WriteBin(c,snonce)
  cauth=ReadBin(c)
  WriteBin(s,cauth)
  sauth=ReadBin(s)
  WriteBin(c,sauth)
  authed=ReadBin(s)
  WriteBin(c,authed)
  WriteBin(s,ReadBin(c)) 
  WriteBin(c,ReadBin(s))
  k=ReadBin(c)
  mySecretBox=nacl.secret.SecretBox(sha256(b"curve25519-shared:"+'\0'*32).digest())
  WriteBin(s,mySecretBox.encrypt(b'getflag'))
  flag=ReadBin(s)
  print(mySecretBox.decrypt(flag))

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

推荐阅读更多精彩内容

  • 互联网的通信安全,建立在SSL/TLS协议之上。 本文简要介绍SSL/TLS协议的运行机制。文章的重点是设计思想和...
    拉肚阅读 2,632评论 0 6
  • 6.1 公钥密钥加密原理 6.1.1 基础知识 密钥:一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算...
    AndroidMaster阅读 4,010评论 1 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • 一、作用 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 (1)窃听风险...
    XLsn0w阅读 10,520评论 2 44
  • 本文摘自 腾讯bugly 的文章《全站 HTTPS 来了》,内容有修改。 大家在使用百度、谷歌或淘宝的时候,是否注...
    bnotes阅读 3,647评论 1 9