-0x01 - Introdução
Este artigo é dedicado à análise de todas as tarefas do Flare-On 2019 - a competição anual de engenharia reversa da FireEye. Nestas competições, participo pela segunda vez. No ano anterior, cheguei ao 11º lugar em termos de tempo de conclusão, tendo resolvido todos os problemas em cerca de 13 dias. Este ano, o conjunto de tarefas foi mais fácil, e eu me encontrei em 54 horas, ocupando ao mesmo tempo 3 lugar em termos de entrega.
Neste artigo, tentei descrever aqueles momentos que despertaram meu maior interesse, portanto, a análise não descreverá a rotina de trabalho na AID, entendendo os algoritmos de cada função e outros pontos não muito interessantes. Espero que, depois de ler isso, você encontre algo novo e útil para si mesmo. Você pode encontrar a análise dos problemas dos autores, bem como algumas estatísticas e prêmios para os vencedores aqui .
Se você estiver interessado, então seja bem-vindo ao gato!
0x00 - Conteúdo
- 0x01 - Memecat Battlestation [Shareware Demo Edition]
- 0x02 - Excessivo
- 0x03 - Flarebear
- 0x04 - Dnschess
- 0x05 - demo
- 0x06 - bmphide
- 0x07 - wopr
- 0x08 - cobra
- 0x09 - recarregado
- 0x0A - Mugatu
- 0x0B - vv_max
- 0x0C - ajuda
- 0x0D - Resumo
0x01 - Memecat Battlestation [Shareware Demo Edition]
Bem-vindo ao sexto desafio de flare-on!
Este é um jogo simples. Faça a engenharia reversa para descobrir em quais "códigos de armas" você precisa entrar para derrotar cada um dos dois inimigos e a tela da vitória revelará a bandeira. Digite a bandeira aqui neste site para pontuar e passar para o próximo nível.
* Este desafio está escrito em .NET. Se você ainda não possui uma ferramenta favorita de engenharia reversa do .NET, recomendo o dnSpy
** Se você já resolveu a versão completa deste jogo em nosso estande no BlackHat ou o lançamento subsequente no twitter, parabéns, insira a bandeira na tela da vitória agora para ultrapassar este nível.
Essa tarefa foi definida com antecedência como parte do Black Hat USA 2019, aproximadamente na mesma época que decidi. Não lembro como ele resolveu A tarefa é bastante simples, portanto não consideraremos sua solução.
0x02 - Excessivo
O segredo deste próximo desafio está inteligentemente escondido. No entanto, com a abordagem correta, encontrar a solução não levará muito tempo.
Dado arquivo x86 .exe. Quando você tenta iniciar, uma mensagem é exibida com o seguinte conteúdo:

Ao analisar o aplicativo, você pode achar que a mensagem está armazenada em alguma codificação com um comprimento variável de caracteres (de 1 a 4 bytes). Quando a função de decodificação é chamada, ela recebe o comprimento do resultado esperado, que é mais curto que a própria mensagem, e é por isso que o sinalizador não está visível. Você pode corrigir o valor do tamanho passado para a função no modo de depuração e obter a mensagem completa com o sinalizador:

