mRuby:mrbgems以及在Ruby对象中储存struct

Author:zumikua Updated at:2015-01-30 16:25:47 UTC

mRuby拥有自己的包管理系统,mrbgems。与gem不同的是,mrbgems静态的,是在编译mruby时发挥作用的,而rubygems则是在ruby编译好以后可以进行动态的扩展。

mrbgems

首先我们来看一下官方给出的样例mrbgem

#include <mruby.h>
#include <stdio.h>

static mrb\_value
mrb\_c\_method(mrb\_state \*mrb, mrb\_value self)
{
  puts("A C Extension");
  return self;
}

void
mrb\_c\_extension\_example\_gem\_init(mrb\_state\* mrb) {
  struct RClass \*class\_cextension = mrb\_define\_module(mrb, "CExtension");
  mrb\_define\_class\_method(mrb, class\_cextension, "c\_method", mrb\_c\_method, MRB\_ARGS\_NONE());
}

void
mrb\_c\_extension\_example\_gem\_final(mrb\_state\* mrb) {
  // finalizer
}

来源于examples\mrbgems\cextensionexample\src\example.c

mrbgem要求你定义mrb\_YOURGEMNAME\_gem\_init(mrb\_state\* mrb)函数以及mrb\_YOURGEMNAME\_gem\_final(mrb\_state\* mrb)函数,分别会在加载与结束的时候被调用。当然即使没有什么内容一个空函数也是不能少的。其中函数的内容就和我们在上一节中所使用的差不多。

下面,我们来看看mrbgem.rake

MRuby::Gem::Specification.new('c\_extension\_example') do |spec|
  spec.license = 'MIT'
  spec.author  = 'mruby developers'
end

其中cextensionexample是你的gem的名字。license是许可证,author是作者,感觉说的好像有点多……具体自己参考这里吧,其对gem的路径格式也有定义。

创建完以后,在mruby的根目录下的build\_config.rb中加一行conf.gem 'mrbgems/mruby-c-extension'当然要记得指向你自己的gem地址,我就不多说了。

最后make一下,编译过程中没错误的话就可以用了。在将其嵌入到程序之前,你可以使用mirb先测试一下。

使用C struct

有时候我们可能会需要使用Ruby变量来储存一些数据,比如C的struct。在一个Ruby对象中储存struct有两种方法,一种是用对象本身包装成一个C Struct。我个人认为这个方法更加麻烦一点,而且可扩展性也不算太高……不能让一个对象拥有两个struct之类……另一种方法是为该对象定义一个实例变量(instance variable),用这个实例变量包装struct。

用实例本身包装

#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/data.h"
#include "mruby/class.h"
#include "mruby/variable.h"
#include <stdlib.h>
#include <stdio.h>
struct Foo {
  int a;
};
static void foo\_free(mrb\_state\* mrb,void\* p){
  free(p);
  printf("Freed\n");
}
static struct mrb\_data\_type foo\_data\_type = {
  "foo", foo\_free
};
mrb\_value new\_foo(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f = (struct Foo\*)DATA\_PTR(self);
  if(f){
    mrb\_free(mrb,f);
  }
  DATA\_TYPE(self) = &foo\_data\_type;
  DATA\_PTR(self) = NULL;
  f  = (struct Foo\*)mrb\_malloc(mrb,sizeof(struct Foo));
  f->a = 0;
  DATA\_PTR(self) = f;
  return self;
}
mrb\_value set\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  int v=0;
  mrb\_get\_args(mrb,"i",&v);
  struct Foo\* f;
  f = DATA\_GET\_PTR(mrb,self,&foo\_data\_type,struct Foo);
  f->a = v;
  return mrb\_nil\_value();
}
mrb\_value get\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f = DATA\_GET\_PTR(mrb,self,&foo\_data\_type,struct Foo);
  return mrb\_fixnum\_value(f->a);
}

