diff --git a/README.md b/README.md new file mode 100644 index 0000000..5794186 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# rlfuzz + +Inspired by [rlfuzz](https://github.com/spolu/rlfuzz) + +## Installation + +```sh +# tested in Ubuntu 16.04.6 +# for LAVA-M +sudo apt install libacl1 libacl1-dev + +# for AFL +sudo sh -c "echo core > /proc/sys/kernel/core_pattern" + +for i in `ls /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor` ; do sudo sh -c "echo performance > $i"; done + +# install rlfuzz, testeded in Python 3.8.5 +pip install . + +# open main.ipynb in ./examples with jupyter, it's a simple test for DDPGAgent +``` + +## Add New Environments + +* modify `rlfuzz/setup.py` to add new target binary in `package_data`. +* modify `rlfuzz/rlfuzz/__init__.py` to register new envs. +* modify `rlfuzz/rlfuzz/envs/__init__.py` to import new env class defined in `rlfuzz/rlfuzz/envs/fuzz_lava_m_env.py`. +* modify `rlfuzz/rlfuzz/mods/Makefile` to generate new target binary in `rlfuzz/rlfuzz/mods/lava-m-mod/`. \ No newline at end of file diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..763513e --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +.ipynb_checkpoints diff --git a/examples/main.ipynb b/examples/main.ipynb new file mode 100644 index 0000000..db689b1 --- /dev/null +++ b/examples/main.ipynb @@ -0,0 +1,242 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "warnings.filterwarnings('ignore',category=FutureWarning)\n", + "\n", + "import numpy as np\n", + "import gym\n", + "import sys\n", + "from tqdm import tqdm\n", + "import time\n", + "\n", + "# pip install .\n", + "import rlfuzz as rf\n", + "\n", + "# pip install tensorflow\n", + "from tensorflow.keras.models import Sequential, Model\n", + "from tensorflow.keras.layers import Dense, Activation, Flatten, Input, Concatenate\n", + "from tensorflow.keras.optimizers import Adam\n", + "\n", + "# pip install keras-rl2\n", + "from rl.agents import DDPGAgent\n", + "from rl.memory import SequentialMemory\n", + "from rl.random import OrnsteinUhlenbeckProcess" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "ENV_NAME = 'FuzzBase64-v0'\n", + "env = gym.make(ENV_NAME)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "env.setDiscreteEnv()\n", + "print(env.action_space.n)\n", + "print(env.observation_space.shape)\n", + "nb_actions = env.action_space.n\n", + "nb_observation = env.observation_space.shape[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "actor_input = Input(shape=(1,) + env.observation_space.shape, name='actor_observation_input')\n", + "f_actor_input = Flatten()(actor_input)\n", + "x = Dense(1024, activation='relu')(f_actor_input)\n", + "x = Dense(64, activation='relu')(x)\n", + "y = Dense(nb_actions, activation='tanh')(x)\n", + "actor = Model(inputs=actor_input, outputs=y, name='Actor')\n", + "actor.summary()\n", + "\n", + "critic_action_input = Input(shape=(env.action_space.n), name='critic_action_input')\n", + "critic_observation_input = Input(shape=(1,) + env.observation_space.shape, name='critic_observation_input')\n", + "f_critic_observation_input = Flatten()(critic_observation_input)\n", + "x = Concatenate()([critic_action_input, f_critic_observation_input])\n", + "x = Dense(1024, activation='relu')(x)\n", + "x = Dense(64, activation='relu')(x)\n", + "y = Dense(1, activation='sigmoid')(x)\n", + "critic = Model(inputs=[critic_action_input, critic_observation_input], outputs=y, name='Critic')\n", + "critic.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = DDPGAgent(nb_actions=nb_actions, \n", + " actor=actor, \n", + " critic=critic, \n", + " critic_action_input=critic_action_input, \n", + " memory=SequentialMemory(limit=100000, window_length=1), \n", + " nb_steps_warmup_critic=2000, \n", + " nb_steps_warmup_actor=2000, \n", + " random_process=OrnsteinUhlenbeckProcess(size=nb_actions, theta=.15, mu=0., sigma=.3), \n", + " gamma=.99, \n", + " target_model_update=1e-3\n", + " )\n", + "agent.compile(Adam(lr=.001, clipnorm=1.), metrics=['mae'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "history = agent.fit(env, nb_steps=5000, visualize=False, verbose=1) # 执行nb_steps步,nb_max_episode_steps步后将done=True\n", + "\n", + "# import pandas as pd\n", + "# pd.DataFrame(history.history).to_csv('../logs/rl_ddpg_{}_history.csv'.format(ENV_NAME))\n", + "# agent.save_weights('../model/ddpg_{}_weights.h5f'.format(ENV_NAME), overwrite=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "sns.set()\n", + "\n", + "def show_graghs(env, history):\n", + " data = env.input_len_history\n", + " plt.figure(figsize=(20,8))\n", + "\n", + " plt.subplot(221)\n", + " plt.plot(data, marker='o', markersize=2, linewidth=1)\n", + " plt.xlabel('step')\n", + " plt.ylabel('length')\n", + "\n", + " plt.axhline(y=max(data), color='r', linewidth=1, linestyle='--')\n", + " plt.text(0, max(data), str(max(data)), fontdict={'size': 8, 'color': 'r'})\n", + " if len(history) > 0:\n", + " for n in history['nb_steps']:\n", + " plt.axvline(x=n, color='r', linewidth=1, linestyle='--')\n", + " plt.text(n, 0, str(n), fontdict={'size': 8, 'color': 'r'})\n", + "\n", + " data = env.transition_count\n", + " plt.subplot(222)\n", + " plt.plot(data, marker='o', markersize=2, linewidth=1)\n", + " plt.xlabel('step')\n", + " plt.ylabel('transition_count')\n", + " plt.axhline(y=max(data), color='r', linewidth=1, linestyle='--')\n", + " plt.text(0, max(data), str(max(data)), fontdict={'size': 8, 'color': 'r'})\n", + " plt.axhline(y=min(data), color='r', linewidth=1, linestyle='--')\n", + " plt.text(0, min(data), str(min(data)), fontdict={'size': 8, 'color': 'r'})\n", + " if len(history) > 0:\n", + " for n in history['nb_steps']:\n", + " plt.axvline(x=n, color='r', linewidth=1, linestyle='--')\n", + " print('[+] Avg of last 1000 steps: {}'.format(sum(data[-1000:])/1000))\n", + "\n", + " data = env.reward_history\n", + " plt.subplot(224)\n", + " plt.plot(data, linewidth=1)\n", + " plt.xlabel('step')\n", + " plt.ylabel('reward_history')\n", + " plt.axhline(y=max(data), color='r', linewidth=1, linestyle='--')\n", + " plt.text(0, max(data), str(max(data)), fontdict={'size': 8, 'color': 'r'})\n", + " plt.axhline(y=min(data), color='r', linewidth=1, linestyle='--')\n", + " plt.text(0, min(data), str(min(data)), fontdict={'size': 8, 'color': 'r'})\n", + "\n", + " from collections import Counter\n", + " data = env.mutate_history\n", + " ct = Counter(data)\n", + " plt.subplot(223)\n", + " plt.barh(list(ct.keys()), [ ct[k] for k in ct.keys() ])\n", + " plt.yticks(range(env.mutate_size), \n", + " ['EraseBytes', 'InsertByte', 'InsertRepeatedBytes', 'ChangeByte', 'ChangeBit', \n", + " 'ShuffleBytes', 'ChangeASCIIInteger', 'ChangeBinaryInteger', 'CopyPart'])\n", + " plt.xlabel('step')\n", + " # plt.ylabel('action')\n", + "\n", + "# plt.savefig('../logs/rl_ddpg_{}.png'.format(ENV_NAME))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "show_graghs(env, history.history)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 加载训练模型\n", + "newAgent = DDPGAgent(nb_actions=nb_actions, \n", + " actor=actor, \n", + " critic=critic, \n", + " critic_action_input=critic_action_input, \n", + " memory=SequentialMemory(limit=100000, window_length=1), \n", + " nb_steps_warmup_critic=2000, \n", + " nb_steps_warmup_actor=2000, \n", + " random_process=OrnsteinUhlenbeckProcess(size=nb_actions, theta=.15, mu=0., sigma=.3), \n", + " gamma=.99, \n", + " target_model_update=1e-3\n", + " )\n", + "newAgent.compile(Adam(lr=.001, clipnorm=1.), metrics=['mae'])\n", + "newAgent.load_weights('../model/ddpg_{}_weights.h5f'.format(ENV_NAME))\n", + "\n", + "newEnv = gym.make(ENV_NAME)\n", + "start = time.time()\n", + "newHistory = newAgent.test(newEnv, visualize=False, nb_max_episode_steps=5000)\n", + "end = time.time()\n", + "print('[+] {} min(s)'.format((end - start) / 60))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "RLFuzz", + "language": "python", + "name": "rlfuzz" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/rlfuzz/.gitignore b/rlfuzz/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/rlfuzz/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/rlfuzz/__init__.py b/rlfuzz/__init__.py new file mode 100644 index 0000000..29df0f9 --- /dev/null +++ b/rlfuzz/__init__.py @@ -0,0 +1,57 @@ +import os + +from gym.envs.registration import register + +def afl_forkserver_path(): + package_directory = os.path.dirname(os.path.abspath(__file__)) + return os.path.join( + package_directory, 'mods/afl-2.52b-mod/afl-2.52b/afl-forkserver', + ) + +# base64_afl +def base64_target_path(): + package_directory = os.path.dirname(os.path.abspath(__file__)) + return os.path.join( + package_directory, 'mods/lava-m-mod/base64_afl', + ) + +register( + id='FuzzBase64-v0', + entry_point='rlfuzz.envs:FuzzBase64Env', +) + +# md5sum_afl +def md5sum_target_path(): + package_directory = os.path.dirname(os.path.abspath(__file__)) + return os.path.join( + package_directory, 'mods/lava-m-mod/md5sum_afl', + ) + +register( + id='FuzzMd5sum-v0', + entry_point='rlfuzz.envs:FuzzMd5sumEnv', +) + +# uniq_afl +def uniq_target_path(): + package_directory = os.path.dirname(os.path.abspath(__file__)) + return os.path.join( + package_directory, 'mods/lava-m-mod/uniq_afl', + ) + +register( + id='FuzzUniq-v0', + entry_point='rlfuzz.envs:FuzzUniqEnv', +) + +# who_afl +def who_target_path(): + package_directory = os.path.dirname(os.path.abspath(__file__)) + return os.path.join( + package_directory, 'mods/lava-m-mod/who_afl', + ) + +register( + id='FuzzWho-v0', + entry_point='rlfuzz.envs:FuzzWhoEnv', +) \ No newline at end of file diff --git a/rlfuzz/coverage/__init__.py b/rlfuzz/coverage/__init__.py new file mode 100644 index 0000000..eff8b62 --- /dev/null +++ b/rlfuzz/coverage/__init__.py @@ -0,0 +1,4 @@ +from rlfuzz.coverage.coverage import Coverage +from rlfuzz.coverage.coverage import Afl +from rlfuzz.coverage.coverage import PATH_MAP_SIZE +from rlfuzz.coverage.forkclient import ForkClient \ No newline at end of file diff --git a/rlfuzz/coverage/coverage.py b/rlfuzz/coverage/coverage.py new file mode 100644 index 0000000..0b90813 --- /dev/null +++ b/rlfuzz/coverage/coverage.py @@ -0,0 +1,66 @@ +import numpy as np + +import operator +from rlfuzz.coverage.forkclient import ForkClient +from rlfuzz.coverage.forkclient import STATUS_CRASHED + +PATH_MAP_SIZE = 2**16 + + +class Coverage: + def __init__(self, coverage_status=None, coverage_data=None, verbose=False): + self.crashes = 0 + self.verbose = verbose + + assert coverage_status is not None + assert coverage_data is not None + + self.coverage_data = np.array( + list(map(self.classify_counts, coverage_data)), dtype=np.uint8) + if coverage_status == STATUS_CRASHED: + self.crashes = 1 + + # Reward + def reward(self): + return self.transition_count() / PATH_MAP_SIZE + + # 运行时经过的跳转数(不重复计算) + def transition_count(self): + return np.nonzero(self.coverage_data)[0].shape[0] + + # classify_counts 对原始 coverage_data 进行预处理 + def classify_counts(self, a): + assert 0 <= a <= 255 + if a == 0: + return 0 + elif a == 1: + return 1 + elif a == 2: + return 2 + elif a == 3: + return 4 + elif 4 <= a <= 7: + return 8 + elif 8 <= a <= 15: + return 16 + elif 16 <= a <= 31: + return 32 + elif 32 <= a <= 127: + return 64 + else: + return 128 + + +""" +AFL ENGINE +""" + + +class Afl: + def __init__(self, target_path, args=[], verbose=False): + self.verbose = verbose + self.fc = ForkClient(target_path, args) + + def run(self, input_data): + (status, data) = self.fc.run(input_data) + return Coverage(status, data, self.verbose) diff --git a/rlfuzz/coverage/forkclient.py b/rlfuzz/coverage/forkclient.py new file mode 100644 index 0000000..f3cc24c --- /dev/null +++ b/rlfuzz/coverage/forkclient.py @@ -0,0 +1,207 @@ +import mmap +import struct +import random +import time +import tempfile +import os +import signal +import subprocess +import threading +import rlfuzz +import datetime +import base64 + +from posix_ipc import SharedMemory, Semaphore, ExistentialError + +# 最大输入大小 +MAX_INPUT_SIZE = (2**16) # 64K +MAP_SIZE = (2**16) + +_ping_struc_hdr = " 0xf * 4 + # 将density分解为2部分 0xff -> 0xf * 2 + self.action_space = spaces.Dict({ + 'mutate' : spaces.Discrete(self.mutate_size), + 'loc' : spaces.Tuple(( + spaces.Discrete(16), + spaces.Discrete(16), + spaces.Discrete(16), + spaces.Discrete(16) + )), + 'density' : spaces.Tuple(( + spaces.Discrete(16), + spaces.Discrete(16) + )) + }) + self.isDiscreteEnv = False # 默认为连续环境 + + self.last_input_data = b'' # 记录上一次生成的input + self.input_len_history = [] # 记录生成input的长度 + self.mutate_history = [] # 记录每次选择的变异策略 + self.reward_history = [] # 记录训练全过程每一步的reward + self.unique_path_history = [] # 记录发现的新路径数量(coverage_data不同) + self.transition_count = [] # 记录每次input运行的EDGE数量 + # self.action_history = [] # 记录每次model计算的原始action值 + + # 记录全局的edge访问 + self.virgin_map = np.array([255] * PATH_MAP_SIZE, dtype=np.uint8) + + # 从配置文件读取保存poc的地址 + self.POC_PATH = r'/tmp' + cfg = ConfigParser() + if cfg.read(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')): + self.POC_PATH = cfg.get('PATH', 'POC_PATH') + + self.reset() + + # 切换到Discrete环境 + def setDiscreteEnv(self): + if not self.isDiscreteEnv: + self.isDiscreteEnv = True + self.mutator = FuzzMutator(self.input_maxsize) + self.action_space = spaces.Discrete(self.mutate_size) + + # 恢复默认环境 + def recoverEnv(self): + if self.isDiscreteEnv: + self.isDiscreteEnv = False + self.mutator = FuzzMutatorPlus(self.input_maxsize) + self.action_space = spaces.Dict({ + 'mutate' : spaces.Discrete(self.mutate_size), + 'loc' : spaces.Tuple(( + spaces.Discrete(16), + spaces.Discrete(16), + spaces.Discrete(16), + spaces.Discrete(16) + )), + 'density' : spaces.Tuple(( + spaces.Discrete(16), + spaces.Discrete(16) + )) + }) + + def reset(self): + self.last_input_data = self._seed + self.input_dict = {} + self.virgin_map = np.array([255] * PATH_MAP_SIZE, dtype=np.uint8) + + # 重置其它初始化信息 + self.input_maxsize = self._input_maxsize # 最大input大小 + if not self.isDiscreteEnv: + self.mutator = FuzzMutatorPlus(self.input_maxsize) # 变异策略 + else: + self.mutator = FuzzMutator(self.input_maxsize) + self.observation_space = spaces.Box(0, 255, shape=(self.input_maxsize,), dtype='int8') # 更新状态空间(set_seed后需要修改) + + # 清空记录 + self.input_len_history = [] # 记录生成input的长度 + self.mutate_history = [] # 记录每次选择的变异策略 + self.reward_history = [] # 记录训练全过程每一步的reward + self.unique_path_history = [] # 记录发现的新路径数量(coverage_data不同) + self.transition_count = [] # 记录每次input运行的EDGE数量 + + assert len(self.last_input_data) <= self.input_maxsize + return list(self.last_input_data) + [0] * (self.input_maxsize - len(self.last_input_data)) + + def actor2actual(self, output, scale): + return int(output * np.ceil(scale/2) + np.ceil(scale/2)) % scale + + def updateVirginMap(self, covData): + res = False + for i in range(PATH_MAP_SIZE): + if covData[i] and covData[i] & self.virgin_map[i]: + res = True + self.virgin_map[i] &= ~(covData[i]) + return res + + def step_raw(self, action): + + if not self.isDiscreteEnv: # 连续环境 + # self.action_history.append(action) + # action[-2:] = np.clip(action[-2:], -1, 1) # noise可能会使tanh的输出超出[-1,1] + # assert self.action_space.contains(action), '{}'.format(action) + + # 模型输出 -> actual + # mutate = self.actor2actual(action[0], self.mutate_size) + if self.action_space.contains(action): + mutate = action['mutate'] + locs = action['loc'] + dens = action['density'] + else: + mutate = np.argmax(action[:self.mutate_size]) + locs = [np.argmax(action[start : start + 16]) for start in range(self.mutate_size, self.mutate_size + 64, 16)] + dens = [np.argmax(action[start : start + 16]) for start in range(self.mutate_size + 64, self.mutate_size + 64 + 32, 16)] + + ll = [12, 8, 4, 0] + loc = sum([n << l for n, l in zip(locs, ll)]) + density = sum([n << l for n ,l in zip(dens, ll[-2:])]) + + # 选择变异策略对last_input_data进行变异操作 + input_data = self.mutator.mutate(mutate, self.last_input_data, loc, density) + else: # 离散环境 + if self.action_space.contains(action): + mutate = action + else: + mutate = np.argmax(action) + assert self.action_space.contains(mutate) + input_data = self.mutator.mutate(mutate, self.last_input_data) + + # 记录动作历史 + self.mutate_history.append(mutate) + + # 记录每一步产生的input字符长度 + self.input_len_history.append(len(input_data)) + + # 执行一步获取覆盖率信息 + self.coverageInfo = self.engine.run(input_data) + + # 记录产生新覆盖记录的input + self.covHash.reset() + self.covHash.update(self.coverageInfo.coverage_data.tostring()) + tmpHash = self.covHash.digest() + # if tmpHash not in list(self.input_dict): # 如果当前变异产生新覆盖则选择变异后样本进行下一次变异 + if self.updateVirginMap(self.coverageInfo.coverage_data): + self.input_dict[tmpHash] = input_data + self.last_input_data = input_data + else: # 从记录中随机选择待变异样本 + self.last_input_data = self.input_dict[random.choice(list(self.input_dict))] + + self.unique_path_history.append(len(self.input_dict)) # 记录每一步之后发现的总的有效样本数 + + # 记录每一步运行的EDGE数量 + self.transition_count.append(self.coverageInfo.transition_count()) + + return { + "reward": self.coverageInfo.reward(), + "input_data": input_data, + "crash_info": True if self.coverageInfo.crashes > 0 else False # 是否发生崩溃 + } + + def step(self, action): + + info = self.step_raw(action) + reward = info['reward'] + assert reward <= 1 + + if info['crash_info']: + # reward = 1 # 调整奖励 + done = True + name = '{}-{}'.format(os.path.basename(self._target_path), datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S.%f')) # 精确到微秒防止冲突 + print(' [+] Find {}'.format(name)) + with open(os.path.join(self.POC_PATH, name), 'wb') as fp: + fp.write(info['input_data']) + else: + done = False + + # 记录reward + self.reward_history.append(reward) + + # 将input_data转化为用于NN的state格式 + state = [m for m in info['input_data']] + trail = [0] * (self.input_maxsize - len(state)) + state += trail + + assert len(state) == self.input_maxsize, '[!] len(state)={}, self.input_maxsize={}'.format(len(state), self.input_maxsize) + + return state, reward, done, {} + + def render(self, mode='human', close=False): + pass + + def eof(self): + return self._dict.eof() + + def dict_size(self): + return self._dict.size() + + def input_size(self): + return self.input_maxsize + + def get_poc_path(self): + return self.POC_PATH \ No newline at end of file diff --git a/rlfuzz/envs/fuzz_lava_m_env.py b/rlfuzz/envs/fuzz_lava_m_env.py new file mode 100644 index 0000000..e301b2c --- /dev/null +++ b/rlfuzz/envs/fuzz_lava_m_env.py @@ -0,0 +1,64 @@ +import rlfuzz +from rlfuzz.envs.fuzz_base_env import FuzzBaseEnv + +import os + +class FuzzBase64Env(FuzzBaseEnv): + def __init__(self): + self._target_path = rlfuzz.base64_target_path() + self._args = ['-d'] + self._seed = b'' # 指定初始变异的文件 + self._input_maxsize = 32 * 1024 # 最大输入文件的大小 + super(FuzzBase64Env, self).__init__() + + def set_seed(self, seed): + assert len(seed) > 0 + assert isinstance(seed, bytes) + self._seed = seed + self._input_maxsize = len(seed) + self.reset() + +class FuzzMd5sumEnv(FuzzBaseEnv): + def __init__(self): + self._target_path = rlfuzz.md5sum_target_path() + self._args = ['-c'] + self._seed = b'' # 指定初始变异的文件 + self._input_maxsize = 32 * 1024 # 最大输入文件的大小 + super(FuzzMd5sumEnv, self).__init__() + + def set_seed(self, seed): + assert len(seed) > 0 + assert isinstance(seed, bytes) + self._seed = seed + self._input_maxsize = len(seed) + self.reset() + +class FuzzUniqEnv(FuzzBaseEnv): + def __init__(self): + self._target_path = rlfuzz.uniq_target_path() + self._args = [] + self._seed = b'' # 指定初始变异的文件 + self._input_maxsize = 32 * 1024 # 最大输入文件的大小 + super(FuzzUniqEnv, self).__init__() + + def set_seed(self, seed): + assert len(seed) > 0 + assert isinstance(seed, bytes) + self._seed = seed + self._input_maxsize = len(seed) + self.reset() + +class FuzzWhoEnv(FuzzBaseEnv): + def __init__(self): + self._target_path = rlfuzz.who_target_path() + self._args = [] + self._seed = b'' # 指定初始变异的文件 + self._input_maxsize = 32 * 1024 # 最大输入文件的大小 + super(FuzzWhoEnv, self).__init__() + + def set_seed(self, seed): + assert len(seed) > 0 + assert isinstance(seed, bytes) + self._seed = seed + self._input_maxsize = len(seed) + self.reset() \ No newline at end of file diff --git a/rlfuzz/envs/fuzz_mutator.py b/rlfuzz/envs/fuzz_mutator.py new file mode 100644 index 0000000..2268ed4 --- /dev/null +++ b/rlfuzz/envs/fuzz_mutator.py @@ -0,0 +1,428 @@ +# 参考libfuzzer实现 +# https://zanderchang.github.io/2019/12/11/libfuzzer%E4%B8%AD%E7%9A%84%E5%8F%98%E5%BC%82%E7%AD%96%E7%95%A5%E5%88%86%E6%9E%90/#more + +from random import randint, shuffle +import struct + +def Rand(a): + if a == 0: + return 0 + else: + return randint(0, a-1) # [0, a-1] + +def isdigit(a): + return ord('0') <= a <= ord('9') + +class FuzzMutator(): + def __init__(self, maxSize, userDict=None): + self.maxSize = maxSize + self.userDict = userDict + self.funcMap = { + 0: self.Mutate_EraseBytes, + 1: self.Mutate_InsertByte, + 2: self.Mutate_InsertRepeatedBytes, + 3: self.Mutate_ChangeByte, + 4: self.Mutate_ChangeBit, + 5: self.Mutate_ShuffleBytes, + 6: self.Mutate_ChangeASCIIInteger, + 7: self.Mutate_ChangeBinaryInteger, + 8: self.Mutate_CopyPart, + # 9: self.Mutate_Random + # 9: self.Mutate_AddWordFromManualDictionary + } + self.methodNum = len(self.funcMap) + + def Mutate_Random(self, data): + idx = Rand(self.methodNum - 1) + return self.funcMap[idx](data) + + def Mutate_EraseBytes(self, data): + if len(data) > 0: + n = Rand(len(data) // 2) + 1 + s = Rand(len(data) - n + 1) + return data[0:s] + data[s+n:] + else: + return data + + def Mutate_InsertByte(self, data): + if len(data) >= self.maxSize: + return data + else: + b = Rand(256) + s = Rand(len(data) + 1) + l = list(data) + l.insert(s, b) + return bytes(l) + + def Mutate_InsertRepeatedBytes(self, data): + kMinBytesToInsert = 3 + if kMinBytesToInsert + len(data) >= self.maxSize: + return data + else: + MaxBytesToInsert = min(self.maxSize - len(data), 128) + repeatedTimes = kMinBytesToInsert + Rand(MaxBytesToInsert - kMinBytesToInsert + 1) + bs = [Rand(256)] * repeatedTimes + s = Rand(len(data) + 1) + l = list(data) + tmpl = l[0 : s] + bs + l[s :] + return bytes(tmpl) + + def Mutate_ChangeByte(self, data): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + b = Rand(256) + s = Rand(len(data)) + l = list(data) + l[s] = b + return bytes(l) + + def Mutate_ChangeBit(self, data): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + s = Rand(len(data)) + l = list(data) + l[s] ^= 1 << Rand(8) + return bytes(l) + + def Mutate_ShuffleBytes(self, data): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + ShuffleAmount = Rand(min(8, len(data))) + 1 + ShuffleStart = Rand(len(data) - ShuffleAmount) + l = list(data) + tmpl = l[ShuffleStart : ShuffleStart + ShuffleAmount] + shuffle(tmpl) + l[ShuffleStart : ShuffleStart + ShuffleAmount] = tmpl + return bytes(l) + + def Mutate_ChangeASCIIInteger(self, data): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + B = Rand(len(data)) + while (B < len(data) and not isdigit(data[B])): B += 1 + if B == len(data): + return data + else: + E = B + 1 + while (E < len(data) and isdigit(data[E])): E += 1 + l = list(data) + digitl = l[B:E] + val = int(bytes(digitl)) + + sw = Rand(5) + if sw == 0: val += 1 + elif sw == 1: val -= 1 + elif sw == 2: val //= 2 + elif sw == 3: val *= 2 + else: val = Rand(val*val) + + digitl = [0] * len(digitl) + de = len(digitl) - 1 + for v in str(val)[::-1]: + if de < 0: + break + digitl[de] = ord(v) + de -= 1 + + l[B:E] = digitl + return bytes(l) + + def Mutate_ChangeBinaryInteger(self, data): + nd = 2 ** (Rand(4)) # 1 2 4 8 + + if nd == 1: + fmt = 'B' + elif nd == 2: + fmt = 'H' + elif nd == 4: + fmt = 'I' + else: + fmt = 'Q' + + if len(data) < nd: + return data + else: + val = [] + Off = Rand(len(data) - nd + 1) + l = list(data) + if Off < 64 and not Rand(4): + size = len(data) % (1 << 8 * nd) + val = list(struct.pack('<' + fmt, size)) # x86小端序 + if Rand(1): + val = list(struct.pack('>' + fmt, size)) # 转为大端序 + else: + val = struct.unpack('<' + fmt, bytes(l[Off:Off + nd]))[0] + Add = Rand(21) - 10 + if Rand(1): + bval = struct.pack('>' + fmt, val) # 大端序pack,小端序unpack来实现__builtin_bswap + val += struct.unpack('<' + fmt, bval)[0] + val += Add + val = val % (1 << 8 * nd) + bval = struct.pack('>' + fmt, val) + val += struct.unpack('<' + fmt, bval)[0] + else: + val += Add + if Add == 0 or Rand(0): + val = -val + val = val % (1 << 8 * nd) + val = list(struct.pack('<' + fmt, val)) + + l[Off:Off + nd] = val + + return bytes(l) + + def Mutate_CopyPart(self, data): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + ToBeg = Rand(len(data)) + FromBeg = Rand(len(data)) + CopySize = Rand(min(len(data) - FromBeg, self.maxSize - len(data))) + l = list(data) + tmpl = l[FromBeg : FromBeg + CopySize] + if Rand(1): # 1 + l = l[0:ToBeg] + tmpl + l[ToBeg+CopySize:] + else: # 0 + l = l[0:ToBeg] + tmpl + l[ToBeg:] + return bytes(l) + + def Mutate_AddWordFromManualDictionary(self, data): + return data + + def mutate(self, idx, data): + if 0 <= idx < self.methodNum: + mutatorFunc = self.funcMap[idx] + return mutatorFunc(data)[:self.maxSize] + +""" +加入位置和强度信息 + loc + density +""" +class FuzzMutatorPlus(): + def __init__(self, maxSize, userDict=None): + self.maxSize = maxSize + self.userDict = userDict + self.funcMap = { + 0: self.Mutate_EraseBytes, + 1: self.Mutate_InsertByte, + 2: self.Mutate_InsertRepeatedBytes, + 3: self.Mutate_ChangeByte, + 4: self.Mutate_ChangeBit, + 5: self.Mutate_ShuffleBytes, + 6: self.Mutate_ChangeASCIIInteger, + 7: self.Mutate_ChangeBinaryInteger, + 8: self.Mutate_CopyPart, + # 9: self.Mutate_Random + # 9: self.Mutate_AddWordFromManualDictionary + } + self.methodNum = len(self.funcMap) + + def Mutate_Random(self, data, loc, density): + return self.funcMap[Rand(self.methodNum - 1)](data, loc, density) + + """ + density [0, len-loc] + """ + def Mutate_EraseBytes(self, data, loc, density): + if len(data) > 0: + return data[0:loc] + data[loc+density//4:] # [0,64] + else: + return data + + """ + loc [0, len] + density [0, 256] + """ + def Mutate_InsertByte(self, data, loc, density): + if len(data) >= self.maxSize: + return data + else: + l = list(data) + l.insert(loc, density) + return bytes(l) + + """ + loc [0, len] + density [0, 256] + """ + def Mutate_InsertRepeatedBytes(self, data, loc, density): + kMinBytesToInsert = 3 + if kMinBytesToInsert + len(data) >= self.maxSize: + return data + else: + MaxBytesToInsert = min(self.maxSize - len(data), 128) + repeatedTimes = kMinBytesToInsert + Rand(MaxBytesToInsert - kMinBytesToInsert + 1) + bs = [density] * repeatedTimes + l = list(data) + tmpl = l[0 : loc] + bs + l[loc :] + return bytes(tmpl) + + """ + loc [0, len] + density [0, 256] + """ + def Mutate_ChangeByte(self, data, loc, density): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + l = list(data) + l[loc] = density + return bytes(l) + + """ + loc [0, len] + density [0, 8] + """ + def Mutate_ChangeBit(self, data, loc, density): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + l = list(data) + l[loc] ^= 1 << (density % 8) + return bytes(l) + + """ + loc [0, len] + density [0, 8] + """ + def Mutate_ShuffleBytes(self, data, loc, density): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + ShuffleAmount = density % 8 + ShuffleStart = loc + l = list(data) + tmpl = l[ShuffleStart : ShuffleStart + ShuffleAmount] + shuffle(tmpl) + l[ShuffleStart : ShuffleStart + ShuffleAmount] = tmpl + return bytes(l) + + """ + loc [0, len] + density [0, 5] + """ + def Mutate_ChangeASCIIInteger(self, data, loc, density): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + B = loc + while (B < len(data) and not isdigit(data[B])): B += 1 + if B == len(data): + return data + else: + E = B + 1 + while (E < len(data) and isdigit(data[E])): E += 1 + l = list(data) + digitl = l[B:E] + val = int(bytes(digitl)) + + sw = density % 5 + if sw == 0: val += 1 + elif sw == 1: val -= 1 + elif sw == 2: val //= 2 + elif sw == 3: val *= 2 + else: val = Rand(val*val) + + digitl = [0] * len(digitl) + de = len(digitl) - 1 + for v in str(val)[::-1]: + if de < 0: + break + digitl[de] = ord(v) + de -= 1 + + l[B:E] = digitl + return bytes(l) + + """ + loc [0, len] + density [0, 3] + """ + def Mutate_ChangeBinaryInteger(self, data, loc, density): + nd = 2 ** (density % 4) # 1 2 4 8 + + if nd == 1: fmt = 'B' + elif nd == 2: fmt = 'H' + elif nd == 4: fmt = 'I' + else: fmt = 'Q' + + if len(data) < nd: + return data + else: + val = [] + Off = loc if loc < len(data) - nd else len(data) - nd # 确保取到nd个 + l = list(data) + if Off < 64 and not density % 4: + size = len(data) % (1 << 8 * nd) + val = list(struct.pack('<' + fmt, size)) # x86小端序 + if loc % 2: + val = list(struct.pack('>' + fmt, size)) # 转为大端序 + else: + val = struct.unpack('<' + fmt, bytes(l[Off:Off + nd]))[0] + Add = Rand(21) - 10 + if loc % 2: + bval = struct.pack('>' + fmt, val) # 大端序pack,小端序unpack来实现__builtin_bswap + val += struct.unpack('<' + fmt, bval)[0] + val += Add + val = val % (1 << 8 * nd) + bval = struct.pack('>' + fmt, val) + val += struct.unpack('<' + fmt, bval)[0] + else: + val += Add + if Add == 0: + val = -val + val = val % (1 << 8 * nd) + val = list(struct.pack('<' + fmt, val)) + + l[Off:Off + nd] = val + + return bytes(l) + + """ + loc [0, len] ToBeg + density [0, len] CopySize + """ + def Mutate_CopyPart(self, data, loc, density): + if len(data) > self.maxSize or len(data) == 0: + return data + else: + ToBeg = loc + FromBeg = Rand(len(data)) + CopySize = density + l = list(data) + tmpl = l[FromBeg : FromBeg + CopySize] + if density % 2: # 1 + l = l[0:ToBeg] + tmpl + l[ToBeg+CopySize:] + else: # 0 + l = l[0:ToBeg] + tmpl + l[ToBeg:] + return bytes(l) + + def Mutate_AddWordFromManualDictionary(self, data, loc, density): + return data + + def mutate(self, idx, data, loc, density): + loc, density = int(loc), int(density) + assert 0 <= idx < self.methodNum + if len(data) > 0: + loc %= len(data) + assert 0 <= loc < len(data) + assert 0 <= density <= 255 + mutatorFunc = self.funcMap[idx] + return mutatorFunc(data, loc, density)[:self.maxSize] + +if __name__ == "__main__": + mutator = FuzzMutatorPlus(128) + input_data = b'' + + from tqdm import tqdm + for i in tqdm(range(1024*1024*2)): + action = Rand(mutator.methodNum) + input_data = mutator.mutate(action, input_data, Rand(128), randint(1, 255)) + print('[{:>35}] {}'.format(mutator.funcMap[action].__func__.__name__, input_data)) + # if not i % 1024*512: + # print('[{:>35}] {}'.format(mutator.funcMap[action].__func__.__name__, input_data)) \ No newline at end of file diff --git a/rlfuzz/mods/Makefile b/rlfuzz/mods/Makefile new file mode 100644 index 0000000..a9e532b --- /dev/null +++ b/rlfuzz/mods/Makefile @@ -0,0 +1,77 @@ +# Builds afl altered by our mod for external fuzzing as well as all of our +# targets. + +NPROC = `nproc` +# 预下载数据所在目录 +DATAPATH=/tmp + +# AFL + +AFL_VERSION=2.52b +AFL_MOD=afl-$(AFL_VERSION)-mod +AFL_DIR=$(AFL_MOD)/afl-$(AFL_VERSION) + +ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +AFL_FUZZ=$(ROOT_DIR)/$(AFL_DIR)/afl-fuzz +AFL_GCC=$(ROOT_DIR)/$(AFL_DIR)/afl-gcc +AFL_GXX=$(ROOT_DIR)/$(AFL_DIR)/afl-g++ +CFLAGS=-fsanitize=address +CXXFLAGS=-fsanitize=address + +$(AFL_DIR): + @echo " **** Downloading afl and applying fuzz1ng mod" + cd $(AFL_MOD)/ && if [ ! -f afl-$(AFL_VERSION).tgz ]; then wget http://lcamtuf.coredump.cx/afl/releases/afl-$(AFL_VERSION).tgz; fi + cd $(AFL_MOD)/ && tar xf afl-$(AFL_VERSION).tgz + cp -R $(AFL_MOD)/mod/* $(AFL_DIR)/ + +$(AFL_DIR)/afl-fuzz: $(AFL_DIR) + @echo " **** Building afl with fuzz1ng mod" + cd $(AFL_DIR)/ && make -j$(NPROC) + + +# LAVA-M +LAVA_M_MOD = lava-m-mod +LAVA_M_DIR = $(LAVA_M_MOD)/lava_corpus +# LAVA_M_DIR = $(DATAPATH)/lava_corpus + +# wget http://panda.moyix.net/~moyix/lava_corpus.tar.xz +# 注意LAVA-M需要手动安装acllib1 +# sudo apt install libacl1 libacl1-dev +$(LAVA_M_DIR): + @echo " **** Downloading lava-m" + if [ ! -f $(DATAPATH)/lava_corpus.tar.xz ]; then wget http://panda.moyix.net/~moyix/lava_corpus.tar.xz -P $(DATAPATH); fi + cd $(LAVA_M_MOD) && if [ ! -f lava_corpus.tar.xz ]; then cp $(DATAPATH)/lava_corpus.tar.xz ./; fi + cd $(LAVA_M_MOD) && if [ ! -d lava_corpus ]; then tar xf lava_corpus.tar.xz; fi + +$(LAVA_M_MOD)/base64_afl: $(LAVA_M_DIR) + @echo " **** Building base64 with afl" + cd $(LAVA_M_DIR)/LAVA-M/base64/coreutils-8.24-lava-safe && ./configure CC="$(AFL_GCC)" LIBS="-lacl"; make -j$(NPROC) + cp $(LAVA_M_DIR)/LAVA-M/base64/coreutils-8.24-lava-safe/src/base64 $(LAVA_M_MOD)/base64_afl + +$(LAVA_M_MOD)/md5sum_afl: $(LAVA_M_DIR) + @echo " **** Building md5sum with afl" + cd $(LAVA_M_DIR)/LAVA-M/md5sum/coreutils-8.24-lava-safe && ./configure CC="$(AFL_GCC)" LIBS="-lacl"; make -j$(NPROC) + cp $(LAVA_M_DIR)/LAVA-M/md5sum/coreutils-8.24-lava-safe/src/md5sum $(LAVA_M_MOD)/md5sum_afl + +$(LAVA_M_MOD)/uniq_afl: $(LAVA_M_DIR) + @echo " **** Building uniq with afl" + cd $(LAVA_M_DIR)/LAVA-M/uniq/coreutils-8.24-lava-safe && ./configure CC="$(AFL_GCC)" LIBS="-lacl"; make -j$(NPROC) + cp $(LAVA_M_DIR)/LAVA-M/uniq/coreutils-8.24-lava-safe/src/uniq $(LAVA_M_MOD)/uniq_afl + +$(LAVA_M_MOD)/who_afl: $(LAVA_M_DIR) + @echo " **** Building who with afl" + cd $(LAVA_M_DIR)/LAVA-M/who/coreutils-8.24-lava-safe && ./configure CC="$(AFL_GCC)" LIBS="-lacl"; make -j$(NPROC) + cp $(LAVA_M_DIR)/LAVA-M/who/coreutils-8.24-lava-safe/src/who $(LAVA_M_MOD)/who_afl + +all: $(AFL_DIR)/afl-fuzz $(LAVA_M_MOD)/base64_afl $(LAVA_M_MOD)/md5sum_afl $(LAVA_M_MOD)/uniq_afl $(LAVA_M_MOD)/who_afl \ + +clean: + rm -rf $(AFL_DIR) +# rm -rf $(AFL_MOD)/afl-$(AFL_VERSION).tgz + rm -rf $(LAVA_M_MOD)/base64_afl + rm -rf $(LAVA_M_MOD)/md5sum_afl + rm -rf $(LAVA_M_MOD)/uniq_afl + rm -rf $(LAVA_M_MOD)/who_afl + rm -rf $(LAVA_M_DIR) +# rm -rf $(LAVA_M_MOD)/lava_corpus.tar.xz diff --git a/rlfuzz/mods/afl-2.52b-mod/.gitignore b/rlfuzz/mods/afl-2.52b-mod/.gitignore new file mode 100644 index 0000000..c6728e6 --- /dev/null +++ b/rlfuzz/mods/afl-2.52b-mod/.gitignore @@ -0,0 +1,2 @@ +afl-2.52b.tgz +afl-2.52b diff --git a/rlfuzz/mods/afl-2.52b-mod/mod/Makefile b/rlfuzz/mods/afl-2.52b-mod/mod/Makefile new file mode 100644 index 0000000..c938d7f --- /dev/null +++ b/rlfuzz/mods/afl-2.52b-mod/mod/Makefile @@ -0,0 +1,156 @@ +# +# american fuzzy lop - makefile +# ----------------------------- +# +# Written and maintained by Michal Zalewski +# +# Copyright 2013, 2014, 2015, 2016, 2017 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# + +PROGNAME = afl +VERSION = $(shell grep '^\#define VERSION ' config.h | cut -d '"' -f2) + +PREFIX ?= /usr/local +BIN_PATH = $(PREFIX)/bin +HELPER_PATH = $(PREFIX)/lib/afl +DOC_PATH = $(PREFIX)/share/doc/afl +MISC_PATH = $(PREFIX)/share/afl + +# PROGS intentionally omit afl-as, which gets installed elsewhere. + +PROGS = afl-gcc afl-fuzz afl-forkserver afl-showmap afl-tmin afl-gotcpu afl-analyze +SH_PROGS = afl-plot afl-cmin afl-whatsup + +CFLAGS ?= -O3 -funroll-loops +CFLAGS += -Wall -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \ + -DAFL_PATH=\"$(HELPER_PATH)\" -DDOC_PATH=\"$(DOC_PATH)\" \ + -DBIN_PATH=\"$(BIN_PATH)\" + +ifneq "$(filter Linux GNU%,$(shell uname))" "" + LDFLAGS += -ldl -lrt -lpthread +endif + +ifeq "$(findstring clang, $(shell $(CC) --version 2>/dev/null))" "" + TEST_CC = afl-gcc +else + TEST_CC = afl-clang +endif + +COMM_HDR = alloc-inl.h config.h debug.h types.h + +all: test_x86 $(PROGS) afl-as test_build all_done + +ifndef AFL_NO_X86 + +test_x86: + @echo "[*] Checking for the ability to compile x86 code..." + @echo 'main() { __asm__("xorb %al, %al"); }' | $(CC) -w -x c - -o .test || ( echo; echo "Oops, looks like your compiler can't generate x86 code."; echo; echo "Don't panic! You can use the LLVM or QEMU mode, but see docs/INSTALL first."; echo "(To ignore this error, set AFL_NO_X86=1 and try again.)"; echo; exit 1 ) + @rm -f .test + @echo "[+] Everything seems to be working, ready to compile." + +else + +test_x86: + @echo "[!] Note: skipping x86 compilation checks (AFL_NO_X86 set)." + +endif + +afl-gcc: afl-gcc.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + set -e; for i in afl-g++ afl-clang afl-clang++; do ln -sf afl-gcc $$i; done + +afl-as: afl-as.c afl-as.h $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + ln -sf afl-as as + +afl-fuzz: afl-fuzz.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +afl-forkserver: afl-forkserver.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +afl-showmap: afl-showmap.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +afl-tmin: afl-tmin.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +afl-analyze: afl-analyze.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +afl-gotcpu: afl-gotcpu.c $(COMM_HDR) | test_x86 + $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) + +ifndef AFL_NO_X86 + +test_build: afl-gcc afl-as afl-showmap + @echo "[*] Testing the CC wrapper and instrumentation output..." + unset AFL_USE_ASAN AFL_USE_MSAN; AFL_QUIET=1 AFL_INST_RATIO=100 AFL_PATH=. ./$(TEST_CC) $(CFLAGS) test-instr.c -o test-instr $(LDFLAGS) +# echo 0 | ./afl-showmap -m none -q -o .test-instr0 ./test-instr +# echo 1 | ./afl-showmap -m none -q -o .test-instr1 ./test-instr + @rm -f test-instr + @cmp -s .test-instr0 .test-instr1; DR="$$?"; rm -f .test-instr0 .test-instr1; if [ "$$DR" = "0" ]; then echo; echo "Oops, the instrumentation does not seem to be behaving correctly!"; echo; echo "Please ping to troubleshoot the issue."; echo; exit 1; fi + @echo "[+] All right, the instrumentation seems to be working!" + +else + +test_build: afl-gcc afl-as afl-showmap + @echo "[!] Note: skipping build tests (you may need to use LLVM or QEMU mode)." + +endif + +all_done: test_build + @if [ ! "`which clang 2>/dev/null`" = "" ]; then echo "[+] LLVM users: see llvm_mode/README.llvm for a faster alternative to afl-gcc."; fi + @echo "[+] All done! Be sure to review README - it's pretty short and useful." + @if [ "`uname`" = "Darwin" ]; then printf "\nWARNING: Fuzzing on MacOS X is slow because of the unusually high overhead of\nfork() on this OS. Consider using Linux or *BSD. You can also use VirtualBox\n(virtualbox.org) to put AFL inside a Linux or *BSD VM.\n\n"; fi + @! tty <&1 >/dev/null || printf "\033[0;30mNOTE: If you can read this, your terminal probably uses white background.\nThis will make the UI hard to read. See docs/status_screen.txt for advice.\033[0m\n" 2>/dev/null + +.NOTPARALLEL: clean + +clean: + rm -f $(PROGS) afl-as as afl-g++ afl-clang afl-clang++ *.o *~ a.out core core.[1-9][0-9]* *.stackdump test .test test-instr .test-instr0 .test-instr1 qemu_mode/qemu-2.10.0.tar.bz2 afl-qemu-trace + rm -rf out_dir qemu_mode/qemu-2.10.0 + $(MAKE) -C llvm_mode clean + $(MAKE) -C libdislocator clean + $(MAKE) -C libtokencap clean + +install: all + mkdir -p -m 755 $${DESTDIR}$(BIN_PATH) $${DESTDIR}$(HELPER_PATH) $${DESTDIR}$(DOC_PATH) $${DESTDIR}$(MISC_PATH) + rm -f $${DESTDIR}$(BIN_PATH)/afl-plot.sh + install -m 755 $(PROGS) $(SH_PROGS) $${DESTDIR}$(BIN_PATH) + rm -f $${DESTDIR}$(BIN_PATH)/afl-as + if [ -f afl-qemu-trace ]; then install -m 755 afl-qemu-trace $${DESTDIR}$(BIN_PATH); fi +ifndef AFL_TRACE_PC + if [ -f afl-clang-fast -a -f afl-llvm-pass.so -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-pass.so afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi +else + if [ -f afl-clang-fast -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi +endif + if [ -f afl-llvm-rt-32.o ]; then set -e; install -m 755 afl-llvm-rt-32.o $${DESTDIR}$(HELPER_PATH); fi + if [ -f afl-llvm-rt-64.o ]; then set -e; install -m 755 afl-llvm-rt-64.o $${DESTDIR}$(HELPER_PATH); fi + set -e; for i in afl-g++ afl-clang afl-clang++; do ln -sf afl-gcc $${DESTDIR}$(BIN_PATH)/$$i; done + install -m 755 afl-as $${DESTDIR}$(HELPER_PATH) + ln -sf afl-as $${DESTDIR}$(HELPER_PATH)/as + install -m 644 docs/README docs/ChangeLog docs/*.txt $${DESTDIR}$(DOC_PATH) + cp -r testcases/ $${DESTDIR}$(MISC_PATH) + cp -r dictionaries/ $${DESTDIR}$(MISC_PATH) + +publish: clean + test "`basename $$PWD`" = "afl" || exit 1 + test -f ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz; if [ "$$?" = "0" ]; then echo; echo "Change program version in config.h, mmkay?"; echo; exit 1; fi + cd ..; rm -rf $(PROGNAME)-$(VERSION); cp -pr $(PROGNAME) $(PROGNAME)-$(VERSION); \ + tar -cvz -f ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz $(PROGNAME)-$(VERSION) + chmod 644 ~/www/afl/releases/$(PROGNAME)-$(VERSION).tgz + ( cd ~/www/afl/releases/; ln -s -f $(PROGNAME)-$(VERSION).tgz $(PROGNAME)-latest.tgz ) + cat docs/README >~/www/afl/README.txt + cat docs/status_screen.txt >~/www/afl/status_screen.txt + cat docs/historical_notes.txt >~/www/afl/historical_notes.txt + cat docs/technical_details.txt >~/www/afl/technical_details.txt + cat docs/ChangeLog >~/www/afl/ChangeLog.txt + cat docs/QuickStartGuide.txt >~/www/afl/QuickStartGuide.txt + echo -n "$(VERSION)" >~/www/afl/version.txt diff --git a/rlfuzz/mods/afl-2.52b-mod/mod/afl-forkserver.c b/rlfuzz/mods/afl-2.52b-mod/mod/afl-forkserver.c new file mode 100644 index 0000000..5fa4e4c --- /dev/null +++ b/rlfuzz/mods/afl-2.52b-mod/mod/afl-forkserver.c @@ -0,0 +1,1995 @@ +/* + american fuzzy lop - forkserver interface + ----------------------------------------- + + Written and maintained by Michal Zalewski + + Forkserver design by Jann Horn + + Copyright 2013, 2014, 2015, 2016, 2017 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + The program wraps the forkserver for "external" fuzzing. It takes an + instrumented binary and exposes the forkserver through shared memory. + + */ + +#define AFL_MAIN + +#define _GNU_SOURCE +#define _FILE_OFFSET_BITS 64 + +#include "config.h" +#include "types.h" +#include "debug.h" +#include "alloc-inl.h" +#include "hash.h" +#include "external.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ + +/* For systems that have sched_setaffinity; right now just Linux, but one + can hope... */ + +#ifdef __linux__ +#define HAVE_AFFINITY 1 +#endif /* __linux__ */ + +/* A toggle to export some variables when building as a library. Not very + useful for the general public. */ + +#ifdef AFL_LIB +#define EXP_ST +#else +#define EXP_ST static +#endif /* ^AFL_LIB */ + +/* Lots of globals, but mostly for the status UI and other things where it + really makes no sense to haul them around as function parameters. */ + +EXP_ST u8 *out_file, /* File to fuzz, if any */ + *doc_path, /* Path to documentation dir */ + *target_path, /* Path to target binary */ + *orig_cmdline; /* Original command line */ + +EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* Configurable exec timeout (ms) */ +static u32 hang_tmout = EXEC_TIMEOUT; /* Timeout used for hang det (ms) */ + +EXP_ST u64 mem_limit = MEM_LIMIT; /* Memory cap for child (MB) */ + +EXP_ST u8 kill_signal, /* Signal that killed the child */ + timeout_given, /* Specific timeout given? */ + uses_asan, /* Target uses ASAN? */ + no_forkserver, /* Disable forkserver? */ + skip_requested, /* Skip request, via SIGUSR1 */ + persistent_mode, /* Running in persistent mode? */ + deferred_mode, /* Deferred forkserver mode? */ + fast_cal; /* Try to calibrate faster? */ + +static s32 out_fd, /* Persistent fd for out_file */ + dev_urandom_fd = -1, /* Persistent fd for /dev/urandom */ + dev_null_fd = -1, /* Persistent fd for /dev/null */ + fsrv_ctl_fd, /* Fork server control pipe (write) */ + fsrv_st_fd; /* Fork server status pipe (read) */ + +static s32 forksrv_pid, /* PID of the fork server */ + child_pid = -1, /* PID of the fuzzed program */ + out_dir_fd = -1; /* FD of the lock file */ + +EXP_ST u8 *trace_bits; /* SHM with instrumentation bitmap */ + +static s32 shm_id; /* ID of the SHM region */ + +static volatile u8 stop_soon, /* Ctrl-C pressed? */ + clear_screen = 1, /* Window resized? */ + child_timed_out; /* Traced process timed out? */ + +EXP_ST u64 total_execs, /* Total execve() calls */ + start_time; /* Unix start time (ms) */ + +static u32 subseq_tmouts; /* Number of timeouts in a row */ + +static s32 cpu_core_count; /* CPU core count */ + +#ifdef HAVE_AFFINITY + +static s32 cpu_aff = -1; /* Selected CPU core */ + +#endif /* HAVE_AFFINITY */ + +static u8 *shared_mem_ptr; /* SHM with external fuzzer */ +static sem_t *ping_sem, *pong_sem; /* Semaphores with external fuzzer */ + +/* Execution status fault codes */ + +enum +{ + /* 00 */ FAULT_NONE, + /* 01 */ FAULT_TMOUT, + /* 02 */ FAULT_CRASH, + /* 03 */ FAULT_ERROR, + /* 04 */ FAULT_NOINST, + /* 05 */ FAULT_NOBITS +}; + +u8 fault_save; /* Saves fault state after a run */ + +/* Handle timeout (SIGALRM). */ + +static void handle_timeout(int sig) +{ + + if (child_pid > 0) + { + + child_timed_out = 1; + kill(child_pid, SIGKILL); + } + else if (child_pid == -1 && forksrv_pid > 0) + { + + child_timed_out = 1; + kill(forksrv_pid, SIGKILL); + } +} + +/* Handle screen resize (SIGWINCH). */ + +static void handle_resize(int sig) +{ + clear_screen = 1; +} + +/* Handle skip request (SIGUSR1). */ + +static void handle_skipreq(int sig) +{ + + skip_requested = 1; +} + +/* Handle stop signal (Ctrl-C, etc). */ + +static void handle_stop_sig(int sig) +{ + stop_soon = 1; + + if (child_pid > 0) + kill(child_pid, SIGKILL); + if (forksrv_pid > 0) + kill(forksrv_pid, SIGKILL); + + // If we are waiting for a semaphore, we want to stop immediatly + if (sem_post(ping_sem)) + ; +} + +/* Set up signal handlers. More complicated that needs to be, because libc on + Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call + siginterrupt(), and does other stupid things. */ + +EXP_ST void setup_signal_handlers(void) +{ + struct sigaction sa; + + sa.sa_handler = NULL; + sa.sa_flags = SA_RESTART; + sa.sa_sigaction = NULL; + + sigemptyset(&sa.sa_mask); + + /* Various ways of saying "stop". */ + + sa.sa_handler = handle_stop_sig; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* Exec timeout notifications. */ + + sa.sa_handler = handle_timeout; + sigaction(SIGALRM, &sa, NULL); + + /* Window resize */ + + sa.sa_handler = handle_resize; + sigaction(SIGWINCH, &sa, NULL); + + /* SIGUSR1: skip entry */ + + sa.sa_handler = handle_skipreq; + sigaction(SIGUSR1, &sa, NULL); + + /* Things we don't care about. */ + + sa.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); +} + +/* Display usage hints. */ + +static void usage(u8 *argv0) +{ + + SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n" + + "Execution control settings:\n\n" + + " -f file - location read by the fuzzed program (stdin)\n" + " -t msec - timeout for each run (auto-scaled, 50-%u ms)\n" + " -m megs - memory limit for child process (%u MB)\n\n" + " -r rnst - random string for shared memory and signal name\n\n" + + "For additional tips, please consult %s/README.\n\n", + + argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); + + exit(1); +} + +/* Check ASAN options. */ + +static void check_asan_opts(void) +{ + u8 *x = getenv("ASAN_OPTIONS"); + + if (x) + { + if (!strstr(x, "abort_on_error=1")) + FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); + } + + x = getenv("MSAN_OPTIONS"); + + if (x) + { + if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) + FATAL("Custom MSAN_OPTIONS set without exit_code=" STRINGIFY(MSAN_ERROR) " - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); + } +} + +/* Make a copy of the current command line. */ + +static void save_cmdline(u32 argc, char **argv) +{ + + u32 len = 1, i; + u8 *buf; + + for (i = 0; i < argc; i++) + len += strlen(argv[i]) + 1; + + buf = orig_cmdline = ck_alloc(len); + + for (i = 0; i < argc; i++) + { + + u32 l = strlen(argv[i]); + + memcpy(buf, argv[i], l); + buf += l; + + if (i != argc - 1) + *(buf++) = ' '; + } + + *buf = 0; +} + +#ifdef HAVE_AFFINITY + +/* Build a list of processes bound to specific cores. Returns -1 if nothing + can be found. Assumes an upper bound of 4k CPUs. */ + +static void bind_to_free_cpu(void) +{ + + DIR *d; + struct dirent *de; + cpu_set_t c; + + u8 cpu_used[4096] = {0}; + u32 i; + + if (cpu_core_count < 2) + return; + + if (getenv("AFL_NO_AFFINITY")) + { + + WARNF("Not binding to a CPU core (AFL_NO_AFFINITY set)."); + return; + } + + d = opendir("/proc"); + + if (!d) + { + + WARNF("Unable to access /proc - can't scan for free CPU cores."); + return; + } + + ACTF("Checking CPU core loadout..."); + + /* Introduce some jitter, in case multiple AFL tasks are doing the same + thing at the same time... */ + + usleep(R(1000) * 250); + + /* Scan all /proc//status entries, checking for Cpus_allowed_list. + Flag all processes bound to a specific CPU using cpu_used[]. This will + fail for some exotic binding setups, but is likely good enough in almost + all real-world use cases. */ + + while ((de = readdir(d))) + { + + u8 *fn; + FILE *f; + u8 tmp[MAX_LINE]; + u8 has_vmsize = 0; + + if (!isdigit(de->d_name[0])) + continue; + + fn = alloc_printf("/proc/%s/status", de->d_name); + + if (!(f = fopen(fn, "r"))) + { + ck_free(fn); + continue; + } + + while (fgets(tmp, MAX_LINE, f)) + { + + u32 hval; + + /* Processes without VmSize are probably kernel tasks. */ + + if (!strncmp(tmp, "VmSize:\t", 8)) + has_vmsize = 1; + + if (!strncmp(tmp, "Cpus_allowed_list:\t", 19) && + !strchr(tmp, '-') && !strchr(tmp, ',') && + sscanf(tmp + 19, "%u", &hval) == 1 && hval < sizeof(cpu_used) && + has_vmsize) + { + + cpu_used[hval] = 1; + break; + } + } + + ck_free(fn); + fclose(f); + } + + closedir(d); + + for (i = 0; i < cpu_core_count; i++) + if (!cpu_used[i]) + break; + + if (i == cpu_core_count) + { + + SAYF("\n" cLRD "[-] " cRST + "Uh-oh, looks like all %u CPU cores on your system are allocated to\n" + " other instances of afl-fuzz (or similar CPU-locked tasks). Starting\n" + " another fuzzer on this machine is probably a bad plan, but if you are\n" + " absolutely sure, you can set AFL_NO_AFFINITY and try again.\n", + cpu_core_count); + + FATAL("No more free CPU cores"); + } + + OKF("Found a free CPU core, binding to #%u.", i); + + cpu_aff = i; + + CPU_ZERO(&c); + CPU_SET(i, &c); + + if (sched_setaffinity(0, sizeof(c), &c)) + PFATAL("sched_setaffinity failed"); +} + +#endif /* HAVE_AFFINITY */ + +/* Make sure that core dumps don't go to a program. */ + +static void check_crash_handling(void) +{ + +#ifdef __APPLE__ + + /* Yuck! There appears to be no simple C API to query for the state of + loaded daemons on MacOS X, and I'm a bit hesitant to do something + more sophisticated, such as disabling crash reporting via Mach ports, + until I get a box to test the code. So, for now, we check for crash + reporting the awful way. */ + + if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) + return; + + SAYF("\n" cLRD "[-] " cRST + "Whoops, your system is configured to forward crash notifications to an\n" + " external crash reporting utility. This will cause issues due to the\n" + " extended delay between the fuzzed binary malfunctioning and this fact\n" + " being relayed to the fuzzer via the standard waitpid() API.\n\n" + " To avoid having crashes misinterpreted as timeouts, please run the\n" + " following commands:\n\n" + + " SL=/System/Library; PL=com.apple.ReportCrash\n" + " launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n" + " sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Crash reporter detected"); + +#else + + /* This is Linux specific, but I don't think there's anything equivalent on + *BSD, so we can just let it slide for now. */ + + s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); + u8 fchar; + + if (fd < 0) + return; + + ACTF("Checking core_pattern..."); + + if (read(fd, &fchar, 1) == 1 && fchar == '|') + { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, your system is configured to send core dump notifications to an\n" + " external utility. This will cause issues: there will be an extended delay\n" + " between stumbling upon a crash and having this information relayed to the\n" + " fuzzer via the standard waitpid() API.\n\n" + + " To avoid having crashes misinterpreted as timeouts, please log in as root\n" + " and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n" + + " echo core >/proc/sys/kernel/core_pattern\n"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Pipe at the beginning of 'core_pattern'"); + } + + close(fd); + +#endif /* ^__APPLE__ */ +} + +/* Check CPU governor. */ + +static void check_cpu_governor(void) +{ + + FILE *f; + u8 tmp[128]; + u64 min = 0, max = 0; + + if (getenv("AFL_SKIP_CPUFREQ")) + return; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); + if (!f) + return; + + ACTF("Checking CPU scaling governor..."); + + if (!fgets(tmp, 128, f)) + PFATAL("fgets() failed"); + + fclose(f); + + if (!strncmp(tmp, "perf", 4)) + return; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r"); + + if (f) + { + if (fscanf(f, "%llu", &min) != 1) + min = 0; + fclose(f); + } + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r"); + + if (f) + { + if (fscanf(f, "%llu", &max) != 1) + max = 0; + fclose(f); + } + + if (min == max) + return; + + SAYF("\n" cLRD "[-] " cRST + "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n" + " between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n" + " kernel is imperfect and can miss the short-lived processes spawned by\n" + " afl-fuzz. To keep things moving, run these commands as root:\n\n" + + " cd /sys/devices/system/cpu\n" + " echo performance | tee cpu*/cpufreq/scaling_governor\n\n" + + " You can later go back to the original state by replacing 'performance' with\n" + " 'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n" + " to make afl-fuzz skip this check - but expect some performance drop.\n", + min / 1024, max / 1024); + + FATAL("Suboptimal CPU scaling governor"); +} + +/* Get the number of runnable processes, with some simple smoothing. */ + +static double get_runnable_processes(void) +{ + + static double res; + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + /* I don't see any portable sysctl or so that would quickly give us the + number of runnable processes; the 1-minute load average can be a + semi-decent approximation, though. */ + + if (getloadavg(&res, 1) != 1) + return 0; + +#else + + /* On Linux, /proc/stat is probably the best way; load averages are + computed in funny ways and sometimes don't reflect extremely short-lived + processes well. */ + + FILE *f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + u32 val = 0; + + if (!f) + return 0; + + while (fgets(tmp, sizeof(tmp), f)) + { + + if (!strncmp(tmp, "procs_running ", 14) || + !strncmp(tmp, "procs_blocked ", 14)) + val += atoi(tmp + 14); + } + + fclose(f); + + if (!res) + { + + res = val; + } + else + { + + res = res * (1.0 - 1.0 / AVG_SMOOTHING) + + ((double)val) * (1.0 / AVG_SMOOTHING); + } + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + return res; +} + +/* Count the number of logical CPU cores. */ + +static void get_core_count(void) +{ + + u32 cur_runnable = 0; + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + size_t s = sizeof(cpu_core_count); + + /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */ + +#ifdef __APPLE__ + + if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0) + return; + +#else + + int s_name[2] = {CTL_HW, HW_NCPU}; + + if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) + return; + +#endif /* ^__APPLE__ */ + +#else + +#ifdef HAVE_AFFINITY + + cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); + +#else + + FILE *f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + + if (!f) + return; + + while (fgets(tmp, sizeof(tmp), f)) + if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) + cpu_core_count++; + + fclose(f); + +#endif /* ^HAVE_AFFINITY */ + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + if (cpu_core_count > 0) + { + + cur_runnable = (u32)get_runnable_processes(); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + /* Add ourselves, since the 1-minute average doesn't include that yet. */ + + cur_runnable++; + +#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ + + OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).", + cpu_core_count, cpu_core_count > 1 ? "s" : "", + cur_runnable, cur_runnable * 100.0 / cpu_core_count); + + if (cpu_core_count > 1) + { + if (cur_runnable > cpu_core_count * 1.5) + { + WARNF("System under apparent load, performance may be spotty."); + } + else if (cur_runnable + 1 <= cpu_core_count) + { + OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path); + } + } + } + else + { + cpu_core_count = 0; + WARNF("Unable to figure out the number of CPU cores."); + } +} + +/* Get rid of shared memory (atexit handler). */ + +static void remove_shm(void) +{ + shmctl(shm_id, IPC_RMID, NULL); +} + +/* Configure shared memory and virgin_bits. This is called at startup. */ + +EXP_ST void setup_shm(void) +{ + u8 *shm_str; + + shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); + + if (shm_id < 0) + PFATAL("shmget() failed"); + + atexit(remove_shm); + + shm_str = alloc_printf("%d", shm_id); + + setenv(SHM_ENV_VAR, shm_str, 1); + + ck_free(shm_str); + + trace_bits = shmat(shm_id, NULL, 0); // 把共享内存区对象映射到调用进程的地址空间 + + if (!trace_bits) + PFATAL("shmat() failed"); +} + +/* Get unix time in milliseconds */ + +static u64 get_cur_time(void) +{ + + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + + return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000); +} + +#define CHK_FORMAT(_divisor, _limit_mult, _fmt, _cast) \ + do \ + { \ + if (val < (_divisor) * (_limit_mult)) \ + { \ + sprintf(tmp[cur], _fmt, ((_cast)val) / (_divisor)); \ + return tmp[cur]; \ + } \ + } while (0) + +/* Describe integer as memory size. */ + +static u8 *DMS(u64 val) +{ + + static u8 tmp[12][16]; + static u8 cur; + + cur = (cur + 1) % 12; + + /* 0-9999 */ + CHK_FORMAT(1, 10000, "%llu B", u64); + + /* 10.0k - 99.9k */ + CHK_FORMAT(1024, 99.95, "%0.01f kB", double); + + /* 100k - 999k */ + CHK_FORMAT(1024, 1000, "%llu kB", u64); + + /* 1.00M - 9.99M */ + CHK_FORMAT(1024 * 1024, 9.995, "%0.02f MB", double); + + /* 10.0M - 99.9M */ + CHK_FORMAT(1024 * 1024, 99.95, "%0.01f MB", double); + + /* 100M - 999M */ + CHK_FORMAT(1024 * 1024, 1000, "%llu MB", u64); + + /* 1.00G - 9.99G */ + CHK_FORMAT(1024LL * 1024 * 1024, 9.995, "%0.02f GB", double); + + /* 10.0G - 99.9G */ + CHK_FORMAT(1024LL * 1024 * 1024, 99.95, "%0.01f GB", double); + + /* 100G - 999G */ + CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64); + + /* 1.00T - 9.99G */ + CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double); + + /* 10.0T - 99.9T */ + CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 99.95, "%0.01f TB", double); + +#undef CHK_FORMAT + + /* 100T+ */ + strcpy(tmp[cur], "infty"); + return tmp[cur]; +} + +/* Spin up fork server (instrumented mode only). The idea is explained here: + + http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html + + In essence, the instrumentation allows us to skip execve(), and just keep + cloning a stopped child. So, we just execute once, and then send commands + through a pipe. The other part of this logic is in afl-as.h. */ + +EXP_ST void init_forkserver(char **argv) +{ + static struct itimerval it; + int st_pipe[2], ctl_pipe[2]; // [0]读 [1]写 + int status; + s32 rlen; + + ACTF("Spinning up the fork server..."); + + if (pipe(st_pipe) || pipe(ctl_pipe)) + PFATAL("pipe() failed"); + + forksrv_pid = fork(); + + if (forksrv_pid < 0) + PFATAL("fork() failed"); + + if (!forksrv_pid) // 子进程 + { + + struct rlimit r; + + /* Umpf. On OpenBSD, the default fd limit for root users is set to + soft 128. Let's try to fix that... */ + + if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) + { + + r.rlim_cur = FORKSRV_FD + 2; + setrlimit(RLIMIT_NOFILE, &r); /* Ignore errors */ + } + + if (mem_limit) + { + + r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; + +#ifdef RLIMIT_AS + + setrlimit(RLIMIT_AS, &r); /* Ignore errors */ + +#else + + /* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but + according to reliable sources, RLIMIT_DATA covers anonymous + maps - so we should be getting good protection against OOM bugs. */ + + setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ + +#endif /* ^RLIMIT_AS */ + } + + /* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered + before the dump is complete. */ + + r.rlim_max = r.rlim_cur = 0; + + setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + + /* Isolate the process and configure standard descriptors. If out_file is + specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */ + + setsid(); + + //dup2(dev_null_fd, 1); + dup2(dev_null_fd, 2); + + if (out_file) + { + dup2(dev_null_fd, 0); + } + else + { + dup2(out_fd, 0); + close(out_fd); + } + + /* Set up control and status pipes, close the unneeded original fds. */ + + if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) + PFATAL("dup2() failed"); + if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) // write + PFATAL("dup2() failed"); + + close(ctl_pipe[0]); + close(ctl_pipe[1]); + close(st_pipe[0]); + close(st_pipe[1]); + + close(out_dir_fd); + close(dev_null_fd); + close(dev_urandom_fd); + + /* This should improve performance a bit, since it stops the linker from + doing extra work post-fork(). */ + + if (!getenv("LD_BIND_LAZY")) + setenv("LD_BIND_NOW", "1", 0); + + /* Set sane defaults for ASAN if nothing else specified. */ + + setenv("ASAN_OPTIONS", "abort_on_error=1:" + "detect_leaks=0:" + "symbolize=0:" + "allocator_may_return_null=1", + 0); + + /* MSAN is tricky, because it doesn't support abort_on_error=1 at this + point. So, we do this in a very hacky way. */ + + setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" + "symbolize=0:" + "abort_on_error=1:" + "allocator_may_return_null=1:" + "msan_track_origins=0", + 0); + + execv(target_path, argv); + + /* Use a distinctive bitmap signature to tell the parent about execv() + falling through. */ + + *(u32 *)trace_bits = EXEC_FAIL_SIG; + exit(0); + } + + /* Close the unneeded endpoints. */ + + close(ctl_pipe[0]); + close(st_pipe[1]); + + fsrv_ctl_fd = ctl_pipe[1]; + fsrv_st_fd = st_pipe[0]; // read + + /* Wait for the fork server to come up, but don't wait too long. */ + + it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000); + it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000; + + setitimer(ITIMER_REAL, &it, NULL); + + rlen = read(fsrv_st_fd, &status, 4); + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + + setitimer(ITIMER_REAL, &it, NULL); + + /* If we have a four-byte "hello" message from the server, we're all set. + Otherwise, try to figure out what went wrong. */ + + if (rlen == 4) + { + OKF("All right - fork server is up."); + return; + } + + if (child_timed_out) + FATAL("Timeout while initializing fork server (adjusting -t may help)"); + + if (waitpid(forksrv_pid, &status, 0) <= 0) // 阻塞,等待子进程运行结束 + PFATAL("waitpid() failed"); + + if (WIFSIGNALED(status)) + { + + if (mem_limit && mem_limit < 500 && uses_asan) + { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! Since it seems to be built with ASAN and you have a\n" + " restrictive memory limit configured, this is expected; please read\n" + " %s/notes_for_asan.txt for help.\n", + doc_path); + } + else if (!mem_limit) + { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! There are several probable explanations:\n\n" + + " - The binary is just buggy and explodes entirely on its own. If so, you\n" + " need to fix the underlying problem or find a better replacement.\n\n" + +#ifdef __APPLE__ + + " - On MacOS X, the semantics of fork() syscalls are non-standard and may\n" + " break afl-fuzz performance optimizations when running platform-specific\n" + " targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n" + +#endif /* __APPLE__ */ + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n"); + } + else + { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! There are several probable explanations:\n\n" + + " - The current memory limit (%s) is too restrictive, causing the\n" + " target to hit an OOM condition in the dynamic linker. Try bumping up\n" + " the limit with the -m setting in the command line. A simple way confirm\n" + " this diagnosis would be:\n\n" + +#ifdef RLIMIT_AS + " ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#else + " ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#endif /* ^RLIMIT_AS */ + + " Tip: you can use http://jwilk.net/software/recidivm to quickly\n" + " estimate the required amount of virtual memory for the binary.\n\n" + + " - The binary is just buggy and explodes entirely on its own. If so, you\n" + " need to fix the underlying problem or find a better replacement.\n\n" + +#ifdef __APPLE__ + + " - On MacOS X, the semantics of fork() syscalls are non-standard and may\n" + " break afl-fuzz performance optimizations when running platform-specific\n" + " targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n" + +#endif /* __APPLE__ */ + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n", + DMS(mem_limit << 20), mem_limit - 1); + } + + FATAL("Fork server crashed with signal %d", WTERMSIG(status)); + } + + if (*(u32 *)trace_bits == EXEC_FAIL_SIG) + FATAL("Unable to execute target application ('%s')", argv[0]); + + if (mem_limit && mem_limit < 500 && uses_asan) + { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. Since it seems to be built with ASAN and\n" + " you have a restrictive memory limit configured, this is expected; please\n" + " read %s/notes_for_asan.txt for help.\n", + doc_path); + } + else if (!mem_limit) + { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. Perhaps there is a horrible bug in the\n" + " fuzzer. Poke for troubleshooting tips.\n"); + } + else + { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. There are %s probable explanations:\n\n" + + "%s" + " - The current memory limit (%s) is too restrictive, causing an OOM\n" + " fault in the dynamic linker. This can be fixed with the -m option. A\n" + " simple way to confirm the diagnosis may be:\n\n" + +#ifdef RLIMIT_AS + " ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#else + " ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#endif /* ^RLIMIT_AS */ + + " Tip: you can use http://jwilk.net/software/recidivm to quickly\n" + " estimate the required amount of virtual memory for the binary.\n\n" + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n", + getenv(DEFER_ENV_VAR) ? "three" : "two", + getenv(DEFER_ENV_VAR) ? " - You are using deferred forkserver, but __AFL_INIT() is never\n" + " reached before the program terminates.\n\n" + : "", + DMS(mem_limit << 20), mem_limit - 1); + } + + FATAL("Fork server handshake failed"); +} + +/* Execute target application, monitoring for timeouts. Return status + information. The called program will update trace_bits[]. */ + +static u8 run_target(char **argv, u32 timeout) +{ + static struct itimerval it; + static u32 prev_timed_out = 0; + + int status = 0; + u32 tb4; + + child_timed_out = 0; + + /* After this memset, trace_bits[] are effectively volatile, so we + must prevent any earlier operations from venturing into that + territory. */ + + memset(trace_bits, 0, MAP_SIZE); + MEM_BARRIER(); + + /* If we're running in "dumb" mode, we can't rely on the fork server + logic compiled into the target program, so we will just keep calling + execve(). There is a bit of code duplication between here and + init_forkserver(), but c'est la vie. */ + + if (no_forkserver) + { + child_pid = fork(); + + if (child_pid < 0) + PFATAL("fork() failed"); + + if (!child_pid) // child process + { + struct rlimit r; + + if (mem_limit) + { + r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; + +#ifdef RLIMIT_AS + + setrlimit(RLIMIT_AS, &r); /* Ignore errors */ + +#else + + setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ + +#endif /* ^RLIMIT_AS */ + } + + r.rlim_max = r.rlim_cur = 0; + + setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + + /* Isolate the process and configure standard descriptors. If out_file is + specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */ + + setsid(); + + dup2(dev_null_fd, 1); + dup2(dev_null_fd, 2); + + if (out_file) // + { + dup2(dev_null_fd, 0); + } + else + { + dup2(out_fd, 0); + close(out_fd); + } + + /* On Linux, would be faster to use O_CLOEXEC. Maybe TODO. */ + + close(dev_null_fd); + close(out_dir_fd); + close(dev_urandom_fd); + + /* Set sane defaults for ASAN if nothing else specified. */ + + setenv("ASAN_OPTIONS", "abort_on_error=1:" + "detect_leaks=0:" + "symbolize=0:" + "allocator_may_return_null=1", + 0); + + setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" + "symbolize=0:" + "msan_track_origins=0", + 0); + + execv(target_path, argv); + + /* Use a distinctive bitmap value to tell the parent about execv() falling through. */ + + *(u32 *)trace_bits = EXEC_FAIL_SIG; + exit(0); + } + } + else + { + s32 res; + + /* In non-dumb mode, we have the fork server up and running, so simply tell it to have at it, and then read back PID. */ + + if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) + { + if (stop_soon) + return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + } + + if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) + { + if (stop_soon) + return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + } + + if (child_pid <= 0) + FATAL("Fork server is misbehaving (OOM?)"); + } + + /* Configure timeout, as requested by user, then wait for child to terminate. */ + + it.it_value.tv_sec = (timeout / 1000); + it.it_value.tv_usec = (timeout % 1000) * 1000; + + setitimer(ITIMER_REAL, &it, NULL); + + /* The SIGALRM handler simply kills the child_pid and sets child_timed_out. */ + + if (no_forkserver) + { + if (waitpid(child_pid, &status, 0) <= 0) // 阻塞,等待子进程 + PFATAL("waitpid() failed"); + } + else + { + s32 res; + + if ((res = read(fsrv_st_fd, &status, 4)) != 4) + { + if (stop_soon) + return 0; + RPFATAL(res, "Unable to communicate with fork server (OOM?)"); + } + } + + if (!WIFSTOPPED(status)) + child_pid = 0; + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + + setitimer(ITIMER_REAL, &it, NULL); + + total_execs++; + + /* Any subsequent operations on trace_bits must not be moved by the + compiler below this point. Past this location, trace_bits[] behave + very normally and do not have to be treated as volatile. */ + + MEM_BARRIER(); + + tb4 = *(u32 *)trace_bits; + + prev_timed_out = child_timed_out; + + /* Report outcome to caller. */ + + if (WIFSIGNALED(status) && !stop_soon) // 子进程因为一个未捕获的信号而终止 + { + + kill_signal = WTERMSIG(status); + + if (child_timed_out && kill_signal == SIGKILL) // 获得导致子进程终止的信号代码 + return FAULT_TMOUT; + + return FAULT_CRASH; + } + + /* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and + must use a special exit code. */ + + if (uses_asan && WEXITSTATUS(status) == MSAN_ERROR) // 取得子进程exit()返回的结束代码 + { + kill_signal = 0; + return FAULT_CRASH; + } + + if (no_forkserver && tb4 == EXEC_FAIL_SIG) + return FAULT_ERROR; + + return FAULT_NONE; +} + +/* Write modified data to file for testing. If out_file is set, the old file + is unlinked and a new one is created. Otherwise, out_fd is rewound and + truncated. */ + +static void write_to_testcase(void *mem, u32 len) +{ + + s32 fd = out_fd; + + if (out_file) + { + + unlink(out_file); /* Ignore errors. */ + + fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + + if (fd < 0) + PFATAL("Unable to create '%s'", out_file); + } + else + lseek(fd, 0, SEEK_SET); + + ck_write(fd, mem, len, out_file); + + if (!out_file) + { + + if (ftruncate(fd, len)) + PFATAL("ftruncate() failed"); + lseek(fd, 0, SEEK_SET); + } + else + close(fd); +} + +/* Write a modified test case, run program, process results. Handle + error conditions, returning 1 if it's time to bail out. This is + a helper function for fuzz_one(). */ + +EXP_ST u8 common_fuzz_stuff(char **argv, u8 *out_buf, u32 len) +{ + + u8 fault; + + write_to_testcase(out_buf, len); // write out_buf -> out_file(-f) + + fault = run_target(argv, exec_tmout); + + // external_mode + fault_save = fault; + + if (stop_soon) + return 1; + + if (fault == FAULT_TMOUT) + { + if (subseq_tmouts++ > TMOUT_LIMIT) + { + return 1; + } + } + else + subseq_tmouts = 0; + + /* Users can hit us with SIGUSR1 to request the current input + to be abandoned. */ + + if (skip_requested) + { + skip_requested = 0; + return 1; + } + + return 0; +} + +/* Destructively classify execution counts in a trace. This is used as a + preprocessing step for any newly acquired traces. Called on every exec, + must be fast. */ + +static const u8 count_class_lookup8[256] = { + + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 4, + [4 ... 7] = 8, + [8 ... 15] = 16, + [16 ... 31] = 32, + [32 ... 127] = 64, + [128 ... 255] = 128 + +}; + +static u16 count_class_lookup16[65536]; + +EXP_ST void init_count_class16(void) +{ + + u32 b1, b2; + + for (b1 = 0; b1 < 256; b1++) + for (b2 = 0; b2 < 256; b2++) + count_class_lookup16[(b1 << 8) + b2] = + (count_class_lookup8[b1] << 8) | + count_class_lookup8[b2]; +} + +/* Detect @@ in args. */ + +EXP_ST void detect_file_args(char **argv) +{ + + u32 i = 0; + u8 *cwd = getcwd(NULL, 0); + + if (!cwd) + PFATAL("getcwd() failed"); + + while (argv[i]) + { + + u8 *aa_loc = strstr(argv[i], "@@"); + + if (aa_loc) + { + + u8 *aa_subst, *n_arg; + + /* If we don't have a file name chosen yet, use a safe default. */ + + if (!out_file) + out_file = alloc_printf("./afl_cur_input-%d", getpid()); + + /* Be sure that we're always using fully-qualified paths. */ + + if (out_file[0] == '/') + aa_subst = out_file; + else + aa_subst = alloc_printf("%s/%s", cwd, out_file); + + /* Construct a replacement argv value. */ + + *aa_loc = 0; + n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); + argv[i] = n_arg; + *aa_loc = '@'; + + if (out_file[0] != '/') + ck_free(aa_subst); + } + + i++; + } + + free(cwd); /* not tracked */ +} + +/* Do a PATH search and find target binary to see that it exists and + isn't a shell script - a common and painful mistake. We also check for + a valid ELF header and for evidence of AFL instrumentation. */ + +EXP_ST void check_binary(u8 *fname) +{ + u8 *env_path = 0; + struct stat st; + + s32 fd; + u8 *f_data; + u32 f_len = 0; + + ACTF("Validating target binary..."); + + if (strchr(fname, '/') || !(env_path = getenv("PATH"))) + { + target_path = ck_strdup(fname); + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || + !(st.st_mode & 0111) || (f_len = st.st_size) < 4) + FATAL("Program '%s' not found or not executable", fname); + } + else + { + while (env_path) + { + u8 *cur_elem, *delim = strchr(env_path, ':'); + + if (delim) + { + cur_elem = ck_alloc(delim - env_path + 1); + memcpy(cur_elem, env_path, delim - env_path); + delim++; + } + else + cur_elem = ck_strdup(env_path); + + env_path = delim; + + if (cur_elem[0]) + target_path = alloc_printf("%s/%s", cur_elem, fname); + else + target_path = ck_strdup(fname); + + ck_free(cur_elem); + + if (!stat(target_path, &st) && S_ISREG(st.st_mode) && + (st.st_mode & 0111) && (f_len = st.st_size) >= 4) + break; + + ck_free(target_path); + target_path = 0; + } + + if (!target_path) + FATAL("Program '%s' not found or not executable", fname); + } + + if (getenv("AFL_SKIP_BIN_CHECK")) + return; + + /* Check for blatant user errors. */ + + if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) || + (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/'))) + FATAL("Please don't keep binaries in /tmp or /var/tmp"); + + fd = open(target_path, O_RDONLY); + + if (fd < 0) + PFATAL("Unable to open '%s'", target_path); + + f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); + + if (f_data == MAP_FAILED) + PFATAL("Unable to mmap file '%s'", target_path); + + close(fd); + + if (f_data[0] == '#' && f_data[1] == '!') + { + + SAYF("\n" cLRD "[-] " cRST + "Oops, the target binary looks like a shell script. Some build systems will\n" + " sometimes generate shell stubs for dynamically linked programs; try static\n" + " library mode (./configure --disable-shared) if that's the case.\n\n" + + " Another possible cause is that you are actually trying to use a shell\n" + " wrapper around the fuzzed component. Invoking shell can slow down the\n" + " fuzzing process by a factor of 20x or more; it's best to write the wrapper\n" + " in a compiled language instead.\n"); + + FATAL("Program '%s' is a shell script", target_path); + } + +#ifndef __APPLE__ + + if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3)) + FATAL("Program '%s' is not an ELF binary", target_path); + +#else + + if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED) + FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path); + +#endif /* ^!__APPLE__ */ + + if (!memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) + { + + SAYF("\n" cLRD "[-] " cRST + "Looks like the target binary is not instrumented! The fuzzer depends on\n" + " compile-time instrumentation to isolate interesting test cases while\n" + " mutating the input data. For more information, and for tips on how to\n" + " instrument binaries, please see %s/README.\n\n" + + " When source code is not available, you may be able to leverage QEMU\n" + " mode support. Consult the README for tips on how to enable this.\n" + + " (It is also possible to use afl-fuzz as a traditional, \"dumb\" fuzzer.\n" + " For that, you can use the -n option - but expect much worse results.)\n", + doc_path); + + FATAL("No instrumentation detected"); + } + + if (memmem(f_data, f_len, "libasan.so", 10) || + memmem(f_data, f_len, "__msan_init", 11)) + uses_asan = 1; + + /* Detect persistent & deferred init signatures in the binary. */ + + if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) + { + + OKF(cPIN "Persistent mode binary detected."); + setenv(PERSIST_ENV_VAR, "1", 1); + persistent_mode = 1; + } + else if (getenv("AFL_PERSISTENT")) + { + + WARNF("AFL_PERSISTENT is no longer supported and may misbehave!"); + } + + if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) + { + + OKF(cPIN "Deferred forkserver binary detected."); + setenv(DEFER_ENV_VAR, "1", 1); + deferred_mode = 1; + } + else if (getenv("AFL_DEFER_FORKSRV")) + { + + WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!"); + } + + if (munmap(f_data, f_len)) + PFATAL("unmap() failed"); +} + +#ifndef AFL_LIB + +/* Inits 2 semaphores to talk with the external fuzzer + a shm region to + exchange PING and PONG messages. */ + +int init_external() +{ + int sval = 0; + int fd_shm = -1; + + // Unlink shared memory and smeaphores + shm_unlink(SHARED_MEM_NAME); + sem_unlink(SEM_PING_SIGNAL_NAME); + sem_unlink(SEM_PONG_SIGNAL_NAME); + + // Get shared memory + if ((fd_shm = shm_open(SHARED_MEM_NAME, O_RDWR | O_CREAT, 0660)) == -1) + FATAL("Could not open shm: %s", SHARED_MEM_NAME); + + if (ftruncate(fd_shm, SHM_SIZE) == -1) + FATAL("Could not ftruncate shm"); + + if ((shared_mem_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) + FATAL("Could not mmap shm"); + + // Initialize the shared memory + memset(shared_mem_ptr, 0, SHM_SIZE); + + // signal semaphore, indicating that a ping message is available + if ((ping_sem = sem_open(SEM_PING_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED) + FATAL("Could not create semaphore %s", SEM_PING_SIGNAL_NAME); + + // signal semaphore, indicating that a pong message is available + if ((pong_sem = sem_open(SEM_PONG_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED) + FATAL("Could not create semaphore %s", SEM_PONG_SIGNAL_NAME); + + // Wait until external fuzzer joined + for (; !sval && !stop_soon; usleep(100000)) + { + if (sem_getvalue(ping_sem, &sval) == -1) + FATAL("Failed to wait on ping semaphore"); + } + + if (stop_soon) + return 0; + + return 1; +} + +/* Un-map the shared memory and unlink the shared memory and semaphore files + before exiting. */ + +void close_external() +{ + munmap(shared_mem_ptr, SHM_SIZE); + + // Unlink shared memory and smeaphores + shm_unlink(SHARED_MEM_NAME); + sem_unlink(SEM_PING_SIGNAL_NAME); + sem_unlink(SEM_PONG_SIGNAL_NAME); +} + +/* Main entry point */ + +int main(int argc, char **argv) +{ + s32 opt; + u8 mem_limit_given = 0; + char **use_argv; + + struct timeval tv; + struct timezone tz; + + SAYF(cCYA "afl-forkserver " cBRI VERSION cRST " by \n"); + + doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; + + gettimeofday(&tv, &tz); + srandom(tv.tv_sec ^ tv.tv_usec ^ getpid()); + + while ((opt = getopt(argc, argv, "+m:t:f:r:")) > 0) + + switch (opt) + { + + case 't': + { /* timeout */ + + u8 suffix = 0; + + if (timeout_given) + FATAL("Multiple -t options not supported"); + + if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 || + optarg[0] == '-') + FATAL("Bad syntax used for -t"); + + if (exec_tmout < 5) + FATAL("Dangerously low value of -t"); + + if (suffix == '+') + timeout_given = 2; + else + timeout_given = 1; + + break; + } + + case 'm': + { /* mem limit */ + + u8 suffix = 'M'; + + if (mem_limit_given) + FATAL("Multiple -m options not supported"); + mem_limit_given = 1; + + if (!strcmp(optarg, "none")) + { + mem_limit = 0; + break; + } + + if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 || + optarg[0] == '-') + FATAL("Bad syntax used for -m"); + + switch (suffix) + { + + case 'T': + mem_limit *= 1024 * 1024; + break; + case 'G': + mem_limit *= 1024; + break; + case 'k': + mem_limit /= 1024; + break; + case 'M': + break; + + default: + FATAL("Unsupported suffix or bad syntax for -m"); + } + + if (mem_limit < 5) + FATAL("Dangerously low value of -m"); + + if (sizeof(rlim_t) == 4 && mem_limit > 2000) + FATAL("Value of -m out of range on 32-bit systems"); + } + + break; + + case 'f': /* target file */ + + if (out_file) + FATAL("Multiple -f options not supported"); + out_file = optarg; + break; + + case 'r' : /* random string */ + { + SEM_PING_SIGNAL_NAME = alloc_printf("%s%s", SEM_PING_SIGNAL_NAME_HEAD, optarg); + SEM_PONG_SIGNAL_NAME = alloc_printf("%s%s", SEM_PONG_SIGNAL_NAME_HEAD, optarg); + SHARED_MEM_NAME = alloc_printf("%s%s", SHARED_MEM_NAME_HEAD, optarg); + } + + break; + + default: + + usage(argv[0]); + } + + if (optind == argc) + usage(argv[0]); + + ACTF("optind %d argv[optind] %s", optind, argv[optind]); + + setup_signal_handlers(); + check_asan_opts(); + + if (getenv("AFL_NO_FORKSRV")) + no_forkserver = 1; + if (getenv("AFL_FAST_CAL")) + fast_cal = 1; + + if (getenv("AFL_HANG_TMOUT")) + { + hang_tmout = atoi(getenv("AFL_HANG_TMOUT")); + if (!hang_tmout) + FATAL("Invalid value of AFL_HANG_TMOUT"); + } + + if (getenv("AFL_PRELOAD")) + { + setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); + setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); + } + + if (getenv("AFL_LD_PRELOAD")) + FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD"); + + save_cmdline(argc, argv); + + get_core_count(); + +#ifdef HAVE_AFFINITY + bind_to_free_cpu(); +#endif /* HAVE_AFFINITY */ + + check_crash_handling(); + check_cpu_governor(); + + setup_shm(); // 设置afl-forkserver和target共享内存,IPC_PRIVATE,设置trace_bits + init_count_class16(); + + dev_null_fd = open("/dev/null", O_RDWR); + if (dev_null_fd < 0) + PFATAL("Unable to open /dev/null"); + + dev_urandom_fd = open("/dev/urandom", O_RDONLY); + if (dev_urandom_fd < 0) + PFATAL("Unable to open /dev/urandom"); + + detect_file_args(argv + optind + 1); // 目标程序参数,将argv中的@@替换为-f + check_binary(argv[optind]); // 目标程序 + start_time = get_cur_time(); + + use_argv = argv + optind; // 运行目标程序和参数 + + // 输出目标程序及其参数 + int ii = 0; + ACTF("%s", target_path); + while (use_argv[ii]) + { + ACTF("[%d] %s", ii, use_argv[ii]); + ++ii; + } + + ACTF("Waiting for external fuzzer to join..."); + if (init_external()) // 设置与的py共享内存SHARED_MEM_NAME + { + OKF("External fuzzer joined"); + } + + if (stop_soon) + goto stop_fuzzing; + + OKF("All set and ready to roll!"); + + /* Woop woop woop */ + + if (!no_forkserver && !forksrv_pid) + init_forkserver(use_argv); // 运行目标程序 + + sleep(4); + start_time += 4000; + + while (1) + { + printf("."); + fflush(stdout); + + // Wait until ping is ready from client with input for us + if (sem_wait(ping_sem) == -1) + FATAL("Failed to wait on ping semaphore"); + + if (stop_soon) + break; + + // Decoding PING message + PING_MSG_HDR *ping_msg_hdr = (PING_MSG_HDR *)shared_mem_ptr; + u8 *input = (u8 *)&shared_mem_ptr[sizeof(PING_MSG_HDR)]; + PONG_MSG pong_msg; + + // Sanity check + if (ping_msg_hdr->inputsize > MAX_INPUT_SIZE) + { + FATAL("Overflow in received ping message"); + } + + common_fuzz_stuff(use_argv, (u8 *)input, ping_msg_hdr->inputsize); // 将input写入文件并以其为参数运行目标程序 + + /* + Computes returned status + 00 FAULT_NONE, + 01 FAULT_TMOUT, + 02 FAULT_CRASH, + 03 FAULT_ERROR, + 04 FAULT_NOINST, + 05 FAULT_NOBITS + */ + s32 status = 0; + switch (fault_save) + { + case FAULT_NONE: + break; + + case FAULT_CRASH: + status |= STATUS_CRASHED; + break; + case FAULT_TMOUT: + status |= STATUS_HANGED; + break; + + default: + status |= STATUS_ERROR; + break; + } + + // Craft a pong response + pong_msg.msgid = ping_msg_hdr->msgid; + pong_msg.status = status; + + // 获取afl运行结果 + memcpy(pong_msg.trace_bits, trace_bits, MAP_SIZE); + // memcpy(&pong_msg.trace_bits[0], trace_bits, MAP_SIZE); + // u32 i = 0; + // u32 j = 0; + + // memset(&(pong_msg.trace_bits), 0, 3 * MAP_SIZE); + + // for (i = 0; i < MAP_SIZE; i++) // 1 << 16 + // { + // if (trace_bits[i] != 0) + // { + // pong_msg.trace_bits[3 * j + 0] = (u8)(i % (1 << 8)); // prev + // pong_msg.trace_bits[3 * j + 1] = (u8)(i / (1 << 8)); // cur + // pong_msg.trace_bits[3 * j + 2] = trace_bits[i]; + // j++; + // } + // } + + // copies to shared_mem + memset(shared_mem_ptr, 0, SHM_SIZE); + memcpy(shared_mem_ptr, &pong_msg, sizeof pong_msg); + + // Tell client that there is a buffer to read + if (sem_post(pong_sem) == -1) + FATAL("Failed to release pong semaphore"); + } + +stop_fuzzing: + close_external(); + + SAYF(CURSOR_SHOW cLRD "\n\n+++ Testing aborted %s +++\n" cRST, + stop_soon == 2 ? "programmatically" : "by user"); + + ck_free(target_path); + alloc_report(); + + OKF("We're done here. Have a nice day!\n"); + + exit(0); +} + +#endif /* !AFL_LIB */ diff --git a/rlfuzz/mods/afl-2.52b-mod/mod/external.h b/rlfuzz/mods/afl-2.52b-mod/mod/external.h new file mode 100644 index 0000000..7cd5374 --- /dev/null +++ b/rlfuzz/mods/afl-2.52b-mod/mod/external.h @@ -0,0 +1,46 @@ +/* + * This has to do with external fuzzer link + * + */ +#ifndef _HAVE_EXTERNAL_H +#define _HAVE_EXTERNAL_H + +#include "types.h" +#include "config.h" + +// 最大输入文件大小 +#define MAX_INPUT_SIZE (1 << 16) // 64KB + +#define SEM_PING_SIGNAL_NAME_HEAD "/afl-ping-signal" +#define SEM_PONG_SIGNAL_NAME_HEAD "/afl-pong-signal" +#define SHARED_MEM_NAME_HEAD "/afl-shared-mem" + +u8 *SEM_PING_SIGNAL_NAME; +u8 *SEM_PONG_SIGNAL_NAME; +u8 *SHARED_MEM_NAME; + +#pragma pack(1) +typedef struct ping_msg_hdr +{ + u32 msgid; + u32 inputsize; +} PING_MSG_HDR; + +typedef struct pong_msg +{ + u32 msgid; + u32 status; + u8 trace_bits[MAP_SIZE]; // 1 << 16 +} PONG_MSG; + +#pragma pack() + +#define SHM_SIZE MAX(sizeof(PONG_MSG), MAX_INPUT_SIZE + sizeof(PING_MSG_HDR)) + +// defines for pong_msg_hdr->status +#define STATUS_CRASHED 0x80000000 +#define STATUS_HANGED 0x40000000 +#define STATUS_ERROR 0x20000000 +#define STATUS_OK 0 + +#endif diff --git a/rlfuzz/mods/lava-m-mod/.gitignore b/rlfuzz/mods/lava-m-mod/.gitignore new file mode 100644 index 0000000..7d66b5e --- /dev/null +++ b/rlfuzz/mods/lava-m-mod/.gitignore @@ -0,0 +1,6 @@ +lava_corpus.tar.xz +lava_corpus +base64_afl +md5sum_afl +uniq_afl +who_afl \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1c6373c --- /dev/null +++ b/setup.py @@ -0,0 +1,46 @@ +import subprocess +import sys + +from setuptools import setup +from distutils.command.build import build as DistutilsBuild + + +class Build(DistutilsBuild): + def run(self): + try: + subprocess.check_call(['make', 'all'], cwd='rlfuzz/mods') + except subprocess.CalledProcessError as e: + sys.stderr.write("Could not build mods: %s.\n" % e) + raise + DistutilsBuild.run(self) + + +setup( + name='rlfuzz', + version='1.0.0', + platforms='Posix', + install_requires=[ + 'gym>=0.10.3', + 'xxhash>=1.0.1', + 'posix_ipc>=1.0.3', + ], + author='adbq, spolu, zheng', + package_data={ + 'rlfuzz.mods': [ + 'afl-2.52b-mod/afl-2.52b/afl-forkserver', + 'lava-m-mod/base64_afl', + 'lava-m-mod/md5sum_afl', + 'lava-m-mod/uniq_afl', + 'lava-m-mod/who_afl', + ], + 'rlfuzz.envs': ['config.ini'] + }, + packages=[ + 'rlfuzz', + 'rlfuzz.coverage', + 'rlfuzz.mods', + 'rlfuzz.envs', + ], + cmdclass={'build': Build}, + include_package_data=True +)