[关闭]
@iPhan 2018-08-03T07:30:29.000000Z 字数 8625 阅读 889

LCTF2017 Crypto write up from 1phan

LCTF2017 xdsec writeup


双线性对

两道题目中没有用到双线性对其他复杂的性质与困难问题,只用到了最基本的一条性质:

e(g^a, h^b) == e(g, h)^(a*b)

Charm

看到许多人卡在安装charm上,略有些惊讶……

  1. pip install charm-crypto

如果没有安装依赖,是无法直接用pip安装charm的。 charm的文档中描述了charm的依赖包,以及如何手动编译安装。

官方文档

Crypto1

0X00. 题目原文

  1. #!/usr/bin/env python3
  2. from charm.toolbox.pairinggroup import *
  3. from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
  4. from urllib import parse, request
  5. logo = """
  6. _| _|_|_| _|_|_|_|_| _|_|_|_|
  7. _| _| _| _|
  8. _| _| _| _|_|_|
  9. _| _| _| _|
  10. _|_|_|_| _|_|_| _| _|
  11. _|_| _| _| _|_|_|_|_|
  12. _| _| _| _| _|_| _|
  13. _| _| _| _| _|
  14. _| _| _| _| _|
  15. _|_|_|_| _| _| _|
  16. """
  17. def sign(message, G, k, g, h, S):
  18. d = ***************************************
  19. message = bytes(message, 'UTF-8')
  20. message = bytes_to_long(message)
  21. if message == 0:
  22. message = G.random(ZR)
  23. mid = S**k
  24. mid = G.serialize(mid)
  25. mid = bytes_to_long(mid)
  26. P = G.pair_prod(g**mid, h**(message + d*k))
  27. return G.serialize(P)
  28. def check_token(token, name):
  29. url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
  30. req = request.Request(url=url)
  31. try:
  32. res = request.urlopen(req)
  33. if res.code == 200:
  34. return True
  35. except :
  36. pass
  37. return False
  38. def main():
  39. print(logo)
  40. S = ************************************************
  41. R0 = ***********************************************
  42. R1 = ************************************************
  43. R2 = ***********************************************
  44. S1 = S + R0
  45. S2 = S + R0*2
  46. G = PairingGroup('SS512')
  47. g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
  48. h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
  49. g = G.deserialize(g)
  50. h = G.deserialize(h)
  51. token_str = input("token:>>")
  52. name = input("team name:>>")
  53. if not check_token(token_str, name):
  54. return 0
  55. else:
  56. try:
  57. token = bytes(token_str,'UTF-8')
  58. token = bytes_to_long(token)
  59. except :
  60. return 0
  61. if token%2 ==1:
  62. point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
  63. else:
  64. point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
  65. print(G.serialize(point))
  66. S = G.pair_prod(g,h)**S
  67. k = G.serialize(S)
  68. k = bytes_to_long(k)
  69. message = input('message to sign:>>')
  70. if "show me flag" in message:
  71. return 0
  72. else:
  73. signed = sign(message, G, k, g, h, S)
  74. print(signed)
  75. signed_from_challenger = input('sign of "show me flag":>>')
  76. if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
  77. with open('./flag') as target:
  78. print(target.read())
  79. if __name__ == '__main__':
  80. main()

0X01. 思路

0x02. 攻破sign

  1. def sign(message, G, k, g, h, S):
  2. d = ************************************************
  3. message = bytes(message, 'UTF-8')
  4. message = bytes_to_long(message)
  5. if message == 0:
  6. message = G.random(ZR)
  7. mid = S**k
  8. mid = G.serialize(mid)
  9. mid = bytes_to_long(mid)
  10. P = G.pair_prod(g**mid, h**(message + d*k))
  11. return G.serialize(P)

读读sign,发现这是对ECDSA的拙劣模仿。

sign(M, ...)返回的结果是这样的:

e(g^(S^k), h^(M + d*k))

其中,S, k, d 三个值现在都不知道;g, h 已知;M可以自由控制但是不能为空,也不能包含子串'show me flag'。

化简一下sign(M, ...)返回的结果:

e(g, h) ^ (S^k * M + S^k * d * k)