void mrb\_mruby\_c\_extension\_gem\_init(mrb\_state\* mrb) {
  struct RClass\* module\_test = mrb\_define\_module(mrb,"Test");
  struct RClass\* class\_foo = mrb\_define\_class\_under(mrb,module\_test,"Foo",mrb->object\_class);
  MRB\_SET\_INSTANCE\_TT(class\_foo, MRB\_TT\_DATA);
  mrb\_define\_method(mrb,class\_foo,"initialize",new\_foo,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a",get\_foo\_a,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a=",set\_foo\_a,MRB\_ARGS\_REQ(1));
}
void mrb\_mruby\_c\_extension\_gem\_final(mrb\_state\* mrb) {
  /\*Do nothing\*/
}

这部分的代码主要参考mruby-time这个gem的代码。首先来看一下new\_foo

首先应该指出的一点就是这里的initialize是一个实例方法,所以说传给new\_fooselfFoo.new生成的是实例自身而不是Foo这个Class类的实例常量

Ruby中内建的类型有一种DATA原型,该原型有一个DATA指针用来储存数据内容,所以说我们可以通过使用该DATA指针来储存struct。不过在此之前,先通过MRB\_SET\_INSTANCE\_TT(class\_foo, MRB\_TT\_DATA)将Foo类的所有实例设为DATA原型。

然后我们就可以通过DATA\_PTR(self)获取实例的DATA指针。注意:DATA\_PTR(self)宏实际上等于((struct RData \*)mrb\_ptr(self))->data,并不是一个字面值常量,所以对其进行赋值是可行的。

在取得DATA指针以后要对其进行释放,然后再将其指向自己创建的新内存中,这部分的代码全部都在new\_foo函数中。关于mrb\_free,目前从源码来看其与free没有区别,但是不保证以后会有什么修改,所以我个人还是倾向Ruby的东西用mrb\_free,自己定义的东西用free。

关于mrb\_data\_type,在这里Matz本人阐述了其作用,主要是Debug,以及定义了一个Data要如何被释放掉。正如代码所示,该struct由一段任意的字符串作为名字以及一个函数指针作为释放时使用的函数组成。

当然,为了让mRuby了解到该实例的类型,也要通过DATA\_TYPE(self) = &foo\_data\_type指定一下实例的类型。

编译后,在mirb下使用如下代码测试

> s = Test::Foo.new
 => #<Test::Foo:0x6d3d30>
> s.a
 => 0
> s.a = 10
 => 10
> s.a
 => 10
> s = 20
 => 20
> GC.start
Freed
 => nil

用实例变量包装

#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/data.h"
#include "mruby/class.h"
#include "mruby/variable.h"
#include <stdlib.h>
#include <stdio.h>
struct Foo {
  int a;
};
static void foo\_free(mrb\_state\* mrb,void\* p){
  free(p);
  printf("Freed\n");
}
static struct mrb\_data\_type foo\_data\_type = {
  "foo", foo\_free
};
mrb\_value new\_foo(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f  = (struct Foo\*)mrb\_malloc(mrb,sizeof(struct Foo));
  f->a = 0;
  mrb\_iv\_set(mrb,self,mrb\_intern\_cstr(mrb,"@data"),
  mrb\_obj\_value(Data\_Wrap\_Struct(mrb,mrb->object\_class,&foo\_data\_type,(void \*)f)));
  return self;
}
mrb\_value set\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  int v=0;
  mrb\_get\_args(mrb,"i",&v);
  struct Foo\* f;
  Data\_Get\_Struct(mrb,mrb\_iv\_get(mrb,self,mrb\_intern\_cstr(mrb,"@data")),&foo\_data\_type,f);
  f = (struct Foo \*)f;
  f->a = v;
  return mrb\_nil\_value();
}
mrb\_value get\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  Data\_Get\_Struct(mrb,mrb\_iv\_get(mrb,self,mrb\_intern\_cstr(mrb,"@data")),&foo\_data\_type,f);
  f = (struct Foo \*)f;
  return mrb\_fixnum\_value(f->a);
}

