Skip to content

Instantly share code, notes, and snippets.

@hhc0null
Last active May 26, 2017 16:00
Show Gist options
  • Save hhc0null/f22bb4879dba558e6f4a to your computer and use it in GitHub Desktop.
Save hhc0null/f22bb4879dba558e6f4a to your computer and use it in GitHub Desktop.
A Writeup for rev100 on "#04 Hokkaido CTF 勉強会"(http://doctf.connpass.com/event/20842/)

rev100のwriteup

Author: hhc0null
 本文は#04 Hokkaido CTF 勉強会で出されたrev100のwriteupである.

概要

 最適化のかかっていない標準的なELF x86のバイナリを解析するという問題. この問題は主に以下のようなことを行っている.

  • 前処理
     主に, flagとなる文字列が判別できないようなバイト列を生成する.
  • 比較・判定部
     ユーザからの入力と前述のバイト列に何らかの処理をして, 比較を行うことでユーザからの入力がflag文字列に等しいか否かを判定する.

各処理について

 ここでは, それぞれの処理がどのようなことを行っているかを観察する.

前処理

 0x804849e 〜 0x80484e2にかけて, flagを何らかの方法によりエンコードしたバイト列をスタック上に展開している.

 804849e:	c6 45 d9 b6          	movb   $0xb6,-0x27(%ebp)
 80484a2:	c6 45 da 8b          	movb   $0x8b,-0x26(%ebp)
 80484a6:	c6 45 db d8          	movb   $0xd8,-0x25(%ebp)
 80484aa:	c6 45 dc 8c          	movb   $0x8c,-0x24(%ebp)
 80484ae:	c6 45 dd ab          	movb   $0xab,-0x23(%ebp)
 80484b2:	c6 45 de 90          	movb   $0x90,-0x22(%ebp)
 80484b6:	c6 45 df b0          	movb   $0xb0,-0x21(%ebp)
 80484ba:	c6 45 e0 ba          	movb   $0xba,-0x20(%ebp)
 80484be:	c6 45 e1 9e          	movb   $0x9e,-0x1f(%ebp)
 80484c2:	c6 45 e2 ac          	movb   $0xac,-0x1e(%ebp)
 80484c6:	c6 45 e3 86          	movb   $0x86,-0x1d(%ebp)
 80484ca:	c6 45 e4 b9          	movb   $0xb9,-0x1c(%ebp)
 80484ce:	c6 45 e5 cf          	movb   $0xcf,-0x1b(%ebp)
 80484d2:	c6 45 e6 8d          	movb   $0x8d,-0x1a(%ebp)
 80484d6:	c6 45 e7 b2          	movb   $0xb2,-0x19(%ebp)
 80484da:	c6 45 e8 9a          	movb   $0x9a,-0x18(%ebp)
 80484de:	c6 45 e9 de          	movb   $0xde,-0x17(%ebp)
 80484e2:	c6 45 ea ff          	movb   $0xff,-0x16(%ebp)

上記のコードより, 最後尾には0xffがあることがわかる. また, 全体的に0x80以降のバイトが多いことがわかるだろう. これを頭の隅に置いておいて欲しい.
 このあとの処理については, ユーザからの入力をコマンドライン引数(argv)により受け付けたり, argv[1]の文字列の長さを判定したりしている. 本質的ではないので割愛する.

比較・判定部

 ここではユーザからの入力とflagとなる文字列の比較, flagか否かの判定を行っているが, この問題においては非常にシンプルなものとなっている. 以下に示す, 0x804855c 〜 0x8048588が比較部で, 0x804858a 〜 0x8048590が判定部となっている.

 804855c:	c7 45 f4 00 00 00 00 	movl   $0x0,-0xc(%ebp)
 8048563:	eb 04                	jmp    8048569 <main+0xde>
 8048565:	83 45 f4 01          	addl   $0x1,-0xc(%ebp)
 8048569:	8b 55 f4             	mov    -0xc(%ebp),%edx
 804856c:	8b 45 ec             	mov    -0x14(%ebp),%eax
 804856f:	01 d0                	add    %edx,%eax
 8048571:	0f b6 08             	movzbl (%eax),%ecx
 8048574:	8d 55 d9             	lea    -0x27(%ebp),%edx
 8048577:	8b 45 f4             	mov    -0xc(%ebp),%eax
 804857a:	01 d0                	add    %edx,%eax
 804857c:	0f b6 00             	movzbl (%eax),%eax
 804857f:	31 c8                	xor    %ecx,%eax
 8048581:	88 45 eb             	mov    %al,-0x15(%ebp)
 8048584:	80 7d eb ff          	cmpb   $0xff,-0x15(%ebp)
 8048588:	74 db                	je     8048565 <main+0xda>
 804858a:	8b 45 f4             	mov    -0xc(%ebp),%eax
 804858d:	3b 45 f0             	cmp    -0x10(%ebp),%eax
 8048590:	75 25                	jne    80485b7 <main+0x12c>
 8048592:	83 ec 0c             	sub    $0xc,%esp
 8048595:	68 89 86 04 08       	push   $0x8048689
 804859a:	e8 a1 fd ff ff       	call   8048340 <puts@plt>
 804859f:	83 c4 10             	add    $0x10,%esp
 80485a2:	83 ec 08             	sub    $0x8,%esp
 80485a5:	ff 75 ec             	pushl  -0x14(%ebp)
 80485a8:	68 94 86 04 08       	push   $0x8048694
 80485ad:	e8 7e fd ff ff       	call   8048330 <printf@plt>
 80485b2:	83 c4 10             	add    $0x10,%esp
 80485b5:	eb 10                	jmp    80485c7 <main+0x13c>
 80485b7:	83 ec 0c             	sub    $0xc,%esp
 80485ba:	68 9d 86 04 08       	push   $0x804869d
 80485bf:	e8 7c fd ff ff       	call   8048340 <puts@plt>

ここでの処理は,

  • ユーザからの入力のひとつひとつのバイトと, 前述したバイト列の排他的論理和(XOR)を求める.
  • その値が0xffと比較して等しかったら0x8048565にjmpしてこれを繰り返す.
  • 等しくない場合はそのまま0x804858aに進み, 今までに繰り返した回数とバイト列の長さを比較する.
  • 比較した結果が等しいものだったなら, ユーザからの入力をflagとして出力する.

というものになっている.

解法

 XORについて,

  • [0] XOR 1 => [1]
  • [1] XOR 1 => [0]

であることから, 1でXORするということはNOTにすることに相当するということが解る(これが情報系の人間の常識である). この問題はユーザからの入力と前述のバイト列をXORし0xff(0b11111111)に等しいか否かを判定しているため, 単に「前述のバイト列をNOTすればよい」ということが解る.  以上を踏まえてソルバを書くと, 次のようなコードとなる.

#! /usr/bin/env python2


encoded_flag = (
    0xb6, 0x8b, 0xd8, 0x8c, 0xab, 0x90, 0xb0, 0xba, 
    0x9e, 0xac, 0x86, 0xb9, 0xcf, 0x8d, 0xb2, 0x9a, 
    0xde, 0xff,
)

def f(x):
    return chr(x ^ 0xff) # this proccess is equivalent to xor.

decoded_flag = "".join(map(f, encoded_flag))

print "key{%s}"%(decoded_flag) 

総括

 以下に元々のコードを示す.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// key{It'sToOEaSyF0rMe!}
int main(int argc, char *argv[])
{
    unsigned char inversed[] = {
        '\xb6', '\x8b', '\xd8', '\x8c', '\xab', '\x90', '\xb0', '\xba', 
        '\x9e', '\xac', '\x86', '\xb9', '\xcf', '\x8d', '\xb2', '\x9a', 
        '\xde', '\xff',
    };

    int count = 0, len = sizeof(inversed);
    unsigned char tmp, *input = argv[1];

    if(argc < 2) {
        printf("Usage: %s <flag>\n", argv[0]);
        exit(EXIT_SUCCESS);
    }

    if(strlen(argv[1]) != len - 1) {
        puts("The length is wrong...");
        exit(EXIT_SUCCESS);
    }

    for(count = 0; (tmp = input[count] ^ inversed[count]) == 0xff; count++);
    if(count == len) {
        puts("Congratz!!");
        printf("key{%s}\n", input);
    } else {
        puts("Wrong...");
    }

    return 0;
}

冗長に書いただけのコードだったので, x86のアセンブリが読める人なら簡単に解る基本的な問題だったと思う. 最後にflagを示しておこう.

flag: key{It'sToOEaSyForMe!} flag: key{It'sToOEaSyF0rMe!}

@hhc0null
Copy link
Author

writeupに書いていたflagが間違っていたので修正. 申し訳ございません><

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