Pwnable.kr - Writeup

Sun, Aug 18, 2019 6-minute read

題目:blackjack, collision, input, random

blackjack

Challenge Info

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?

Running at : nc pwnable.kr 9009

Rule

遊戲規則就是二十一點,一開始設定賭金 bet,然後開始遊戲,如果贏了:cash += bet,否則 cash -= bet

目標是賺到足夠多的錢。

Analysis

設定賭金的 function 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

可以看到唯一的限制是 bet > cash,但沒有規定 bet 必須為正數,因此解法很明確了。

設定賭金為一個極小的負數如 -1000000000 ,然後輸一局遊戲就可以得到 flag。

Flag

YaY_I_AM_A_MILLIONARE_LOL


collision

Challenge Info

Daddy told me about cool MD5 hash collision today.

I wanna do something like that too!

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

Solution

連線上去後看到 vol.c,內容如下:

 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
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

int main(int argc, char* argv[]){
	if(argc<2){
		printf("usage : %s [passcode]\n", argv[0]);
		return 0;
	}
	if(strlen(argv[1]) != 20){
		printf("passcode length should be 20 bytes\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\n");
	return 0;
}

可以知道取得 flag 的條件為:

  1. argv[1]的大小為20byte
  2. 根據check_passwordargv[1]每4byte作為一個int之總和需為0x21DD09EC

也就是建造五個int且總和為0x21DD09EC即可。


我將前四個int設為0x01020304,因此最後一個int0x21DD09EC - 0x01020304 * 4

使用pwntools幫忙建構,指令如下:

1
./col `python2 -c "from pwn import *; print(p32(0x01020304) * 4 + p32(0x21DD09EC - 0x01020304 * 4))"`

執行後得到flag

Flag

daddy! I just managed to create a hash collision :)


input

Challenge Info

Mom? how can I pass my input to a computer program?

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

Solution

input.c 內容如下:

 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
	printf("Welcome to pwnable.kr\n");
	printf("Let's see if you know how to give input to program\n");
	printf("Just give me correct inputs then you will get the flag :)\n");

	// argv
	if(argc != 100) return 0;
	if(strcmp(argv['A'],"\x00")) return 0;
	if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
	printf("Stage 1 clear!\n");

	// stdio
	char buf[4];
	read(0, buf, 4);
	if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
	read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
	printf("Stage 2 clear!\n");

	// env
	if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
	printf("Stage 3 clear!\n");

	// file
	FILE* fp = fopen("\x0a", "r");
	if(!fp) return 0;
	if( fread(buf, 4, 1, fp)!=1 ) return 0;
	if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
	fclose(fp);
	printf("Stage 4 clear!\n");

	// network
	int sd, cd;
	struct sockaddr_in saddr, caddr;
	sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd == -1){
		printf("socket error, tell admin\n");
		return 0;
	}
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons( atoi(argv['C']) );
	if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
		printf("bind error, use another port\n");
    		return 1;
	}
	listen(sd, 1);
	int c = sizeof(struct sockaddr_in);
	cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
	if(cd < 0){
		printf("accept error, tell admin\n");
		return 0;
	}
	if( recv(cd, buf, 4, 0) != 4 ) return 0;
	if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
	printf("Stage 5 clear!\n");

	// here's your flag
	system("/bin/cat flag");
	return 0;
}

目標很明確,根據輸入的類型分成五關,都通過之後得到 flag。

Stage 1

第一關要處理參數的輸入

argc : 參數數量

argv[0] : 第0個參數

也就是要求傳入 100 個參數,並且第 56、57 個參數分別為 \x00\x20\x0a\x0d

要注意的是 function 本身也是一個參數,如 ./input A B C 就有四個參數

Stage 2

第二關要用 stdinstderr 輸入

Stage 3

第三關要處理 env

使用 pwntools 的話將指定內容存成 dictionary 作為 processenv 參數即可。

Stage 4

第四關是檔案讀取

題目要讀入檔名為 \x0a 的檔案,且裡面的內容為 \x00\x00\x00\x00

python 的話輕鬆解

Stage 5

第五關要用 socket 輸入

簡單來說它會啟用一個 socket server 監聽 port argv['C'],收到 \xde\xad\xbe\xef 的話就過關。

使用 pwntools 的話,用 remote , host : 127.0.0.1, port : argv['C'] 再傳 deadbeef 過去即可。


使用 python 的完整原始碼如下:

 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
from pwn import *
import os