void mrb\_mruby\_c\_extension\_gem\_init(mrb\_state\* mrb) {
  struct RClass\* module\_test = mrb\_define\_module(mrb,"Test");
  struct RClass\* class\_foo = mrb\_define\_class\_under(mrb,module\_test,"Foo",mrb->object\_class);
  mrb\_define\_method(mrb,class\_foo,"initialize",new\_foo,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a",get\_foo\_a,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a=",set\_foo\_a,MRB\_ARGS\_REQ(1));
}
void mrb\_mruby\_c\_extension\_gem\_final(mrb\_state\* mrb) {
  /\*Do nothing\*/
}

在这里,我们定义了一个实例变量@data,用其存储struct数据。该变量通过Data\_Wrap\_Struct(mrb,mrb->object\_class,&foo\_data\_type,(void \*)f)中的mrb->object\_class被指定为了Object的实例。

我们使用了两个宏,Data\_Wrap\_Struct以及Data\_Get\_Struct,前者返回一个指定类的Data原型的实例,另一个则是从指定的对象中取得数据指针,它与DATAGETPTR是一模一样的,而与DATA\_PTR相比多了一个对类型的检测。

__Data\_Get\_Struct已经废弃,请使用DATA\_GET\_PTR替代__

mrb\_intern\_cstr会返回一个可供Ruby使用的标识符,其参数应该不用我多说。

mrb\_iv\_setmrb\_iv\_get的声明如下:

void mrb\_vm\_iv\_set(mrb\_state\*, mrb\_sym, mrb\_value);

mrb\_value mrb\_iv\_get(mrb\_state \*mrb, mrb\_value obj, mrb\_sym sym);

应该比较容易理解吧?

其他好像也没什么需要具体讲解的内容了,直接编译试试效果吧。

> s = Test::Foo.new
 => #<Test::Foo:0x903d30 @data=#<Object:0x903d18>>
> s.instance\_variables
 => [:@data]
> s.instance\_eval{@data}.class
 => Object
> s.instance\_eval{@data}.methods - Object.methods
 => []
> s.a = 10
 => 10
> s
 => #<Test::Foo:0x903d30 @data=#<Object:0x903d18>>
> s.a
 => 10
> s.instance\_eval{@data = 0}
 => 0
> s.a
(mirb):1: wrong argument type Fixnum (expected Data) (TypeError)
> s.a=102
(mirb):1: wrong argument type Fixnum (expected Data) (TypeError)
> s = 3
 => 3
> GC.start
Freed
 => nil

可以看出GC的确在好好工作。顺便如果不想让@data被修改的话可以把@data命名为data,这样它就不会被访问到了。

总结

这两种方法虽然实现方式不同,但是原理上是差不多的,都是通过DATA原型提供的DATA指针来存储数据,将struct用Ruby对象包装起来使用。不同的只是包装用的Ruby对象是什么、属于哪个类的实例而已而已。顺带一提,如果你想的话,你也可以把Data\_Wrap\_Struct中的mrb->object\_class改成其他的类,甚至是Foo类自身都可以。

