This post explains the process of reversing about the pwnable problem, nolibc from the Project SEKAI CTF 2024.
Total reversed source code is located in Github below.
Everything is stripped.
Ferthermore, it's hard to solve strip because executable file doesn't use libc.
Why call all the function calls use syscall
[Fig 1] Stored syscall numbers in .data segment
__int64 __fastcall printf(unsigned __int8 *a1)
{
__int64 result; // rax
while ( 1 ) {
result = *a1;
if ( !(_BYTE)result )
break;
__asm { syscall; LINUX - }
++a1;
}
return result;
}
[Fig 2] Assembly of
printf
function - 1
[Fig 3] Assembly of
printf
function - 2
[Fig 3] has information of syscall which is called from printf
function.
These informations are summarized in the table below.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
write | 0x01 | 1 | rdi (a1) | 1 |
printf(unsigned __int8* a1)
function
: Print the string indicated by receiveda1
one byte at a time until Null terminator.
__int64 __fastcall printfn(unsigned __int8 *a1)
{
__int64 result; // rax
printf(a1);
result = syscall_write;
__asm { syscall; LINUX - }
return result;
}
[Fig 4] Assembly of
printfn
function
[Fig 5] The value stored at
unk_3000
[Fig 4] has information of syscall which is called from printfn
function.
These informations are summarized in the table below.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
write | 0x01 | 1 | address of '\n' | 1 |
printfn(unsigned __int8* a1)
function
: Print the string indicated by receiveda1
one byte at a time until Null terminator.
Then add the'\n'
at the end
__int64 __fastcall read_Helper(__int64 a1, signed int length)
{
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; (int)i < length; ++i ) {
v8 = syscall_read; // 0
v7 = 0LL;
v6 = &v3;
v5 = 1LL;
__asm { syscall; LINUX - } // read(0, v6, 1)
v4 = syscall_read;
if ( v3 == '\n' ) { // enter
*(_BYTE *)((int)i + a1) = 0;
return i;
}
*(_BYTE *)(a1 + (int)i) = v3;
}
return i;
}
[Fig 6] Assembly of
read_Helper
function - 1
[Fig 7] Assembly of
read_Helper
function - 2
[Fig 8] Assembly of
read_Helper
function - 3
[Fig 7] has information of syscall which is called from read_Helper
function.
These informations are summarized in the table below.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
read | 0x00 | 0 | address of v3 (rbp-0x31) | 1 |
read_Helper(__int64 a1, signed int length)
함수의 기능
: Read input one byte at a time up to thelength
and write it to the memory pointed to bya1
.
If'\n'
is received within thelength
limit, convert the'\n'
to a NULL terminator, and end the input.
If'\n'
isn't received, end the input without adding a NULL terminator.
After the input ends, return the length of the received string.
__int64 custom_scanf()
{
char v1[36]; // [rsp+0h] [rbp-30h] BYREF
int v2; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
unsigned int v4; // [rsp+2Ch] [rbp-4h]
v4 = 0;
v2 = read_Helper((__int64)v1, 32);
for ( i = 0; i < v2; ++i ) {
if ( v1[i] <= 0x2F || v1[i] > 0x39 ) // only '1' ~ '9'
return 0xFFFFFFFFLL;
v4 = 10 * v4 + v1[i] - '0';
}
return v4;
}
custom_scanf()
function
: Receive 32 byte length string that consists only of decimal numbers,
return the integer converted from the input string
__int64 __fastcall strlen_includeNULL(__int64 a1)
{
unsigned int i; // [rsp+14h] [rbp-4h]
for ( i = 0; *(_BYTE *)((int)i + a1); ++i )
;
return i;
}
strlen_includeNULL(__int64 a1)
functions
: Return the length of the string pointed by receiveda1
.
This function calculates the length of the string including the NULL terminator.
_BOOL8 __fastcall strcmp(__int64 a1, __int64 a2)
{
int len_a1; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]
len_a1 = strlen_includeNULL(a1);
if ( len_a1 != (unsigned int)strlen_includeNULL(a2) )
return 0LL;
for ( i = 0; i < len_a1 && *(_BYTE *)(i + a1) == *(_BYTE *)(i + a2); ++i )
;
return !*(_BYTE *)(i + a1) && !*(_BYTE *)(i + a2) && len_a1 == i;
}
strcmp(__int64 a1, __int64 a2)
function
: Determine whether two strings pointed by receiveda1
anda2
are equals and return the result.
If two strings are the same then return 1, if not return 0.
void __noreturn exit()
{
__asm { syscall; LINUX - }
}
[Fig 9] Assembly of
exit
function
[Fig 9] has information of syscall which is called from exit
function.
These informations are summarized in the table below.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
exit | 0x3c | 0 | ????? | 0 |
exit()
function
: Terminate the program
__int64 __fastcall printInt(int a1)
{
int v1; // eax
int v2; // eax
int v3; // eax
int v5; // [rsp+Ch] [rbp-34h]
unsigned __int8 v6[35]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int8 v7; // [rsp+33h] [rbp-Dh]
int v8; // [rsp+34h] [rbp-Ch]
int i; // [rsp+38h] [rbp-8h]
int v10; // [rsp+3Ch] [rbp-4h]
v5 = a1;
v10 = 0;
if ( a1 ) {
if ( a1 < 0 ) {
v2 = v10++;
v6[v2] = 45;
v5 = -a1;
}
v8 = v10;
while ( v5 ) {
v3 = v10++;
v6[v3] = (char)v5 % 10 + 48;
v5 /= 10;
}
for ( i = v8; i < v10 / 2; ++i ) {
v7 = v6[i];
v6[i] = v6[v10 - i - 1];
v6[v10 - i - 1] = v7;
}
} else {
v1 = v10++;
v6[v1] = 48;
}
v6[v10] = 0;
return printf(v6);
}
printInt(int a1)
함수의 기능
: Convert received integer,a1
, to string and print it.
__int64 __fastcall strstr(__int64 a1, __int64 a2)
{
int j; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; *(_BYTE *)(i + a1); ++i ) {
for ( j = 0; *(_BYTE *)(j + a2) && *(_BYTE *)(i + j + a1) == *(_BYTE *)(j + a2); ++j )
;
if ( !*(_BYTE *)(j + a2) )
return 1LL;
}
return 0LL;
}
strstr(__int64 a1, __int64 a2)
function
: Determine whether the strings pointed by receiveda2
has the string pointed by receiveda1
and return the result.
If the string pointed bya2
has the string pointed bya1
then return 1, if not return 0.
// (filename, v2, v6)
__int64 open_write_close()
{
__asm { syscall; LINUX - }
if ( syscall_open < 0 )
return 0xFFFFFFFFLL;
__asm
{
syscall; LINUX -
syscall; LINUX -
}
return (unsigned int)syscall_write;
}
[Fig 10] Assembly of
open_write_close
function - 1
[Fig 11] Assembly of
open_write_close
function - 2
[Fig 12] Assembly of
open_write_close
function - 3
[Fig 13] Assembly of
open_write_close
function - 4
[Fig 11], [Fig 12], [Fig 13] has information of 3 syscalls which are called from open_write_close
function.
These informations are summarized in the table below in the order of calls.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) | return |
---|---|---|---|---|---|
open | 0x02 | rdi (filename) | 0x41 (O_CREAT | O_WRONLY) | 0x1b6 (0u0666 : rw-rw-rw-) |
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
write | 0x01 | fd (eax) | rsi (v2) | edx |
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
close | 0x03 | fd | 0 | 0 |
open_write_close()
function
: Openfilename
file,
Readv6
byte from the memory pointed byv2
and write it intofilename
file,
Closefilename
file.
// (path, buf, 0x7FFF)
__int64 open_read_close()
{
__asm { syscall; LINUX - }
if ( syscall_open < 0 ) // read only로 path 파일 열기
return 0xFFFFFFFFLL;
__asm
{
syscall; LINUX - // 읽은 내용을 buf에 넣기
syscall; LINUX -
} // close
return (unsigned int)syscall_read;
}
[Fig 14] Assembly of
open_read_close
function - 1
[Fig 15] Assembly of
open_read_close
function - 2
[Fig 16] Assembly of
open_read_close
function - 3
[Fig 17] Assembly of
open_read_close
function - 4
[Fig 15], [Fig 16], [Fig 17] has information of 3 syscalls which are called from open_read_close
function.
These informations are summarized in the table below in the order of calls.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) | return |
---|---|---|---|---|---|
open | 0x02 | rdi (path) | 0 | 0 | fd (rax) |
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
read | 0x00 | fd (rax) | rsi (buf) | edx (0x7FFF) |
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
---|---|---|---|---|
close | 0x03 | fd | 0 | 0 |
open_read_close()
function
: Openpath
file,
Read0x7FFF (edx)
byte from this file and read this to the memory pointed bybuf
,
Closepath
file.
// 1 <= size <= 257 or 32 or 0x7FFF or large
int *__fastcall malloc_guess(int size)
{
__int64 v2; // [rsp+4h] [rbp-20h]
signed int aligned_size; // [rsp+10h] [rbp-14h]
__int64 v4; // [rsp+14h] [rbp-10h]
__int64 v5; // [rsp+1Ch] [rbp-8h]
if ( !size )
return 0LL;
aligned_size = (size + 15) & 0xFFFFFFF0;
v5 = first_freed_heap;
v4 = 0LL;
while ( 1 ) {
if ( !v5 )
return 0LL;
if ( aligned_size <= *(_DWORD *)v5 )
break;
v4 = v5;
v5 = *(_QWORD *)(v5 + 8);
}
if ( *(int *)v5 >= (unsigned __int64)(aligned_size + 16LL) ) {
v2 = aligned_size + 16LL + v5;
*(_DWORD *)v2 = *(_DWORD *)v5 - aligned_size - 16;
*(_QWORD *)(v2 + 8) = *(_QWORD *)(v5 + 8);
*(_QWORD *)(v5 + 8) = v2;
*(_DWORD *)v5 = aligned_size;
}
if ( v4 )
*(_QWORD *)(v4 + 8) = *(_QWORD *)(v5 + 8);
else
first_freed_heap = *(_QWORD *)(v5 + 8);
return (int *)(v5 + 16);
}
We can find the code below while analyzing the decompiled binary.
sub_12C8("Enter a string: ");
v1 = sub_1031((unsigned int)(v2 + 1));
if ( !v1 )
{
sub_1322("Failed to allocate memory");
sub_1322(&unk_3124);
sub_18E6(&unk_3124);
}
In the above code, we can guess if the return value of the function, sub_1031
is 0 then this binary prints Failed to allocate memory
. (If you understand at least 2 functions related to print (printf
, printfn
) then you can confidence sub_1322
prints given string.)
At this point, we can guess that the function, sub_1031
is related to allocating the memory same as the function, malloc
. If you analyze the process of this function then you can be confident that sub_1031
performs the malloc
.
I wrote the process of this function at [English] Project SEKAI CTF 2024 - nolibc : Attack Surface in malloc_guess.
In addition, you can find the function performs the free
, if you do cross-reference to parameter on sub_1031
or the function called after sub_1031
is called from the function which calls sub_1031
.
Additionally, by cross-referencing the parameters passed on to sub_1031
, or analyzing the functions called after sub_1031
is called, we can also find the functions that function as free
.
int *__fastcall free_guess(unsigned __int64 ptr)
{
int *result; // rax
unsigned __int64 free_heap_header; // [rsp+18h] [rbp-18h]
unsigned __int64 prev_heap_header; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
if ( ptr ) {
result = (int *)&unk_5000;
if ( ptr >= (unsigned __int64)&unk_5000 ) {
result = &syscall_read;
if ( ptr < (unsigned __int64)&syscall_read ) {
free_heap_header = ptr - 16;
v4 = first_freed_heap;
prev_heap_header = 0LL;
while ( v4 && v4 < free_heap_header ) {
// go to the heap right before the ptr heap
prev_heap_header = v4;
v4 = *(_QWORD *)(v4 + 8);
}
if ( prev_heap_header ) {
*(_QWORD *)(free_heap_header + 8) = *(_QWORD *)(prev_heap_header + 8);
*(_QWORD *)(prev_heap_header + 8) = free_heap_header;
} else {
// modify freed heap, connect previous freed heap
*(_QWORD *)(free_heap_header + 8) = first_freed_heap;
first_freed_heap = ptr - 16;
}
return (int *)merge_guess();
}
}
}
return result;
}
__int64 merge_guess()
{
__int64 result; // rax
int v1; // [rsp+0h] [rbp-Ch]
int *heap_header; // [rsp+4h] [rbp-8h]
result = first_freed_heap;
heap_header = (int *)first_freed_heap;
while ( heap_header ) {
result = *((_QWORD *)heap_header + 1);
if ( !result )
break;
if ( (int *)((char *)heap_header + *heap_header + 16) == *((int **)heap_header + 1) ) {
*heap_header += **((_DWORD **)heap_header + 1) + 16;
result = (__int64)heap_header;
*((_QWORD *)heap_header + 1) = *(_QWORD *)(*((_QWORD *)heap_header + 1) + 8LL);
} else {
result = *((_QWORD *)heap_header + 1);
heap_header = (int *)result;
}
}
if ( heap_header ) {
v1 = (_DWORD)&unk_10000 - ((char *)heap_header - (char *)&unk_5000);
result = (unsigned int)*heap_header;
if ( v1 > (int)result ) {
result = (__int64)heap_header;
*heap_header = v1 - 16;
}
}
return result;
}
The process pf above two functions
prev_heap_header
), which is located at a lower memory address than the current chunk to be freed (free_heap_header
), points to the current chunk.free_heap_header
) to point to the chunk that the previously freed chunk (prev_heap_header
) was pointing to. [Fig 18] The process of
free_guess
& merge_guess
example - 1
[Fig 19] The process of
free_guess
& merge_guess
example - 2
void __noreturn start()
{
int choice_login_reg_exit; // [rsp+8h] [rbp-8h]
int choice_UserMenu; // [rsp+Ch] [rbp-4h]
init();
while ( 1 ) {
printfn((__int64)"Welcome to String Storage!");
printfn((__int64)"Please login or register an account to continue :)");
printfn((__int64)&enter); // 개행?
while ( UserIndex == -1 ) {
printfn((__int64)"1. Login");
printfn((__int64)"2. Register");
printfn((__int64)"3. Exit");
printf("Choose an option: ");
choice_login_reg_exit = custom_scanf();
printfn((__int64)&enter); // 개행?
if ( choice_login_reg_exit == 1 ) {
login();
} else if ( choice_login_reg_exit == 2 ) {
register();
} else {
if ( choice_login_reg_exit == 3 )
exit();
printfn((__int64)"Invalid option");
}
printfn((__int64)&enter);
}
printf("Welcome to String Storage, ");
printf(*(unsigned __int8 **)UserInfo_struct_list[UserIndex]);
printfn((__int64)"!");
printfn((__int64)&enter);
while ( 1 ) {
printfn((__int64)"1. Add string");
printfn((__int64)"2. Delete string");
printfn((__int64)"3. View strings");
printfn((__int64)"4. Save to File");
printfn((__int64)"5. Load from File");
printfn((__int64)"6. Logout");
printf("Choose an option: ");
choice_UserMenu = custom_scanf();
printfn((__int64)&enter);
switch ( choice_UserMenu ) {
case 1:
AddString();
goto LABEL_26;
case 2:
DelString();
goto LABEL_26;
case 3:
ViewString();
goto LABEL_26;
case 4:
SaveFile();
goto LABEL_26;
case 5:
LoadFile();
goto LABEL_26;
}
if ( choice_UserMenu == 6 )
break;
printfn((__int64)"Invalid option");
LABEL_26:
printfn((__int64)&enter);
}
UserIndex = -1;
}
}
void *init() {
void *result; // rax
first_freed_heap = (__int64)&unk_5000;
unk_5000 = (_DWORD)&unk_10000;
result = &unk_5000;
*((_QWORD *)&unk_5000 + 1) = 0LL;
return result;
}
__int64 login()
{
__int64 password; // [rsp+8h] [rbp-18h]
__int64 name; // [rsp+10h] [rbp-10h]
int i; // [rsp+1Ch] [rbp-4h]
printf("Username: ");
name = malloc_guess(64LL);
if ( name ) {
read_Helper(name, 64);
if ( (unsigned int)strlen_includeNULL(name) ) {
printf("Password: ");
password = malloc_guess(64LL);
if ( password ) {
read_Helper(password, 64);
if ( (unsigned int)strlen_includeNULL(password) ) {
if ( NumUser ) {
for ( i = 0; i < NumUser; ++i ) {
if ( (unsigned int)strcmp(**((_QWORD **)&UserInfo_struct_list + i), name)// UserInfo_struct = &name; &password
&& (unsigned int)strcmp(*(_QWORD *)(*((_QWORD *)&UserInfo_struct_list + i) + 8LL), password) ) {
UserIndex = i; // 찾은 User의 index
}
}
if ( UserIndex == -1 )
printfn((__int64)"Invalid username or password");
else
printfn((__int64)"Logged in successfully!");
free_guess(name);
return free_guess(password);
} else {
return printfn((__int64)"No users registered");
}
} else {
printfn((__int64)"Invalid password");
free_guess(password);
return login();
}
} else {
printfn((__int64)"Invalid password");
free_guess(0LL);
return login();
}
} else {
printfn((__int64)"Invalid username");
free_guess(name);
return login();
}
} else {
printfn((__int64)"Invalid username");
free_guess(0LL);
return login();
}
}
__int64 register()
{
struct_UserInfo_struct *UserInfo_struct; // [rsp+8h] [rbp-18h]
int *password; // [rsp+10h] [rbp-10h]
int *name; // [rsp+18h] [rbp-8h]
if ( NumUser > 0 )
return printfn((__int64)"You can only register one account!");
printf("Username: ");
name = malloc_guess(32);
if ( name ) {
read_Helper((__int64)name, 32);
if ( (unsigned int)strlen_includeNULL((__int64)name) ) {
printf("Password: ");
password = malloc_guess(32);
if ( password ) {
read_Helper((__int64)password, 32);
if ( (unsigned int)strlen_includeNULL((__int64)password) ) {
UserInfo_struct = (struct_UserInfo_struct *)malloc_guess(0x4010);
UserInfo_struct->UserName = name;
UserInfo_struct->PassWord = password;
UserInfo_struct->strIndex = 0;
UserInfo_struct_list[NumUser++] = UserInfo_struct;
return printfn((__int64)"User registered successfully!");
} else {
printfn((__int64)"Invalid password");
free_guess((unsigned __int64)password);
return register();
}
} else {
printfn((__int64)"Invalid password");
free_guess(0LL);
return register();
}
} else {
printfn((__int64)"Invalid username");
free_guess((unsigned __int64)name);
return register();
}
} else {
printfn((__int64)"Invalid username");
free_guess(0LL);
return register();
}
}
UserName
.PassWord
.UserInfo_struct
which stores information about User. [Fig 20]
UserInfo_struct
structure
+) strIndex
stores the number of strings stored in the current UserInfo_struct
- 1.
__int64 AddString()
{
int *strbuf; // [rsp+0h] [rbp-10h]
int strlength; // [rsp+Ch] [rbp-4h]
if ( *(int *)(UserInfo_struct_list[UserIndex] + 16LL) > 2046 )
return printfn((__int64)"You have reached the maximum number of strings");
printf("Enter string length: ");
strlength = custom_scanf();
if ( strlength > 0 && strlength <= 256 ) {
printf("Enter a string: ");
strbuf = malloc_guess(strlength + 1);
if ( !strbuf ) {
printfn((__int64)"Failed to allocate memory");
printfn((__int64)&enter);
exit();
}
read_Helper((__int64)strbuf, strlength + 1);
*(_QWORD *)(UserInfo_struct_list[UserIndex]
+ 8 * ((int)(*(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL))++ + 2LL)
+ 8) = strbuf;
return printfn((__int64)"String added successfully!");
} else {
printfn((__int64)"Invalid length");
return printfn((__int64)&enter);
}
}
Receive an integer between 1 and 256, allocate memory of that length (+1) and store the input string in the allocated memory.
Then, store the address of the saved string in the UserInfo_struct
structure.
The string can have a maximum length of 2047 (0x7FF) characters.
__int64 DelString()
{
int del_Index; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
if ( *(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL) ) {
printf("Enter the index of the string to delete: ");
del_Index = custom_scanf();
if ( del_Index >= 0 && del_Index < *(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL) ) {
free_guess(*(_QWORD *)(UserInfo_struct_list[UserIndex] + 8 * (del_Index + 2LL) + 8));
for ( i = del_Index; i < *(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL) - 1; ++i )
*(_QWORD *)(UserInfo_struct_list[UserIndex] + 8 * (i + 2LL) + 8) = *(_QWORD *)(UserInfo_struct_list[UserIndex]
+ 8 * (i + 1 + 2LL)
+ 8);
--*(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL);
return printfn((__int64)"String deleted successfully!");
} else {
printfn((__int64)"Invalid index");
return printfn((__int64)&enter);
}
} else {
printfn((__int64)"No strings to delete");
return printfn((__int64)&enter);
}
}
Receive an index, delete the string stored at that index, and free that memory space.
Then, iterate through the UserInfo_struct
structure and shift the string pointers forward by one position to fill the space left by the deleted string pointer.
__int64 ViewString()
{
__int64 result; // rax
int i; // [rsp+Ch] [rbp-4h]
if ( *(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL) ) {
for ( i = 0; ; ++i ) {
result = *(unsigned int *)(UserInfo_struct_list[UserIndex] + 16LL);
if ( i >= (int)result )
break;
printf("String ");
printInt((unsigned int)i);
printf(": ");
printfn(*(_QWORD *)(UserInfo_struct_list[UserIndex] + 8 * (i + 2LL) + 8));
}
} else {
printfn((__int64)"No strings to view");
return printfn((__int64)&enter);
}
return result;
}
int *SaveFile()
{
int v1; // [rsp+8h] [rbp-28h]
int *buf; // [rsp+10h] [rbp-20h]
int *filename; // [rsp+18h] [rbp-18h]
int j; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
unsigned int v6; // [rsp+2Ch] [rbp-4h]
printf("Enter the filename: ");
filename = malloc_guess(32);
if ( filename
&& (read_Helper((__int64)filename, 32), (unsigned int)strlen_includeNULL((__int64)filename))
&& !(unsigned int)strstr((__int64)filename, (__int64)"flag") ) {
buf = malloc_guess(0x7FFF);
if ( !buf ) {
printfn((__int64)"Failed to allocate memory");
printfn((__int64)&enter);
exit();
}
v6 = 0;
for ( i = 0; i < *(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL); ++i ) {
v1 = strlen_includeNULL(*(_QWORD *)(UserInfo_struct_list[UserIndex] + 8 * (i + 2LL) + 8));
for ( j = 0; j < v1; ++j )
*((_BYTE *)buf + (int)v6++) = *(_BYTE *)(*(_QWORD *)(UserInfo_struct_list[UserIndex] + 8 * (i + 2LL) + 8) + j);
*((_BYTE *)buf + (int)v6++) = '\n';
}
if ( (int)open_write_close(filename, buf, v6) >= 0 )// write string in buf to file filename {
printfn((__int64)"Strings saved to file successfully!");
return free_guess((unsigned __int64)buf);
} else {
printfn((__int64)"Failed to write file");
return (int *)printfn((__int64)&enter);
}
} else {
printfn((__int64)"Invalid filename");
return (int *)printfn((__int64)&enter);
}
}
Receive a file name with a size of 32 bytes and write the strings stored so far to that file.
If the file name contains the string "flag", immediately exit the SaveFile
function.
Before writing to the file, allocate memory of size 0x7FFF bytes (buf
) to be used for file writing.
If the file name passes the "flag"
filtering but fails to open or write, do not free the allocated memory space (buf
).
If the file name passes the "flag"
filtering and the file is successfully opened and written to, free the allocated memory space (buf
).
int *LoadFile()
{
int *v1; // [rsp+0h] [rbp-30h]
int read_length; // [rsp+Ch] [rbp-24h]
int *buf; // [rsp+10h] [rbp-20h]
int *path; // [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: ");
path = malloc_guess(32);
if ( path
&& (read_Helper((__int64)path, 32), (unsigned int)strlen_includeNULL((__int64)path))
&& !(unsigned int)strstr((__int64)path, (__int64)"flag") ) {
buf = malloc_guess(0x7FFF);
if ( !buf ) {
printfn("Failed to allocate memory");
printfn((unsigned __int8 *)&enter);
exit();
} // (path, buf, 0x7FFF)
read_length = open_read_close(); // 정상적으로 파일 열림 => 읽은 byte 수 / 비정상적 => -1
if ( read_length >= 0 ) {
v8 = 0;
v7 = 0;
while ( v8 < read_length ) {
v6 = 0;
while ( *((_BYTE *)buf + v8) != '\n' ) {
++v6;
++v8;
}
v1 = malloc_guess(v6 + 1);
if ( !v1 ) {
printfn("Failed to allocate memory");
printfn((unsigned __int8 *)&enter);
exit();
}
for ( i = 0; i < v6; ++i )
*((_BYTE *)v1 + i) = *((_BYTE *)buf + v7++);
*((_BYTE *)v1 + v6) = 0;
*(_QWORD *)(UserInfo_struct_list[UserIndex]
+ 8 * ((int)(*(_DWORD *)(UserInfo_struct_list[UserIndex] + 16LL))++ + 2LL)
+ 8) = v1;
++v8;
++v7;
}
printfn("Strings loaded from file successfully!");
return free_guess((unsigned __int64)buf);
} else {
printfn("Failed to read file");
return (int *)printfn((unsigned __int8 *)&enter);
}
} else {
printfn("Invalid filename");
return (int *)printfn((unsigned __int8 *)&enter);
}
}
Receive a file name with a size of 32 bytes and store the contents of that file in the UserInfo_struct
.
If the file name contains the string "flag"
, immediately exit the LoadFile
function.
Before storing the file contents, allocate memory of size 0x7FFF bytes (buf
) to be used for reading the file.
If the file name passes the "flag"
filtering but fails to open or read, do not free the allocated memory space (buf
).
If the file name passes the "flag"
filtering and the file is successfully opened and read, free the allocated memory space (buf
).