Little Tommy
What it does?
The program is basically a simple bank account system:
#################### Welcome to Little Tommy's Handy yet Elegant and Advanced Program ####################
1. Create account
2. Display account
3. Delete account
4. Add memo
5. Print flag
Please enter an operation number:
The Print flag
option obviously returns a NOPE, so let's start searching for something to make its opinion change.
Checksec
First we can check the binary security:
┌──(kali㉿kali)-[~/Desktop/HTB/Little Tommy]
└─$ checksec little_tommy
[*] '/home/kali/Desktop/HTB/Little Tommy/little_tommy'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
We see that it has no PIE, what can be useful. Next step is to open Ghidra and check the code.
Digging
The program code is this according to Ghidra:
void main(void)
{
int iVar1;
int iVar2;
size_t sVar3;
int in_GS_OFFSET;
char local_114 [256];
undefined4 local_14;
undefined *puStack16;
puStack16 = &stack0x00000004;
local_14 = *(undefined4 *)(in_GS_OFFSET + 0x14);
puts(
"\n#################### Welcome to Little Tommy\'s Handy yet Elegant and Advanced Program ####################"
);
do {
printf(
"\n1. Create account\n2. Display account\n3. Delete account\n4. Add memo\n5. Print flag\n\nPlease enter an operation number: "
);
iVar1 = getchar();
do {
iVar2 = getchar();
if ((char)iVar2 == '\n') break;
} while ((char)iVar2 != -1);
switch((char)iVar1) {
case '1':
main_account = (char *)malloc(0x48);
printf("\nFirst name: ");
fgets(local_114,0x100,stdin);
strncpy(main_account,local_114,0x1e);
sVar3 = strlen(main_account);
if ((int)sVar3 < 0x1f) {
main_account[sVar3 - 1] = '\0';
}
else {
main_account[0x1f] = '\0';
}
printf("Last name: ");
fgets(local_114,0x100,stdin);
strncpy(main_account + 0x20,local_114,0x1e);
sVar3 = strlen(main_account + 0x20);
if ((int)sVar3 < 0x1f) {
main_account[sVar3 + 0x1f] = '\0';
}
else {
main_account[0x3f] = '\0';
}
printf("\nThank you, your account number %d.\n",main_account);
break;
case '2':
if (main_account == (char *)0x0) {
puts("\nSorry, no account found.");
}
else {
printf("\n################ Account no. %d ################\nFirst name: %s\nLast name: %s\nAccount balance: %d\n\n"
,main_account,main_account,main_account + 0x20,*(undefined4 *)(main_account + 0x40));
}
break;
case '3':
if (main_account == (char *)0x0) {
puts("\nSorry, no account found.");
}
else {
free(main_account);
puts("\nAccount deleted successfully");
}
break;
case '4':
puts("\nPlease enter memo:");
fgets(local_114,0x100,stdin);
memo = strdup(local_114);
printf("\nThank you, please keep this reference number number safe: %d.\n",memo);
break;
case '5':
if ((main_account == (char *)0x0) || (*(int *)(main_account + 0x40) != 0x6b637566)) {
puts("\nNope.");
}
else {
system("/bin/cat flag");
}
}
} while( true );
}
Looking around, looks like the flag option only will print the flag if the content of main_account[64]
is 0x6b637566
. That value transformed to string is basically the string f**k
(Yep I redacted that, all of you know the word) and according to the code that shows the account information, it represents the account balance.
The Add memo
option asks for a string and reserve memory for it using again malloc
.
The main_account
buffer is 72 character long and is reserved using malloc
. We can easily see that the Delete account
option just free that memory, the thing is that the pointer is not reset so the program is still accessing that part of the memory to read information from.
The Add memo
option also reserve memory with malloc
which is perfect for us in this situation. malloc
will try to reuse a previous memory area that was free'd if the size of it is more or less the same that the memory we need to allocate. This means that if we create and delete an account we can use the Add memo
option to allocate again the memory that once belonged to the main_account
buffer and write to it whatever we want. Since the pointer of main_account
was not reseted, the Print flag
option will evaluate the memory area we control.
Exploit time!
To exploit the vulnerability we discovered, first we need to create an account and delete it, then we have to write a 64 bytes long string followed by the magic word f**k
to the buffer created by the Add memo
option. Our payload will be injected in the same memory area where the main_account
was allocated so since we now fulfill the requirements we can ask for the flag!