《东京残响》网络入侵相关考据(什么竟然有Ruby?

Author:zumikua Updated at:2014-08-08 14:30:45 UTC

《东京残响》是部好作品。好到什么程度呢?好到就连我这样的懒到极致的人都放下游戏写了这么一篇考据文。 《东京残响》在表现入侵方面到底有多严谨?很遗憾,在我看来,并不是非常严谨。当然我本人也不过是一名信息安全专业的在校大学生而已,学识尚浅,技艺不精,难免在某些地方有所疏漏或者是谬误,还望指正。 根据我模糊的记忆以及撰文前的一次粗略的温习,《东京残响》最早表现入侵过程是从第四集开始的,即Nine入侵警察局网络。 让我们首先来看看这个: ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_06_49%5D%5B20140808-154358-1%5D.JPG)(注意看上面的密码中有一个f@ckpolice) 嗯很明显这是在用字典暴力破解密码嘛……通过访问一个后门页面顺带附加一个测试密码,如果返回的HTTP Status Code 为404(这个错误代码大家应该很熟悉吧,国内一些网站的内容被和谐以后就会404)。如果密码正确的话自然就是200啦。 这里有一个细节是这样的: ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_06_46%5D%5B20140808-154345-0%5D.JPG) 然后Nine就自己输了一个密码,再然后就bingo啦!简直是有如神助般的技巧! 那么是怎么做到的呢? 那就是侧信道攻击(又叫边信道攻击,英文side channel attack)。 具体的实现方法我也不知道(给我们上课的老头子只是稍微提了一下啊……),而且乍一听这种方法好像很高大上的样子实际上是很歪门邪道的天方夜谭一般的故事。简单来说,就是通过CPU在进行密码运算的时候所消耗的时间/功耗/声音(别笑,真有)来推测出真正的密码。是不是想起了那些把耳朵贴在保险箱上开锁的大盗了? 如果还不理解的话来看一个简单的例子。假如我是服务器,我的任务就是验证访客传给我的密码是不是正确,那么基本的想法是通过一个for循环,把访客给的密码和正确的密码一个字符一个字符地进行比对(这只是举例,一般来说现在都不会直接存明文密码了,都是保存经过不可逆的哈希算法加密过的密码),为了效率着想,一般来说在发现有不同的一位字符以后会直接返回,因为既然已经有一位密码对不上那么这个密码肯定就是错的了。 现在问题来了,如果我是黑客的话,我就可以通过记录不同错误密码返回的时间来猜测真正的密码是什么。比如我试验abcdefg这个密码需要1s返回,abcdcba这个密码需要2s返回,我就可以知道abcdcba这个密码从左数的正确位个数比abcdefg要高,而abcdefg与abcdcba的公共部分是abcd,所以abcd就是正确密码的前四位(如果abcd不是正确密码的前四位的话在比较到错误位的时候会直接返回导致两个返回时间相同)。 解决方法也很简单,不要发现不同之后立刻返回而是等全部比较完再返回。而现在主要的防侧信道攻击方式也和这种方法差不多,都是通过降低效率解决问题的。 现在回到动画中来,动画中的情况很明显是经过了过分的夸大的。首先计算机还不会因为几个比较的运算量而有人能感受到的差异,而且在网络环境下响应的传送速度本身就不固定,ping一个网站每次的差距还有十几毫秒呢这难道还掩盖不了cpu的响应时间?而且,通过一次的异常情况就可以直接猜到密码(而且猜出来的密码还和上面的密码半毛钱关系没有),这能力与运气简直无情啊。 成功蒙到密码进去以后,Nine(还是eleven来着)说了一句WebShell。那么这个WebShell到底是什么呢? ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_06_57%5D%5B20140808-154828-2%5D.JPG) Shell,可以理解为和操作系统进行交互的窗口。在Windows中我们用鼠标和图形界面进行交互,但是没有图形界面的话呢?我们就只能用shell了,而在服务器端,为了节约资源一般会采用没有图形界面的Linux作为操作系统(Window也有Shell,叫做命令提示行,起源于DOS,功能比Linux下的各种shell弱多了,但是不幸的是Windows服务器也有WebShell)。也就是说,拥有了Shell,你就可以给操作系统下各种各样的命令,就相当于控制了整个操作系统。(当然这里还有一个权限的问题,Linux是多用户操作系统,黑客能够在WebShell里干什么程度的坏事全在于Web程序是以什么权限的用户在执行,如果是root的话……呵呵) WebShell,就是通过网页这种形式展现的一种shell,攻击者通过浏览器把命令传给WebShell,WebShell再去把命令传给操作系统,这样的话就相当于攻击者通过WebShell控制了操作系统。具体攻击者能执行什么样的命令就看执行Web程序的用户的权限了。 WebShell属于后门的一种,是需要注入的,也就是说不会有网站管理员傻乎乎地为了自己管理方便放个WebShell供黑客使用,他们有更好用的管理工具,FTP以及网站后台,或者ssh,另一种远程shell,不过更安全。那么WebShell怎么来呢?当然是黑客自己放上去的。一些网站允许用户上传文件,但是没做好防御措施,使攻击者上传的文件被成功执行了。(不知道有没有上传一个代码文件就能直接执行的网站……)或者利用一些服务器程序的漏洞来上传WebShell。 现在一般抵御WebShell的解决方法是限制上传类型,以及及时封堵漏洞。不过随着Rails等有自己的路由系统的framework诞生,webshell也变得不流行了吧。(当然你route规则和controller写的傻逼神也救不了你)。不过在现在,一堆政府及高校网站上还是有大量WebShell滋生…… [部分高校WebShell被曝光](http://wooyun.org/bugs/wooyun-2010-070368) (人家压根就不认为这个有影响直接就忽略了) 不过说道WebShell的话会因为密码错误而返回404代码这点倒是比较稀奇,通过我观察的这几个WebShell来看输错密码人家直接返回个HTTP 200让我"Fuck,Get away"了…… 至于说Nine在WebShell里面输的内容,只不过是把这个文件下下来而已……而且这个文件的域名和之前攻击的域名一样……也就是说警察局在自己电脑的服务器上准备了黑自己系统用的工具…… 当然什么为什么只不过下下来就能直接登录到警方后台这种事情就不用在意了,艺术源于生活高于生活嘛。 接下来是第五集 ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_14_21%5D%5B20140808-153213-1%5D.JPG) 当时看到这里我小激动了一下,这是货真价实的Ruby代码啊!虽然语法上有点小问题的样子…… 这里左边仍是在进行上一集出现过的穷举大法,不过看起来没什么突破。右边有行号的部分应该是Ruby代码,然后没有行号的部分我猜是Shell的输出(为什么是我猜呢因为这部分的内容很像是shell输出的但是动画里明明是Nine一个字一个字打出来的)…… 至于说具体的Ruby代码的含义……无视就好,说实在的第一行就让我看不懂……第二行好像有语法问题的样子……剩下几行大概就是把函数执行出来的结果赋值……具体在干什么我也看不大懂……什么为什么在def里面用attr_reader啊,end死哪去了啊之类的完全没法吐槽…… 顺达扯一句按照一般约定对于模块里的方法我们都是用.来调用的,只有对于模块里的常量才会用::调用,这里虽然没有违反语法但是和大部分人的习惯不相吻合…… 至于shell的内容……应该是和用户相关的……具体我没看懂……不知道是我学识有限还是说是动画脑洞大…… ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_14_54%5D%5B20140808-153415-3%5D.JPG) 然后到这里,看上去像是已经侵入到了电车机构内部,/var/www 这个路径一般都是服务器用来存放网站文件的,个人使用的笔记本除了测试用途不会建这种路径……顺便不知道为啥shell里的空格全部被吃掉了…… ![](http://zumikua.u.qiniudn.com/%5BKTXP%5D%5BZankyou_%5B00_16_07%5D%5B20140808-153910-7%5D.JPG) 动画中还有其他的地方有Ruby的身影,不过很可惜的是都是些片段我看不懂就是了……不过在第三行的sz到底是哪里来的我实在搞不清楚……代码才刚开始执行为什么就已经有变量定义了require的也不过是一个都是常量的Module而已总之果然还是无意义的代码吧。 好了扯了这么多不外乎就是表达两点一东京残响很屌,二Ruby大法好天灭Python婊。总而言之还是很期待后面会有什么样的发展以及什么样高端大气上档次的黑客手段让我等P民见识一下。啊希望不要发展成攻壳机动队那样一堆人在虚拟空间闪几道光打几场架扯一点专有名词就算入侵成功了,这实在是太cyberpunk范又实在是不科学了,臣妾实在是接受不了啊。 最后的最后,菅野大妈我信你啊!第四集最后那点BGM帅呆了啊!理纱是我的啊谁都不许和我抢啊!
Main