- Published on
BackToFlag - Reversing - AirTech 2024
- Authors
- Name
- Hamza Haroon
- @thegr1ffyn
BackToFlag - Reversing - AirTech 2024
Description:
This time doc was smart enough to save the seed value in a text file only problem is he accidently deleted config.txt which contained the key for password, i dont think there's any way to open this file OR IS THERE?
Author: CY30RG
Solution:
We were given two files named backtoflag
which is an elf
file with a text file named seed.txt
containing random numbers of 274520360673462036
.
I opened the file in Ghidra and saw the functions of main
and readSeedFromFile
of our need.
Source Codes
The main
function contains of following code.
undefined8 main(void){
char cVar1;
basic_ostream *pbVar2;
basic_string local_d8 [32];
basic_string local_b8 [32];
basic_string local_98 [32];
basic_string local_78 [32];
basic_string<char,std::char_traits<char>,std::allocator<char>> local_58 [47];
allocator local_29;
allocator *local_28;
undefined4 local_20;
undefined4 local_1c;
local_28 = &local_29;
/* try { // try from 001026d8 to 001026dc has its CatchHandler @ 0010289c */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_58,"config.txt",&local_29);
/* try { // try from 001026e4 to 001026e8 has its CatchHandler @ 0010288b */
local_1c = readSeedFromFile((basic_string *)local_58);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
(local_58);
std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_29);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string();
/* try { // try from 00102725 to 00102753 has its CatchHandler @ 001028f3 */
std::operator<<((basic_ostream *)std::cout,"Enter the password: ");
std::operator>>((basic_istream *)std::cin,local_78);
generatePassword[abi:cxx11]((int)register0x00000020 - 0x98);
cVar1 = verifyPassword(local_78,local_98);
if (cVar1 == '\0') {
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string();
/* try { // try from 00102795 to 001027d8 has its CatchHandler @ 001028cb */
std::operator<<((basic_ostream *)std::cout,"Enter the key: ");
std::operator>>((basic_istream *)std::cin,local_d8);
local_20 = deriveSeed(local_d8);
generateFlag[abi:cxx11]((int)register0x00000020 - 0xb8);
/* try { // try from 001027ed to 00102818 has its CatchHandler @ 001028b7 */
pbVar2 = std::operator<<((basic_ostream *)std::cout,"Congratulations! Here\'s your flag: ");
pbVar2 = std::operator<<(pbVar2,local_b8);
std::basic_ostream<char,std::char_traits<char>>::operator<<
((basic_ostream<char,std::char_traits<char>> *)pbVar2,
std::endl<char,std::char_traits<char>>);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
((basic_string<char,std::char_traits<char>,std::allocator<char>> *)local_b8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
((basic_string<char,std::char_traits<char>,std::allocator<char>> *)local_d8);
}
else {
/* try { // try from 0010284d to 00102863 has its CatchHandler @ 001028df */
pbVar2 = std::operator<<((basic_ostream *)std::cout,"Incorrect password. Access denied.");
std::basic_ostream<char,std::char_traits<char>>::operator<<
((basic_ostream<char,std::char_traits<char>> *)pbVar2,
std::endl<char,std::char_traits<char>>);
}
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
((basic_string<char,std::char_traits<char>,std::allocator<char>> *)local_98);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
((basic_string<char,std::char_traits<char>,std::allocator<char>> *)local_78);
return 0;
}
The readSeedFromFile
contains of the following code:
uint readSeedFromFile(basic_string *param_1){
char cVar1;
basic_ostream *pbVar2;
long *plVar3;
uint local_22c;
basic_string local_228 [536];
std::basic_ifstream<char,std::char_traits<char>>::basic_ifstream(local_228,(_Ios_Openmode)param_1)
;
cVar1 = std::basic_ifstream<char,std::char_traits<char>>::is_open();
if (cVar1 != '\x01') {
/* try { // try from 00102394 to 0010243b has its CatchHandler @ 0010245f */
pbVar2 = std::operator<<((basic_ostream *)std::cerr,"Error: Unable to open file: ");
pbVar2 = std::operator<<(pbVar2,param_1);
std::basic_ostream<char,std::char_traits<char>>::operator<<
((basic_ostream<char,std::char_traits<char>> *)pbVar2,
std::endl<char,std::char_traits<char>>);
/* WARNING: Subroutine does not return */
exit(1);
}
plVar3 = (long *)std::basic_istream<char,std::char_traits<char>>::operator>>
((basic_istream<char,std::char_traits<char>> *)local_228,&local_22c);
cVar1 = std::basic_ios<char,std::char_traits<char>>::operator!
((basic_ios<char,std::char_traits<char>> *)
((long)plVar3 + *(long *)(*plVar3 + -0x18)));
if (cVar1 != '\0') {
pbVar2 = std::operator<<((basic_ostream *)std::cerr,"Error: Unable to read seed from file: ");
pbVar2 = std::operator<<(pbVar2,param_1);
std::basic_ostream<char,std::char_traits<char>>::operator<<
((basic_ostream<char,std::char_traits<char>> *)pbVar2,
std::endl<char,std::char_traits<char>>);
/* WARNING: Subroutine does not return */
exit(1);
}
std::basic_ifstream<char,std::char_traits<char>>::~basic_ifstream
((basic_ifstream<char,std::char_traits<char>> *)local_228);
return local_22c;
}
Running the elf file, we get the following output:
└─$ ./backtoflag
Error: Unable to read seed from file: config.txt
We need to bypass two conditions here. In the readSeedfromFile
function, there is a local variable named local_22c
which is a uint
that can hold 32 bytes of data at max. Its an unsigned integer used to store the seed value read from the file.
File Opening:
The function attempts to open the file specified by param_1
using std::basic_ifstream
. If the file cannot be opened (is_open()
returns false
), it prints an error message to std::cerr
and exits the program with an error code of 1.
Reading Seed Value:
If the file is successfully opened, the function attempts to read a seed value from the file using std::basic_istream::operator>>
. It stores the result in local_22c
.
Bypassing First Condition
We can simply touch a file named config.txt
and add random numbers uptil 10 in the same directory.
└─$ cat config.txt
1234567
Now when we run the file, we get new output where it asks for a password.
└─$ ./backtoflag
Enter the password: 111
Incorrect password. Access denied.
Bypassing Second Condition
In the main
function, we have a loophole where we can bypass the password condition at the following line
if (cVar1 != '\0') {
pbVar2 = std::operator<<((basic_ostream *)std::cerr,"Error: Unable to read seed from file: ");
Ghidra also provides us with a functionality to edit Assembly code to tweak the application to our need. The above mentioned code comes up to the following.
001023fa 74 4a JZ LAB_00102446
Here we can replace the JZ instruction with a JNZ instruction. This will cause the jump to be taken if the zero flag is not set, effectively bypassing the original condition of checking the password.
Editing this and then saving this as a new elf file in Ghidra using Export Program
.
Executing the file and entering random numbers in when it asks for password bypasses the password check condition.
└─$ ./backtoflagnew
Enter the password: 123
Enter the key:
Now it asks us for a key which has already been given to us in the form of seed.txt
above, entering it gives us the flag.
└─$ ./backtoflagnew
Enter the password: 123
Enter the key: 274520360673462036
Congratulations! Here's your flag: AT24{JKYZCRLCYJ}
We can summarize our learnings as follows: The vulnerability in the provided scenario lies in the inadequate input validation and error handling in the readSeedFromFile
function, as well as the possibility to modify the control flow of the program to bypass password authentication.
Summary
Vulnerabilities:
File Opening Vulnerability:
- The
readSeedFromFile
function attempts to open the file specified by theparam_1
parameter without adequate validation of the input. If an attacker provides a malicious or unexpected file path, it could lead to unintended file operations, potentially allowing the attacker to read sensitive data or perform unauthorized actions.
- The
Control Flow Vulnerability:
- In the
main
function, the program checks if the seed value can be successfully read from the file. If successful, it proceeds to prompt the user for a password. However, an attacker could bypass this password prompt by modifying the control flow of the program, such as replacing theJZ
(jump if zero) instruction with aJNZ
(jump if not zero) instruction. This allows the attacker to skip the password check entirely and proceed to obtain the flag without authentication.
- In the
Exploitation:
Bypassing File Opening Check:
- By creating a file named
config.txt
with arbitrary contents (e.g., random numbers), an attacker can bypass the file opening check in thereadSeedFromFile
function. This allows the attacker to proceed to the next stage of the program without encountering the expected error condition.
- By creating a file named
Control Flow Modification:
- By editing the assembly code of the program, an attacker can replace the
JZ
instruction with aJNZ
instruction, effectively bypassing the password authentication check in themain
function. This allows the attacker to directly obtain the flag without providing the correct password.
- By editing the assembly code of the program, an attacker can replace the