'''Stage 1'''
payload1 = ['a' for i in range(100)]
payload1[ord('A')] = '\x00'
payload1[ord('B')] = '\x20\x0a\x0d'

'''Stage 2'''
payload2 = '\x00\x0a\x00\xff'
with open('./stderr', 'w') as f:
    f.write('\x00\x0a\x02\xff')

'''Stage 3'''
env = {"\xde\xad\xbe\xef": "\xca\xfe\xba\xbe"}

'''Stage 4'''
with open('./\x0a', 'w') as f:
    f.write('\x00\x00\x00\x00')

'''Stage 5'''
payload1[ord('C')] = '1234'

'''Execution'''
os.system('ln -s /home/input2/flag flag')
exe = '/home/input2/input'
p = process(argv=payload1,
            executable=exe,
            env=env,
            stderr=open('./stderr'))

p.sendline(payload2)
r = remote('127.0.0.1', 1234)
r.sendline('\xde\xad\xbe\xef')
p.interactive()

Flag

Mommy! I learned how to pass various input in Linux :)


random

Challenge Info

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

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

Solution

random.c 內容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#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() 不是真隨機,每次執行的 seed 都是 1,所以 random 為定值。

只要知道 random 的值,就能夠算出 key (= 0xdeadbeef ^ random) 進而得到 flag。


簡單寫個程式輸出 rand() 即可,例如:

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
    unsigned int random = rand();
    printf("%d", random);
    return 0;
}

得到 random = 1804289383,因此 key = 0xdeadbeef ^ 1804289383 = 3039230856

執行 random 之後得到 flag。


趁機練習用 gdb 找 random

 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
gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x00000000004005e7 <+0>:		push   rbp
   0x00000000004005e8 <+1>:		mov    rbp,rsp
   0x00000000004005eb <+4>:		sub    rsp,0x10
   0x00000000004005ef <+8>:		mov    eax,0x0
   0x00000000004005f4 <+13>:	call   0x4004f0 <rand@plt>
   0x00000000004005f9 <+18>:	mov    DWORD PTR [rbp-0x4],eax
   0x00000000004005fc <+21>:	mov    DWORD PTR [rbp-0x8],0x0
   0x0000000000400603 <+28>:	lea    rax,[rbp-0x8]
   0x0000000000400607 <+32>:	mov    rsi,rax
   0x000000000040060a <+35>:	lea    rdi,[rip+0xd7]        # 0x4006e8
   0x0000000000400611 <+42>:	mov    eax,0x0
   0x0000000000400616 <+47>:	call   0x4004e0 <__isoc99_scanf@plt>
   0x000000000040061b <+52>:	mov    eax,DWORD PTR [rbp-0x8]
   0x000000000040061e <+55>:	xor    eax,DWORD PTR [rbp-0x4]
   0x0000000000400621 <+58>:	cmp    eax,0xdeadbeef
   0x0000000000400626 <+63>:	jne    0x40064c <main+101>
   0x0000000000400628 <+65>:	lea    rdi,[rip+0xbc]        # 0x4006eb
   0x000000000040062f <+72>:	call   0x4004c0 <puts@plt>
   0x0000000000400634 <+77>:	lea    rdi,[rip+0xb6]        # 0x4006f1
   0x000000000040063b <+84>:	mov    eax,0x0
   0x0000000000400640 <+89>:	call   0x4004d0 <system@plt>
   0x0000000000400645 <+94>:	mov    eax,0x0
   0x000000000040064a <+99>:	jmp    0x40065d <main+118>
   0x000000000040064c <+101>:	lea    rdi,[rip+0xad]        # 0x400700
   0x0000000000400653 <+108>:	call   0x4004c0 <puts@plt>
   0x0000000000400658 <+113>:	mov    eax,0x0
   0x000000000040065d <+118>:	leave
   0x000000000040065e <+119>:	ret

把中斷點設在 0x0000000000400621 <+58>: cmp eax,0xdeadbeef,此時 random 會在 rbp-4

1
2
3
4
gdb-peda$ x/10xw $rbp-4
0x7ffc45f1d16c:	0x6b8b4567	0x00400660	0x00000000	0x24f28b97
0x7ffc45f1d17c:	0x00007f93	0x00000001	0x00000000	0x45f1d258
0x7ffc45f1d18c:	0x00007ffc	0x00008000

得到 random = 0x6b8b4567

Flag

Mommy, I thought libc random is unpredictable...