今天参加了MegCup,然而一整天就做出了一道题来。。。我还是太菜了QvQ
Linked List
背景
小Q有一些排好序的链表放在内存里。为了更紧凑地利用空间,小Q运行了一下内存整理程序,把链表们整齐的放在了一个内存地址连续的数组里。 每个链表元素包含所含数的大小,以及下一个链表元素在数组里的zero-based的下标。"-1"表示没有下一个元素。每个链表里有10000个元素,也就是说,下标的范围是0~9999。
对于每一个整理好的链表,小Q想知道是否存在某个给定的数,但除了按照链表顺序或数组顺序遍历所有元素外他并不知道怎么做。 然而小Q的电脑年代久远,内存访问很慢,所以他希望用尽量少的访存次数能知道是否存在某个数。 但他很忙,所以他出了这道题,希望你们能用最少访问次数回答一个排好序的放在数组里的链表是否存在某个数的询问。
思路
首先这个数字可以理解成:
1 | { |
也就是数组【下标】所指的内容中【包含了链表元素】,其中链表是排序了的,但是数组并没有排序。
要注意看题目,重点在于用尽量少的访存次数,找到数字
然后下面呢的提示中有提到:
- 本题为Web API交互题。你一共要对20个链表进行解题,对每个链表,要在400次查询内得到答案。答对其中的15个即为通过。为了节约你的时间,我们提供封装过的python API供下载。你可以在页面"Python API下载"处下载。
相当于这个访问细节我们不需要考虑,于是我们只需要考虑我们如何减少访问的次数即可。由于是一个打乱过 的数组,但是链表却又是[排过序],那么我们减少访问次数的方法最直观的想法就是【二分查找】。然而我们的数据确实打乱过,二分要求的却是要找到最大值和最小值,从中取值。
后来仔细想,二分查找的本质思想就是【在某个区间中查找我们需要的数值】,于是得到启发:
从区间取值 —> 此时的链表是排好序的 -----> 利用链表形成区间 ----->用随机数取值从而形成大致的区间
于是我们随机选取50个数字,以这些数字作为下标来选取内容:
然后将这些节点按value内容排序,从而形成一个个区间,然后让我们的target(需要查找的值)找到自己所处的区间,这样的话理想状态下就能够在 10000/50 +50 = 250 次中完成查找。
这里附上代码和比赛api:
1 | import math |
最后正好挂了5个测试。。。。所以正好通过了
=======================================
(没有做出来的有趣的题目们)
Bad Proxy
旷工小Q最近发现他的AI robot有了一些智能化的倾向,开始自动修改一些代码和配置, 目前已经剥夺了小Q账户的root权限,但貌似还没有更进一步的行动。小Q认为这很有有趣 也很危险,所以希望先终止AI的运行再慢慢调查;然而杀掉AI进程需要root权限,而强行 关机可能造成数据损失。
好在小Q早就考虑到了这一点,他运行的是修改过的linux kernel。在主板上有一个加密芯 片,存储有令牌和私钥。如果能够用私钥对令牌签名,并把签名结果直接发送到wifi interface上,该kernel就会直接终止AI进程。签名可以通过一台签名服务器和前面的代理 来访问。签名服务器的代码是放在ROM里的,不可被修改,可以被整个系统访问,但需要 sessionid作为认证;而代理服务器只能通过OTG线访问,所以不需要认证。现在小Q已经成 功连接上了代理服务器,然而他发现狡猾的AI在里面加了两行:
if page == ‘signtoken’:
return make_response(‘permission denied’, 403)
而现在小Q已经没有权限修改proxy的代码了。所以他希望你能帮助他,绕过代理服务器的权限检查,得到令牌签名,终止AI。
代理服务器:通过47.93.114.77:38700来访问; 源代码:proxy.py
签名服务器:通过47.93.114.77:38701来访问; 源代码:server.py
1 | #!/usr/bin/env python3 |
1 | #!/usr/bin/env python3 |
思路介绍
这题当时比赛时,为了看懂题目花了很久很久。。。。后来才明白,首先我们通过访问proxy的服务器,能够出发下列关键代码:
1 | UPSTREAM_URL = 'http://localhost:38701' |
通过输入了username和page访问url,从而将自己的username签名成sessionid,然后访问到38701端口,也就是server服务器上。然后server服务器上总共提供三种不同的服务:
- echo:打印当前request请求中的args
- eval:执行request中的expr参数后面的表达式
- signtoken:目标,对request中的signtoken参数进行签名并且返回
这三种服务都是需要登陆验证的。一般网站的登陆验证机制就是由sessionid进行确认,这里也不例外:
1 | try: |
由于我们在proxy中发现了signtoken这个字符串已经被过滤了,也就是说我们不能按照代理转发的方式去访问。自己做得时候一度怀疑和eval这个功能有关系,然而事实证明,这个功能就是个陷阱。。。。根本啥都做不来。
看了别的大佬的代码之后,发现其实根本就不是利用这个eval,而是要利用proxu中有一段debug信息:
1 | if request.form.get('debug'): |
首先给自己扫盲一下,这个debug参数的意思是:当存在于form表单中(也就是post请求)并且值不为0,就能够返回true
然后沃恩可以看到,这里将我们的headers参数加入了格式化字符串,headers中都有什么呢:
1 | <br /><hr>proxy debug<br />server response headers: <pre>{'Server': 'nginx/1.4.6 (Ubuntu)', 'Date': 'Mon, 27 Mar 2017 08:43:03 GMT', 'Content-Length': '238', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Content-Type': 'text/html; charset=utf-8', 'Content-Encoding': 'gzip'}</pre> |
这里要关注如下变量:
- Content-Length:头部内容长度
- Content-Encoding:当前html的加密方式
继续给 web渣渣的自己扫盲:
1.Accept-Encoding是当client向server请求的一个参数,这个参数中指示了浏览器支持的加密方式。
2.Web服务器接受到client的请求后,首先检查是否支持压缩,然后检查请求文件的后缀名
3.如果是静态界面,比如CSS,HTML,那么就会在本地压缩缓冲目录中查找当前请求文件是否已经已经被压缩
4.如果已经压缩过,则直接返回请求文件的压缩文件
5.如果没有压缩过,那么直接返回未压缩的文件,同时将文件压缩存放在压缩目录
6.如果是动态内容,则直接压缩后返回给浏览器,并且不存放在本地。
我们可以看到此时使用的加密算法是gzip,gzip是一种基于deflate算法的压缩算法。deflate是一种压缩数据流的算法,任何需要压缩流的地方都可以用。
delfate算法由下面两个算法一起构成:
- Huffman
- LZ77
其中,Huffman就是数据结构课本上那个,根据字符出现的频率,设计出一颗二叉树,产生相应的前缀编码,从而将数据进行压缩。
LZ77是一种基于滑动窗口的算法。这个算法我看了个半天都没怎么看懂,就大致了解了一下实现后的压缩效果:
例如:
abcdcdcdcdcdce – > dict=abcd 编码为(2, 9 ,e)[从下标为2的字符串开始为重复字符串,长度为9,之后的下一个字符为e]
也就是一种能够将重复的字符压缩的算法,而且更厉害的还能够这样:
由图可知,此时能够将aacaacabcabaaac压缩成**(_,0,a)(1,1,c)(3,4,b)(3,3,a)(1,2,c)**,可以见得其压缩率。
加密算法为:
1 | Repeat: |
解密算法为
1 | for(i = p, k = 0; k < length; i++, k++) |
通过上述的学习,大概能够意识到一点:如果使用了gzip进行了压缩的网站,那么在遇到【相同字符串】的时候,就会将对应的字符串进行压缩。反过来,当我们需要猜测某个会存在于某个会话中内容的时候,我们可以用这种方法去暴力猜测!
以Cookie为例子。假如我们的Cookie内容是:
1 | Cookie:secret=123xxx |
那么,虽然这个secret我们获取不到,但是我们可以通过泄露头部的方法将Cookie:secret=这段内容拿到手。当我们在html中填充内容:
1 | Cookie:secret= |
的时候,gzip压缩算法中的DEFLATE就会认出这段字符串并且将其压缩成上文提到的那个形式。接下来我们可以尝试猜测后面增加额度字符串:
1 | Cookie:secret=a |
在正确的猜测之前,这个字符串由于之前是没有出现过的,所以并不会被压缩。然而,当我们猜测正确后:
1 | Cookie:secret=1 |
此时DEFLATE会将1也压缩至之前的长度中,这就说明了此时的1是压缩成功的,换句话是,cookie的第一位长度就是1.
看到我们的proxy端的代码,可以看到这里会将我们的Content-Length给输出来,那么我们就可以通过构造这个Cookie:sessionid=字符串,进行sessionid的猜测,从而完成攻击。
然而实际实现起来远远没有这么简单。。。。完全是暴力破解,因为很多数据同时会在不同的状态下被压缩,从而导致长度不发生变化。。。。最后还是得靠暴力破解。。。。
1 | # -*- coding:utf-8 -*- |
这个只能找到19位的sessionid。。。。从大佬的blog看得找到24位才行。。。GG看起来只能暴力猜测了
参考博客:
http://hzp.iteye.com/blog/1833619
http://www.cnblogs.com/en-heng/p/4992916.html