Você também pode reescrever o algoritmo de decodificação no Python e obter uma flag:
msg = [ ... ]
0x03 - Flarebear
Nós da Flare criamos nosso próprio animal de estimação Tamagotchi, o urso de flar. Ele é muito exigente. Mantenha-o vivo e feliz e ele lhe dará a bandeira.
Nesta tarefa, Android
um arquivo apk
para Android
. Considere um método de solução sem iniciar o próprio aplicativo.
O primeiro passo é obter o código fonte do aplicativo. Para fazer isso, usando o dex2jar
utilitários dex2jar
converta apk
em jar
e obtenha o código-fonte Java
usando o descompilador, que eu prefiro usar cfr
.
~/retools/d2j/d2j-dex2jar.sh flarebear.apk java -jar ~/retools/cfr/cfr-0.146.jar --outputdir src flarebear-dex2jar.jar
Analisando o código-fonte do aplicativo, você pode encontrar um método .danceWithFlag()
interessante, localizado no arquivo FlareBearActivity.java
. Dentro de .danceWithFlag()
, raw
recursos raw
aplicativo são descriptografados usando o método .decrypt(String, byte[])
, cujo primeiro argumento é a string obtida usando o método .getPassword()
. Certamente a bandeira está em recursos criptografados, então vamos tentar decifrá-los. Para fazer isso, decidi reescrever um pouco o código descompilado, me livrando das dependências do Android
e deixando apenas os métodos necessários para a descriptografia, para que, como resultado, o código resultante pudesse ser compilado. Além disso, durante a análise, verificou-se que o método .getPassword()
depende de três valores de estado inteiro. Cada valor está em um pequeno intervalo de 0
a N
, para que você possa percorrer todos os valores possíveis em busca da senha desejada.
O resultado é o seguinte código:
Main.java import java.io.InputStream; import java.nio.charset.Charset; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.Collections; import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public final class Main { public static void main (String args []) throws Exception { Main a = new Main(); InputStream inputStream = new FileInputStream("ecstatic"); long fileSize = new File("ecstatic").length(); byte[] file1 = new byte[(int) fileSize]; inputStream.read(file1); inputStream = new FileInputStream("ecstatic2"); fileSize = new File("ecstatic2").length(); byte[] file2 = new byte[(int) fileSize]; inputStream.read(file2); for(int i = 0; i < 9; i++) { for(int j = 0; j < 7; j++) { for(int k = 1; k < 16; k++) { String pass = a.getPassword(i, j, k); try { byte[] out1 = a.decrypt(pass, file1); byte[] out2 = a.decrypt(pass, file2); OutputStream outputStream = new FileOutputStream("out1"); outputStream.write(out1); outputStream = new FileOutputStream("out2"); outputStream.write(out2); System.out.println("yep!"); } catch (javax.crypto.BadPaddingException ex) { } } } } } public final byte[] decrypt(Object object, byte[] arrby) throws Exception { Object object2 = Charset.forName("UTF-8"); object2 = "pawsitive_vibes!".getBytes((Charset)object2); object2 = new IvParameterSpec((byte[])object2); object = ((String)object).toCharArray(); Object object3 = Charset.forName("UTF-8"); object3 = "NaClNaClNaCl".getBytes((Charset)object3); object = new PBEKeySpec((char[])object, (byte[])object3, 1234, 256); object = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret((KeySpec)object); object3 = new SecretKeySpec(((SecretKey)object).getEncoded(), "AES"); object = Cipher.getInstance("AES/CBC/PKCS5Padding"); ((Cipher)object).init(2, (Key)object3, (AlgorithmParameterSpec)object2); object = ((Cipher)object).doFinal(arrby); return (byte [])object; } public final String getPassword(int n, int n2, int n3) { String string2 = "*"; String string3 = "*"; switch (n % 9) { case 8: { string2 = "*"; break; } case 7: { string2 = "&"; break; } case 6: { string2 = "@"; break; } case 5: { string2 = "#"; break; } case 4: { string2 = "!"; break; } case 3: { string2 = "+"; break; } case 2: { string2 = "$"; break; } case 1: { string2 = "-"; break; } case 0: { string2 = "_"; } } switch (n3 % 7) { case 6: { string3 = "@"; break; } case 4: { string3 = "&"; break; } case 3: { string3 = "#"; break; } case 2: { string3 = "+"; break; } case 1: { string3 = "_"; break; } case 0: { string3 = "$"; } case 5: } String string4 = String.join("", Collections.nCopies(n / n3, "flare")); String string5 = String.join("", Collections.nCopies(n2 * 2, this.rotN("bear", n * n2))); String string6 = String.join("", Collections.nCopies(n3, "yeah")); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(string4); stringBuilder.append(string2); stringBuilder.append(string5); stringBuilder.append(string3); stringBuilder.append(string6); return stringBuilder.toString(); } public final String rotN(String charSequence, int n) { Collection<String> collection = new ArrayList(charSequence.length()); for (int i = 0; i < charSequence.length(); ++i) { char c; char c2 = c = charSequence.charAt(i); if (Character.isLowerCase(c)) { char c3; c2 = c3 = (char)(c + n); if (c3 > 'z') { c2 = c3 = (char)(c3 - n * 2); } } collection.add(Character.valueOf(c2).toString()); } return collection.stream().collect(Collectors.joining());
Extrairemos os recursos criptografados, compilaremos e executaremos o arquivo resultante:
$ ~/retools/apktool/apktool d flarebear.apk $ cp flarebear/res/raw/* . $ javac Main.java $ java Main
Felizmente, apenas uma de todas as opções de senha selecionadas é adequada. Como resultado, obtemos duas imagens com uma bandeira:
~/flareon2019/3 - Flarebear$ file out* out1: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced out2: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced


0x04 - Dnschess
Algum tráfego de rede suspeito levou-nos a este programa de xadrez não autorizado em execução em um desktop Ubuntu. Este parece ser o trabalho de hackers do ciberespaço. Você precisará fazer os movimentos certos para resolver este. Boa sorte
Esta tarefa contém um despejo de tráfego, um arquivo executável ELF
ChessUI
e uma biblioteca ChessAI.so
. Ao executar o arquivo executável, você pode ver o tabuleiro de xadrez.

Vamos começar a análise com um despejo de tráfego.

Todo o tráfego consiste em consultas para um servidor DNS
tipo A
As consultas em si consistem nos nomes das peças, na descrição do movimento no jogo de xadrez e na parte constante de .game-of-thrones.flare-on.com
, por exemplo rook-c3-c6.game-of-thrones.flare-on.com
. Pela parte constante, você pode encontrar facilmente o lugar certo na biblioteca ChessAI.so
:
signed __int64 __fastcall getNextMove(int idx, const char *chess_name, unsigned int pos_from, unsigned int pos_to, \__int64 a5) { struct hostent *v9;
Pode-se ver no código que, com base nos endereços ip
recebidos, uma determinada sequência de bytes é descriptografada, que é armazenada em outra área de memória, que chamei de flag
.
Para resolver a tarefa, a primeira coisa a fazer é obter todos os endereços ip
do despejo de tráfego. Você pode fazer isso com o seguinte comando:
tshark -r capture.pcap | grep -P -o '127.(\d+).(\d+).(\d+)' | grep -v '127.0.0.1'
ips
salvar todos os endereços ip
no arquivo ips
, ips
pode usar o seguinte código Python
para obter a sinalização:
with open('ips') as f: ips = f.read().split() flag = bytearray(64) key = b'yZ\xb8\xbc\xec\xd3\xdf\xdd\x99\xa5\xb6\xac\x156\x85\x8d\t\x08wRMqT}\xa7\xa7\x08\x16\xfd\xd7' for ip in ips: a, b, c, d = map(int, ip.split('.')) if d & 1: continue idx = c & 0xf if idx > 14: continue flag[2*idx] = b ^ key[2*idx] flag[2*idx + 1] = b ^ key[2*idx + 1] print(flag.decode() + '@flare-on.com')
0x05 - demo
Alguém da equipe do Flare tentou nos impressionar com suas habilidades de demonstração. Parece em branco. Veja se você consegue descobrir ou talvez tenhamos que demiti-los. Sem pressão.
Dado o arquivo executável 4k.exe
, que usa o DirectX
. Quando iniciado, o logotipo rotativo do FlareOn
é exibido na janela principal.

A análise estática do programa revela uma única função, que é o ponto de entrada. No conteúdo, a função se assemelha à implementação da descriptografia de código. Não perderemos tempo analisando o algoritmo dessa função, basta colocar um ponto de interrupção na instrução ret
e ver para onde o controle é transferido. Depois de retornar, nos encontramos no endereço 0x00420000
, o código no qual é desmontado como algo adequado:

Decidiu-se transferir esse código do modo de depuração para o banco de dados IDA
usando a API
e continuar a análise estática.
O novo código no início importa as funções necessárias de várias bibliotecas. A tabela dessas funções também pode ser restaurada em dinâmica. O resultado é o seguinte conjunto de funções:

O ponto de entrada "real" do programa será:

Observe a criação de DeviceInterface
tipo IDirect3DDevice9 **
. No futuro, essa interface será usada ativamente e, para simplificar o inverso, é necessário definir uma tabela de seus métodos. Foi possível encontrar rapidamente a definição da interface, por exemplo, aqui . Analisei esta tabela e a converti em uma estrutura para a IDA
. A aplicação do tipo resultante ao DeviceInterface
pode simplificar significativamente a análise de código adicional. As capturas de tela a seguir mostram o resultado do descompilador para a função principal do ciclo de renderização da cena antes e depois da aplicação do tipo.


Após uma análise mais aprofundada, verificou-se que duas malhas poligonais (malha, malha poligonal) são criadas no programa, embora quando o programa esteja em execução, vemos apenas um objeto. Além disso, ao criar grades, seus vértices são criptografados usando o XOR
, o que também gera suspeitas. Vamos decifrar e visualizar os vértices. A segunda grade é de maior interesse, pois possui significativamente mais vértices. matplotlib
todos os vértices, descobri que a coordenada Z
de cada um deles é 0; portanto, para visualização, decidiu-se desenhar gráficos bidimensionais usando o matplotlib
. O código e resultado a seguir com um sinalizador foram exibidos:
import struct import matplotlib.pyplot as plt with open('vertexes', 'rb') as f: data = f.read() n = len(data) // 4 data = list(struct.unpack('{}I'.format(n), data)) key = [0xCB343C8, 0x867B81F0, 0x84AF72C3] data = [data[i] ^ key[i % 3] for i in range(len(data))] data = struct.pack('{}I'.format(n), *data) data = list(struct.unpack('{}f'.format(n), data)) x = data[0::3] y = data[1::3] z = data[2::3] print(z) plt.plot(x, y) plt.show()

0x06 - bmphide
Tyler Dean subiu o Monte. Elbert (a montanha mais alta do Colorado) às 2 da manhã para capturar esta foto no momento perfeito. Nunca pule o dia da perna. Encontramos essa foto e executável em um pen drive que ele deixou na cabeça da trilha. Ele pode ser confiável?
Na tarefa, são image.bmp
o arquivo executável bmphide.exe
e a imagem image.bmp
. Pode-se supor que alguma mensagem esteja oculta na imagem usando métodos de esteganografia.
O binário é escrito em C#
, então usei o utilitário dnSpy
para análise. Você pode perceber imediatamente que a maioria dos nomes dos métodos está ofuscada. Se você observar o método Program.Main
, poderá entender a lógica do programa e fazer suposições sobre a finalidade de alguns deles:
- O aplicativo é inicializado usando o método
Program.Init()
- Ler arquivo de dados e arquivo de imagem
- Usando o método
byte [] Program.h(byte [])
, algumas conversões de dados são realizadas - Usando o método
Program.i(Bitmap, byte[])
, os dados convertidos são inseridos na imagem - A imagem resultante é salva com um novo nome.
Quando o aplicativo é inicializado, vários métodos da classe A
chamados. Uma análise superficial da classe mostrou a semelhança de alguns de seus métodos com os métodos do ofuscador ConfuserEx
(arquivo AntiTamper.JIT.cs
). O aplicativo está realmente protegido contra depuração. Ao mesmo tempo, não foi possível remover os mecanismos de proteção usando o utilitário de4dot
e seus garfos, por isso foi decidido continuar a análise.
Considere o método Program.i
, que é usado para inserir dados em uma imagem.
public static void i(Bitmap bm, byte[] data) { int num = Program.j(103); for (int i = Program.j(103); i < bm.Width; i++) { for (int j = Program.j(103); j < bm.Height; j++) { bool flag = num > data.Length - Program.j(231); if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & Program.j(27)) | ((int)data[num] & Program.j(228)); int green = ((int)pixel.G & Program.j(27)) | (data[num] >> Program.j(230) & Program.j(228)); int blue = ((int)pixel.B & Program.j(25)) | (data[num] >> Program.j(100) & Program.j(230)); Color color = Color.FromArgb(Program.j(103), red, green, blue); bm.SetPixel(i, j, color); num += Program.j(231); } } }
Muito semelhante ao LSB
clássico, no entanto, em locais onde são esperadas constantes, o método int Program.j(byte)
é usado. O resultado do seu trabalho depende de vários valores globais obtidos, inclusive durante a inicialização no método Program.Init()
. Foi decidido não reverter seu trabalho, mas obter todos os valores possíveis em tempo de execução. dnSpy
permite editar o código do aplicativo descompilado e salvar os módulos modificados. Aproveitamos isso e reescrevemos o método Program.Main
seguinte maneira:
private static void Main(string[] args) { Program.Init(); Program.yy += 18; for (int i = 0; i < 256; i++) { Console.WriteLine(string.Format("j({0}) = {1}", i, Program.j((byte)i))); } }
Na inicialização, obtemos os seguintes valores:
E:\>bmphide_j.exe j(0) = 206 j(1) = 204 j(2) = 202 j(3) = 200 j(4) = 198 j(5) = 196 j(6) = 194 j(7) = 192 j(8) = 222 j(9) = 220 j(10) = 218 j(11) = 216 j(12) = 214 j(13) = 212 j(14) = 210 j(15) = 208 j(16) = 238 j(17) = 236 j(18) = 234 j(19) = 232 j(20) = 230 ...
Substitua as chamadas para Program.j
no método Program.j
pelas constantes resultantes:
public static void i(Bitmap bm, byte[] data) { int num = 0; for (int i = 0; i < bm.Width; i++) { for (int j = 0; j < bm.Height; j++) { bool flag = num > data.Length - 1; if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & 0xf8) | ((int)data[num] & 0x7); int green = ((int)pixel.G & 0xf8) | (data[num] >> 3 & 0x7); int blue = ((int)pixel.B & 0xfc) | (data[num] >> 6 & 0x3); Color color = Color.FromArgb(0, red, green, blue); bm.SetPixel(i, j, color); num += 1; } } }
Agora fica claro como inserir cada byte da mensagem na imagem:
- os bits de 0 a 2 são colocados nos 3 LSBs do canal vermelho do ponto
- os bits 3 a 5 são colocados nos 3 LSBs do canal verde do ponto
- os bits 6 a 7 são colocados nos 2 LSBs do canal azul do ponto
Em seguida, tentei repetir o algoritmo do método de conversão de dados, mas o resultado do cálculo não correspondia à saída do programa. Como se viu, a classe A
também possui funcionalidade para substituir métodos (em A.VerifySignature(MethodInfo m1, MethodInfo m2)
) e modificar a IL
do código de bytes do método (em A.IncrementMaxStack
).
Para selecionar os métodos que precisam ser substituídos em Program
, em Program.Init
, o bytecode IL
métodos é hash e comparado com valores pré-calculados. No total, dois métodos são substituídos. Para descobrir quais, A.VerifySignature
o aplicativo no depurador, definindo pontos de interrupção nas chamadas A.VerifySignature
e você deve pular a chamada A.CalculateStack()
em Program.Init
, porque Impede a depuração.

Como resultado, você pode ver que o método Program.a
é substituído por Program.b
e Program.c
é substituído por Program.d
.
Agora você precisa entender a modificação do bytecode:
private unsafe static uint IncrementMaxStack(IntPtr self, A.ICorJitInfo* comp, A.CORINFO_METHOD_INFO* info, uint flags, byte** nativeEntry, uint* nativeSizeOfCode) { bool flag = info != null; if (flag) { MethodBase methodBase = Ac(info->ftn); bool flag2 = methodBase != null; if (flag2) { bool flag3 = methodBase.MetadataToken == 100663317; if (flag3) { uint flNewProtect; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 23, 20); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 62, 20); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect, out flNewProtect); } else { bool flag4 = methodBase.MetadataToken == 100663316; if (flag4) { uint flNewProtect2; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect2); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 6, 309030853); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 18, 209897853); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect2, out flNewProtect2); } } } } return A.originalDelegate(self, comp, info, flags, nativeEntry, nativeSizeOfCode); }
É claro que métodos com valores específicos de MetadataToken
, ou seja, 0x6000015
e 0x6000014
, serão modificados. Esses tokens correspondem aos métodos Program.h
Program.g
. dnSpy
possui um editor hex
embutido, no qual, ao passar o mouse, os dados do método são destacados: seu título (destacado em roxo) e código de bytes (destacado em vermelho), conforme mostrado na captura de tela. Você pode acessar o método desejado no editor hex
clicando no endereço correspondente no comentário antes do método descompilado (por exemplo, File Offset: 0x00002924
).

Vamos tentar aplicar todas as modificações descritas: criaremos uma cópia do arquivo; em qualquer editor hexadecimal, alteraremos os valores nas compensações necessárias, aprendidas com o dnSpy
e substituiremos os métodos a c -> d
no Program.h
. Também removemos do Program.Init
todas as chamadas para o módulo A
Se tudo for feito corretamente, quando tentarmos inserir alguma mensagem na imagem usando o aplicativo modificado, obteremos o mesmo resultado de quando o aplicativo original estava funcionando. As capturas de tela abaixo mostram o código descompilado dos métodos dos aplicativos originais e modificados.


Resta criar um algoritmo de transformação inversa. Como é bastante simples, darei apenas o script Python
resultante:
from PIL import Image
Ao executar este script, obtemos outra imagem bmp
sem uma bandeira. Repetindo o procedimento, obtemos a imagem final com a bandeira.

0x07 - wopr
Usamos nossas próprias habilidades de hackers para "encontrar" essa IA em um supercomputador militar. Assemelha-se fortemente ao clássico filme de 1983, WarGames. Talvez a vida imite a arte? Se você encontrar os códigos de lançamento para nós, deixaremos você passar para o próximo desafio. Prometemos não começar uma guerra termonuclear.
Na tarefa, o aplicativo de console worp.exe
. Aparentemente, para resolvê-lo, você precisa pegar algum código.

A análise do ponto de entrada mostra que este é um arquivo de extração automática. Na inicialização, a variável de ambiente _MEIPASS2
. Se essa variável não existir, será criado um diretório temporário no qual o conteúdo do archive será descompactado e o aplicativo iniciará novamente com a variável de ambiente fornecida _MEIPASS2
. Conteúdo do arquivo:
. ├── api-ms-win-core-console-l1-1-0.dll ├── ... ├── ... ├── api-ms-win-crt-utility-l1-1-0.dll ├── base_library.zip ├── _bz2.pyd ├── _ctypes.pyd ├── _hashlib.pyd ├── libcrypto-1_1.dll ├── libssl-1_1.dll ├── _lzma.pyd ├── pyexpat.pyd ├── python37.dll ├── select.pyd ├── _socket.pyd ├── _ssl.pyd ├── this │ ├── __init__.py │ └── key ├── ucrtbase.dll ├── unicodedata.pyd ├── VCRUNTIME140.dll └── wopr.exe.manifest 1 directory, 56 files
A julgar pelo conteúdo, estamos lidando com um aplicativo Python
empacotado em exe
. Para apoiar isso, no binário principal, você pode encontrar a importação dinâmica das funções correspondentes da biblioteca Python
: PyMarshal_ReadObjectFromString
, PyEval_EvalCode
e outros. Para uma análise mais aprofundada, você precisa extrair o bytecode do Python
. Para fazer isso, salve o conteúdo do arquivo _MEIPASS2
do diretório temporário e _MEIPASS2
caminho para ele na variável de ambiente _MEIPASS2
. Execute o binário principal no modo de depuração, definindo um ponto de interrupção na função PyMarshal_ReadObjectFromString
. Essa função usa como argumento um ponteiro para um buffer com um código Python
serializado e seu comprimento. Despejamos o conteúdo do buffer de tamanho conhecido para cada uma das chamadas. Eu recebi apenas duas chamadas, enquanto na segunda, o objeto serializado é muito maior e vamos analisá-lo.
Uma maneira bastante simples de analisar os dados recebidos é convertê-los para o formato de arquivos .pyc
(bytecode compilado do Python
) e descompilar usando o uncompyle6
. Para fazer isso, basta adicionar um cabeçalho de 16 bytes aos dados recebidos. Como resultado, obtive o seguinte arquivo:
00000000: 42 0d 0d 0a 00 00 00 00 de cd 57 5d 00 00 00 00 B.........W].... 00000010: e3 00 00 00 00 00 00 00 00 00 00 00 00 09 00 00 ................ 00000020: 00 40 00 00 00 73 3c 01 00 00 64 00 5a 00 64 01 .@...s<...dZd 00000030: 64 02 6c 01 5a 01 64 01 64 02 6c 02 5a 02 64 01 dlZddlZd
Em seguida, descompilamos o arquivo resultante usando uncompyle6
:
uncompyle6 task.pyc > task.py
Se tentarmos executar o arquivo descompilado, obteremos uma exceção na linha BOUNCE = pkgutil.get_data('this', 'key')
. Isso é facilmente corrigido, simplesmente atribuindo o conteúdo do arquivo- key
do arquivo à variável BOUNCE
. Re-executando o script, veremos apenas a inscrição LOADING...
Aparentemente, algumas técnicas são usadas na tarefa que evitam a descompilação. Prosseguimos analisando o código Python
resultante. No final, vemos o seguinte ciclo:
for i in range(256): try: print(lzma.decompress(fire(eye(__doc__.encode()), bytes([i]) + BOUNCE))) except Exception: pass
Você pode entender que a função de print
é realmente substituída como exec
, e seu argumento depende apenas de __doc__.encode()
- o texto no início do arquivo.No início da execução do código, salve a função print
com um nome diferente e substitua-a print
no bloco try-except
. Quando executamos o script resultante, nada será exibido para nós novamente. Talvez a descompilação tenha __doc__
sido registrada incorretamente. Vamos tentar extrair o valor __doc__
diretamente do código serializado da seguinte maneira:
import marshal with open('pycode1', 'rb') as inp: data = inp.read() code = marshal.loads(data) doc = code.co_consts[0] with open('doc.txt', 'w') as outp: outp.write(doc)
Execute o script novamente, substituindo o conteúdo __doc__
. Como resultado, em um determinado valor i
, o código é exibido com sucesso na tela. Nós o salvamos em um novo arquivo e analisamos. Na função, wrong
você pode encontrar a seguinte linha:
trust = windll.kernel32.GetModuleHandleW(None)
, . 0x100000
wrong
, . , .
. z3
:
from z3 import * from stage2 import wrong xor = [212, 162, 242, 218, 101, 109, 50, 31, 125, 112, 249, 83, 55, 187, 131, 206] h = list(wrong()) h = [h[i] ^ xor[i] for i in range(16)] b = 16 * [None] x = [] for i in range(16): x.append(BitVec('x' + str(i), 32)) b[0] = x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[11] ^ x[14] b[1] = x[0] ^ x[1] ^ x[8] ^ x[11] ^ x[13] ^ x[14] b[2] = x[0] ^ x[1] ^ x[2] ^ x[4] ^ x[5] ^ x[8] ^ x[9] ^ x[10] ^ x[13] ^ x[14] ^ x[15] b[3] = x[5] ^ x[6] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[15] b[4] = x[1] ^ x[6] ^ x[7] ^ x[8] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[5] = x[0] ^ x[4] ^ x[7] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[6] = x[1] ^ x[3] ^ x[7] ^ x[9] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[15] b[7] = x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[10] ^ x[11] ^ x[14] b[8] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[12] b[9] = x[6] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[15] b[10] = x[0] ^ x[3] ^ x[4] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[11] = x[0] ^ x[2] ^ x[4] ^ x[6] ^ x[13] b[12] = x[0] ^ x[3] ^ x[6] ^ x[7] ^ x[10] ^ x[12] ^ x[15] b[13] = x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7] ^ x[11] ^ x[12] ^ x[13] ^ x[14] b[14] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[7] ^ x[11] ^ x[13] ^ x[14] ^ x[15] b[15] = x[1] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[13] ^ x[15] solver = Solver() for i in range(16): solver.add(x[i] < 128) for i in range(16): solver.add(b[i] == h[i]) if solver.check() == sat: m = solver.model() print(bytes([m[i].as_long() for i in x])) else: print('unsat')
, : 5C0G7TY2LWI2YXMB

0x08 — snake
The Flare team is attempting to pivot to full-time twitch streaming video games instead of reverse engineering computer software all day. We wrote our own classic NES game to stream content that nobody else has seen and watch those subscribers flow in. It turned out to be too hard for us to beat so we gave up. See if you can beat it and capture the internet points that we failed to collect.
NES
- . FCEUX
, .. . , .

, , 0x25
. , . NES
- IDA
. inesldr . 0x25
. C82A
, . 0x33
.

, — 0x32
0x25
. , . , FCEUX
. .

0x09 — reloadered
This is a simple challenge, enter the password, receive the key. I hear that it caused problems when trying to analyze it with ghidra. Remember that valid flare-on flags will always end with @flare-on.com
reloaderd.exe
, . , , . , , XOR
, @FLAG.com
, .

, NOP
. , , . . , . , , . , , NOP
, .
, , XOR
, . @flare-on.com
, . :
flag = bytearray(b'D)6\n)\x0f\x05\x1be&\x10\x04+h0/\x003/\x05\x1a\x1f\x0f8\x02\x18B\x023\x1a(\x04*G?\x04&dfM\x107>(>w\x1c?~64*\x00') for i in range(0x539): for j in range(0x34): if (i % 3) == 0 or (i % 7) == 0: flag[j] ^= (i & 0xff) end = b'@flare-on.com' def xor(a, b): return bytes([i^j for i, j in zip(a, b)]) for i in range(len(flag)): print(i, xor(end, flag[i:])) print(xor(flag, b'3HeadedMonkey'*4))

0x0A — Mugatu
Olá
I'm working an incident response case for Derek Zoolander. He clicked a link and was infected with MugatuWare! As a result, his new headshot compilation GIF was encrypted.
To secure an upcoming runway show, Derek needs this GIF decrypted; however, he refuses to pay the ransom.
We received an additional encrypted GIF from an anonymous informant. The informant told us the GIF should help in our decryption efforts, but we were unable to figure it out.
We're reaching out to you, our best malware analyst, in hopes that you can reverse engineer this malware and decrypt Derek's GIF.
I've included a directory full of files containing:
- MugatuWare malware
- Ransom note (GIFtToDerek.txt)
- Encrypted headshot GIF (best.gif.Mugatu)
- Encrypted informant GIF (the_key_to_success_0000.gif.Mugatu)
Obrigado,
Roy
:
- best.gif.Mugatu
- GIFtToDerek.txt
- Mugatuware.exe
- the_key_to_success_0000.gif.Mugatu
, , GIF
-. , .Mugatu
. Mugatuware.exe
. , — . , , .

IDA
, :
import ida_segment import ida_name import ida_bytes import ida_typeinf idata = ida_segment.get_segm_by_name('.idata') type_map = {} for addr in range(idata.start_ea, idata.end_ea, 4): name = ida_name.get_name(addr) if name: tp = ida_typeinf.idc_get_type(addr) if tp: type_map[name] = tp for addr in range(idata.start_ea, idata.end_ea, 4): imp = ida_bytes.get_dword(addr) if imp != 0: imp_name = ida_name.get_name(imp) name_part = imp_name.split('_')[-1] ida_name.set_name(addr, name_part + '_imp') if name_part in type_map: tp = type_map[name_part] ida_typeinf.apply_decl(addr, tp.replace('(', 'func(') + ';')
:

, , in-memory
PE
-. , CrazyPills!!!
. , . Sleep
, http
-. , , , . , , , . .

- :
- ;
- ;
Mailslots
;really, really, really, ridiculously good looking gifs
;.gif
. .Mugatu
. GIFtToDerek.txt
.
, — 8 . XOR
CrazyPills!!!
, . , :

XTEA
, — BYTE
, DWORD
. . Python
:
def crypt(a, b, key): i = 0 for _ in range(32): t = (i + key[i & 3]) & 0xffffffff a = (a + (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff i = (0x100000000 + i - 0x61C88647) & 0xffffffff t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (b + (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff return a, b def decrypt(a, b, key): i = 0xc6ef3720 for _ in range(32): t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (0x100000000 + b - (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff i = (i + 0x61C88647) & 0xffffffff t = (i + key[i & 3]) & 0xffffffff a = (0x100000000 + a - (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff return a, b
, the_key_to_success_0000.gif.Mugatu
. , . :

, , . C
. GIF
-.
#include <stdio.h> #include <unistd.h> void decrypt(unsigned int * inp, unsigned int * outp, unsigned char * key) { unsigned int i = 0xc6ef3720; unsigned int a = inp[0]; unsigned int b = inp[1]; unsigned int t; for(int j = 0; j < 32; j++) { t = i + key[(i >> 11) & 3]; b -= t ^ (a + ((a >> 5) ^ (a << 4))); i += 0x61C88647; t = i + key[i & 3]; a -= t ^ (b + ((b >> 5) ^ (b << 4))); } outp[0] = a; outp[1] = b; } int main() { int fd = open("best.gif.Mugatu", 0); unsigned int inp[2]; unsigned int outp[2]; unsigned int key = 0; read(fd, inp, 8); close(fd); for(unsigned long long key = 0; key < 0x100000000; key++) { if((key & 0xffffff) == 0) { printf("%lf\n", ((double)key) / ((double)0x100000000) * 100.0); } decrypt(inp, outp, &key); if( ((char *)outp)[0] == 'G' && ((char *)outp)[1] == 'I' && ((char *)outp)[2] == 'F' && ((char *)outp)[5] == 'a') { printf("%#llx\n", key); } } }
0xb1357331
:

0x0B — vv_max
Hey, at least its not subleq.
vv_max.exe
, . 256- . AVX2
, vpermd
, vpslld
. - :
0000 clear_regs 0001 r0 = 393130324552414c46 0023 r1 = 3030303030303030303030303030303030303030303030303030303030303030 0045 r3 = 1a1b1b1b1a13111111111111111111151a1b1b1b1a1311111111111111111115 0067 r4 = 1010101010101010080408040201101010101010101010100804080402011010 0089 r5 = b9b9bfbf041310000000000000000000b9b9bfbf04131000 00ab r6 = 2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f 00cd r10 = 140014001400140014001400140014001400140014001400140014001400140 00ef r11 = 1100000011000000110000001100000011000000110000001100000011000 0111 r12 = ffffffff0c0d0e08090a040506000102ffffffff0c0d0e08090a040506000102 0133 r13 = ffffffffffffffff000000060000000500000004000000020000000100000000 0155 r16 = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0177 r17 = 6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19 0199 r18 = 428a2f9871374491b5c0fbcfe9b5dba53956c25b59f111f1923f82a4ab1c5ed5 01bb r19 = 300000002000000010000000000000007000000060000000500000004 01dd r20 = 0 01ff r21 = 100000001000000010000000100000001000000010000000100000001 0221 r22 = 200000002000000020000000200000002000000020000000200000002 0243 r23 = 300000003000000030000000300000003000000030000000300000003 0265 r24 = 400000004000000040000000400000004000000040000000400000004 0287 r25 = 500000005000000050000000500000005000000050000000500000005 02a9 r26 = 600000006000000060000000600000006000000060000000600000006 02cb r27 = 700000007000000070000000700000007000000070000000700000007 02ed r20 = vpermd(r0, r20) 02f1 r21 = vpermd(r0, r21) 02f5 r22 = vpermd(r0, r22) 02f9 r23 = vpermd(r0, r23) 02fd r24 = vpermd(r0, r24) 0301 r25 = vpermd(r0, r25) 0305 r26 = vpermd(r0, r26) 0309 r27 = vpermd(r0, r27) 030d r7 = vpsrld(r1, 4) 0311 r28 = r20 ^ r21 0315 r28 = r28 ^ r22 0319 r28 = r28 ^ r23 031d r28 = r28 ^ r24 0321 r28 = r28 ^ r25 0325 r28 = r28 ^ r26 0329 r28 = r28 ^ r27 032d r7 = r7 & r6 0331 r29 = vpslld(r17, 7) 0335 r30 = vpsrld(r17, 25) 0339 r15 = r29 | r30 033d r8 = vpcmpeqb(r1, r6) 0341 r29 = vpslld(r17, 21) 0345 r30 = vpsrld(r17, 11) 0349 r29 = r29 | r30 034d r15 = r15 ^ r29 0351 r8 = vpcmpeqb(r1, r6) 0355 r29 = vpslld(r17, 26) 0359 r30 = vpsrld(r17, 6) 035d r29 = r29 | r30 0361 r15 = r15 ^ r29 0365 r29 = r20 ^ r16 0369 r30 = r20 & r18 036d r29 = r29 ^ r30 0371 r15 = add_d(r29, r15) 0375 r20 = add_d(r15, r0) 0379 r7 = add_b(r8, r7) 037d r29 = r20 ^ r28 0381 r17 = vpermd(r29, r19) 0385 r7 = vpshufb(r5, r7) 0389 r29 = vpslld(r17, 7) 038d r30 = vpsrld(r17, 25) 0391 r15 = r29 | r30 0395 r29 = vpslld(r17, 21) 0399 r30 = vpsrld(r17, 11) 039d r29 = r29 | r30 03a1 r15 = r15 ^ r29 03a5 r29 = vpslld(r17, 26) 03a9 r30 = vpsrld(r17, 6) 03ad r29 = r29 | r30 03b1 r15 = r15 ^ r29 03b5 r2 = add_b(r1, r7) 03b9 r29 = r21 ^ r16 03bd r30 = r21 & r18 03c1 r29 = r29 ^ r30 03c5 r15 = add_d(r29, r15) 03c9 r21 = add_d(r15, r0) 03cd r29 = r21 ^ r28 03d1 r17 = vpermd(r29, r19) 03d5 r20 = r20 ^ r21 03d9 r29 = vpslld(r17, 7) 03dd r30 = vpsrld(r17, 25) 03e1 r15 = r29 | r30 03e5 r29 = vpslld(r17, 21) 03e9 r30 = vpsrld(r17, 11) 03ed r29 = r29 | r30 03f1 r15 = r15 ^ r29 03f5 r29 = vpslld(r17, 26) 03f9 r30 = vpsrld(r17, 6) 03fd r29 = r29 | r30 0401 r15 = r15 ^ r29 0405 r7 = vpmaddubsw(r2, r10) 0409 r29 = r22 ^ r16 040d r30 = r22 & r18 0411 r29 = r29 ^ r30 0415 r15 = add_d(r29, r15) 0419 r22 = add_d(r15, r0) 041d r29 = r22 ^ r28 0421 r17 = vpermd(r29, r19) 0425 r20 = r20 ^ r22 0429 r29 = vpslld(r17, 7) 042d r30 = vpsrld(r17, 25) 0431 r15 = r29 | r30 0435 r29 = vpslld(r17, 21) 0439 r30 = vpsrld(r17, 11) 043d r29 = r29 | r30 0441 r15 = r15 ^ r29 0445 r29 = vpslld(r17, 26) 0449 r30 = vpsrld(r17, 6) 044d r29 = r29 | r30 0451 r15 = r15 ^ r29 0455 r2 = vpmaddwd(r7, r11) 0459 r29 = r23 ^ r16 045d r30 = r23 & r18 0461 r29 = r29 ^ r30 0465 r15 = add_d(r29, r15) 0469 r23 = add_d(r15, r0) 046d r29 = r23 ^ r28 0471 r17 = vpermd(r29, r19) 0475 r20 = r20 ^ r23 0479 r29 = vpslld(r17, 7) 047d r30 = vpsrld(r17, 25) 0481 r15 = r29 | r30 0485 r29 = vpslld(r17, 21) 0489 r30 = vpsrld(r17, 11) 048d r29 = r29 | r30 0491 r15 = r15 ^ r29 0495 r29 = vpslld(r17, 26) 0499 r30 = vpsrld(r17, 6) 049d r29 = r29 | r30 04a1 r15 = r15 ^ r29 04a5 r29 = r24 ^ r16 04a9 r30 = r24 & r18 04ad r29 = r29 ^ r30 04b1 r15 = add_d(r29, r15) 04b5 r24 = add_d(r15, r0) 04b9 r29 = r24 ^ r28 04bd r17 = vpermd(r29, r19) 04c1 r20 = r20 ^ r24 04c5 r29 = vpslld(r17, 7) 04c9 r30 = vpsrld(r17, 25) 04cd r15 = r29 | r30 04d1 r29 = vpslld(r17, 21) 04d5 r30 = vpsrld(r17, 11) 04d9 r29 = r29 | r30 04dd r15 = r15 ^ r29 04e1 r29 = vpslld(r17, 26) 04e5 r30 = vpsrld(r17, 6) 04e9 r29 = r29 | r30 04ed r15 = r15 ^ r29 04f1 r29 = r25 ^ r16 04f5 r30 = r25 & r18 04f9 r29 = r29 ^ r30 04fd r15 = add_d(r29, r15) 0501 r25 = add_d(r15, r0) 0505 r29 = r25 ^ r28 0509 r17 = vpermd(r29, r19) 050d r20 = r20 ^ r25 0511 r2 = vpshufb(r2, r12) 0515 r29 = vpslld(r17, 7) 0519 r30 = vpsrld(r17, 25) 051d r15 = r29 | r30 0521 r29 = vpslld(r17, 21) 0525 r30 = vpsrld(r17, 11) 0529 r29 = r29 | r30 052d r15 = r15 ^ r29 0531 r29 = vpslld(r17, 26) 0535 r30 = vpsrld(r17, 6) 0539 r29 = r29 | r30 053d r15 = r15 ^ r29 0541 r29 = r26 ^ r16 0545 r30 = r26 & r18 0549 r29 = r29 ^ r30 054d r15 = add_d(r29, r15) 0551 r26 = add_d(r15, r0) 0555 r29 = r26 ^ r28 0559 r17 = vpermd(r29, r19) 055d r20 = r20 ^ r26 0561 r29 = vpslld(r17, 7) 0565 r30 = vpsrld(r17, 25) 0569 r15 = r29 | r30 056d r29 = vpslld(r17, 21) 0571 r30 = vpsrld(r17, 11) 0575 r29 = r29 | r30 0579 r15 = r15 ^ r29 057d r29 = vpslld(r17, 26) 0581 r30 = vpsrld(r17, 6) 0585 r29 = r29 | r30 0589 r15 = r15 ^ r29 058d r2 = vpermd(r2, r13) 0591 r29 = r27 ^ r16 0595 r30 = r27 & r18 0599 r29 = r29 ^ r30 059d r15 = add_d(r29, r15) 05a1 r27 = add_d(r15, r0) 05a5 r29 = r27 ^ r28 05a9 r17 = vpermd(r29, r19) 05ad r20 = r20 ^ r27 05b1 r19 = ffffffffffffffffffffffffffffffffffffffffffffffff 05d3 r20 = r20 & r19 05d7 r31 = 2176620c3a5c0f290b583618734f07102e332623780e59150c05172d4b1b1e22
FLARE2019
. , , . , FLARE2019
. r2
r20
. , r20
. r2
— 6 r2
. , 6 . Frida
:
// vvmax.js var modules = Process.enumerateModules(); var base = modules[0].base; Interceptor.attach(base.add(0x1665), function() { var p = this.context.rdx.add(0x840); var res = p.readByteArray(32); send(null, res); });
:

0x0C — help
You're my only hope FLARE-On player! One of our developers was hacked and we're not sure what they took. We managed to set up a packet capture on the network once we found out but they were definitely already on the system. I think whatever they installed must be buggy — it looks like they crashed our developer box. We saved off the dump file but I can't make heads or tails of it — PLEASE HELP!!!!!!
. RAM
- . 4444
, 6666
, 7777
8888
. , , , RAM
-. volatility
. volatility
Win10x64_15063
, , Win7SP1x64
, .
volatility
:
$ volatility --profile Win7SP1x64 -f help.dmp modules Volatility Foundation Volatility Framework 2.6 Offset(V) Name Base Size File ------------------ -------------------- ------------------ ------------------ ---- 0xfffffa800183e890 ntoskrnl.exe 0xfffff80002a49000 0x5e7000 \SystemRoot\system32\ntoskrnl.exe ... 0xfffffa800428ff30 man.sys 0xfffff880033bc000 0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys
:
$ volatility --profile Win7SP1x64 -f help.dmp moddump --base 0xfffff880033bc000 -D drivers Volatility Foundation Volatility Framework 2.6 Module Base Module Name Result ------------------ -------------------- ------ 0xfffff880033bc000 man.sys Error: e_magic 0000 is not a valid DOS signature.
, . volshell
.
$ volatility --profile Win7SP1x64 -f help.dmp volshell In [1]: db(0xfffff880033bc000) 0xfffff880033bc000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ In [2]: db(0xfffff880033bc000 + 0x1100) 0xfffff880033bd100 01 48 8b 4c 24 20 48 8b 44 24 28 48 89 41 08 48 .HL$.HD$(HAH 0xfffff880033bd110 83 c4 18 c3 cc cc cc cc cc cc cc cc cc cc cc cc ................ 0xfffff880033bd120 48 89 4c 24 08 48 83 ec 38 48 8b 44 24 40 0f be HL$.H..8H.D$@.. 0xfffff880033bd130 48 43 48 8b 44 24 40 0f be 40 42 83 c0 01 3b c8 HCH.D$@..@B...;. 0xfffff880033bd140 7e 27 45 33 c9 41 b8 15 5b 00 00 48 8d 15 de 44 ~'E3.A..[..H...D 0xfffff880033bd150 00 00 48 8d 0d 07 45 00 00 ff 15 71 4f 00 00 c7 ..H...E....qO... 0xfffff880033bd160 44 24 20 00 00 00 00 eb 08 c7 44 24 20 01 00 00 D$........D$.... 0xfffff880033bd170 00 48 8b 44 24 40 48 8b 80 b8 00 00 00 48 83 c4 .HD$@H......H.. In [4]: man = addrspace().read(0xfffff880033bc000, 0xf000) In [5]: with open('man_writeup.sys', 'wb') as f: ...: f.write(man) ...:
, , moddump
. . . - , , .
RC4
. , .
user-space
. DLL
- . DLL
- ( m.dll
), . , . :
- (
+0x8
) _EPROCESS
( +0x68
)- (
+0x48
) - (
+0x58
)
DLL
- RC4
, 0x2c
- , 0x48
.
volatility
volshell
:
import struct from Crypto.Cipher import ARC4 head = 0xfffff880033c8158 krnl = addrspace() def u64(x): return struct.unpack('Q', x)[0] fd = u64(krnl.read(head, 8)) while True: proc_addr = u64(krnl.read(fd + 0x68, 8)) base = u64(krnl.read(fd + 0x48, 8)) key = krnl.read(fd + 0x48, 0x2c) sz = u64(krnl.read(fd + 0x58, 8)) fd = u64(krnl.read(fd, 8)) p = obj.Object('_EPROCESS', proc_addr, krnl) print p.ImageFileName.v(), hex(proc_addr), hex(base), hex(sz) proc_space = p.get_process_address_space() dump = proc_space.read(base, sz) if dump[:0x100] == '\x00' * 0x100: dump = ARC4.new(key).decrypt(dump) with open('proc_{:016x}'.format(base), 'wb') as f: f.write(dump) if fd == head: break
, , RC4
. IDA
, , :
IDA from __future__ import print_function import sys import re from idaapi import get_func, decompile, get_name_ea, auto_wait, BADADDR from idaapi import cot_call, cot_obj, init_hexrays_plugin, qexit import ida_typeinf import ida_lines def rc4(key, data): S = list(range(256)) j = 0 for i in list(range(256)): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] j = 0 y = 0 out = [] for char in data: j = (j + 1) % 256 y = (y + S[j]) % 256 S[j], S[y] = S[y], S[j] out.append(chr(ord(char) ^ S[(S[j] + S[y]) % 256])) return ''.join(out) def decrypt_stack_str_args(ea): func = get_func(ea) if func is None: return try: c_func = decompile(func) c_func.pseudocode except Exception as ex: return for citem in c_func.treeitems: citem = citem.to_specific_type if citem.is_expr() and\ citem.op == cot_call and\ citem.ea == ea: args = [] key = citem.a[0] key_len = citem.a[1] s = citem.a[2] s_len = citem.a[3] def get_var_idx(obj): while obj.opname != 'var': if obj.opname in ('ref', 'cast'): obj = obj.x else: raise Exception('can\'t find type') return obj.v.idx if key_len.opname != 'num' or s_len.opname != 'num': print('[!] can\'t get length: 0x{:08x}'.format(ea)) else: try: key_len_val = key_len.n._value s_len_val = s_len.n._value print('0x{:08x}'.format(ea), 'key_len =', key_len_val, ', s_len =', s_len_val) hx_view = idaapi.open_pseudocode(ea, -1) key_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(key)].location.stkoff() s_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(s)].location.stkoff() key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(key_len_val), 0) hx_view.set_lvar_type(key_var, tif) s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(s_len_val + 1), 0) hx_view.set_lvar_type(s_var, tif) key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] key_regex = re.compile('{}\[(.+)\] = (.+);'.format(key_var.name)) s_regex = re.compile('{}\[(.+)\] = (.+);'.format(s_var.name)) key = bytearray(key_len_val) s = bytearray(s_len_val + 1) src = '\n'.join([ida_lines.tag_remove(i.line) for i in hx_view.cfunc.pseudocode]) for i, j in s_regex.findall(src): s[int(i)] = (0x100 + int(j)) & 0xff for i, j in key_regex.findall(src): key[int(i)] = (0x100 + int(j)) & 0xff key = ''.join(chr(i) for i in key) s = ''.join(chr(i) for i in s) result = rc4(key, s[:-1])
:

. :
m.dll
— , . 4444
. — ;n.dll
— 192.168.1.243
;c.dll
— RC4
. ;k.dll
— (keylogger);s.dll
— ;f.dll
— .
, XOR
8. 4444
, .. . : , — . , - .
( 4444
) . , - . , . :
- keys.kdb
- C:\
- C:\keypass\keys.kdb
, f.dll
: keys.kdb
, .
6666
. LZNT1
RC4
XOR
. , XOR
- , .. . RC4
, RAM
-: FLARE ON 2019
. , GetUserNameA
, , - , RC4
. LZNT1
:
from ctypes import * nt = windll.ntdll for fname in ['input']: with open(fname, 'rb') as f: buf = f.read() dec_data = create_string_buffer(0x10000) final_size = c_ulong(0) status = nt.RtlDecompressBuffer( 0x102,
6666
. :
00000000: CC 69 94 FA 6A 37 18 29 CB 8D 87 EF 11 63 8E 73 .i..j7.).....cs 00000010: FE AB 43 3B B3 94 28 4B 4D 19 00 00 00 4F DB C7 ..C;..(KM....O.. 00000020: F3 1E E4 13 15 34 8F 51 A9 2B C2 D7 C1 96 78 F7 .....4.Q.+....x. 00000030: 91 98
, :
00000000: 19 00 00 00 4F DB C7 F3 1E E4 13 15 34 8F 51 A9 ....O.......4.Q. 00000010: 2B C2 D7 C1 96 78 F7 91 98 +....x...
4 — , 25. :
00000000: 12 B0 00 43 3A 5C 6B 65 79 70 61 04 73 73 01 70 ...C:\keypa.ss.p 00000010: 73 2E 6B 64 62 s.kdb
C:\keypass\keys.kdb
. , , . 6666
— KeePass
.
7777
BMP
. XOR
, , , .. . , , KeePass
.


8888
k.dll
— .
C:\Windows\system32\cmd.exe nslookup googlecom ping 1722173110 nslookup soeblogcom nslookup fiosquatumgatefiosrouterhome C:\Windows\system32\cmd.exe Start Start menu Start menu chrome www.flare-on.com - Google Chrome tis encrypting something twice better than once Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu keeKeePass <DYN_TITLE> th1sisth33nd111 KeePass keys.kdb - KeePass Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111 Open Database - keys.kdb KeePass Start Start menu Start menu KeePass Start menu Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111
th1sisth33nd111
, . , . , keylogger . , , ping
. hashcat
KeePass
, . :
$ strings help.dmp | grep -i '3nd!' !s_iS_th3_3Nd!!!
Th
.

. , -.
0x0D —
, . , , . , volatility
, . ( UTC+3:00):