一个直观的思路

  1. s = s' * (s1 / s2)
  2. = (e(g, h) ^ (S^k * M' + S^k * d * k)) * ((e(g, h) ^ (S^k * M1 + S^k * d * k)) / (e(g, h) ^ (S^k * M2 + S^k * d * k)))
  3. = e(g, h) ^ (S^k * (M' + M1 - M2) + S^k * d * k)
  4. = e(g, h) ^ (S^k * (M' + k) + S^k * d * k)
  5. = e(g, h) ^ (S^k * M + S^k * d * k)
  6. = sign('show me flag', ...)

至此crypto1的解计算完成。提交时看看题目给出代码中的判断部分注意提交格式。

Crypto2

0X00. 题目原文

  1. #!/usr/bin/env python3
  2. from charm.toolbox.pairinggroup import *
  3. from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
  4. from urllib import parse, request
  5. logo = """
  6. _| _|_|_| _|_|_|_|_| _|_|_|_|
  7. _| _| _| _|
  8. _| _| _| _|_|_|
  9. _| _| _| _|
  10. _|_|_|_| _|_|_| _| _|
  11. _|_| _| _| _|_|_|_|_|
  12. _| _| _| _| _|_| _|
  13. _| _| _| _| _|
  14. _| _| _| _| _|
  15. _|_|_|_| _| _| _|
  16. """
  17. def sign(message, G, k, g, h, S):
  18. d = ********************************************
  19. message = bytes(message, 'UTF-8')
  20. message = bytes_to_long(message)
  21. if message == 0:
  22. message = G.random(ZR)
  23. mid = S**k
  24. mid = G.serialize(mid)
  25. mid = bytes_to_long(mid)
  26. P = G.pair_prod(g**mid, h**(message + d*k))
  27. return G.serialize(P)
  28. def check_token(token, name):
  29. url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
  30. req = request.Request(url=url)
  31. try:
  32. res = request.urlopen(req)
  33. if res.code == 200:
  34. return True
  35. except :
  36. pass
  37. return False
  38. def main():
  39. print(logo)
  40. S = ***********************************************
  41. R0 = ************************************************
  42. R1 = ************************************************
  43. R2 = ************************************************
  44. S1 = S + R0
  45. S2 = S + R0*2
  46. G = PairingGroup('SS512')
  47. g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
  48. h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
  49. g = G.deserialize(g)
  50. h = G.deserialize(h)
  51. token_str = input("token:>>")
  52. name = input("team name:>>")
  53. if not check_token(token_str, name):
  54. return 0
  55. else:
  56. try:
  57. token = bytes(token_str,'UTF-8')
  58. token = bytes_to_long(token)
  59. except :
  60. return 0
  61. if token%2 ==1:
  62. point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
  63. else:
  64. point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
  65. print(G.serialize(point))
  66. S = G.pair_prod(g,h)**S
  67. k = G.serialize(S)
  68. k = bytes_to_long(k)
  69. message = 'abcd'
  70. signed = sign(message, G, k, g, h, S)
  71. print('signed of "abcd":>>', signed)
  72. signed_from_challenger = input('sign of "show me flag":>>')
  73. if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
  74. with open('./flag2') as target:
  75. print(target.read())
  76. if __name__ == '__main__':
  77. main()

0X01. 思路

比赛结束前三小时crypto2放出了hint:“这不止是一道crypto题目,它还是一道……”

两道题目只有几行代码不同,crypto2中不允许用户提交自己的字符串。只会返回sign('abcd', ...)。无法选择明文了,允许输入的地方只有三个:token,队名,和最后的变量 signed_from_challenger。其中‘队名’这个变量是打酱油的,丝毫用处都没有。(此处偷偷谴责一下写token校验api的兄台 :-P)

读完代码后应该可以意识到crypto2里sign函数也几乎是打酱油的,全程只有可能执行sign('abcd')与sign('show me flag')。

那么问题肯定出在前面那一坨代码上了。

读一下前面的代码,意识到前面的代码其实是在双线性对映射出的那个群中一点e(g, h)的指数上实现了一个两层递归的 shamir门限方案。这个shamir树型结构也是CP-ABE(Cipher Policy - Attributes Based Encryption)的基础结构。

图示:

shamir递归树

题目中如下代码实现了这颗树。

  1. if token%2 ==1:
  2. point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
  3. else:
  4. point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
  5. print(G.serialize(point))

在实际代码中,奇数选手将得到 e(g, h)^(token*R1 + S1);偶数选手将得到 e(g, h)^(token*R2 + S2)。从更靠前的代码可以看到S1,S2的来源:已知S1,S2时,它们组成了一个简单的二元一次方程组。如果想要恢复出sign函数输入中的S和k,就需要先拿到S,或者拿到S的一些特征,比如说e(g, h)^S。

  1. S1 = S + R0
  2. S2 = S + R0*2

对于S,如果只知道S1,或者只知道S2,是无法解出S的。毕竟“K元一次方程需要至少K个一组才可能有解,否则一定有无穷多解”。

对于选手能直接得到的 e(g, h)^(token*R1 + S1) ,有两个未知量R1,S1,在只有一个token时也是有无穷多解的。因此需要两个奇数token,两个偶数token才有可能恢复出S(这里对应给出的hint:它不只是一道crypto题目,它还是一道社工题)。在实际操作中,我们只能恢复到e(g, h)^S,不过这已经足够我们求出'show me flag'的sign啦。

因为我们只能得到'abcd'的sign,即 e(g^(S^k), h^('abcd' + d*k))
(注:此处代码前,S被赋值成了e(g, h)^S,详情见题目原文)
在我们能得到S = e(g, h)^S,且可以根据S求得k时,我们就可以给任意消息做签名了。

0X02. writeUp.py

  1. from charm.toolbox.pairinggroup import *
  2. from Crypto.Util.number import bytes_to_long, inverse
  3. def main():
  4. G = PairingGroup('SS512')
  5. g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
  6. h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
  7. # 4 different token. 2 even 2 odd
  8. t1 = bytes_to_long(b'4795968fe0bf73a1e39e6fec844dee01')
  9. S1 = b'3:cOlYveeItjU4ZHh8B58RjWUYJwdtFi/FXzqtd2GnnqEMJ9AFKzNjV90eUoPDLkinkWsdmbYTJxFTq5bvucwVHE98Uvw2laNvrsCFY9Mw766YdEPAtj7smBt/tIDl+u1mORufxZX8Q31F3dJjnzEoYhlxRZ9e9JFVtK7nW2Di6Iw='
  10. t2 = bytes_to_long(b'f11ca9db1f547b71a1b9592659553814')
  11. S2 = b'3:NjqqiCxaQtlFS1FEsSD+jmuO9Z0srysMi4K1nVCg2yAxJRjX62PPMSbY5JAa+Y4Ap25p9+u1EZ05f1RSwOXyZiIAZoDoS0crKDHRLJtE40aswcnPaf1JklMGBOGLdBUOZ3+nknLRDLACyBFnTW8y6FnHzLICGruBHisLhschvHM='
  12. t3 = bytes_to_long(b'97fddec1d9e630075803fc67d4220b05')
  13. S3 = b'3:GYcIbust8E1tcYZghIgC4x6YhrAyJUvy0lHHUxfvIOD7S/ann03RFrhO4qKb0jQ4vcU7pHJPv9Q+WDDPV/mAcH224dIfSyGcv91adl0tuhS6z0Fr4tBz03YUFUcGvAvi7bHvjnywwAjkTe1ZmMybyUnc9bMTPUxIZ3kli2b3PRs='
  14. t4 = bytes_to_long(b'adafcd958bbe176dd9cc96ef3aaa6438')
  15. S4 = b'3:R7Zhznj9aRtEv9ifZfLf9aqt4PSZzrMCSXuxkwZDdLEC2pqRPC1dWtP41BLR0UbbZVbTyOuojif9HYVuDu7oFSMTtj3zUxwXUW2x5sCYnkY3MOhSKM9JJxzAktSF0H2rIVvw4iBhQoh6Ecy3qRYfjZSha4Bc729DXHbYx0sMxd4='
  16. # SS512's order. get from G.order()
  17. order = 730750818665451621361119245571504901405976559617
  18. #init
  19. g = G.deserialize(g)
  20. h = G.deserialize(h)
  21. S1 = G.deserialize(S1)
  22. S2 = G.deserialize(S2)
  23. S3 = G.deserialize(S3)
  24. S4 = G.deserialize(S4)
  25. #compute x**S1
  26. t_S1 = ((S1**t3)/(S3**t1))**inverse((t3-t1), G.order())
  27. print(t_S1)
  28. #compute x**S2
  29. t_S2 = ((S2**t4)/(S4**t2))**inverse((t4-t2), G.order())
  30. print(t_S2)
  31. #compute x**S
  32. t = (t_S1*t_S1)/t_S2
  33. print(t)
  34. #compute k
  35. k = G.serialize(t)
  36. k = bytes_to_long(k)
  37. #compute mid
  38. mid = t**k
  39. mid = G.serialize(mid)
  40. mid = bytes_to_long(mid)
  41. #compute sign
  42. signed = b'3:loZKMHi9WWkS46zTQyidX5546U2Sg/JLnNi18X2KRklZdJSth4Kyj5FPg0J8sVpc9hyClgIo2P8xOGsRK6Zxc2AW6euFkyaOUWI9ZmYp2AhE0kcOypR4vASF9vWYtNqj0qlsExtMThSUtS53HYHCczbxcxA2Vcr/tkFagicyU30='
  43. signed = G.deserialize(signed)
  44. message = bytes_to_long(b'abcd')
  45. signed = signed/(G.pair_prod(g, h)**(mid*message))
  46. message = bytes_to_long(b'show me flag')
  47. signed = signed*(G.pair_prod(g, h)**(mid*message))
  48. print(str(G.serialize(signed)))
  49. if __name__ == '__main__':
  50. main()
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注