CSAW CTF 2015 - Sharpturn

I think my SATA controller is dying.

Sharpturn은 400점짜리 포렌식 문제다. sharpturn.tar.xz파일이 주어졌는데, 이런 파일이 들어있었다.

HEAD  branches/  config  description  hooks/  info/  objects/  refs/

Git 저장소의 .git/ 디렉토리임을 대번에 알 수 있었다.

1. 파일 복구하기

커밋 로그로부터 원본 파일을 복구하는 건 전혀 어렵지 않다. 여러 가지 방법이 있는데, 나는 local repository를 clone하는 방법을 써서 Makefilesharp.cpp, 두 파일을 얻었다. sharp.cpp는 이렇게 생겼다.

#include <iostream>
#include <string>
#include <algorithm>

#include <stdint.h>
#include <stdio.h>
#include <openssl/sha.h>

using namespace std;

std::string calculate_flag(
                std::string &part1,
                int64_t part2,
                std::string &part4,
                uint64_t factor1,
                uint64_t factor2)
{

        std::transform(part1.begin(), part1.end(), part1.begin(), ::tolower);
        std::transform(part4.begin(), part4.end(), part4.begin(), ::tolower);

        SHA_CTX ctx;
        SHA1_Init(&ctx);

        unsigned int mod = factor1 % factor2;
        for (unsigned int i = 0; i < mod; i+=2)
        {
                SHA1_Update(&ctx,
                                reinterpret_cast<const unsigned char *>(part1.c_str()),
                                part1.size());
        }


        while (part2-- > 0)
        {
                SHA1_Update(&ctx,
                                reinterpret_cast<const unsigned char *>(part4.c_str()),
                                part1.size());
        }

        unsigned char *hash = new unsigned char[SHA_DIGEST_LENGTH];
        SHA1_Final(hash, &ctx);

        std::string rv;
        for (unsigned int i = 0; i < SHA_DIGEST_LENGTH; i++)
        {
                char *buf;
                asprintf(&buf, "%02x", hash[i]);
                rv += buf;
                free(buf);
        }

        return rv;
}

int main(int argc, char **argv)
{
        (void)argc; (void)argv; //unused

        std::string part1;
        cout << "Part1: Enter flag:" << endl;
        cin >> part1;

        int64_t part2;
        cout << "Part2: Input 51337:" << endl;
        cin >> part2;

        std::string part3;
        cout << "Part3: Watch this: https://www.youtube.com/watch?v=PBwAxmrE194" << endl;
        cin >> part3;

        std::string part4;
        cout << "Part4: C.R.E.A.M. Get da _____: " << endl;
        cin >> part4;

        uint64_t first, second;
        cout << "Part5: Input the two prime factors of the number 270031727027." << endl;
        cin >> first;
        cin >> second;

        uint64_t factor1, factor2;
        if (first < second)
        {
                factor1 = first;
                factor2 = second;
        }
        else
        {
                factor1 = second;
                factor2 = first;
        }

        std::string flag = calculate_flag(part1, part2, part4, factor1, factor2);
        cout << "flag{";
        cout << &lag;
        cout << "}" << endl;

        return 0;
}

일단 코드에 오타가 있어서 조금 고쳐서 컴파일 해야 한다. 게다가 270031727027의 소인수가 네 개라서 어느 두 소수를 말하는 건지 알 수도 없었다. 그래서 프로그램이 하라는 대로 입력을 넣고, 마지막 part5에 소인수 네 개중 두 개를 골라 넣어도 그렇게 나온 flag는 정답이 아니다.

2. 진짜 파일 복구하기

문제에서 SATA 컨트롤러에 문제가 있다고 하니 파일이 손상되었을 가능성을 생각해 보아야 한다. 관련 키워드로 인터넷을 뒤져보니 git fsck를 돌려보란다.

$ git fsck
Checking object directories: 100% (256/256), done.
error: sha1 mismatch 354ebf392533dce06174f9c8c093036c138935f3
error: 354ebf392533dce06174f9c8c093036c138935f3: object corrupt or missing
error: sha1 mismatch d961f81a588fcfd5e57bbea7e17ddae8a5e61333
error: d961f81a588fcfd5e57bbea7e17ddae8a5e61333: object corrupt or missing
error: sha1 mismatch f8d0839dd728cb9a723e32058dcc386070d5e3b5
error: f8d0839dd728cb9a723e32058dcc386070d5e3b5: object corrupt or missing
missing blob 354ebf392533dce06174f9c8c093036c138935f3
missing blob f8d0839dd728cb9a723e32058dcc386070d5e3b5
missing blob d961f81a588fcfd5e57bbea7e17ddae8a5e61333

