Exploitation via DTORS and mitigating factors in GCC 4.4
The rewriting of the DTORS section is a fairly known technique to exploit a program compiled with GCC. Described shortly, the DTORS section makes it possible to call function after the execution of a program. For example, adding the following code will have the effect of calling the stop() function after the execution of the main() function :
void stop() __attribute__ ((destructor));
void stop() {
printf("THE END\n");
}
The list of of functions to be called at the end of the program are written in the DTORS section, which is writable and thus can be used to redirect execution. For more details concerning this technique, consult the following article.
While trying to use this technique I found that it wasn't working if there where no destructor functions in the original code (contrarily to what is said in the first edition of The Art of Exploitation and in the article cited above). A look at the assembly code explains why :
First, the nm command gives us the location of the DTORS section.
$ nm no_dtors
080496a8 D __DTOR_END__
080496a4 d __DTOR_LIST__
Next, we look at the content of the __do_global_dtors which is handling the task of calling the destructors. We obtain the code with the objdump -d no_dtors command.
08048440 <__do_global_dtors_aux>:
8048440: 55 push %ebp
8048441: 89 e5 mov %esp,%ebp
8048443: 53 push %ebx
8048444: 83 ec 04 sub $0x4,%esp
8048447: 80 3d bc 97 04 08 00 cmpb $0x0,0x80497bc
804844e: 75 3f jne 804848f <__do_global_dtors_aux+0x4f>
8048450: a1 c0 97 04 08 mov 0x80497c0,%eax
8048455: bb a8 96 04 08 mov $0x80496a8,%ebx
804845a: 81 eb a4 96 04 08 sub $0x80496a4,%ebx
8048460: c1 fb 02 sar $0x2,%ebx
8048463: 83 eb 01 sub $0x1,%ebx
8048466: 39 d8 cmp %ebx,%eax
8048468: 73 1e jae 8048488 <__do_global_dtors_aux+0x48>
804846a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048470: 83 c0 01 add $0x1,%eax
8048473: a3 c0 97 04 08 mov %eax,0x80497c0
8048478: ff 14 85 a4 96 04 08 call *0x80496a4(,%eax,4)
804847f: a1 c0 97 04 08 mov 0x80497c0,%eax
8048484: 39 d8 cmp %ebx,%eax
8048486: 72 e8 jb 8048470 <__do_global_dtors_aux+0x30>
8048488: c6 05 bc 97 04 08 01 movb $0x1,0x80497bc
804848f: 83 c4 04 add $0x4,%esp
8048492: 5b pop %ebx
8048493: 5d pop %ebp
8048494: c3 ret
8048495: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048499: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
The code excerpt is fairly long so I put the interesting part in blue. We can see that the adresses of the beginning and the end of the DTORS section (in red) are directly in the code. In fact, the beginning address is subtracted to the end address to determine the length of the DTORS section. The next instructions (sar and sub) are used to check if the length of the section is smaller or equal to 4 bytes. If it's the case then the execution jumps to the adress 0x08048488 and the content of the DTORS section is never read (the call instruction in red is never reached).
The program used as an example didn't have any destructor so it wasn't exploitable. If we add a destructor to the code, we obtain the following result :
8048455: bb ac 96 04 08 mov $0x80496ac,%ebx
804845a: 81 eb a4 96 04 08 sub $0x80496a4,%ebx
This time, the length of the DTORS section is 8 bytes and the addresses that it contains (in this case just one) are called.
I think that the behaviour generated by GCC was changed in the recent versions to limit the risks of DTORS exploitation, since most programs don't have destructors. I didn't look at GCC's source code though.