├── LICENSE ├── README.md └── linux-kernel └── fgkaslr_gadgets.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kyle Zeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pwning-toolset 2 | Personal pwning toolset developed/used by myself. Use at your own risk. 3 | -------------------------------------------------------------------------------- /linux-kernel/fgkaslr_gadgets.py: -------------------------------------------------------------------------------- 1 | import re 2 | from elftools.elf.elffile import ELFFile 3 | 4 | from pwn import * 5 | 6 | KERNEL_ELF = None 7 | elf = None 8 | gadgets = [] 9 | functions = [] 10 | holes = [] 11 | 12 | class Gadget: 13 | def __init__(self, addr, assembly, raw_bytes): 14 | self.addr = addr 15 | self.assembly = assembly 16 | self.raw_bytes = raw_bytes 17 | self.start_addr = addr 18 | self.end_addr = addr + len(raw_bytes) 19 | 20 | def is_overlap(self, hole): 21 | if self.end_addr <= hole[0]: 22 | return False 23 | if self.start_addr >= hole[1]: 24 | return False 25 | return True 26 | 27 | class Function: 28 | def __init__(self, name, addr, size): 29 | self.name = name 30 | self.addr = addr 31 | self.size = size 32 | self.start_addr = addr 33 | self.end_addr = addr + size 34 | 35 | def get_func_info(): 36 | log.info("Trying to collect position-variant function information...") 37 | for x in elf.sections: 38 | if not x.name.startswith(".text."): 39 | continue 40 | func = Function(x.name[6:], x.header.sh_addr, len(x.data())) 41 | functions.append(func) 42 | log.success("%d position-variant functions collected!", len(functions)) 43 | 44 | def get_all_gadgets(): 45 | """ 46 | search for all gadgets in the kernel .text section 47 | translate virtual address and file offset back and forth so that fking ROPgadget won't eat up the memory of my computer 48 | """ 49 | log.info("Collecting all gadgets...") 50 | #MIN_ADDR = 0xffffffff81000000 51 | #MAX_ADDR = 0xffffffff81c00000 52 | with open(KERNEL_ELF, "rb") as f: 53 | elffile = ELFFile(f) 54 | text = elffile.get_section_by_name(".text") 55 | TEXT_START = text.header['sh_addr'] 56 | TEXT_END = TEXT_START + text.header['sh_size'] 57 | print("start: %#x" % TEXT_START) 58 | print("end : %#x" % TEXT_END) 59 | min_phys_off = elf.vaddr_to_offset(TEXT_START) 60 | max_phys_off = elf.vaddr_to_offset(TEXT_END) 61 | 62 | cmd = "ROPgadget --binary %s --rawArch=x86 --rawMode=64 --range %#x-%#x --dump --all" % (KERNEL_ELF, min_phys_off, max_phys_off) 63 | output = subprocess.getoutput(cmd) 64 | 65 | log.info("Parsing all gadgets...") 66 | for line in output.splitlines(): 67 | if not line.startswith("0x"): 68 | continue 69 | 70 | # parse each gadget entry 71 | res = re.match('(0x[0-f]+) : (.+) // (.*)', line) 72 | assert res is not None 73 | 74 | addr = elf.offset_to_vaddr(int(res.group(1), 16)) 75 | assembly = res.group(2) 76 | raw_bytes = bytes.fromhex(res.group(3)) 77 | 78 | # create each gadget object 79 | gadget = Gadget(addr, assembly, raw_bytes) 80 | gadgets.append(gadget) 81 | log.success("%d gadgets collected!", len(gadgets)) 82 | 83 | def filter_gadgets(): 84 | global gadgets 85 | log.info("Filtering gadgets that overlap with position-variant functions...") 86 | raw_gadgets = gadgets 87 | gadgets = [] 88 | for idx, gadget in enumerate(raw_gadgets): 89 | for hole in holes: 90 | if gadget.is_overlap(hole): 91 | break 92 | else: 93 | gadgets.append(gadget) 94 | log.success("%d position-invariant gadgets collected!", len(gadgets)) 95 | 96 | def clean_gadgets(): 97 | # de-duplicate gadgets 98 | seen = set() 99 | new_gadgets = [] 100 | for gadget in gadgets: 101 | if gadget.assembly in seen: 102 | continue 103 | new_gadgets.append(gadget) 104 | seen.add(gadget.assembly) 105 | 106 | # sort gadgets 107 | new_gadgets.sort(key = lambda x: x.assembly) 108 | log.success("%d unique position-invariant gadgets collected!", len(new_gadgets)) 109 | return new_gadgets 110 | 111 | def show_gadgets(): 112 | for gadget in gadgets: 113 | line = "%#x : %s" % (gadget.addr, gadget.assembly) 114 | print(line) 115 | 116 | def merge_holes(): 117 | log.info("Trying to reduce search complexity by merging holes...") 118 | global holes 119 | tmp_holes = [] 120 | for idx, func in enumerate(functions): 121 | 122 | # determine the start of next function 123 | if idx != len(functions) - 1: 124 | next_start_addr = functions[idx+1].start_addr 125 | else: 126 | next_start_addr = func.end_addr 127 | 128 | # check whether the function padding is interesting, if not, we include the padding 129 | # in the hole to reduce search complexity 130 | hole_start = func.start_addr 131 | func_pad_len = next_start_addr-func.end_addr 132 | func_padding = elf.read(func.end_addr, func_pad_len) 133 | if func_padding == b'\x00' * func_pad_len: 134 | hole_end = next_start_addr 135 | else: 136 | hole_end = func.end_addr 137 | 138 | tmp_holes.append((hole_start, hole_end)) 139 | 140 | # merge holes to reduce search complexity 141 | while True: 142 | for idx in range(len(tmp_holes)-1): 143 | hole = tmp_holes[idx] 144 | next_hole = tmp_holes[idx+1] 145 | if hole[1] != next_hole[0]: 146 | continue 147 | new_hole = (hole[0], next_hole[1]) 148 | tmp_holes[idx+1] = new_hole 149 | tmp_holes.remove(hole) 150 | break 151 | else: 152 | break 153 | 154 | holes = tmp_holes 155 | print(holes) 156 | log.success("%d holes detected!", len(holes)) 157 | 158 | if __name__ == "__main__": 159 | import argparse 160 | 161 | import monkeyhex 162 | parser = argparse.ArgumentParser(description='Script to find position-invariant gadgets in Linux kernels compiled with FG-KASLR', 163 | usage="%(prog)s [options] ") 164 | parser.add_argument('vmlinux_path', type=str, 165 | help="path to vmlinux") 166 | args = parser.parse_args() 167 | 168 | KERNEL_ELF = args.vmlinux_path 169 | elf = ELF(args.vmlinux_path) 170 | 171 | get_func_info() 172 | merge_holes() 173 | get_all_gadgets() 174 | filter_gadgets() 175 | gadgets = clean_gadgets() 176 | show_gadgets() 177 | --------------------------------------------------------------------------------