├── GoUtils ├── Firstmoduledata.py ├── GoStrings.py ├── Gopclntab.py ├── PatchNewVersion.py ├── Types.py ├── Utils.py └── __init__.py ├── GolangHelper.py └── README.md /GoUtils/Firstmoduledata.py: -------------------------------------------------------------------------------- 1 | 2 | import idautils 3 | import struct 4 | from . import Utils 5 | 6 | def findFirstModuleData(addr, bt): 7 | # print(f"{addr:x}") 8 | possible_addr = [x for x in idautils.XrefsTo(addr)] 9 | for p_a in possible_addr: 10 | # print(f"Checking addr: {p_a}") 11 | if Utils.is_hardcoded_slice(p_a.frm, bt): 12 | return p_a.frm 13 | elif Utils.is_hardcoded_slice(p_a.frm+bt.size, bt): 14 | return p_a.frm 15 | return None 16 | 17 | def isGo17(addr, bt): 18 | addr += bt.size * 27 19 | addr2 = addr + bt.size * 6 # for go1.7 this addr will be for modulename 20 | return Utils.is_hardcoded_slice(addr, bt) and (not Utils.is_hardcoded_slice(addr2, bt)) 21 | 22 | 23 | def isGo18_10(addr, bt): 24 | addr += bt.size * 27 25 | addr2 = addr + bt.size * 6 # for go1.8 this addr will be for itablinks 26 | return Utils.is_hardcoded_slice(addr, bt) and (Utils.is_hardcoded_slice(addr2, bt)) 27 | 28 | def isGo116(addr, bt): 29 | addr += bt.size * 1 30 | # addr2 = addr + bt.size * 6 # for go1.8 this addr will be for itablinks 31 | return Utils.is_hardcoded_slice(addr, bt) 32 | 33 | def getTypeinfo17(addr, bt): 34 | addr2 = addr + bt.size * 25 35 | robase = bt.ptr(addr2) 36 | addr += bt.size * 27 37 | beg = bt.ptr(addr) 38 | size = bt.ptr(addr+bt.size) 39 | return beg, beg+size*4, robase 40 | 41 | 42 | def getTypeinfo18(addr, bt): 43 | addr2 = addr + bt.size * 25 44 | robase = bt.ptr(addr2) 45 | addr += bt.size * 30 46 | beg = bt.ptr(addr) 47 | size = bt.ptr(addr+bt.size) 48 | return beg, beg+size*4, robase 49 | 50 | def getTypeinfo116(addr, bt): 51 | addr2 = addr + bt.size * 35 52 | robase = bt.ptr(addr2) 53 | addr += bt.size * 40 54 | beg = bt.ptr(addr) 55 | size = bt.ptr(addr+bt.size) 56 | print(robase) 57 | return beg, beg+size*4, robase 58 | 59 | def getTypeinfo117(addr, bt): 60 | return getTypeinfo116(addr, bt) 61 | 62 | def getTypeinfo(addr, bt): 63 | addr += bt.size * 25 64 | beg = bt.ptr(addr) 65 | size = bt.ptr(addr+bt.size) 66 | 67 | return beg, beg+size*bt.size 68 | 69 | """ 70 | 1.16 71 | type moduledata struct { 72 | 0 pcHeader *pcHeader 73 | 1 funcnametab []byte 74 | 4 cutab []uint32 75 | 7 filetab []byte 76 | 10 pctab []byte 77 | 13 pclntable []byte 78 | 16 ftab []functab 79 | 19 findfunctab uintptr 80 | 20 minpc, maxpc uintptr 81 | 82 | 22 text, etext uintptr 83 | 24 noptrdata, enoptrdata uintptr 84 | 26 data, edata uintptr 85 | 28 bss, ebss uintptr 86 | 30 noptrbss, enoptrbss uintptr 87 | 32 end, gcdata, gcbss uintptr 88 | 35 types, etypes uintptr 89 | 90 | 37 textsectmap []textsect 91 | 40 typelinks []int32 // offsets from types 92 | itablinks []*itab 93 | 94 | ptab []ptabEntry 95 | 96 | pluginpath string 97 | pkghashes []modulehash 98 | 99 | modulename string 100 | modulehashes []modulehash 101 | 102 | hasmain uint8 // 1 if module contains the main function, 0 otherwise 103 | 104 | gcdatamask, gcbssmask bitvector 105 | 106 | typemap map[typeOff]*_type // offset to *_rtype in previous module 107 | 108 | bad bool // module failed to load and should be ignored 109 | 110 | next *moduledata 111 | } 112 | 1.10 - same as 1.10 113 | 1.9 - same as 1.8 114 | 1.8 115 | type moduledata struct { 116 | 3 pclntable []byte 117 | 6 ftab []functab 118 | 9 filetab []uint32 119 | 120 | 10 findfunctab uintptr 121 | 12 minpc, maxpc uintptr 122 | 14 text, etext uintptr 123 | 16 noptrdata, enoptrdata uintptr 124 | 18 data, edata uintptr 125 | 20 bss, ebss uintptr 126 | 22 noptrbss, enoptrbss uintptr 127 | 25 end, gcdata, gcbss uintptr 128 | 27 types, etypes uintptr 129 | 30 textsectmap []textsect 130 | typelinks []int32 // offsets from types 131 | itablinks []*itab 132 | ptab []ptabEntry 133 | pluginpath string 134 | pkghashes []modulehash 135 | modulename string 136 | modulehashes []modulehash 137 | gcdatamask, gcbssmask bitvector 138 | typemap map[typeOff]*_type // offset to *_rtype in previous module 139 | next *moduledata 140 | } 141 | 142 | 1.7 143 | type moduledata struct { 144 | 3 pclntable []byte 145 | 6 ftab []functab 146 | 9 filetab []uint32 147 | 10 findfunctab uintptr 148 | 12 minpc, maxpc uintptr 149 | 150 | 14 text, etext uintptr 151 | 16 noptrdata, enoptrdata uintptr 152 | 18 data, edata uintptr 153 | 20 bss, ebss uintptr 154 | 22 noptrbss, enoptrbss uintptr 155 | 25 end, gcdata, gcbss uintptr 156 | 27 types, etypes uintptr 157 | 158 | typelinks []int32 // offsets from types 159 | itablinks []*itab 160 | 161 | modulename string 162 | modulehashes []modulehash 163 | 164 | gcdatamask, gcbssmask bitvector 165 | 166 | typemap map[typeOff]*_type // offset to *_rtype in previous module 167 | 168 | next *moduledata 169 | } 170 | """ 171 | 172 | """1.6 173 | type moduledata struct { 174 | 3 pclntable []byte 175 | 6 ftab []functab 176 | 9 filetab []uint32 177 | 10 findfunctab uintptr 178 | 12 minpc, maxpc uintptr 179 | 180 | 14 text, etext uintptr 181 | 16 noptrdata, enoptrdata uintptr 182 | 18 data, edata uintptr 183 | 20 bss, ebss uintptr 184 | 22 noptrbss, enoptrbss uintptr 185 | 25 end, gcdata, gcbss uintptr 186 | 187 | typelinks []*_type 188 | 189 | modulename string 190 | modulehashes []modulehash 191 | 192 | gcdatamask, gcbssmask bitvector 193 | 194 | next *moduledata 195 | } 196 | """ 197 | """1.5 198 | type moduledata struct { 199 | 3 pclntable []byte 200 | 6 ftab []functab 201 | 9 filetab []uint32 202 | 10 findfunctab uintptr 203 | 12 minpc, maxpc uintptr 204 | 205 | 14 text, etext uintptr 206 | 16 noptrdata, enoptrdata uintptr 207 | 18 data, edata uintptr 208 | 20 bss, ebss uintptr 209 | 22 noptrbss, enoptrbss uintptr 210 | 25 end, gcdata, gcbss uintptr 211 | 212 | typelinks []*_type 213 | 214 | modulename string 215 | modulehashes []modulehash 216 | 217 | gcdatamask, gcbssmask bitvector 218 | 219 | next *moduledata 220 | } 221 | """ 222 | -------------------------------------------------------------------------------- /GoUtils/GoStrings.py: -------------------------------------------------------------------------------- 1 | 2 | import ida_bytes 3 | import idaapi 4 | import idautils 5 | import idc 6 | from . import Utils 7 | 8 | ptr = Utils.get_bitness() 9 | def is_string(addr, length): 10 | val = ptr.ptr(addr) 11 | if idc.is_loaded(val): return False 12 | if val < 0x100 and val % 8 == 0: return False 13 | if not idc.is_loaded(addr): return False 14 | if length <= 0 or not idc.is_loaded(addr + length - 1): return False 15 | try: 16 | b = ida_bytes.get_bytes(addr, length) 17 | if 0 in b: return False 18 | b.decode() 19 | return True 20 | except: 21 | return False 22 | 23 | def detect_string(): 24 | str_table = [] 25 | for seg_start in idautils.Segments(): 26 | perm = idc.get_segm_attr(seg_start, idc.SEGATTR_PERM) 27 | if perm & 1: continue # x 28 | if not (perm & 4): continue # r 29 | seg_end = idc.get_segm_end(seg_start) 30 | if seg_start & (ptr.size - 1): 31 | seg_start = seg_start + (ptr.size - 1) & ~(ptr.size - 1) 32 | for i in range(seg_start, seg_end - ptr.size, ptr.size): 33 | length = ptr.ptr(i + ptr.size) 34 | # check length 35 | if length < 1 or length >= 0x800: continue 36 | addr = ptr.ptr(i) 37 | if length and addr not in str_table and is_string(addr, length): 38 | str_table.append(addr) 39 | s = ida_bytes.get_bytes(addr, length).decode() 40 | print('detected string at 0x%x (0x%x, %d): %s' % (i, addr, length, s)) 41 | ida_bytes.del_items(addr) 42 | ida_bytes.create_strlit(addr, length, -1) 43 | 44 | str_table.sort() 45 | for i in range(len(str_table) - 1): 46 | addr = str_table[i] 47 | end_addr = str_table[i + 1] 48 | addr += idc.get_item_size(addr) 49 | while addr < end_addr: 50 | if idc.get_item_size(addr) != 1: 51 | ida_bytes.del_items(addr) 52 | if not next(idautils.XrefsTo(addr), None): break 53 | length = 1 54 | while idc.is_loaded(addr + length) and\ 55 | idc.get_item_size(addr + length) == 1 and\ 56 | not next(idautils.XrefsTo(addr + length), None): 57 | length += 1 58 | if not is_string(addr, length): break 59 | s = ida_bytes.get_bytes(addr, length).decode() 60 | print('extra string detected at 0x%x (%d): %s' % (addr, length, s)) 61 | ida_bytes.del_items(addr) 62 | ida_bytes.create_strlit(addr, length, -1) 63 | addr += length 64 | -------------------------------------------------------------------------------- /GoUtils/Gopclntab.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idaapi 3 | import ida_bytes 4 | import ida_funcs 5 | import ida_search 6 | import ida_segment 7 | import ida_kernwin 8 | from . import Utils 9 | 10 | def check_is_gopclntab(addr): 11 | ptr = Utils.get_bitness(addr) 12 | if addr & (ptr.size - 1): return False 13 | if ida_bytes.get_byte(addr + 7) != ptr.size: return False 14 | first_entry = ptr.ptr(addr+8+ptr.size) 15 | first_entry_off = ptr.ptr(addr+8+ptr.size*2) 16 | addr_func = addr+first_entry_off 17 | func_loc = ptr.ptr(addr_func) 18 | if func_loc == first_entry: 19 | return True 20 | return False 21 | 22 | def check_is_gopclntab16(addr): 23 | ptr = Utils.get_bitness(addr) 24 | if addr & (ptr.size - 1): return False 25 | if ida_bytes.get_byte(addr + 7) != ptr.size: return False 26 | offset = 8 + ptr.size * 6 27 | # print(f"{addr+offset:x}") 28 | first_entry = ptr.ptr(addr+offset) + addr 29 | # print(f"{first_entry:x}") 30 | func_loc = ptr.ptr(first_entry) 31 | struct_ptr = ptr.ptr(first_entry+8) + first_entry 32 | first_entry = ptr.ptr(struct_ptr) 33 | if func_loc == first_entry: 34 | return True 35 | return False 36 | 37 | def check_is_gopclntab18_20(addr): 38 | # print(f"renzo-----header_addr: {addr:x}") 39 | ptr = Utils.get_bitness(addr) 40 | if addr & (ptr.size - 1): return False 41 | if ida_bytes.get_byte(addr + 7) != ptr.size: return False 42 | offset = 8 + ptr.size * 7 43 | first_entry = ptr.ptr(addr+offset) + addr 44 | # print(f"renzo-----pclntable_addr: {first_entry:x}") 45 | func_loc = idc.get_wide_dword(first_entry) 46 | struct_ptr = idc.get_wide_dword(first_entry+4) + first_entry 47 | first_entry = idc.get_wide_dword(struct_ptr) 48 | # print(f"renzo-----func_loc: {func_loc:x}") 49 | # print(f"renzo-----first_entry: {first_entry:x}") 50 | if func_loc == first_entry: 51 | return True 52 | return False 53 | 54 | def set_funcname(func_addr, name_addr): 55 | # if make_funcs == True: 56 | if True: 57 | # ida_bytes.del_items(func_addr, 1, ida_bytes.DELIT_DELNAMES) 58 | ida_bytes.del_items(func_addr) 59 | ida_funcs.add_func(func_addr) 60 | nameb = ida_bytes.get_strlit_contents(name_addr, -1, -1) 61 | if nameb == None: 62 | print(f"{func_addr:x} ('{idaapi.get_name(func_addr)}') has no name!") 63 | return 64 | ida_bytes.del_items(name_addr) 65 | if ida_bytes.get_byte(name_addr + len(nameb)) == 0: 66 | ida_bytes.create_strlit(name_addr, len(nameb) + 1, -1) 67 | else: 68 | ida_bytes.create_strlit(name_addr, len(nameb), -1) 69 | name = Utils.relaxName(nameb.decode()) 70 | if name == idaapi.get_name(func_addr): return 71 | print(f"{func_addr:x} ('{idaapi.get_name(func_addr)}') -> '{nameb.decode()}' ('{name}') ... ", end='') 72 | if Utils.rename(func_addr, name): print('done') 73 | else: print('error') 74 | 75 | # def rename(beg, ptr, make_funcs = True): 76 | def rename(beg, ptr): # !!! Not tested !!! 77 | base = beg 78 | pos = beg + 8 #skip header 79 | size = ptr.ptr(pos) 80 | pos += ptr.size 81 | end = pos + (size * ptr.size * 2) 82 | # print("%x" % end) 83 | while pos < end: 84 | offset = ptr.ptr(pos + ptr.size) 85 | ptr.maker(pos) #in order to get xrefs 86 | ptr.maker(pos+ptr.size) 87 | pos += ptr.size * 2 88 | ptr.maker(base+offset) 89 | func_addr = ptr.ptr(base+offset) 90 | name_addr = base + idc.get_wide_dword(base+offset+ptr.size) 91 | set_funcname(func_addr, name_addr) 92 | 93 | # def rename16(beg, ptr, make_funcs = True): 94 | def rename16(beg, ptr): 95 | base = beg 96 | first_entry = ptr.ptr(base+ptr.size * 6 + 8) + base 97 | cnt = ptr.ptr(base + 8) 98 | print('first_entry: ' + hex(first_entry)) 99 | print('function count: %d' % cnt) 100 | funcname_start = base + 8 + ptr.size *7 101 | for i in range(cnt): 102 | struct_ptr = ptr.ptr(first_entry + i*ptr.size*2 + 8) + first_entry 103 | # print(f"{struct_ptr:x}") 104 | func_addr = ptr.ptr(first_entry + i*ptr.size*2) 105 | name_addr = ida_bytes.get_dword(struct_ptr+8) + funcname_start 106 | set_funcname(func_addr, name_addr) 107 | 108 | # def rename20(beg, ptr, make_funcs = True): 109 | def rename20(beg, ptr): 110 | base = beg 111 | first_entry = ptr.ptr(base+ptr.size * 7 + 8) + base 112 | cnt = ptr.ptr(base + 8) 113 | print('first_entry: ' + hex(first_entry)) 114 | print('function count: %d' % cnt) 115 | func_entry = ptr.ptr(base + 8 + ptr.size * 2) 116 | # print("renzo-----func_entry: {:x}".format(func_entry)) 117 | funcname_start = base + ptr.ptr(base + 8 + ptr.size * 3) 118 | # print("renzo-----funcname_start: {:x}".format(funcname_start)) 119 | 120 | for i in range(cnt): 121 | struct_ptr = idc.get_wide_dword(first_entry + i*4*2 + 4) + first_entry 122 | # print(f"{struct_ptr:x}") 123 | func_addr = func_entry + idc.get_wide_dword(first_entry + i*4*2) 124 | name_addr = idc.get_wide_dword(struct_ptr+4) + funcname_start 125 | set_funcname(func_addr, name_addr) 126 | 127 | info = idaapi.get_inf_structure() 128 | try: 129 | is_be = info.is_be() 130 | except: 131 | is_be = info.mf 132 | 133 | lookup = "FF FF FF FB 00 00" if is_be else "FB FF FF FF 00 00" 134 | lookup16 = "FF FF FF FA 00 00" if is_be else "FA FF FF FF 00 00" 135 | lookup18 = "FF FF FF F0 00 00" if is_be else "F0 FF FF FF 00 00" 136 | lookup20 = "FF FF FF F1 00 00" if is_be else "F1 FF FF FF 00 00" 137 | magic_bytes_lookup = {} 138 | check_gopclntab = {} 139 | rename_functions = {} 140 | for i in range(16): 141 | magic_bytes_lookup['go 1.' + str(i)] = bytes.fromhex(lookup) 142 | check_gopclntab['go 1.' + str(i)] = check_is_gopclntab 143 | rename_functions['go 1.' + str(i)] = rename 144 | magic_bytes_lookup['go 1.16'] = bytes.fromhex(lookup16) 145 | magic_bytes_lookup['go 1.17'] = bytes.fromhex(lookup16) 146 | magic_bytes_lookup['go 1.18'] = bytes.fromhex(lookup18) 147 | magic_bytes_lookup['go 1.19'] = bytes.fromhex(lookup18) 148 | magic_bytes_lookup['go 1.20'] = bytes.fromhex(lookup20) 149 | check_gopclntab['go 1.16'] = check_is_gopclntab16 150 | check_gopclntab['go 1.17'] = check_is_gopclntab16 151 | check_gopclntab['go 1.18'] = check_is_gopclntab18_20 152 | check_gopclntab['go 1.19'] = check_is_gopclntab18_20 153 | check_gopclntab['go 1.20'] = check_is_gopclntab18_20 154 | rename_functions['go 1.16'] = rename16 155 | rename_functions['go 1.17'] = rename16 156 | rename_functions['go 1.18'] = rename20 157 | rename_functions['go 1.19'] = rename20 158 | rename_functions['go 1.20'] = rename20 159 | 160 | def findGoPcLn(): 161 | seg = ida_segment.get_segm_by_name('.gopclntab') 162 | if seg: 163 | possible_loc = seg.start_ea 164 | init_bytes = ida_bytes.get_bytes(possible_loc, 6) 165 | if init_bytes == bytes.fromhex(lookup20) and check_is_gopclntab18_20(possible_loc): 166 | # print("Looks like this is go1.20 binary") 167 | return possible_loc 168 | elif init_bytes == bytes.fromhex(lookup18) and check_is_gopclntab18_20(possible_loc): 169 | # print("Looks like this is go1.18 binary") 170 | return possible_loc 171 | elif init_bytes == bytes.fromhex(lookup16) and check_is_gopclntab16(possible_loc): 172 | # print("Looks like this is go1.16 binary") 173 | return possible_loc 174 | elif init_bytes == bytes.fromhex(lookup) and check_is_gopclntab(possible_loc): 175 | return possible_loc 176 | possible_loc = ida_search.find_binary(0, idc.BADADDR, lookup20, 16, idc.SEARCH_DOWN) #header of gopclntab 177 | while possible_loc != idc.BADADDR: 178 | # print(f"found possible 1.20 gopclntab") 179 | if check_is_gopclntab18_20(possible_loc): 180 | # print("Looks like this is go1.20 binary") 181 | return possible_loc 182 | else: 183 | # keep searching till we reach end of binary 184 | possible_loc = ida_search.find_binary(possible_loc+1, idc.BADADDR, lookup20, 16, idc.SEARCH_DOWN) 185 | possible_loc = ida_search.find_binary(0, idc.BADADDR, lookup18, 16, idc.SEARCH_DOWN) #header of gopclntab 186 | while possible_loc != idc.BADADDR: 187 | # print(f"found possible 1.18 gopclntab") 188 | if check_is_gopclntab18_20(possible_loc): 189 | # print("Looks like this is go1.18 binary") 190 | return possible_loc 191 | else: 192 | #keep searching till we reach end of binary 193 | possible_loc = ida_search.find_binary(possible_loc+1, idc.BADADDR, lookup18, 16, idc.SEARCH_DOWN) 194 | possible_loc = ida_search.find_binary(0, idc.BADADDR, lookup, 16, idc.SEARCH_DOWN) #header of gopclntab 195 | while possible_loc != idc.BADADDR: 196 | if check_is_gopclntab(possible_loc): 197 | return possible_loc 198 | else: 199 | #keep searching till we reach end of binary 200 | possible_loc = ida_search.find_binary(possible_loc+1, idc.BADADDR, lookup, 16, idc.SEARCH_DOWN) 201 | possible_loc = ida_search.find_binary(0, idc.BADADDR, lookup16, 16, idc.SEARCH_DOWN) #header of gopclntab 202 | while possible_loc != idc.BADADDR: 203 | # print(f"found possible 1.16 gopclntab") 204 | if check_is_gopclntab16(possible_loc): 205 | # print("Looks like this is go1.16 binary") 206 | return possible_loc 207 | else: 208 | #keep searching till we reach end of binary 209 | possible_loc = ida_search.find_binary(possible_loc+1, idc.BADADDR, lookup16, 16, idc.SEARCH_DOWN) 210 | return None 211 | 212 | def check_go_version(gopclntab, go_version): 213 | magic_bytes = ida_bytes.get_bytes(gopclntab, 6) 214 | if magic_bytes_lookup[go_version] != magic_bytes: return False 215 | return check_gopclntab[go_version](gopclntab) 216 | 217 | def get_inexact_version(gopclntab): 218 | magic_bytes = ida_bytes.get_bytes(gopclntab, 6) 219 | for version in magic_bytes_lookup: 220 | if magic_bytes_lookup[version] == magic_bytes: 221 | return version 222 | return None 223 | 224 | def renameFunctions(gopclntab, go_version, bt_obj): 225 | if not check_go_version(gopclntab, go_version): 226 | print('check version error') 227 | return 228 | rename_functions[go_version](gopclntab, bt_obj) 229 | -------------------------------------------------------------------------------- /GoUtils/PatchNewVersion.py: -------------------------------------------------------------------------------- 1 | 2 | from . import Gopclntab 3 | from . import Types 4 | 5 | def apply_patches(GolangHelper): 6 | for i in ['go 1.21', 'go 1.22']: 7 | GolangHelper.VERSIONS[-1].append(i) 8 | Gopclntab.magic_bytes_lookup[i] = Gopclntab.magic_bytes_lookup['go 1.20'] 9 | Gopclntab.check_gopclntab[i] = Gopclntab.check_gopclntab['go 1.20'] 10 | Gopclntab.rename_functions[i] = Gopclntab.rename_functions['go 1.20'] 11 | Types.get_typelink_and_types_offset[i] = Types.get_typelink_and_types_offset['go 1.20'] 12 | Types.get_typename[i] = Types.get_typename['go 1.20'] 13 | -------------------------------------------------------------------------------- /GoUtils/Types.py: -------------------------------------------------------------------------------- 1 | 2 | import ida_bytes 3 | import idc 4 | import ida_kernwin 5 | 6 | def read_varint(addr): 7 | val = 0 8 | size = 0 9 | while True: 10 | b = ida_bytes.get_byte(addr + size) 11 | val |= (b & 0x7f) << (size * 7) 12 | size += 1 13 | if b & 0x80 == 0: break 14 | return size, val 15 | 16 | def get_typename_varlen(name_addr): 17 | size, val = read_varint(name_addr + 1) 18 | return ida_bytes.get_bytes(name_addr + 1 + size, val).decode() 19 | 20 | 21 | get_typelink_and_types_offset = {} 22 | get_typename = {} 23 | for i in range(16): 24 | get_typelink_and_types_offset['go 1.' + str(i)] = lambda bt_obj: (bt_obj.size * 30, bt_obj.size * 25) 25 | get_typename['go 1.' + str(i)] = lambda name_addr: ida_bytes.get_bytes(name_addr + 3, int.from_bytes(ida_bytes.get_bytes(name_addr + 1, 2), 'big')).decode() 26 | get_typelink_and_types_offset['go 1.16'] = lambda bt_obj: (bt_obj.size * 40, bt_obj.size * 35) 27 | get_typelink_and_types_offset['go 1.17'] = lambda bt_obj: (bt_obj.size * 40, bt_obj.size * 35) 28 | get_typelink_and_types_offset['go 1.18'] = lambda bt_obj: (bt_obj.size * 42, bt_obj.size * 35) 29 | get_typelink_and_types_offset['go 1.19'] = lambda bt_obj: (bt_obj.size * 42, bt_obj.size * 35) 30 | get_typelink_and_types_offset['go 1.20'] = lambda bt_obj: (bt_obj.size * 44, bt_obj.size * 37) 31 | get_typename['go 1.16'] = get_typename_varlen 32 | get_typename['go 1.17'] = get_typename_varlen 33 | get_typename['go 1.18'] = get_typename_varlen 34 | get_typename['go 1.19'] = get_typename_varlen 35 | get_typename['go 1.20'] = get_typename_varlen 36 | 37 | def parse_type_names_recursive(types_addr, type_offset, version, bt_obj, parsed_types_offset): 38 | if type_offset in parsed_types_offset: return parsed_types_offset[type_offset] 39 | 40 | type_addr = types_addr + type_offset 41 | name_addr = ida_bytes.get_dword(type_addr + 4 * bt_obj.size + 8) + types_addr 42 | type_name = "Unknown" 43 | try: 44 | type_name = get_typename[version](name_addr) 45 | except: 46 | pass 47 | 48 | parsed_types_offset[type_offset] = type_name 49 | print('gotype at 0x%x: "%s"' % (type_addr, type_name)) 50 | idc.set_cmt(type_addr, 'gotype "' + type_name + '"', 0) 51 | ptrtothis_off = ida_bytes.get_dword(type_addr + 4 * bt_obj.size + 12) 52 | if ptrtothis_off: 53 | t = parse_type_names_recursive(types_addr, ptrtothis_off, version, bt_obj, parsed_types_offset) 54 | idc.set_cmt(type_addr + 4 * bt_obj.size + 12, 'ptr_to_this gotype "' + t + '" (0x%x)' % (types_addr + ptrtothis_off), 0) 55 | # print(hex(type_addr)) 56 | return type_name 57 | 58 | def parse_type_names(fmd, version, bt_obj): 59 | typelink_offset, types_offset = get_typelink_and_types_offset[version](bt_obj) 60 | typelink_addr = bt_obj.ptr(fmd + typelink_offset) 61 | type_cnt = bt_obj.ptr(fmd + typelink_offset + bt_obj.size) 62 | types_addr = bt_obj.ptr(fmd + types_offset) 63 | parsed_types_offset = {} 64 | 65 | print(hex(types_addr)) 66 | print(hex(typelink_addr)) 67 | print(hex(type_cnt)) 68 | 69 | for i in range(type_cnt): 70 | # assert ida_kernwin.ask_long(i, '12345') 71 | type_offset = ida_bytes.get_dword(typelink_addr + i * 4) 72 | parse_type_names_recursive(types_addr, type_offset, version, bt_obj, parsed_types_offset) 73 | 74 | def parse_type_name_at(type_addr, fmd, version, bt_obj): 75 | _, types_offset = get_typelink_and_types_offset[version](bt_obj) 76 | types_addr = bt_obj.ptr(fmd + types_offset) 77 | name_addr = ida_bytes.get_dword(type_addr + 4 * bt_obj.size + 8) + types_addr 78 | type_name = "Unknown" 79 | try: 80 | type_name = get_typename[version](name_addr) 81 | except: 82 | pass 83 | ptrtothis_off = ida_bytes.get_dword(type_addr + 4 * bt_obj.size + 12) 84 | if type_name != "Unknown" and ptrtothis_off: 85 | idc.set_cmt(type_addr, 'gotype "' + type_name + '" (ptrtothis: 0x%x)' % (ptrtothis_off + types_addr), 0) 86 | else: 87 | idc.set_cmt(type_addr, 'gotype "' + type_name + '"', 0) 88 | return type_name 89 | -------------------------------------------------------------------------------- /GoUtils/Utils.py: -------------------------------------------------------------------------------- 1 | import idaapi 2 | import ida_ida 3 | import ida_enum 4 | import ida_struct 5 | import idc 6 | import string 7 | import random 8 | 9 | class bitZ(object): 10 | def __init__(self, ptr, size, maker): 11 | self.ptr = ptr 12 | self.size = size 13 | self.maker = maker 14 | 15 | 16 | bits32 = bitZ(idc.get_wide_dword, 4, idc.create_dword) 17 | bits64 = bitZ(idc.get_qword, 8, idc.create_qword) 18 | 19 | 20 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): 21 | return ''.join(random.choice(chars) for _ in range(size)) 22 | 23 | 24 | def rename(offset, name): 25 | ''' 26 | res = idc.set_name(offset, name, idc.SN_NOWARN) 27 | if res == 0: 28 | name = name+"_autogen_"+id_generator() 29 | idc.set_name(offset, name, idc.SN_NOWARN) 30 | ''' 31 | res = idc.set_name(offset, name, idc.SN_NOWARN) 32 | if res == 0 and (idaapi.get_name(offset) == '' or idaapi.get_name(offset).startswith('sub_')): 33 | index = 1 34 | while index < 100 and res == 0: # set max 99 to avoid potential endless loop 35 | res = idc.set_name(offset, name + '_%d' % index, idc.SN_NOWARN) 36 | index += 1 37 | return False 38 | return res 39 | 40 | def relaxName(name): 41 | # ida can handle many chars. 42 | # name = name.replace('.', '_').replace("<-", '_chan_left_').replace('*', '_ptr_').replace('-', '_').replace(';','').replace('"', '').replace('\\', '') 43 | # name = name.replace('(', '').replace(')', '').replace('/', '_').replace(' ', '_').replace(',', 'comma').replace('{','').replace('}', '').replace('[', '').replace(']', '') 44 | # replace only those needed 45 | name = name\ 46 | .replace('/', '_')\ 47 | .replace('*', '_')\ 48 | .replace(' ', '_')\ 49 | .replace(';', '_')\ 50 | .replace('-', '_')\ 51 | .replace('{', '_')\ 52 | .replace('}', '_') 53 | return name 54 | 55 | 56 | def get_bitness(addr=None): 57 | if addr == None: addr = ida_ida.inf_get_min_ea() 58 | ptr = bits32 59 | if idc.get_segm_attr(addr, idc.SEGATTR_BITNESS) == 2: 60 | ptr = bits64 61 | return ptr 62 | 63 | 64 | def is_hardcoded_slice(addr, bt_obj): 65 | #compiled slices will have valid ptr 66 | if bt_obj.ptr(bt_obj.ptr(addr)) == idc.BADADDR: 67 | return False 68 | addr = addr + bt_obj.size 69 | val1 = bt_obj.ptr(addr) 70 | val2 = bt_obj.ptr(addr + bt_obj.size) 71 | if val1 != val2: 72 | return False 73 | return True 74 | 75 | 76 | class StructCreator(object): 77 | def __init__(self, bt_obj): 78 | self.types_id = {} 79 | if bt_obj.size == 8: 80 | self.uintptr = (idc.FF_QWORD|idc.FF_DATA, -1, bt_obj.size) 81 | else: 82 | self.uintptr = (idc.FF_DWORD | idc.FF_DATA, -1, bt_obj.size) 83 | 84 | self.baseptr = (idc.FF_DWORD | idc.FF_DATA, 0, 4) 85 | 86 | def configBase(self, robase): 87 | self.baseptr = (idc.FF_DWORD|idc.FF_0OFF, robase, 4) 88 | 89 | def createStruct(self, name): 90 | sid = ida_struct.get_struc_id(name) 91 | if sid != idc.BADADDR: 92 | idc.del_struc(sid) 93 | sid = idc.add_struc(-1, name, 0) 94 | self.types_id['name'] = sid 95 | return sid 96 | 97 | def fillStruct(self, sid, data): 98 | for i in data: 99 | new_type = None 100 | #(i1, i2, i3) = self.stepper.parseField(i[1]) 101 | name = i[1] 102 | if name[0] == "*": 103 | name = name[1:] 104 | 105 | member_sid = ida_struct.get_struc_id(i[1]) 106 | if i[1] == 'baseptr': 107 | i1, i2, i3 = self.baseptr 108 | elif i[1] == 'uintptr': 109 | i1, i2, i3 = self.uintptr 110 | elif member_sid != idc.BADADDR: 111 | i1, i2, i3 = (idc.FF_STRUCT, member_sid, ida_struct.get_struc_size(member_sid)) 112 | elif i[1].endswith(' *'): # It is a pointer to some class 113 | i1, i2, i3 = self.uintptr 114 | else: 115 | i1,i2,i3 = (idc.FF_BYTE|idc.FF_DATA, -1, 1) 116 | 117 | if name == i[1]: 118 | new_type = i[1] 119 | else: 120 | new_type = name + " *" 121 | res = idc.add_struc_member(sid, i[0], -1, i1, i2, i3) 122 | use_name = i[0] 123 | if res == -1: #Bad name 124 | #print("Bad name %s for struct member" % i[0]) 125 | use_name = i[0] + "_autogen_"+id_generator() 126 | idc.add_struc_member(sid, use_name, -1, i1, i2, i3) 127 | if new_type is not None: 128 | offset = idc.get_member_offset(sid, use_name) 129 | #print("Setting %s as %s" % (i[0], new_type)) 130 | idc.SetType(idc.get_member_id(sid, offset), new_type) 131 | 132 | def makeStruct(self, i): 133 | print("Creating structure %s" % (i[0])) 134 | sid = self.createStruct(i[0]) 135 | self.fillStruct(sid, i[1]) 136 | 137 | def createTypes(self, types): 138 | for i in types: 139 | self.makeStruct(i) 140 | 141 | def createEnum(self, enum): 142 | eid = idc.add_enum(-1, enum[0], 0x1100000) #what is this flag? 143 | ida_enum.set_enum_bf(eid, 1) 144 | val = 0 145 | mask = 0x1f 146 | ida_enum.set_enum_width(eid, 1) 147 | for i in enum[1]: 148 | idc.add_enum_member(eid, i, val, mask) 149 | val += 1 150 | 151 | def createEnums(self, enums): 152 | for i in enums: 153 | self.createEnum(i) 154 | -------------------------------------------------------------------------------- /GoUtils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import Gopclntab 2 | from . import Types 3 | from . import Firstmoduledata 4 | from . import Utils 5 | from . import GoStrings 6 | 7 | import idc 8 | import idautils 9 | import ida_ida 10 | import ida_bytes 11 | import ida_segment 12 | import idaapi 13 | import re 14 | 15 | bt_obj = Utils.get_bitness(ida_ida.inf_get_min_ea()) 16 | def find_gopclntab(): 17 | seg = ida_segment.get_segm_by_name('.gopclntab') 18 | if seg: 19 | print('.gopclntab segment found: 0x%x' % seg.start_ea) 20 | return seg.start_ea 21 | else: 22 | addr = Gopclntab.findGoPcLn() 23 | if addr: 24 | print('possible gopclntab found: 0x%x (Might be wrong. Consider setting it manually)' % addr) 25 | return addr 26 | 27 | def find_go_version(gopclntab): 28 | possible_go_versions = [] 29 | addr = ida_bytes.bin_search(0, idc.BADADDR, b'go1.', None, 0, ida_bytes.BIN_SEARCH_FORWARD) 30 | while addr != idc.BADADDR: 31 | print('go version string found at 0x%x' % addr) 32 | for i in idautils.XrefsTo(addr): 33 | size = bt_obj.ptr(i.frm + bt_obj.size) 34 | # print(hex(addr), size) 35 | if size == 0 or size > 10: continue 36 | s = ida_bytes.get_bytes(addr, size).decode() 37 | if not re.match('go1\.[0-9]+\.[0-9]+', s): continue 38 | s = 'go ' + s[2: s.rindex('.')] 39 | if s not in possible_go_versions and Gopclntab.check_go_version(gopclntab, s): 40 | possible_go_versions.append(s) 41 | addr = ida_bytes.bin_search(addr + 1, idc.BADADDR, b'go1.', None, 0, ida_bytes.BIN_SEARCH_FORWARD) 42 | if len(possible_go_versions) == 1: 43 | return possible_go_versions[0] 44 | elif len(possible_go_versions) > 1: 45 | print('Found possible go version:') 46 | for i in possible_go_versions: 47 | print(i) 48 | return possible_go_versions[0] 49 | else: # go version string not found 50 | print('Version may be inexact:') 51 | return Gopclntab.get_inexact_version(gopclntab) 52 | 53 | def renameFunctions(gopclntab, go_version): 54 | return Gopclntab.renameFunctions(gopclntab, go_version, bt_obj) 55 | 56 | def to_full_reg_name(reg): 57 | return ['rax', 'rbx', 'rcx', 'rdi', 'rsi', 'r8', 'r9', 'r10', 'r11'][ 58 | [ 59 | 'rax', 'rbx', 'rcx', 'rdi', 'rsi', 'r8', 'r9', 'r10', 'r11', 60 | 'eax', 'ebx', 'ecx', 'edi', 'esi', 'r8d', 'r9d', 'r10d', 'r11d', # in ida it is r8d not er8 61 | 'ax', 'bx', 'cx', 'di', 'si', 'r8w', 'r9w', 'r10w', 'r11w', # not sure 62 | 'al', 'bl', 'cl', 'dil', 'sil', 'r8b', 'r9b', 'r10b', 'r11b' 63 | ].index(reg) % 9 64 | ] 65 | 66 | def generate_functype(args): 67 | # 5 return regs may be enough 68 | # Instead of "_OWORD __spoils @", you can also 69 | # set return type as "pair__slice_err @<0: rax, 8: rbx, 16: rcx, 24: rdi, 32: rsi>" 70 | # But it does little help to F5. This is enough. 71 | if not args: return '_OWORD __usercall __spoils _@()' 72 | 73 | # 9 argument regs may be enough 74 | go_args = ['rax', 'rbx', 'rcx', 'rdi', 'rsi', 'r8', 'r9', 'r10', 'r11'] 75 | if len(args) > len(go_args): 76 | assert False, args 77 | for i in range(len(args)): 78 | if go_args[i] not in args: 79 | assert False, args 80 | return '_OWORD __usercall __spoils _@(_QWORD@<' + '>, _QWORD@<'.join(go_args[: len(args)]) + '>)' 81 | 82 | def retype_gofunc(addr): 83 | func_addr = addr 84 | functype = idc.get_type(func_addr) 85 | if functype and '__usercall' in functype: # already defined, or edit by user. 86 | return 87 | code = idc.GetDisasm(addr) 88 | addr += idc.get_item_size(addr) 89 | if re.match('cmp +rsp, \[r14\+.*\]', code): # cmp rsp, [r14+10h] 90 | pass 91 | elif re.match('lea +r12, \[rsp.+\]', code) and re.match('cmp +r12, \[r14\+.*\]', idc.GetDisasm(addr)): # lea r12, [rsp+var_28+8]; cmp r12, [r14+10h] 92 | addr += idc.get_item_size(addr) 93 | else: 94 | return 95 | code = idc.GetDisasm(addr) 96 | if not re.match('jbe +.*', code): # jbe loc_xxxxxx 97 | return 98 | if 'short' in code: 99 | assert idc.get_item_size(addr) == 2, hex(addr) 100 | addr = addr + ida_bytes.get_byte(addr + 1) + 2 101 | else: 102 | assert idc.get_item_size(addr) == 6, hex(addr) 103 | addr = addr + ida_bytes.get_dword(addr + 2) + 6 104 | regs_to_save = [] 105 | code = idc.GetDisasm(addr) 106 | while not re.match('call +.*', code): 107 | if re.match('mov +(.* ptr )?\[rsp.+\], .*', code): # mov [rsp+0A0h+var_98], rax 108 | reg = code[code.rindex(' ') + 1:] 109 | reg = to_full_reg_name(reg) 110 | regs_to_save.append(reg) 111 | addr += idc.get_item_size(addr) 112 | code = idc.GetDisasm(addr) 113 | functype = generate_functype(regs_to_save) 114 | print('0x%x -> %s' % (func_addr, functype)) 115 | idc.SetType(func_addr, functype) 116 | 117 | 118 | def retypeFunctions(): 119 | if idaapi.get_inf_structure().get_procName() != 'metapc' or not idaapi.get_inf_structure().is_64bit(): 120 | print('Only x86-64 supported.') 121 | return 122 | error_lines = [] 123 | for func_addr in idautils.Functions(): 124 | try: 125 | retype_gofunc(func_addr) 126 | except Exception as e: 127 | error_lines.append('Error at 0x%x: %s' % (func_addr, str(e))) 128 | for i in error_lines: 129 | print(i) 130 | 131 | def parse_types(gopclntab, go_version): 132 | fmd = Firstmoduledata.findFirstModuleData(gopclntab, bt_obj) 133 | Types.parse_type_names(fmd, go_version, bt_obj) 134 | 135 | def parse_type(type_addr, gopclntab, go_version): 136 | fmd = Firstmoduledata.findFirstModuleData(gopclntab, bt_obj) 137 | return Types.parse_type_name_at(type_addr, fmd, go_version, bt_obj) 138 | 139 | def detect_string(): 140 | GoStrings.detect_string() 141 | -------------------------------------------------------------------------------- /GolangHelper.py: -------------------------------------------------------------------------------- 1 | import idaapi 2 | import ida_kernwin 3 | 4 | class GolangHelper: 5 | VERSIONS = [ 6 | ['go 1.2', 'go 1.3', 'go 1.4', 'go 1.5', 'go 1.6', 'go 1.7', 'go 1.8', 'go 1.9', 'go 1.10', 'go 1.11', 'go 1.12', 'go 1.13', 'go 1.14', 'go 1.15'], 7 | ['go 1.16', 'go 1.17'], 8 | ['go 1.18', 'go 1.19'], 9 | ['go 1.20'] 10 | ] 11 | class MyForm(idaapi.Form): 12 | def __init__(self): 13 | import GoUtils 14 | idaapi.require("GoUtils") 15 | idaapi.require("GoUtils.Gopclntab") 16 | idaapi.require("GoUtils.Utils") 17 | idaapi.require("GoUtils.Types") 18 | idaapi.require("GoUtils.Firstmoduledata") 19 | idaapi.require("GoUtils.GoStrings") 20 | idaapi.require("GoUtils.PatchNewVersion") 21 | GoUtils.PatchNewVersion.apply_patches(GolangHelper) 22 | 23 | self.gopclntab = 0 24 | self.go_version = 'go 1.2' 25 | self.invert = False 26 | form_item = r'''STARTITEM {id:cGoVers} 27 | GolangHelper 28 | 29 | {FormChangeCb} 30 | <##Detect go version and gopclntab:{detect_btn}> 31 | <## Set gopclntab manually :{set_gopclntab_btn}> 32 | Go version: 33 | ''' 34 | for versions in GolangHelper.VERSIONS: 35 | if len(versions) == 1: 36 | form_item += '<%s:{%s}>\n' % (versions[0], versions[0]) 37 | else: 38 | form_item += '<%s-%s:{%s}>\n' % (versions[0], versions[-1], versions[0]) 39 | 40 | form_item = form_item[: -1] + '''{cGoVers}> 41 | -------------------------------- 42 | <## Rename functions :{rename_func_btn}> 43 | <## Parse go type names :{parse_types_btn}> 44 | <## Parse current go type name :{parse_type_btn}> 45 | -------------------------------- 46 | <## Set function types (simple) :{retype_func_btn}> 47 | <## Detect strings (slow) :{detect_string_btn}> 48 | ''' 49 | # print(repr(form_item)) 50 | idaapi.Form.__init__(self, form_item, { 51 | 'detect_btn': idaapi.Form.ButtonInput(self.detect), 52 | 'set_gopclntab_btn': idaapi.Form.ButtonInput(self.set_gopclntab), 53 | 'cGoVers': idaapi.Form.RadGroupControl(tuple(i[0] for i in GolangHelper.VERSIONS)), 54 | 'rename_func_btn': idaapi.Form.ButtonInput(self.rename_func), 55 | 'parse_types_btn': idaapi.Form.ButtonInput(self.parse_types), 56 | 'parse_type_btn': idaapi.Form.ButtonInput(self.parse_type), 57 | 'retype_func_btn': idaapi.Form.ButtonInput(self.retype_func), 58 | 'detect_string_btn': idaapi.Form.ButtonInput(self.detect_string), 59 | 'FormChangeCb': idaapi.Form.FormChangeCb(self.OnFormChange), 60 | }) 61 | 62 | def find_version(self, gopclntab): 63 | vers = GoUtils.find_go_version(gopclntab) 64 | if vers: 65 | for i in range(len(GolangHelper.VERSIONS)): 66 | if vers in GolangHelper.VERSIONS[i]: 67 | self.go_version = vers 68 | print('Set go version "' + vers + '"') 69 | self.SetControlValue(self.cGoVers, i) 70 | break 71 | else: 72 | print('Unknown go version: ' + vers) 73 | else: 74 | print('Failed to find go version') 75 | 76 | def detect(self, code=0): 77 | val = GoUtils.find_gopclntab() 78 | if val: 79 | print('Set gopclntab to 0x%x' % val) 80 | self.gopclntab = val 81 | self.find_version(val) 82 | else: 83 | print('Failed to find gopclntab') 84 | 85 | def set_gopclntab(self, code=0): 86 | val = ida_kernwin.ask_addr(self.gopclntab, "Input gopclntab address:") 87 | if isinstance(val, int): 88 | self.gopclntab = val 89 | print('Set gopclntab to 0x%x' % val) 90 | self.find_version(val) 91 | 92 | def rename_func(self, code=0): 93 | if self.gopclntab == 0: 94 | print('Please set gopclntab and go version first.') 95 | return 96 | if self.go_version not in GolangHelper.VERSIONS[self.GetControlValue(self.cGoVers)]: 97 | self.go_version = GolangHelper.VERSIONS[self.GetControlValue(self.cGoVers)][0] 98 | print('Set go version "' + self.go_version + '"') 99 | GoUtils.renameFunctions(self.gopclntab, self.go_version) 100 | 101 | def parse_types(self, code=0): 102 | if self.gopclntab == 0: 103 | print('Please set gopclntab and go version first.') 104 | return 105 | GoUtils.parse_types(self.gopclntab, self.go_version) 106 | 107 | def parse_type(self, code=0): 108 | if self.gopclntab == 0: 109 | print('Please set gopclntab and go version first.') 110 | return 111 | print(GoUtils.parse_type(ida_kernwin.get_screen_ea(), self.gopclntab, self.go_version)) 112 | 113 | def retype_func(self, code=0): 114 | GoUtils.retypeFunctions() 115 | 116 | def detect_string(self, code=0): 117 | GoUtils.detect_string() 118 | 119 | def OnFormChange(self, fid): 120 | if self.go_version not in GolangHelper.VERSIONS[self.GetControlValue(self.cGoVers)]: 121 | self.go_version = GolangHelper.VERSIONS[self.GetControlValue(self.cGoVers)][0] 122 | print('Set go version "' + self.go_version + '"') 123 | return 1 124 | 125 | def main(self): 126 | f = self.MyForm() 127 | f.Compile() 128 | ok = f.Execute() 129 | f.Free() 130 | 131 | def GolangHelper_main(ctx): 132 | GolangHelper().main() 133 | 134 | class GolangHelperPlugin(idaapi.plugin_t): 135 | wanted_name = "GolangHelper" 136 | comment, help, wanted_hotkey = "", "", "" 137 | flags = idaapi.PLUGIN_FIX | idaapi.PLUGIN_HIDE | idaapi.PLUGIN_MOD 138 | 139 | class ActionHandler(idaapi.action_handler_t): 140 | def __init__(self, name, label, shortcut=None, tooltip=None, icon=-1, flags=0): 141 | idaapi.action_handler_t.__init__(self) 142 | self.name = name 143 | self.action_desc = idaapi.action_desc_t(name, label, self, shortcut, tooltip, icon, flags) 144 | 145 | def register_action(self, callback, menupath): 146 | self.callback = callback 147 | if not idaapi.register_action(self.action_desc): 148 | return False 149 | if menupath and not idaapi.attach_action_to_menu(menupath, self.name, idaapi.SETMENU_APP): 150 | return False 151 | return True 152 | 153 | def activate(self, ctx): 154 | self.callback(ctx) 155 | 156 | def update(self, ctx): 157 | return idaapi.AST_ENABLE_ALWAYS 158 | 159 | def init(self): 160 | menu_name = "MyTools" 161 | idaapi.create_menu(menu_name, menu_name, "Help") 162 | action = GolangHelperPlugin.ActionHandler("GolangHelper:main", "GolangHelper main") 163 | if action.register_action(GolangHelper_main, menu_name): 164 | print('[+] GolangHelper loaded') 165 | return idaapi.PLUGIN_OK 166 | print('[-] GolangHelper failed to load') 167 | return idaapi.PLUGIN_SKIP 168 | 169 | def run(self, ctx): 170 | return 171 | 172 | def term(self): 173 | return 174 | 175 | def PLUGIN_ENTRY(): 176 | return GolangHelperPlugin() 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Updated 2 | 3 | - 2024 01.23: Add support for 1.22 (updated `GoUtils/PatchNewVersion.py`) 4 | - 2023 08.29: Add support for 1.21 (updated `GolangHelper.py` and added a file `GoUtils/PatchNewVersion.py`) 5 | - 2023 07.03: Updated `get_typename` in `Types.py` 6 | - 2023 06.27: Renamed folder GO_Utils to GoUtils (used in GolangHelper.py) 7 | 8 | # Task Done 9 | 10 | ## Preparation 11 | 12 | Download and place file `GolangHelper.py` and directory `GoUtils` under ida plugin directory, shown as below. 13 | 14 | ``` 15 | |- $IDA_HOME 16 | |- ... 17 | |- plugins 18 | |- ... 19 | |- GoUtils 20 | |- *.py 21 | |- GolangHelper.py 22 | |- ... 23 | |- ... 24 | ``` 25 | 26 | When you open ida, you can see `[+] GolangHelper loaded` in the output window and an extra menu in the menu bar: 27 | 28 | ![image](https://github.com/IchildYu/IDAGolangHelper/assets/54837947/21173e8a-6737-4217-91f3-0587790924e9) 29 | 30 | And, to use GolangHelper, you need a golang binary. 31 | 32 | ## Plugin main 33 | 34 | After loading a golang binary and finishing the initial autoanalysis of ida, just click `GolangHelper main`, and a form emerges. 35 | 36 | ![image](https://github.com/IchildYu/IDAGolangHelper/assets/54837947/117f57fd-b370-4258-a6c0-2557b9614888) 37 | 38 | ## Set function types 39 | 40 | The last 2 functions do not need go version and gopclntab. 41 | 42 | Golang uses a different calling convention. From ida 7.7 this calling convention was introduced and named as `__golang`. But I guess many of us are still using 7.5, so it's difficult recover beautiful pseudocode with F5. 43 | 44 | Luckily, we have another option: `__usercall`(see [Igor’s tip of the week #51: Custom calling conventions – Hex Rays](https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/)). We can set function type with `__usercall` and specify every register in the parameters. But we still need to edit every function type manually, that's painful. 45 | 46 | Later I found something interesting. Most functions would check stack size and call `runtime.morestack_noctxt` at the beginning, which is clear in the pseudocode. But there's something more, this function would save the parameters before going to `runtime.morestack_noctx`, shown as below. 47 | 48 | ![image](https://github.com/IchildYu/IDAGolangHelper/assets/54837947/ee3460a9-d1b8-4c26-843d-6eba3e83cf4f) 49 | 50 | Apparently, we can utilize this and get to know the count of parameters of this function automatically, and use `__usercall`! For return values, it's still difficult to specify more than 2 registers (in fact, we can, but it makes little effect in pseudocode), so I just set 2 regs to return and spoils other 3 regs: `_OWORD __usercall _@([parameters...])`. 51 | 52 | Just try clicking `Set function types`, the effect will be obvious. 53 | 54 | ## Detect strings 55 | 56 | Unlike C string ending with NULL, golang gathers many strings together without NULL, and specifies the length of every string (either in data, as string structure or in code). And it's another pain for ida 7.5, that ida only recognize this whole string. 57 | 58 | `Detect strings` would take them apart. 59 | 60 | ## Detect go version and gopclntab 61 | 62 | Similar to that of original IDAGolangHelper. But this can be wrong. So I added `Set gopclntab manually` option if you can find the correct address. How to find gopclntab manually is not the topic here. 63 | 64 | All functions below rely on gopclntab and version. 65 | 66 | ## Rename functions 67 | 68 | Similar to that of original IDAGolangHelper. 69 | 70 | ## Parse go type names 71 | 72 | Parses only type names. 73 | 74 | ## Parse current go type name 75 | 76 | Place your cursor at the type and click this after setting gopclntab. 77 | 78 | =========================== 79 | 80 | # Original README: 81 | 82 | =========================== 83 | 84 | # IDAGolangHelper 85 | Set of IDA Pro scripts for parsing GoLang types information stored in compiled binary 86 | 87 | 88 | This is update for https://gitlab.com/zaytsevgu/GoUtils2.0 89 | 90 | Differences: 91 | 1. Add support for go1.8 and go1.9, go1.10 (well actually it seems no difference from go1.9) 92 | 2. Automatically add user-defined types to IDA. (Can be checked in Shift+f9 view) 93 | 3. Add some not very advanced string recognition. You can press Shift+S to process current function 94 | 95 | 96 | https://2016.zeronights.ru/wp-content/uploads/2016/12/GO_Zaytsev.pdf - My presentation about Golang reversing 97 | 98 | support go 1.20 but no 1.18(i have no the sample with go 1.18), only works in rename functions 99 | 100 | pcln struct and version magic number: https://go.dev/src/debug/gosym/pclntab.go 101 | 102 | func struct: https://go.dev/src/runtime/runtime2.go 103 | 104 | 105 | ## how to rename functions 106 | 107 | click button 1 "try to detemine go version based on moduledata" 108 | 109 | if not work 110 | 111 | click button 2 "try to detemine go version based on version string" 112 | 113 | then click button 3 rename functions 114 | 115 | ## about go 1.18 116 | 117 | Go 1.18 and Go 1.20 have the same struct in pcln and func table. 118 | 119 | U can add go 1.18 magic number detection in Gopclnatb.findGoPcLn, and add go 1.18 string in __init__.getVersionByString. 120 | 121 | Then use Gopclnatb.check_is_gopclntab18_20, Gopclnatb.rename120 in go 1.18 version 122 | --------------------------------------------------------------------------------