源码: jfoote/exploitable: The ‘exploitable’ GDB plugin (github.com)
example usage:
(gdb) source exploitable.py
(gdb) exploitable
class ExploitableCommand(gdb.Command):
'''
判断当前状态的可利用程度,将结果打印到stdout 或者 提取到文件中
'''
_cmdstr = "exploitable"
_re_gdb_pc = re.compile(r"^=>.*$")
def __init__(self):
gdb.Command.__init__(self, self._cmdstr, gdb.COMMAND_OBSCURE)
def print_disassembly(self):
pass
def invoke(self, argstr, from_tty):
'''输入命令的时候被调用'''
check_version()#检查
op = NiceArgParser(prog=self._cmdstr, description=self.__doc__)
op.add_argument("-v", "--verbose", action="store_true",
help="打印分析信息")
op.add_argument("-m", "--machine", action="store_true",
help="以机器可解析的格式打印输出")
op.add_argument("-p", "--pkl-file", type=argparse.FileType("wb"),
help="打包可利用性分类对象并存储到PKL_FILE")
op.add_argument("-a", "--asan-log", type=argparse.FileType(),
help="符号化并分析AddressSanitizer的输出")
op.add_argument("-b", "--backtrace-limit", type=int,
help="将回溯中的堆栈帧数限制为所提供的值"
"0 代表不限制.", default=1000)
try:
args = op.parse_args(gdb.string_to_argv(argstr))
except NiceArgParserExit:
return
import logging
try:
target = arch.getTarget(args.asan_log, args.backtrace_limit) #获得目标
c = classifier.Classifier().getClassification(target) #分类
except Exception as e:
logging.exception(e)
raise e
if args.pkl_file: #如果打包则进行打包
import pickle as pickle
pickle.dump(c, args.pkl_file, 2)
return
if args.verbose: #打印分析信息
print("'exploitable' version {}".format(version)) #版本信息
print(" ".join([str(i) for i in os.uname()]))
print("Signal si_signo: {} Signal si_addr: {}".format(target.si_signo(), target.si_addr())) #信号编码及信号地址
print("Nearby code:")
self.print_disassembly() #打印汇编
print("Stack trace:")
print(str(target.backtrace())) #栈回溯
print("Faulting frame: {}".format(target.faulting_frame())) #
if args.machine:
print_machine_string(c, target)
else:
gdb.write(str(c))
gdb.flush()
ExploitableCommand()
其框架很简单,看到其重要为其中有颜色的两个步骤1. 获得目标 2.分类器分类 。
arch.getTarget()
获取目标getTarget()
函数def getTarget(asan_log_file=None, bt_limit=0):
''' 返回当前的Target,它是一个Python包装器,代表底层Linux GDB Inferior对象的当前状态。
'''
# 获得操作系统信息
osabi = Target._re_gdb_osabi.search(str(gdb.execute("show osabi", False,
True))).group(1)
# 尝试用python的API判断架构
arch = gdb.selected_frame().architecture().name()
# 反馈解析
if arch == None:
arch_str = str(gdb.execute("show architecture", False, True))
# 如果我们在gdbmultiarch中设置了arch,它就是 "假定的"
if "assumed" in arch_str:
arch = arch_str
else:
arch = Target._re_gdb_arch.search(arch_str).group(1)
# 根据参数和操作系统信息实例化一个目标。
# ASAN + i386 + *
if asan_log_file and arch.startswith("i386"):
target = ASanTarget(asan_log_file.read(), bt_limit)
target.analyzer = ASanAnalyzer(target)
return target
# ASAN + ARM + QNX
elif asan_log_file and arch.lower()[:3] == "arm" and osabi == "QNX Neutrino":
target = QnxASanTarget(asan_log_file.read(), bt_limit)
target.analyzer = ASanAnalyzer(target)
return target
# ASAN + ARM + *
elif asan_log_file and arch.lower()[:3] == "arm":
target = ArmASanTarget(asan_log_file.read(), bt_limit)
target.analyzer = ArmASanAnalyzer(target)
return target
# * + ARM + QNX
elif arch.lower()[:3] == "arm" and osabi == "QNX Neutrino":
target = QnxTarget(bt_limit)
target.analyzer = ArmAnalyzer(target)
return target
# * + i386 + *
elif arch.startswith("i386"):
target = x86Target(bt_limit)
target.analyzer = Analyzer(target)
return target
# * + ARM + *
elif arch.lower()[:3] == "arm":
target = ArmTarget(bt_limit)
target.analyzer = ArmAnalyzer(target)
return target
elif "mips" in arch.lower():
target = MipsTarget(bt_limit)
target.analyzer = MipsAnalyzer(target)
return target
else:
raise NotImplementedError("no support for arch=%s and osabi=%s" % (arch, osabi))
return Target(bt_limit)
总结起来,这这个函数完成了以下步骤
target类型比较多,我们其中的X86Target来分析以下
X86Target()
class x86Target(Target):
'''
A wrapper for an x86 Linux GDB Inferior.
'''
def __init__(self, bt_limit=0):
Target.__init__(self, bt_limit)
self._check_inferior_state()
gdb.execute("set disassembly-flavor intel", False, True) #将汇编设置为intel格式
而target的结构如下
class Target(object):
def __init__(self, bt_limit=0):
self._check_inferior_state()
self.bt_limit = bt_limit
def _check_inferior_state(self):
@memoized
def backtrace(self):
def hash(self):
@memoized
def procmaps(self):
@memoized
def faulting_frame(self):
@staticmethod
def sym_addr(sym):
@memoized
def current_instruction(self):
def _getInstruction(self, gdbstr):
@memoized
def pc(self):
@memoized
def stack_pointer(self):
@memoized
def pid(self):
@memoized
def pointer_size(self):
@memoized
def si_signo(self):
@memoized
def si_addr(self):
classifier.Classifier().getClassification(target)
分类这部分就是exploitable插件的核心部分,即根据gdb的状态来判断是否可利用及类型。
Classfifier()
分类器class Classifier(object):
_major_hash_depth = 5
def getRules(self, target):
'''用于分类的规则(dicts)的嵌套列表 在 rules.py 中指定的规则被组织成 AttrDicts ("规则")。
每个规则由
一个标签
一个 match_function
组成。
'''
processed_rules = []
num_rules = sum(len(rl) for (_, rl) in rules.rules)
ranking = 1
for cat, user_rule_list in rules.rules:
for user_rule in user_rule_list:
match_function = partial(getattr(target.analyzer, user_rule["match_function"]))
tag_data = copy.deepcopy(user_rule)
del tag_data["match_function"]
tag_data["ranking"] = (ranking, num_rules)
tag_data["category"] = cat
rule = AttrDict(matches=match_function, tag=Tag(tag_data))
processed_rules.append(rule)
ranking += 1
return processed_rules
def getClassification(self, target):
'''
返回目标的可利用性分类
'''
c = Classification(target) #获得分类器
# 规则匹配
for rule in self.getRules(target):
try:
match = rule.matches()
# 如果匹配成功,则打上标签
if match:
c += rule.tag
except Exception as e:
warnings.warn("Error while analyzing rule {}: {}\n{}".format(
rule.tag, e, traceback.format_exc()))
c.hash = target.hash() #计算hash
return c
Classification
分类器查看Classification类的代码
class Classification(AttrDict):
''' 描述可利用程序
'''
def __init__(self, target):
AttrDict.__init__(self)
self.tags = []
def __add__(self, tag):
if not issubclass(type(tag), Tag):
raise TypeError("cannot add type {} to type {}".format(type(tag), type(self)))
self.tags.append(tag)
self.tags.sort()
for k, v in self.tags[0].__dict__.items():
self[k] = v
return self
# for python3
def __lt__(self, other):
def __cmp__(self, other):
def __str__(self):
可以看到其继承了AttrDict
,然后重载了一些方法。
class AttrDict(dict):
def __getattribute__(self, name):
try:
return dict.__getattribute__(self, name)
except AttributeError:
if name in self:
return self[name]
raise
def __setattr__(self, name, value):
dict.__setattr__(self, name, value)
try:
del self[name]
except KeyError:
pass
而AttrDict
继承于字典,则Classification()
就是一个关于tag的字典。
rule.rule()
判断规则在rule.py
中使用字典记录了各种漏洞类型及漏洞可利用性规则,进行总结分类后如下:
A. exploitable可利用
标示 | 描述 | 判断函数 | 判断逻辑 (x86为例) |
---|---|---|---|
ReturnAv | 目标在一条返回指令 上崩溃,这可能表明堆栈损坏 | isReturnAv |
1. 信号为 SIGSEGV , SIGBUS 之一2. 有当前指令且指令的助记符为 iret ,ret 之一 |
UseAfterFree | 目标试图访问 一个位于已经被释放的堆缓冲区内的地址。 | isUseAfterFree |
1. ASAN 的原因为heap-use-after-free 2. 没有ASAN直接返回假 |
SegFaultOnPc | 目标试图在一个与程序计数器相匹配的地址上访问数据 。 | isSegFaultOnPcNotNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 段错误的地址等于PC的值 |
BranchAv | 目标在一条分支指令 上崩溃了,这可能表明控制流被污染了 | isBranchAvNotNearNull |
1. 是分支地址(ja ,jae 等等或者call ,callq )崩溃2. 错误地址不 接近 NULL |
StackCodeExecution | 目标在进程的堆栈区域内执行代码 时因错误而停止 | isErrorWhileExecutingFromStack |
1. 不 是良性的信号(SIGTERM 等)2. PC指在stack中 |
StackBufferOverflow | 由于检测到堆栈缓冲区溢出 ,目标在处理由libc 产生的信号时停止 | isStackBufferOverflow |
1. 栈回溯中包含__fortify_fail ,__stack_chk_fail ,__GI___fortify_fail ,__stack_chk_fail |
PossibleStackCorruption | GDB在解开堆栈 时产生了一个错误,并且/或者堆栈中包含的返回地址没有映射到下级的进程地址空间中,并且/或者堆栈指针指向默认堆栈区域之外的位置。也就是堆栈可能遭到损坏。 | isPossibleStackCorruption |
1. 不 是良性的信号(SIGTERM 等)2. 不 是栈溢出 3. 是异常终止 ,且方向追踪中名字为空 4. 反向追踪没有映射区域 5. 没有栈指针或者栈指针的不指向栈 |
DestAv | 目标在一个与指令目的操作数 相匹配的地址上因访问违规而崩溃。这很可能表明是写访问违规, | isDestAvNotNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 错误地址为 dest 地址3. 错误地址不 接近 NULL |
BadInstruction | 目标试图执行一条畸形的或有特权 的指令 | isMalformedInstructionSignal |
1. 信号为"SIGILL , SIGSYS 之一 |
HeapError | 目标的回溯显示libc检测到一个堆错误 ,或者目标在停止时正在执行一个堆函数 | isHeapError |
1. 栈回溯中包含abort ,__libc_message ,等函数 |
B. probably exploitable 可能可利用
标示 | 描述 | 判断函数 | 判断逻辑 (x86为例) |
---|---|---|---|
StackOverflow | 目标因访问违规 而崩溃,其中故障指令的助记符和堆栈指针似乎表明堆栈溢出 | isStackOverflow |
1. 信号为 SIGSEGV , SIGBUS 之一2. 由 push 或call 指令,且错误指令指向目标地址3. 堆栈指针在默认的堆栈区域之外 |
SegFaultOnPcNearNull | 类似SegFaultOnPc,但是地址接近NULL | isSegFaultOnPcNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 错误地址为 dest 地址3. 错误地址接近 NULL |
BranchAvNearNull | 类似BranchAv,但是地址接近NULL | isBranchAvNearNull |
1. 是分支地址(ja ,jae 等等或者call ,callq )崩溃2. 错误地址接近 NULL |
BlockMoveAv | 目标在块移动 过程中崩溃,这可能表明攻击者可以控制缓冲区溢出。 | isBlockMove |
1. 不 是良性的信号(SIGTERM 等)2. 指令匹配 *mov |
isDestAvNearNull | 类似DestAv,但是地址接近NULL | isDestAvNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 错误地址为 dest 地址3. 错误地址接近 NULL |
C. probably not exploitable 可能不可利用
标示 | 描述 | 判断函数 | 判断逻辑 (x86为例) |
---|---|---|---|
SourceAvNearNull | 目标在一个与当前指令的源操作数相匹配的地址上,因访问违规而崩溃.这可能意味着应用程序在对数据结构进行简单的NULL解除引用时崩溃,对处理器的控制没有直接影响。 | isSourceAvNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 错误地址指向源地址 3. 地址接近NULL |
FloatingPointException | 目标在一个浮点异常 上崩溃了 | isFloatingPointException |
1. 信号为SIGFPE |
BenignSignal | 目标在没有显示错误或显示一般认为不可利用的错误的信号下被停止 | isBenignSignal |
1. 信号是良性的信号(SIGTERM 等) |
D. unkown 未知
标示 | 描述 | 判断函数 | 判断逻辑 (x86为例) |
---|---|---|---|
SourceAv | 目标在一个与当前指令的源操作数相匹配的地址上,因访问违规而崩溃 | isSourceAvNotNearNull |
1. 信号为 SIGSEGV , SIGBUS 之一2. 错误地址指向源地址 3. 地址不接近NULL |
AbortSignal | 目标在SIGABRT中被停止 | isAbortSignal |
1. 信号是SIGABRT |
AccessViolation | 目标由于访问违规而崩溃,但没有足够的额外信息来确定可利用性。 | isAccessViolationSignal |
1. 信号为 SIGSEGV , SIGBUS 之一 |
UncategorizedSignal | 未分类的信号 | isUncategorizedSignal |
1. 没有分类的信号 |
exploitable
是一个GDB中判断当前状态的分类 及可利用性 判断的一个工具。它的基本原理是根据信号量,ASAN信息,指令,栈回溯等信息去使用规则 来判断。