├── huahooks ├── build.sh ├── headers.h └── huahooks.c ├── make-firmware-solid-again ├── lzma.exe └── make-firmware-solid-again.py ├── README.md └── patch-dat-vrp └── patch-dat-vrp.py /huahooks/build.sh: -------------------------------------------------------------------------------- 1 | rm -f huahooks.so 2 | mips64-linux-gnuabi64-gcc -g -O0 -fPIC -shared -o huahooks.so huahooks.c -ldl -lpthread -------------------------------------------------------------------------------- /make-firmware-solid-again/lzma.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embedi/Huawei-firewall-tools/HEAD/make-firmware-solid-again/lzma.exe -------------------------------------------------------------------------------- /huahooks/headers.h: -------------------------------------------------------------------------------- 1 | #define UINT8 unsigned char 2 | #define UINT16 unsigned short 3 | #define UINT32 unsigned int 4 | #define UINT64 unsigned long long 5 | 6 | #define INIT_HOOK(lib, func) _##func = dlsym(lib, #func) 7 | 8 | typedef void (*callback_function)(void); 9 | 10 | typedef struct { 11 | char sProgName [64]; 12 | char sModName [64]; 13 | unsigned char sPattern [128]; 14 | int iPatternLen; 15 | unsigned char sProlog [128]; 16 | unsigned char sHook[32]; 17 | callback_function pfnCallback; 18 | void* pResolvedAddr; 19 | } HOOK_ENTRY; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | These are some tools we developed during [Huawei USG-6330 research](https://embedi.com/blog/first-glance-on-os-vrp-by-huawei/). 2 | 3 | Few tips: 4 | 5 | * To build binaries for the device, you will need [OCTEON-SDK toolchain](http://www.cnusers.org/index.php?option=com_remository&Itemid=32&func=fileinfo&id=181) 6 | * To temporarily put a file to Linux you can do the following - set up the ftp server on your computer, pull a file from it to a flash card of a firewall using VRP commands, log in to Linux and run ldfs_tst_get.out to pull the file to the Linux side. Retrieving files from Linux can be done in a similar manner and is easily automated. 7 | 8 | 9 | 10 | # make-firmware-solid-again 11 | 12 | This tool is used to view, retrieve, replace files from Huawei firmware containers. We tested it with a few firmwares of Huawei USG-6330, and it worked fine, but the code needs to be cleaned and slightly redesigned to work with firmwares of other devices (see object_types). 13 | 14 | View which files firmware contains: 15 | 16 | `python make-firmware-solid-again.py USG6000V100R001C30SPC600.bin -list` 17 | 18 | Retrieve file from firmware: 19 | 20 | `python make-firmware-solid-again.py USG6000V100R001C30SPC600.bin -extract ROOTFS` 21 | 22 | Replace file/files: 23 | 24 | `python make-firmware-solid-again.py USG6000V100R001C30SPC600.bin -filename "MP File!" -replacement VRP_patched -type lzma` 25 | 26 | If you want to add more files, just add another group of `filename replacement type` to the command line. 27 | 28 | Currently, the tool can handle files of two types - lzma (used when replacing VRP, for example) and bin (plain). For lzma compression, we used a separate binary since Python lzma module lacked the required compression parameters. We also could not implement adding of tar.gz archives because Python tarfile module had not those parameters as well. When we needed to replace a .tar.gz we did the following: 29 | 30 | * `tar cf usrbin_usg_mod.tar *; gzip -Nk --best usrbin_usg_mod.tar` 31 | * Replaced file in firmware with `-type bin` 32 | 33 | 34 | # patch-dat-vrp 35 | 36 | As input, it takes the .c file with the code of injection, path to the VRP file, the name of function the code of which will be overwritten, and optionally an address where to trampoline to the body will be written. The tool compiles the source code to the object file and parses it, looking for references to external function names, then resolves them with corresponding function addresses in VRP by patching the assembly code of injection. Finally, it replaces .text section of VRP with patched code. The script handles the VRP functions only, not global variables. So, they have to be hardcoded as addresses in a source of injection. Also, it can inject only one-function .c files – the code of main(). 37 | 38 | You can find a proper example in the article. 39 | 40 | 41 | # huahooks 42 | 43 | The library that implements hooking mechanism can be LD_PRELOAD'ed to every process (modify /etc/mpua.start). You can specify a process name, module name, and pattern you want to be hooked. 44 | For sample usage look at huahooks.c 45 | -------------------------------------------------------------------------------- /patch-dat-vrp/patch-dat-vrp.py: -------------------------------------------------------------------------------- 1 | import argparse, re, struct 2 | from subprocess import check_output 3 | from capstone import * 4 | from elftools.elf.elffile import ELFFile 5 | 6 | # two of these functions can be replaced :) 7 | 8 | lFuncsCanBeReplaced = { 9 | 'cvmx_error_initialize_cn68xx' : None, 10 | 'cvmx_error_initialize_cn66xx' : None, 11 | 'cvmx_error_initialize_cn61xx' : None 12 | } 13 | 14 | COMPILER_PATH = '/home/userr/work/OCTEON-SDK/tools-gcc-4.3/bin/mips64-octeon-linux-gnu-gcc' 15 | 16 | parser = argparse.ArgumentParser(description='VRP patcher') 17 | parser.add_argument('vrp', help='Path to vrp to mess with') 18 | parser.add_argument('injection', help='Path to .c (or object .o) file with code to be injected') 19 | parser.add_argument('-splice_addr', help='Hex string representing virtual address of splice') 20 | parser.add_argument('-donor', help='Name of VRP function to be replace.') 21 | args = parser.parse_args() 22 | 23 | if args.injection[-2:] == '.c': 24 | print '\nCompiling source file...' 25 | # mips64-linux-gnuabi64-gcc 26 | result = check_output([COMPILER_PATH,\ 27 | '-fpic', '-c', args.injection]) 28 | print '\nSuccessfully compiled code!' 29 | args.injection = args.injection[:-2] + '.o' 30 | 31 | print '\nGetting info about functions we can overwrite...' 32 | 33 | if args.donor: 34 | lFuncsCanBeReplaced[args.donor] = None 35 | 36 | vrp = ELFFile(open(args.vrp, 'rb')) 37 | for section in vrp.iter_sections(): 38 | if 'SymbolTableSectio' in str(type(section)): 39 | for sFuncName in lFuncsCanBeReplaced.keys(): 40 | lFuncsCanBeReplaced[sFuncName] = section.get_symbol_by_name(sFuncName)[0].entry.st_value 41 | if lFuncsCanBeReplaced[sFuncName] is None: 42 | print '\nCouldn\'t find symbol for %s, deleting list entry' % sFuncName 43 | lFuncsCanBeReplaced.pop(sFuncName, None) 44 | if args.donor == sFuncName: 45 | print 'Exitting' 46 | exit() 47 | 48 | print '\nParsing object file of code to be injected' 49 | iInjectEOP = None 50 | injection = ELFFile(open(args.injection, 'rb')) 51 | # find unresolved symbols 52 | lToResolve = {} 53 | lWontResolve = ['__gnu_local_gp'] 54 | for section in injection.iter_sections(): 55 | if 'SymbolTableSection' in str(type(section)): 56 | for i in xrange(section.num_symbols()): 57 | t = section.get_symbol(i) 58 | # print '0x%x | 0x%x | %s | 0x%x' % (i, t.entry.st_size, t.name, t.entry.st_value) 59 | if '.' not in t.name and \ 60 | len(t.name) >= 1 and \ 61 | not any(x in t.name for x in lWontResolve): 62 | lToResolve[i] = { 63 | 'name' : t.name, 64 | 'isInternal': False if t.entry.st_size == 0 else True 65 | } 66 | if t.name == 'main': 67 | iInjectEOP = t.entry.st_value 68 | 69 | 70 | # TO DO choose function somehow better 71 | if args.donor: 72 | iBaseOfInjectInVrp = lFuncsCanBeReplaced[args.donor] 73 | else: 74 | iBaseOfInjectInVrp = lFuncsCanBeReplaced['cvmx_error_initialize_cn68xx'] 75 | 76 | # parse relocs and pull 77 | sRelocs = injection.get_section_by_name('.rela.text').data() 78 | for i in xrange(0, len(sRelocs), 0x18): 79 | offset = struct.unpack('>Q', sRelocs[i:i+8])[0] 80 | symbolIndex = struct.unpack('>I', sRelocs[i+8:i+12])[0] 81 | relType = struct.unpack('>I', sRelocs[i+12:i+16])[0] 82 | 83 | if symbolIndex in lToResolve and relType == 0xB: 84 | try: 85 | if lToResolve[symbolIndex]['offset']: 86 | pass 87 | except KeyError: 88 | lToResolve[symbolIndex]['offset'] = [] 89 | 90 | lToResolve[symbolIndex]['offset'].append(offset) 91 | # print '%s | 0x%x | 0x%x' % (lToResolve[symbolIndex]['name'], symbolIndex, lToResolve[symbolIndex]['offset']) 92 | if lToResolve[symbolIndex]['isInternal'] == False: 93 | _object = vrp 94 | msg = 'vrp' 95 | else: 96 | _object = injection 97 | msg = 'injection' 98 | for section in _object.iter_sections(): 99 | if 'SymbolTableSectio' in str(type(section)): 100 | toResolve = section.get_symbol_by_name(lToResolve[symbolIndex]['name']) 101 | if toResolve is None: 102 | print 'Failed to resolve \'%s\' function used in %s' % \ 103 | (lToResolve[symbolIndex]['name'], msg) 104 | exit() 105 | else: 106 | lToResolve[symbolIndex]['resolvedAddr'] = toResolve[0].entry.st_value 107 | if msg == 'injection': 108 | lToResolve[symbolIndex]['resolvedAddr'] += iBaseOfInjectInVrp 109 | 110 | for k in lToResolve.keys(): 111 | if 'offset' not in lToResolve[k]: 112 | del lToResolve[k] 113 | 114 | 115 | print '\nList of functions to resolve with reloc info:' 116 | for i in lToResolve: 117 | print '\t%s of index 0x%x should be resolved at %s with 0x%x' % \ 118 | (lToResolve[i]['name'], i, str(lToResolve[i]['offset']), lToResolve[i]['resolvedAddr']) 119 | 120 | # ------ No optimizations! ------ 121 | # ld $t9, (do_meh & 0xFFFF)($gp) 122 | # jalr $t9 123 | # nop 124 | # ------ Goes to ------ 125 | # nop 126 | # jal the_real_proc_addr 127 | # nop 128 | 129 | 130 | sResolvedCode = injection.get_section_by_name('.text').data() 131 | for i in lToResolve: 132 | for offset in lToResolve[i]['offset']: 133 | sResolvedCode = sResolvedCode[:offset] + \ 134 | '\x00' * 4 + \ 135 | struct.pack('>I', (3 << 26) | lToResolve[i]['resolvedAddr'] >> 2) + '\x00' * 4 + \ 136 | sResolvedCode[offset+12:] 137 | 138 | # rewrite code in VRP binary 139 | 140 | sVrp = open(args.vrp, 'rb').read() 141 | oVrpText = vrp.get_section_by_name('.text') 142 | iVrpTextOffs = oVrpText['sh_offset'] 143 | iVrpTextSize = oVrpText['sh_size'] 144 | iVrpTextAddr = oVrpText['sh_addr'] 145 | # print 'sh_offset == 0x%x' % iVrpTextOffs 146 | # print 'sh_size == 0x%x' % iVrpTextSize 147 | # print 'sh_addr == 0x%x' % iVrpTextAddr 148 | 149 | # replace .text with patched one 150 | oVrpText = sVrp[iVrpTextOffs:iVrpTextOffs+iVrpTextSize] 151 | oVrpText = oVrpText[:iBaseOfInjectInVrp-iVrpTextAddr] + \ 152 | sResolvedCode + \ 153 | oVrpText[iBaseOfInjectInVrp-iVrpTextAddr+len(sResolvedCode):] 154 | 155 | if args.splice_addr: 156 | # inject splice 157 | print '\niInjectEOP at 0x%x' % iInjectEOP 158 | iSpliceAddr = int(args.splice_addr, 16) 159 | # jae INJECTION_ADDR 160 | # nop 161 | # jr $ra 162 | # nop 163 | sSplice = struct.pack('>I', (3 << 26) | (iBaseOfInjectInVrp + iInjectEOP) >> 2) + \ 164 | '\x00' * 4 + '\x03\xE0\x00\x08' + '\x00' * 4 165 | 166 | oVrpText = oVrpText[:iSpliceAddr-iVrpTextAddr] + \ 167 | sSplice + \ 168 | oVrpText[iSpliceAddr-iVrpTextAddr+len(sSplice):] 169 | 170 | # combine ELF back 171 | sVrp = sVrp[:iVrpTextOffs] + \ 172 | oVrpText + \ 173 | sVrp[iVrpTextOffs+iVrpTextSize:] 174 | 175 | 176 | open(args.vrp + '_patched', 'wb').write(sVrp) 177 | print '\nDone!' -------------------------------------------------------------------------------- /huahooks/huahooks.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include "headers.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | extern char *__progname; 14 | 15 | #define NUMBER_OF_HOOKS 10 16 | #define SPLICE_SIZE 7*4 17 | #define INVALID_MOD_BASE 0x1337 18 | 19 | 20 | void fPrintToFile(const char *fmt, ...) { 21 | char buffer[4096]; 22 | memset(buffer, 0, 4096); 23 | va_list args; 24 | va_start(args, fmt); 25 | int rc = vsnprintf(buffer, sizeof(buffer), fmt, args); 26 | va_end(args); 27 | 28 | FILE* f; 29 | f = fopen("/fpath.log", "a"); 30 | fwrite(buffer, strlen(buffer), 1, f); 31 | fclose(f); 32 | } 33 | 34 | void fGetModBaseByName(char *cModName, ModuleRange* pModRange) { 35 | // read /proc/$pid/maps -_- 36 | char cProcStr[256] = {0}; 37 | char cLine[256] = {0}; 38 | size_t len; 39 | ssize_t read; 40 | unsigned char* pStart = INVALID_MOD_BASE; 41 | unsigned char* pEnd = INVALID_MOD_BASE; 42 | 43 | sprintf(cProcStr, "/proc/%i/maps", getpid()); 44 | 45 | FILE* fp = fopen(cProcStr, "r"); 46 | if (!fp) { 47 | fPrintToFile("\tFailed to open \"%s\"\n", cProcStr); 48 | perror("\t"); 49 | } 50 | while ( fgets(cLine, 255, fp) != 0) { 51 | if (strstr(cLine, cModName) != 0) { 52 | char* pMinus = strstr(cLine, "-"); 53 | *pMinus = '\x00'; 54 | pStart = strtol(cLine, 0, 16); 55 | *(pMinus+9+2) = '\x00'; 56 | pEnd = strtol(pMinus+1, 0, 16); 57 | break; 58 | } 59 | } 60 | 61 | pModRange->pStart = pStart; 62 | pModRange->pEnd = pEnd; 63 | } 64 | 65 | 66 | 67 | __attribute__((constructor)) int fInit() { 68 | pthread_mutex_init(&lock, NULL); 69 | 70 | stdout = stderr; 71 | memset(lHookEntries, 0, NUMBER_OF_HOOKS * sizeof(HOOK_ENTRY)); 72 | 73 | void* std = dlopen("libc.so.6", RTLD_LAZY); 74 | if (!std) { 75 | fPrintToFile("\tNo libc.so.6\n"); 76 | return -1; 77 | } 78 | 79 | // set up hooks here like that: 80 | // 81 | // unsigned char sPattern[] = {0x67,0xBD,0xFF,0x70,0xFF,0xBC,0x00,0x78,0x3C,0x1C,0x00,0x03,0xFF,0xB5,0x00,0x60,0x03,0x99,0xE0,0x2D,0xFF,0xB2,0x00,0x48,0x67,0x9C,0x84,0x88,0xFF,0xBF,0x00,0x88,0x00,0xA0,0xA8,0x2D,0xFF,0xBE,0x00,0x80,0x00,0xA0,0x90,0x2D,0xFF,0xB7,0x00,0x70,0xFF,0xB6,0x00,0x68,0xFF,0xB4,0x00,0x58}; 82 | // strcpy(lHookEntries[0].sProgName, "fpath.out"); 83 | // strcpy(lHookEntries[0].sModName, "/lib64/libpthread-2.11.1.so"); 84 | // memcpy(lHookEntries[0].sPattern, sPattern, sizeof(sPattern)); 85 | // lHookEntries[0].iPatternLen = sizeof(sPattern); 86 | // lHookEntries[0].pfnCallback = fHookPthreadCreate; 87 | 88 | 89 | ModuleRange ModRange; 90 | fGetModBaseByName(lHookEntries[0].sModName, &ModRange); 91 | fPrintToFile("fInit | fGetModBaseByName | %p %p\n", ModRange.pStart, ModRange.pEnd); 92 | if (ModRange.pStart != INVALID_MOD_BASE) { 93 | fSetHooks(__progname, lHookEntries[0].sModName, &ModRange); 94 | } 95 | return 0; 96 | } 97 | 98 | void fInstallHook(int iHookId) { 99 | HOOK_ENTRY* oHook = &lHookEntries[iHookId]; 100 | memcpy(oHook->pResolvedAddr, oHook->sHook, SPLICE_SIZE); 101 | cacheflush(oHook->pResolvedAddr, SPLICE_SIZE, BCACHE); 102 | return; 103 | } 104 | 105 | void fRestoreProlog(int iHookId) { 106 | HOOK_ENTRY* oHook = &lHookEntries[iHookId]; 107 | memcpy(oHook->pResolvedAddr, oHook->sProlog, SPLICE_SIZE); 108 | cacheflush(oHook->pResolvedAddr, SPLICE_SIZE, BCACHE); 109 | return; 110 | } 111 | 112 | 113 | // at the moment doesn't jump to original function 114 | void fSetHook(HOOK_ENTRY* oHook) { 115 | // addresses are of 40 bits 116 | 117 | fPrintToFile("\tfSetHook called: pEntry at %p | pfnCallback at %p\n", 118 | oHook->pResolvedAddr, oHook->pfnCallback); 119 | 120 | size_t pagesize = sysconf(_SC_PAGESIZE); 121 | if (mprotect((uintptr_t)oHook->pResolvedAddr & -pagesize, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC)) { 122 | perror("\tmprotect failed\n"); 123 | return; 124 | } 125 | 126 | UINT32 cSplice[SPLICE_SIZE]; 127 | 128 | fPrintToFile("\tHigher part = 0x%x\n", (unsigned int)((UINT64)oHook->pfnCallback >> 24)); 129 | fPrintToFile("\tMiddle part = 0x%x\n", (unsigned int)(((UINT64)oHook->pfnCallback >> 8) & 0xFFFF)); 130 | 131 | // move $at, $zero 132 | cSplice[0] = 0x200824; 133 | // lui $at A(40:24) 134 | cSplice[1] = 0x3C010000 + (unsigned int)((UINT64)oHook->pfnCallback >> 24); 135 | // ori $at, A(24:8) 136 | cSplice[2] = 0x34210000 + (unsigned int)(((UINT64)oHook->pfnCallback >> 8) & 0xFFFF); 137 | // dsll $at, 8 138 | cSplice[3] = 0x10A38; 139 | // ori $at, A(8:0) 140 | cSplice[4] = 0x34210000 + (unsigned int)((UINT64)oHook->pfnCallback & 0xFF); 141 | // jr $at 142 | cSplice[5] = 0x200008; 143 | // move $t9, $at - used in gp calculations 144 | cSplice[6] = 0x20C825; 145 | 146 | if (memcmp(oHook->pResolvedAddr, cSplice, SPLICE_SIZE) == 0) { 147 | fPrintToFile("\tfSetHook tried to hook already hooked function!!!\n"); 148 | return; 149 | } 150 | 151 | memcpy(oHook->sHook, cSplice, SPLICE_SIZE); 152 | memcpy(oHook->sProlog, oHook->pResolvedAddr, SPLICE_SIZE); 153 | memcpy(oHook->pResolvedAddr, oHook->sHook, SPLICE_SIZE); 154 | 155 | return; 156 | } 157 | 158 | 159 | void fSetHooks(char* sProgName, char* sModName, ModuleRange* pModRange) { 160 | fPrintToFile("\tfSetHooks called: sProgName = \"%s\" sModName = \"%s\"\n\t\tpStart = %p\tpEnd = %p\n", sProgName, sModName, pModRange->pStart, pModRange->pEnd); 161 | if (pModRange->pStart == INVALID_MOD_BASE) 162 | return; 163 | for (HOOK_ENTRY* heTemp = lHookEntries; heTemp->iPatternLen != 0; heTemp++) { 164 | if ( strcmp(sProgName, heTemp->sProgName) == 0 && 165 | strcmp(sModName, heTemp->sModName) == 0 ) { 166 | 167 | unsigned char* p = pModRange->pStart; 168 | 169 | while (p < pModRange->pEnd) { 170 | int bMatch = 1; 171 | if (*p == heTemp->sPattern[0]) { 172 | for (unsigned char* i = p; i < p + heTemp->iPatternLen; i++) { 173 | if (*i != heTemp->sPattern[(int) (i - p)]) { 174 | bMatch = 0; 175 | break; 176 | } 177 | } 178 | if (bMatch) { 179 | heTemp->pResolvedAddr = p; 180 | fPrintToFile("\t\tFound pattern at %p\n", heTemp->pResolvedAddr); 181 | break; 182 | } 183 | } 184 | p += 1; 185 | } 186 | 187 | if (heTemp->pResolvedAddr == 0) { 188 | fPrintToFile("\tPattern not found\n"); 189 | return; 190 | } 191 | 192 | fSetHook(heTemp); 193 | } 194 | } 195 | } 196 | 197 | 198 | void* dlopen(const char *filename, int flag) { 199 | fPrintToFile("\t%04i | %s | %s loaded!\n", getpid(), __progname, filename); 200 | link_map *result = ((__typeof__(dlopen) *)dlsym(RTLD_NEXT, __FUNCTION__))(filename, flag); 201 | if (filename == 0) { 202 | filename = __progname; 203 | } 204 | ModuleRange ModRange; 205 | 206 | if (result->l_addr) { 207 | ModRange.pStart = result->l_addr; 208 | ModRange.pEnd = result->l_addr+0x1000; // FIXME 209 | //fSetHooks(__progname, filename, (unsigned char*) result->l_addr); 210 | } else { 211 | char progname [256] = {0}; 212 | progname[0] = '/'; 213 | strcpy(&progname[1], __progname); 214 | 215 | fGetModBaseByName(progname, &ModRange); 216 | } 217 | 218 | fSetHooks(__progname, filename, &ModRange); 219 | 220 | return result; 221 | } -------------------------------------------------------------------------------- /make-firmware-solid-again/make-firmware-solid-again.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tarfile 3 | import struct 4 | import os 5 | from crc16 import crc16xmodem 6 | import string 7 | import random 8 | import argparse 9 | from subprocess import check_output 10 | 11 | 12 | def walklevel(some_dir, level=1): 13 | some_dir = some_dir.rstrip(os.path.sep) 14 | assert os.path.isdir(some_dir) 15 | num_sep = some_dir.count(os.path.sep) 16 | for root, dirs, files in os.walk(some_dir): 17 | yield root, dirs, files 18 | num_sep_this = root.count(os.path.sep) 19 | if num_sep + level <= num_sep_this: 20 | del dirs[:] 21 | 22 | # doesn't compress in a way VRP wants, thus useless 23 | def make_tarfile(output_filename, source_dir): 24 | with tarfile.open(output_filename, "w:gz") as tar: 25 | subdirs = [x[0] for x in walklevel(source_dir)] 26 | subdirs = subdirs[1:] 27 | for subdir in subdirs: 28 | print 'Adding to tar %s | %s' % (subdir, os.path.basename(subdir)) 29 | tar.add(subdir, arcname=os.path.basename(subdir)) 30 | 31 | 32 | def gen_id(): 33 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(7)) 34 | 35 | 36 | class Stream(object): 37 | @staticmethod 38 | def pck(data, size): 39 | if data is None: 40 | data = self.data 41 | try: 42 | s = {1: 'B', 2: '>H', 4: '>I'}[size] 43 | except: 44 | print 'Dont know how to pack; You fucked up with your code' 45 | exit(-1) 46 | try: 47 | return struct.pack(s, data) 48 | except struct.error: 49 | print 'Cant pack dat shit' 50 | return 0 51 | 52 | def unp(self, offset, size, data=None): 53 | if data is None: 54 | data = self.data 55 | try: 56 | s = {1: 'B', 2: '>H', 4: '>I'}[size] 57 | except: 58 | print 'Dont know how to unpack; You fucked up with your code' 59 | exit(-1) 60 | return struct.unpack(s, data[offset:offset + size])[0] 61 | 62 | def read(self, offset, size=0, data=None): 63 | if data is None: 64 | data = self.data 65 | if not size: 66 | return data[offset:] 67 | else: 68 | return data[offset:offset + size] 69 | 70 | 71 | class HuaweiFirmware(Stream): 72 | def __init__(self, data): 73 | self.file_table = FileTable(data) 74 | 75 | def Gen(self): 76 | global object_types_str 77 | self.file_table.FixSelf() 78 | return self.file_table.Gen() 79 | 80 | 81 | class FileTable(Stream): 82 | def __init__(self, data, object_type=None): 83 | self.data = data 84 | self.header1 = self.read(0, 2) 85 | self.crc16 = self.read(2, 2) 86 | self.header2 = self.read(4, 8) 87 | self.number_of_records = self.unp(0xc, 4) 88 | self.some_junk = self.read(0x10, 0x18) 89 | self.crc16_header = self.unp(0x182a, 2) 90 | self.file_records = [] 91 | self.data = None 92 | self.header_padding_size = 0 93 | for i in range(self.number_of_records): 94 | self.file_records.append(FileRecord(data, 0x28 + i * 24)) 95 | 96 | def FixSelf(self): 97 | # fix sizes and crcs 98 | 99 | self.header_padding_size = min(self.file_records, key=lambda x: x.start).start - ( 100 | self.number_of_records * 24 + 0x28) 101 | 102 | for i in self.file_records: 103 | i.FixSelf() 104 | 105 | t = self.file_records 106 | t[0].start = 0x182c 107 | for i in range(1, len(t)): 108 | t[i - 1].padding_size = (t[i - 1].start + t[i - 1].size) % 8 109 | t[i].PrettyPrint() 110 | t[i].start = t[i - 1].start + t[i - 1].size + t[i - 1].padding_size 111 | 112 | 113 | 114 | def Gen(self): 115 | r = self.header2 + self.pck(self.number_of_records, 4) + self.some_junk 116 | k = '' 117 | for i in self.file_records: 118 | t = i.Gen() 119 | r += t[0] 120 | k += t[1] 121 | data = r + '\x00' * (self.header_padding_size-2) 122 | self.crc16_header = crc16xmodem(data[:-2]) 123 | data += self.pck(self.crc16_header, 2) + k 124 | return self.header1 + self.pck(crc16xmodem(data), 2) + data 125 | 126 | 127 | class TarFile(Stream): 128 | def __init__(self, data, object_type=None): 129 | self.data = data 130 | self.foobar = self.read(0, 6) 131 | self.crc16 = self.unp(6, 2) 132 | self.foobar2 = self.read(8, 4) # 01 68 01 01 133 | self.foobar3 = self.read(0xc, 4) # 00 01 00 00 134 | self.foobar4 = self.read(0x10, 0xc) # 00 00 00 01 01 68 01 01 00 00 00 00 135 | self.size = self.unp(0x1c, 4) 136 | self.foobar5 = self.read(0x20, 4) # 00 00 00 00 137 | self.filename = self.read(0x24, 0x20).replace('\x00', '') 138 | self.location = self.read(0x44, 0x93c) 139 | self.object = File(self.data[0x980:]) 140 | self.obj_size = 0 141 | 142 | 143 | def FixSelf(self): 144 | self.object.FixSelf() 145 | self.filename = self.filename + '\x00' * (0x20 - len(self.filename)) 146 | self.obj_size = 0x980 + self.object.obj_size 147 | self.size = self.object.size 148 | self.crc16 = crc16xmodem(self.foobar2 + self.foobar3 + \ 149 | self.foobar4 + self.pck(self.size, 4) + self.foobar5 + \ 150 | self.filename + self.location + self.object.Gen()[:0x180]) 151 | 152 | 153 | def Gen(self): 154 | r = self.foobar + self.pck(self.crc16, 2) + self.foobar2 + self.foobar3 + \ 155 | self.foobar4 + self.pck(self.size, 4) + self.foobar5 + \ 156 | self.filename + self.location + self.object.Gen() 157 | return r 158 | 159 | 160 | class File(Stream): 161 | def __init__(self, data, object_type=None): 162 | global file_types, str_file_types 163 | self.data = data 164 | self.foobar = self.read(0, 8) 165 | self.type = self.unp(8, 2) 166 | self.foobar2 = self.read(0xa, 0x8a) 167 | self.filename = self.read(0x94, 0xe0).replace('\x00', '') 168 | self.size = self.unp(0x174, 4) 169 | self.junk = self.read(0x178, 2) 170 | self.crc16 = self.unp(0x17a, 2) 171 | self.junk2 = self.read(0x17c, 4) 172 | self.data = self.read(0x180, self.size) 173 | self.obj_size = 0 174 | try: 175 | self.object = file_types[object_type](self.data) 176 | except KeyError: 177 | self.object = BinaryBlob(self.data) 178 | 179 | 180 | if self.crc16 != crc16xmodem(self.data): 181 | print 'Meh, CRC is wrong!' 182 | exit(0) 183 | 184 | def FixSelf(self): 185 | self.object.FixSelf() 186 | self.crc16 = crc16xmodem(self.object.Gen()) 187 | self.size = self.object.obj_size 188 | self.obj_size = 0x180 + self.size 189 | self.filename = self.filename + '\x00' * (0xe0 - len(self.filename)) 190 | 191 | def Gen(self): 192 | r = self.foobar + self.pck(self.type, 2) + self.foobar2 + self.filename + self.pck(self.size, 4) + self.junk \ 193 | + self.pck(self.crc16, 2) + self.junk2 + self.object.Gen() 194 | return r 195 | 196 | 197 | class BinaryBlob(Stream): 198 | def __init__(self, data, object_type=None): 199 | self.data = data 200 | self.obj_size = len(self.data) 201 | 202 | def FixSelf(self): 203 | self.obj_size = len(self.data) 204 | 205 | def Gen(self): 206 | return self.data 207 | 208 | 209 | class FileRecord(Stream): 210 | def __init__(self, data, offset): 211 | global object_types, object_types_str 212 | 213 | self.data = data[offset:] 214 | self.minor_object_type = self.unp(0, 2) 215 | self.foobar = self.read(2, 2) 216 | self.start = self.unp(4, 4) 217 | self.size = self.unp(8, 4) 218 | self.some_flag = self.unp(0xc, 2) 219 | self.crc16 = self.unp(0xe, 2) 220 | self.major_object_type = self.unp(0x10, 4) 221 | self.foobar2 = self.read(0x14, 4) 222 | self.data = None 223 | self.padding_size = 0 224 | self.object = object_types[self.major_object_type](data[self.start:self.start + self.size], self.minor_object_type) 225 | 226 | def PrettyPrint(self): 227 | global object_types_str 228 | 229 | if object_types_str[self.major_object_type] == 'File': 230 | pass 231 | elif object_types_str[self.major_object_type] == 'FileTable': 232 | t = 0x28 + self.object.number_of_records * 24 233 | t += self.object.header_padding_size 234 | # plus sizes of all files 235 | for i in self.object.file_records: 236 | t += i.size 237 | elif object_types_str[self.major_object_type] == 'BinaryBlob': 238 | pass 239 | 240 | def FixSelf(self): 241 | global object_types_str 242 | 243 | self.object.FixSelf() 244 | 245 | ot = object_types_str[self.major_object_type] 246 | if ot == 'File': 247 | self.size = self.object.obj_size#len(self.object.data) + 0x180 248 | self.crc16 = self.object.crc16#crc16xmodem(self.data[0x180:]) # TODO: FIX CRC FOR ALL TYPE OF FILES 249 | self.object.crc16 = self.crc16 250 | 251 | elif ot == 'BinaryBlob': 252 | self.size = self.object.obj_size#len(self.object.data) 253 | 254 | elif ot == 'FileTable': 255 | self.size = 0x28 + self.object.number_of_records * 24 256 | self.size += self.object.header_padding_size 257 | # plus sizes of all files 258 | for i in self.object.file_records: 259 | self.size += i.size + i.padding_size 260 | 261 | def Gen(self): 262 | self.PrettyPrint() 263 | file_record = self.pck(self.minor_object_type, 2) + self.foobar + self.pck(self.start, 4) \ 264 | + self.pck(self.size, 4) + self.pck(self.some_flag,2) + self.pck(self.crc16, 2) + \ 265 | self.pck(self.major_object_type, 4) + self.foobar2 266 | _object = self.object.Gen() + '\x00' * self.padding_size 267 | return [file_record, _object] 268 | 269 | file_types = { 270 | 0x810e: TarFile 271 | } 272 | 273 | file_types_str = { 274 | 0x810e: 'TarFile' 275 | } 276 | 277 | 278 | def FindFilesWithGivenName(object, name, level=0, verbose=False): 279 | results = [] 280 | t = str(type(object)) 281 | n = '' 282 | try: 283 | n = object.filename.replace('\x00', '').replace('\n', '').replace('\r', '') 284 | except: 285 | pass 286 | 287 | if ".FileTable'" in t: 288 | if verbose: 289 | print '%s ------ FILETABLE ------' % ('\t' * level) 290 | for i in object.file_records: 291 | results += FindFilesWithGivenName(i.object, name, level+1, verbose=verbose) 292 | 293 | # for both TarFile and File 294 | elif "File'" in t: 295 | results += FindFilesWithGivenName(object.object, name, level+1, verbose=verbose) 296 | prettyName = object.filename.replace('\x00', '').replace('\n', '').replace('\r', '') 297 | if verbose: 298 | pass 299 | print '%s "%s"' % ('\t' * level, prettyName) 300 | if name == prettyName: 301 | results.append(object) 302 | 303 | else: 304 | pass 305 | 306 | return results 307 | 308 | 309 | def GetPathForObject(cur_object, object): 310 | if 'FileTable' in str(type(cur_object)): 311 | path = ['File Table of %i records' % cur_object.number_of_records] 312 | for i in cur_object.file_records: 313 | t = GetPathForObject(i.object, object) 314 | if t != []: 315 | return path + t 316 | 317 | try: 318 | path = [cur_object.filename] 319 | except AttributeError: 320 | return [] 321 | 322 | if cur_object == object: 323 | return path 324 | t = GetPathForObject(cur_object.object, object) 325 | if t != []: 326 | return path+t 327 | else: 328 | return [] 329 | 330 | def PrettyPrintPath(path): 331 | r = '' 332 | for i in path: 333 | r += '[%s] --> ' % i 334 | return r[:-len(' --> ')] 335 | 336 | 337 | object_types = { 338 | 65536: File, 339 | 65606: File, 340 | 196617: File, 341 | 65542: File, 342 | 196616: File, 343 | 7: FileTable, 344 | 0: File, 345 | 1: FileTable, 346 | 196615: File, 347 | 65537: File, 348 | 65541: File, 349 | 65544: File, 350 | 60: File, 351 | 61: File, 352 | 1179048851: BinaryBlob, 353 | 1162279811: BinaryBlob, 354 | 38: BinaryBlob, 355 | 48: BinaryBlob 356 | } 357 | 358 | object_types_str = { 359 | 65536: 'File', 360 | 65606: 'File', 361 | 196617: 'File', 362 | 65542: 'File', 363 | 196616: 'File', 364 | 7: 'FileTable', 365 | 0: 'File', 366 | 1: 'FileTable', 367 | 196615: 'File', 368 | 65537: 'File', 369 | 65541: 'File', 370 | 65544: 'File', 371 | 60: 'File', 372 | 61: 'File', 373 | 1179048851: 'BinaryBlob', 374 | 1162279811: 'BinaryBlob', 375 | 38: 'BinaryBlob', 376 | 48: 'BinaryBlob' 377 | } 378 | 379 | parser = argparse.ArgumentParser(description='Huawei firmware toolkit') 380 | parser.add_argument('firmware', help='Path to firmware to mess with') 381 | parser.add_argument('-filename', action='append', help='Name of file in the firmware filesystem to be replaced') 382 | parser.add_argument('-replacement', action='append', help='Path to file containing new content of replaced file') 383 | parser.add_argument('-type', action='append', help='Type of payload: bin, tar.gz, lzma. Default is plain bin') 384 | parser.add_argument('-extract', action='append', help='Filename to be extracted') 385 | parser.add_argument('-list', action='store_true', help='Just show which files are in firmware') 386 | args = parser.parse_args() 387 | 388 | if not args.list and not args.extract: 389 | if len(args.replacement) != len(args.type) or \ 390 | len(args.replacement) != len(args.filename): 391 | print '\n\tFor every -replacement value you should pass -filename and -type' 392 | print '\n\t\tFilename: %i' % len(args.filename) 393 | print '\n\t\tReplacement: %i' % len(args.replacement) 394 | print '\n\t\tType: %i' % len(args.type) 395 | 396 | exit(0) 397 | 398 | print '\n\tParsing firmware...' 399 | a = HuaweiFirmware(open(args.firmware, 'rb').read()) 400 | 401 | if args.list: 402 | print '\nFiles in dat firmware:' 403 | files = FindFilesWithGivenName(a.file_table, '', verbose=True) 404 | exit() 405 | 406 | if args.extract: 407 | files = FindFilesWithGivenName(a.file_table, args.extract[0]) 408 | if len(files) == 0: 409 | print 'There are no files in this firmware with such name. Exitting' 410 | exit(-1) 411 | elif len(files) > 1: 412 | print 'Found multiple files with the same name. Choose one of them to replace:' 413 | j = 1 414 | for i in files: 415 | print j 416 | print '\t%i) Name: %s | Size: 0x%x | Crc16: 0x%x' % (j, i.filename, i.size, i.crc16) 417 | print '\t\t%s' % PrettyPrintPath(GetPathForObject(a.file_table, i)) 418 | j += 1 419 | 420 | try: 421 | index = int(raw_input('# : ')) 422 | except ValueError: 423 | print "That's not a number!" 424 | exit(-1) 425 | 426 | try: 427 | to_be_extracted = files[index-1] 428 | except IndexError: 429 | print 'Number out of range, motherfucker!' 430 | exit(-1) 431 | else: 432 | to_be_extracted = files[0] 433 | 434 | try: 435 | d = to_be_extracted.object.object.data 436 | except: 437 | d = to_be_extracted.object.data 438 | open(args.extract[0], 'wb').write(d) 439 | 440 | print '\n\tExtracted to "%s"' % args.extract[0] 441 | exit() 442 | 443 | for repl in range(len(args.replacement)): 444 | # let's try to find file with given name 445 | print '\n\tProcessing (%s <- %s)' % (args.filename[repl], 446 | args.replacement[repl]) 447 | 448 | files = FindFilesWithGivenName(a.file_table, args.filename[repl]) 449 | 450 | # get rid of "branch" objects, cus we need only "leafs" 451 | i = 0 452 | while i < len(files): 453 | if ".File'" in str(type(files[i])) and 'BinaryBlob' not in str(type(files[i].object)): 454 | del files[i] 455 | continue 456 | i += 1 457 | 458 | if len(files) == 0: 459 | print 'There are no files in this firmware with such name. Exitting' 460 | exit(-1) 461 | elif len(files) > 1: 462 | print 'Found multiple files with the same name. Choose one of them to replace:' 463 | j = 1 464 | for i in files: 465 | print j 466 | print '\t%i) Name: %s | Size: 0x%x | Crc16: 0x%x' % (j, i.filename, i.size, i.crc16) 467 | print '\t\t%s' % PrettyPrintPath(GetPathForObject(a.file_table, i)) 468 | j += 1 469 | 470 | try: 471 | index = int(raw_input('# : ')) 472 | except ValueError: 473 | print "That's not a number!" 474 | exit(-1) 475 | 476 | try: 477 | to_be_mod = files[index-1] 478 | except IndexError: 479 | print 'Wrong number!' 480 | exit(-1) 481 | else: 482 | to_be_mod = files[0] 483 | 484 | sNewFile = open(args.replacement[repl], 'rb').read() 485 | if args.type[repl] == 'bin': 486 | try: 487 | to_be_mod.object.object.data = sNewFile 488 | except AttributeError: 489 | to_be_mod.object.data = sNewFile 490 | 491 | # VRP CAN'T HANDLE THEM, DO NOT USE THIS OPTION 492 | elif args.type[repl] == 'tar.gz': 493 | to_be_mod.object.object.data = open(args.replacement[repl], 'rb').read() 494 | 495 | elif args.type[repl] == 'lzma': 496 | print '\n\t\tCompressing file...' 497 | t = gen_id() 498 | check_output(['lzma.exe', 'e', args.replacement[repl], \ 499 | t, '-lc4', '-lp2', '-pb2', '-d23']) 500 | sCompressed = open(t, 'rb').read() 501 | to_be_mod.object.data = sCompressed 502 | os.remove(t) 503 | 504 | print '\n\tGenerating new firmware' 505 | open(args.firmware + '_mod.bin', 'wb').write(a.Gen()) 506 | print '\n\tDone!' --------------------------------------------------------------------------------