October 01, 2017

pwnable.kr unlink solution


Daddy! how can I exploit unlink corruption?





This is a game that reads the "flag" with "unlink_pwn" group permission.

When it is run, I can type something.




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;

void shell(){
system("/bin/sh");
}

void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));

// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;

printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);

// exploit this unlink!
unlink(B);
return 0;
}


This is the full source code.

There is struct variables A, B and C that is linked each other. and unlink() unlinks them.

The hint "exploit this unlink" is in the source code. "gets(A->buf)" seems to be able to cause a heap overflow exploiting "unlink(B)".





I identified the addresses of major functions before analysis.





"[ebp-0x4]" value affects "esp", which means that it affects the return value of the main() function.

Eventually, if [ebp-0x4] is manipulated, the main() function can go to the shell() function on return.





"OBJ" variable is consisted of two pointers(4 Bytes * 2) and eight characters(1 Bytes * 8). Total is 16 bytes.

And before the return of main(), there is the "unlink(B)" function.

"unlink(B)" means this.
    [B->fd]->bk = B->bk
    [B->bk]->fd = B->fd

It may seem complicated, but it is easy to understand when memory is referred. I inserted the value and identified the structure.



The distance between "A" and "B" is 16. I checked in <+78> ~ <+115> of main() disassembled code.

Think of "->fd" as "+0x0" and think of "->bk" as "+0x4".

So "unlink(B)" means this.
    [B]+0x4 = B+0x4
    [B+0x4] = B





Based on the functionality of unlink(B), I thinked this kind of overflow.

"[ebp-0x4]" will have "A+0x8+0x4".
* [B+0x4] = B


And look at this image again.

The "ebp-0x4" pointing value is stored in "ecx" and the [ecx-0x4] address value is stored in "esp".

Therefore, "A+0x8+0x4" is stored in "ecx" and "A+0x8" is stored in "esp". and "ret" stores "shell()" address pointed to by "esp" to "eip".






For reference, the address of "B" and the address of "EBP-0x4" change continuously. Since "B" is in heap area, the address of B is checked by the distance from the allocated address of "A". And since EBP is in stack area, the address of "ebp-0x4" is checked by the distance from the variable address of "A".

In summary, the data to be transmitted is composed as follows.

"shell() address(4 Bytes)" + "dummy(12 Bytes)" + "A+0x8+0x4" + "ebp-0x4"