개고생했는데 일단 아직못풀었습니다.. 그냥풀이과정적어놨습니다 일단
(gdb) disas main
Dump of assembler code for function main:
0x08048889 <main+0>: push ebp
0x0804888a <main+1>: mov ebp,esp
0x0804888c <main+3>: and esp,0xfffffff0
0x0804888f <main+6>: sub esp,0x20
0x08048892 <main+9>: mov DWORD PTR [esp],0x20
0x08048899 <main+16>: call 0x8048ff2 <malloc>
0x0804889e <main+21>: mov DWORD PTR [esp+0x14],eax
0x080488a2 <main+25>: mov DWORD PTR [esp],0x20
0x080488a9 <main+32>: call 0x8048ff2 <malloc>
0x080488ae <main+37>: mov DWORD PTR [esp+0x18],eax
0x080488b2 <main+41>: mov DWORD PTR [esp],0x20
0x080488b9 <main+48>: call 0x8048ff2 <malloc>
0x080488be <main+53>: mov DWORD PTR [esp+0x1c],eax
0x080488c2 <main+57>: mov eax,DWORD PTR [ebp+0xc]
0x080488c5 <main+60>: add eax,0x4
0x080488c8 <main+63>: mov eax,DWORD PTR [eax]
0x080488ca <main+65>: mov DWORD PTR [esp+0x4],eax
0x080488ce <main+69>: mov eax,DWORD PTR [esp+0x14]
0x080488d2 <main+73>: mov DWORD PTR [esp],eax
0x080488d5 <main+76>: call 0x8048750 <strcpy@plt>
0x080488da <main+81>: mov eax,DWORD PTR [ebp+0xc]
0x080488dd <main+84>: add eax,0x8
0x080488e0 <main+87>: mov eax,DWORD PTR [eax]
0x080488e2 <main+89>: mov DWORD PTR [esp+0x4],eax
0x080488e6 <main+93>: mov eax,DWORD PTR [esp+0x18]
0x080488ea <main+97>: mov DWORD PTR [esp],eax
0x080488ed <main+100>: call 0x8048750 <strcpy@plt>
0x080488f2 <main+105>: mov eax,DWORD PTR [ebp+0xc]
0x080488f5 <main+108>: add eax,0xc
0x080488f8 <main+111>: mov eax,DWORD PTR [eax]
0x080488fa <main+113>: mov DWORD PTR [esp+0x4],eax
0x080488fe <main+117>: mov eax,DWORD PTR [esp+0x1c]
0x08048902 <main+121>: mov DWORD PTR [esp],eax
0x08048905 <main+124>: call 0x8048750 <strcpy@plt>
0x0804890a <main+129>: mov eax,DWORD PTR [esp+0x1c]
0x0804890e <main+133>: mov DWORD PTR [esp],eax
0x08048911 <main+136>: call 0x8049824 <free>
0x08048916 <main+141>: mov eax,DWORD PTR [esp+0x18]
0x0804891a <main+145>: mov DWORD PTR [esp],eax
0x0804891d <main+148>: call 0x8049824 <free>
0x08048922 <main+153>: mov eax,DWORD PTR [esp+0x14]
0x08048926 <main+157>: mov DWORD PTR [esp],eax
0x08048929 <main+160>: call 0x8049824 <free>
0x0804892e <main+165>: mov DWORD PTR [esp],0x804ac27
0x08048935 <main+172>: call 0x8048790 <puts@plt>
0x0804893a <main+177>: leave
0x0804893b <main+178>: ret
End of assembler dump.
일단 우리의 목적은 winner()를 실행시키는거임.
winner()의 위치는
(gdb) p winner
$1 = {void (void)} 0x8048864 <winner>
이렇게 나타나있음. 이제 이걸 어떻게 호출하느냐인데..
우선 코드를 분석해볼 필요가 있다.
void winner(){
printf("성공 블라블라");
}
int main(int argc, char *argv[]){
char *buf1, *buf2, *buf3;
buf1 = malloc(32);
buf2 = malloc(32);
buf2 = malloc(32);
strcpy(buf1, argv[1]);
strcpy(buf2, argv[2]);
strcpy(buf3, argv[3]);
free(buf1);
free(buf2);
free(buf3);
printf("다이너마이트 어쩌고");
}
어셈이 간단하니 금방 c코드로 바꿀 수 있었다.
아무튼 우리는 여기서 금방 찾을 수 있는 취약점이 있다.
길이 체크도 하지 않고 strcpy를 통해 동적 할당된 버퍼에 복사한다는 점이다.
하지만 이걸가지고 뭘 할 수 있을까?
우선..malloc의 과정을 살펴볼 필요가 있다.
메모리가 동적 할당될 경우 어떤 일이 발생할까?
http://expointer.tistory.com/38 에 간단하게 적어놨긴하지만 다시 한 번 짚어보자..
기본적으로 메모리 할당 함수들은 할당 단위가 보통 4kb(페이지 사이즈)이기 때문에 작은 메모리를 할당해주기 위해서 heap management가 필요하다.
리눅스에선 이러한 management를 위해 sbrk나 brk 시스템 콜을 사용해 크기를 확장하거나 변경할 수 있다.
아무튼 잘게 쪼개진 메모리 블록들은 최소 단위가 8byte이고(부가 정보를 저장하기 위한 데이터 사이즈) 실제 할당되는 사이즈까지 합하면 최소 16byte의 사이즈를 가지는데, 이말은 5바이트 할당해도 8바이트가 할당된다 그말이다. 부가 정보까지 합하면 8바이트 더해서 16바이트가 되겠지?
(실 예제로 예를 들자면 32byte를 할당했기 때문에 40byte가 할당된다)
여기서 이 '부가 정보'라고 칭하는 것은 이 블록이 사용 중인 블록인지, 얼마나 할당되었는지, 현재 이전 블록의 크기는 몇인지가 기록되어있다.
이 블록을 가리켜 'chunk'라고 부른다..
이 chunk라는건 구조를 가지고 있는데
메모리가 할당되면 [prev size] [size(+flag)] [data] 이런식으로 저장되었다가
free가 되면 [prev size] [size] [fd pointer] [bk pointer] [unused space] 식으로 바뀐다.
할당됫을때 size 뒤에 flag라는게 보일텐데, 저건 그냥 맨 마지막 bit가 0으로 되어있으면 사용되지 않는 chunk라고 봐도 된다.
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
#define NON_MAIN_ARENA 0x4
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
뭐 이렇게 생겼는데 자세한건 검색하시고.. free 프로세스상 인접한 chunk가 사용 중이 아닐 경우 해당 chunk와 병합을 수행하는데
이 때 중요하게 사용되는게 fd pointer랑 bk pointer이다(fd는 forward, bk는 backward pointer를 의미한다).
이 병합과정에서 unlink라는 매크로를 통해 free의 더블 링크드 리스트의 각 이전 이후의 포인터를 바꾼다.
#unlink (P, BK, FD){
BK = P -> bk;
FD = P-> fd;
FD-> bk = BK;
BK-> fd = FD;
}
이 과정을 통해 FD가 가리키는 BK를 P가 가리켰던 BK로, BK가 가리키는 FD를 P가 가리켰던 FD로 변환하게 된다.
그럼 만약, FD와 BK를 변조시킨 후, flag를 0으로 만들어서 사용하지 않는 chunk로 인식되게 하면 어떨까?
우선 `perl -e 'print "A"x32'` `perl -e 'print "B"x32'` `perl -e 'print "C"x32'`를 통해 버퍼를 구성하고 free를 통해 할당 해제를 하면 다음과 같은 모습을 볼 수 있다.
(gdb) x/12x 0x804c000
0x804c000: 0x00000000 0x00000029 0x0804c028 0x41414141
0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c020: 0x41414141 0x41414141 0x00000000 0x00000029
(gdb) x/12x 0x804c028
0x804c028: 0x00000000 0x00000029 0x0804c050 0x42424242
0x804c038: 0x42424242 0x42424242 0x42424242 0x42424242
0x804c048: 0x42424242 0x42424242 0x00000000 0x00000029
(gdb) x/12x 0x804c050
0x804c050: 0x00000000 0x00000029 0x00000000 0x43434343
0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343
0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89
각각 buf1, buf2, buf3이 저장되는 위치이다. 각각 풀어서 설명하면 다음과 같다..
buf1
prev_size : 0
size,flag : 0x29
FD Pointer : 0x804c028
BK Pointer : ?
buf2
prev_size : 0
size,flag : 0x29
FD Pointer : 0x804c050
BK Pointer : ?
buf3
prev_size : 0
size,flag : 0x29
FD Pointer : ?
BK Pointer : ?
이정도?.. bk pointer랑 prev_size는 왜 설정이 안된걸까? 혹시나 하고 사이즈 변경을 시도해보았다.
`perl -e 'print "A"x32, "D"x4, "\x28"'` `perl -e 'print "B"x32'` `perl -e 'print "C"x32'`
(gdb) x/12x 0x804c000
0x804c000: 0x00000000 0x00000029 0x41414141 0x41414141
0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c020: 0x41414141 0x41414141 0x44444444 0x00000028
(gdb) x/12x 0x804c028
0x804c028: 0x44444444 0x00000028 0x42424242 0x42424242
0x804c038: 0x42424242 0x42424242 0x42424242 0x42424242
0x804c048: 0x42424242 0x42424242 0x00000000 0x00000029
(gdb) x/12x 0x804c050
0x804c050: 0x00000000 0x00000029 0x43434343 0x43434343
0x804c060: 0x43434343 0x43434343 0x43434343 0x43434343
0x804c070: 0x43434343 0x43434343 0x00000000 0x00000f89
끙 무튼 삽질 진짜 오래해봤는데 애매모호한 결과만 나옴..
1. prev_size는 왜 null인가?
2. bk는 왜없는가?
3. 할당 해제를 해도 왜 flag는 변경되지않는가?