문제로 주어진 저장소에는 sharp.cpp가 총 네 가지 버전이 있는데, sha1 에러가 난 세 object는 각각 sharp.cpp의 두번째, 세번째, 네번째 버전이다.
두 번째 버전(354ebf392533dce06174f9c8c093036c138935f3)은 다음과 같다.

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

int main(int argc, char **argv)
{
        (void)argc; (void)argv; //unused

        std::string part1;
        cout << "Part1: Enter flag:" << endl;
        cin >> part1;

        int64_t part2;
        cout << "Part2: Input 51337:" << endl;
        cin >> part2;

        std::string part3;
        cout << "Part3: Watch this: https://www.youtube.com/watch?v=PBwAxmrE194" << endl;
        cin >> part3;

        std::string part4;
        cout << "Part4: C.R.E.A.M. Get da _____: " << endl;
        cin >> part4;

        return 0;
}

이 파일에 문제가 있어서 sha1 해시가 다르다는 건데, 틀릴 만한 부분은 숫자밖에 없다고 가정하고 sha1을 맞추려고 해보았다.
그 결과 ‘51337’을 ‘31337’로 고치면 sha1해시가 맞게 된다는 것을 알아냈다.

세 번째 버전(d961f81a588fcfd5e57bbea7e17ddae8a5e61333)은 두 번째 버전에 다음 부분이 추가된 버전이다.

+     uint64_t first, second;
+     cout << "Part5: Input the two prime factors of the number 270031727027." << endl;
+     cin >> first;
+     cin >> second;
+
+     uint64_t factor1, factor2;
+     if (first < second)
+     {
+             factor1 = first;
+             factor2 = second;
+     }
+     else
+     {
+             factor1 = second;
+             factor2 = first;
+     }

일단, 내용을 바꿔가며 sha1을 계산하기 전에 이때 파일의 앞부분에 있는 51337을 31337로 바꿔야 한다. 여기서 틀릴 만한 부분은 저 문제의 270031727027일 것이다. 그러나 모든 숫자를 테스트 하기엔 너무 숫자가 크다. 그래서 숫자 하나만 바뀌었다고 가정하고 숫자를 바꿔가며 sha1을 계산해보았다.
그 결과 저 숫자를 272031727027로 바꿔야 한다는 결론이 나왔다. 272031727027은 31357 * 8675311이기 때문에 맞는 것 같았다.

이렇게 두 숫자를 고치고 컴파일 한 뒤 다음과 같은 입력을 주면 올바른 flag를 출력한다.

$ ./sharp
flag
31337
asdf
money
31357 8675311
flag{3b532e0a187006879d262141e16fa5f05f2e6752}

아래는 sha1을 맞출 때 사용한 스크립트이다.

#!/usr/bin/env python
import itertools
import hashlib
import sys
import zlib

def compute_hash(s):
    try:
        h = hashlib.sha1(s).hexdigest()
        return h
    except:
        return None

def brute_number(s):
    for i in range(100000):
        t = s.replace("51337",str(i))
        h = compute_hash(t)
        if h == goal:
            print i, h
            print "Found!"
            return

def brute_number2(s):
    s = s.replace('51337','31337')
    n = list('270031727027')
    for i in range(12):
        for j in range(10):
            m = n[:]
            m[i] = str(j)
            t = s.replace('270031727027',''.join(m))
            h = compute_hash(t)
            if h == goal:
                print ''.join(m), h
                print "Found!"
                return

if __name__ == '__main__':
    f = open("sharpturn/objects/35/4ebf392533dce06174f9c8c093036c138935f3")
    goal = "354ebf392533dce06174f9c8c093036c138935f3"
    s = zlib.decompress(f.read())
    f.close()
    brute_number(s)

    f = open("sharpturn/objects/d9/61f81a588fcfd5e57bbea7e17ddae8a5e61333")
    goal = "d961f81a588fcfd5e57bbea7e17ddae8a5e61333"
    s = zlib.decompress(f.read())
    f.close()
    brute_number2(s)

Other posts (list)


Blogging in Github
CSAW CTF 2015 - HackingTime
CSAW CTF 2015 - Sharpturn
CSAW CTF 2015 - pcapin
AVR Memory and Registers