C++设置和取消磁盘只读(实现diskpart功能)

出门在外,为了保护U盘文件的安全,有时候需要设置只读

使用windows自带的程序diskpart可以实现这个功能,不过相当麻烦,需要先list disk列出磁盘,再select disk选择磁盘,最后attributes disk set readonly设置只读或者attributes disk clear readonly清除只读

diskpart也不支持命令行调用。为了简化以上操作,只能自己实现一个diskpart

参考https://stackoverflow.com/questions/40676088/how-to-set-a-flash-drive-on-readonly 可以知道,向磁盘发送个控制代码就能设置只读

这里封装成四个函数方便调用

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
static HANDLE OpenPhysicalDrive(DWORD number) {
std::string disk = "\\\\.\\PhysicalDrive";
disk += std::to_string(number);

return CreateFileA(disk.c_str(), FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
}

static BOOL SetDiskReadOnly(HANDLE hDisk) {
SET_DISK_ATTRIBUTES sda = { sizeof(sda), TRUE };
sda.AttributesMask = DISK_ATTRIBUTE_READ_ONLY;
sda.Attributes = DISK_ATTRIBUTE_READ_ONLY;

return DeviceIoControl(hDisk, IOCTL_DISK_SET_DISK_ATTRIBUTES, &sda, sizeof(sda), 0, 0, 0, 0);
}

static BOOL ClearDiskReadOnly(HANDLE hDisk) {
SET_DISK_ATTRIBUTES sda = { sizeof(sda), TRUE };
sda.AttributesMask = DISK_ATTRIBUTE_READ_ONLY;
sda.Attributes = 0;

return DeviceIoControl(hDisk, IOCTL_DISK_SET_DISK_ATTRIBUTES, &sda, sizeof(sda), 0, 0, 0, 0);
}

static BOOL IsDiskReadOnly(HANDLE hDisk) {
GET_DISK_ATTRIBUTES gda = { sizeof(gda) };
return (DeviceIoControl(hDisk, IOCTL_DISK_GET_DISK_ATTRIBUTES, 0, 0, &gda, sizeof(gda), 0, 0) && gda.Attributes == DISK_ATTRIBUTE_READ_ONLY);
}

然后是枚举物理磁盘,就是先枚举分卷再通过分卷查找到对应的物理磁盘

参考https://learn.microsoft.com/en-us/answers/questions/600061/how-to-list-all-physical-drives-and-find-there-dri

同样的封装成了函数方便调用

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

struct VOLUME_INFO
{
//盘符,大写,例如'C'
char driver;

//类型
char type;
};

struct DISK_INFO
{
//物理磁盘号
DWORD number;

//是否只读
bool readOnly;

//类型,物理磁盘和分卷的类型是不一样的,详情参考相关文档
char type;

//卷信息
std::vector<VOLUME_INFO> volumes;
};

static std::vector<DISK_INFO> EnumDisk() {
std::vector<VOLUME_INFO> volumes;
std::vector<DISK_INFO> result;

DWORD dwLen = GetLogicalDriveStringsA(0, NULL);
char* pszDriver = new char[dwLen];
GetLogicalDriveStringsA(dwLen, pszDriver);

for (; *pszDriver != '\0'; pszDriver += strlen(pszDriver) + 1) {
//获取卷信息
VOLUME_INFO vi = { 0 };
vi.driver = pszDriver[0];
vi.type = GetDriveTypeA(pszDriver);

//通过卷查找物理磁盘
std::string buffer = "\\\\.\\";
buffer += pszDriver[0];
buffer += ':';

HANDLE hVol = CreateFileA(buffer.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hVol == INVALID_HANDLE_VALUE) {
printf("无法打开卷 %s 错误码 %d\n", buffer.c_str(), GetLastError());
continue;
}

STORAGE_DEVICE_NUMBER sdn = { 0 };
DWORD dwBytesReturned;
BOOL success = DeviceIoControl(hVol, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);

VOLUME_DISK_EXTENTS PVolDiskExtent = { 0 };
DWORD cbBytes;
success |= DeviceIoControl(hVol, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, &PVolDiskExtent, sizeof(VOLUME_DISK_EXTENTS), &cbBytes, NULL);

CloseHandle(hVol);
if (success == 0) {
printf("无法获取卷信息 %s 错误码 %d\n", buffer.c_str(), GetLastError());
continue;
}

bool matched = false;
for (auto& disk : result) {
if (disk.number == PVolDiskExtent.Extents[0].DiskNumber) {
disk.volumes.push_back(vi);
matched = true;
}
}

if (!matched) {
DISK_INFO di = { 0 };
di.number = PVolDiskExtent.Extents[0].DiskNumber;
di.type = sdn.DeviceType;
di.volumes.push_back(vi);
result.push_back(di);
}
}

for (auto& disk : result) {
HANDLE hDisk = OpenPhysicalDrive(disk.number);
if (hDisk == INVALID_HANDLE_VALUE) {
printf("无法打开物理磁盘 %d 错误码 %d\n", disk.number, GetLastError());
continue;
}

disk.readOnly = IsDiskReadOnly(hDisk);

CloseHandle(hDisk);
}

return result;
}

然后实现一个用户接口就行了。这里我实现了一个TUI,尽可能简化操作

l列出磁盘

r [磁盘号]设置/清除只读

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
static vector<string> StringTokenizer(const string& s) {
vector<string> tokens;
string token;
bool inQuotes = false;

for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];

// Toggle inQuotes flag when encountering double quotes
if (c == '"') {
inQuotes = !inQuotes;
continue; // Skip the quote character itself
}

// If inside quotes, add character to current token
if (inQuotes) {
token += c;
}
// If not inside quotes, check for delimiters (space or tab)
else if (c == ' ' || c == '\t') {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
// Continue to skip multiple spaces/tabs
}
// Add character to the current token if it's not a delimiter
else {
token += c;
}
}

// Add the last token if any
if (!token.empty()) {
tokens.push_back(token);
}

return tokens;
}

static void List(const std::vector<DISK_INFO>&result) {
printf("\n磁盘号 只读 盘符\n");
for (const auto& disk : result) {
printf("%d %d ", disk.number, disk.readOnly);
for (const auto& volume : disk.volumes) {
printf("%c:\\ ", volume.driver);
}
if (disk.volumes[0].type == DRIVE_REMOVABLE) {
printf(" 可移动");
}
else if(disk.volumes[0].type == DRIVE_FIXED) {
printf(" 固定");
}
else {
printf(" 未知类型");
}
printf("\n");
}
}

int main() {
std::vector<DISK_INFO> result = EnumDisk();
List(result);

while (1) {
printf("\n>");
string input;
getline(cin, input);

vector<string> cmd = StringTokenizer(input);
if (cmd.size() == 0)continue;
if (cmd[0] == "quit" || cmd[0] == "q") {
exit(0);
}
else if (cmd[0] == "l" || cmd[0] == "ls" || cmd[0] == "list") {
result = EnumDisk();
List(result);
}
else if (cmd[0] == "r" || cmd[0] == "ro" || cmd[0] == "readonly") {
DWORD number;
try {
number = stoi(cmd[1]);
}
catch (...) {
continue;
}

if (number == 0) {
printf("请不要把系统盘设置为只读,这只会害了你!(笑");
continue;
}

bool matched = false;
char type;
for (const auto& disk : result) {
if (disk.number == number) {
type = disk.volumes[0].type;
matched = true;
}
}

if (!matched) {
printf("错误:磁盘号未找到,请输入正确的磁盘号");
continue;
}

if (type == DRIVE_FIXED) {
printf("警告:不建议将固定磁盘设置为只读,这可能导致严重后果");
system("pause");
}

HANDLE hDisk = OpenPhysicalDrive(number);
if (hDisk == INVALID_HANDLE_VALUE) {
printf("错误:无法打开物理磁盘,错误码 %d\n", GetLastError());
continue;
}

if (IsDiskReadOnly(hDisk)) {
if (ClearDiskReadOnly(hDisk)) {
printf("成功清除只读");
}
else {
printf("错误:无法清除只读,错误码 %d\n", GetLastError());
}
}
else {
if (SetDiskReadOnly(hDisk)) {
printf("成功设置只读");
}
else {
printf("错误:无法设置只读,错误码 %d\n", GetLastError());
}
}

CloseHandle(hDisk);
}
else {
printf("错误:未知指令");
}
}
}

设置了只读,恶意程序也可以清除,无非就是多写点代码多写点判断的问题

不过我还没有见过可以清除只读的恶意程序(特指一些勒索软件,感染型病毒)


C++设置和取消磁盘只读(实现diskpart功能)
https://crackme.net/articles/mydiskpart/
作者
Brassinolide
发布于
2024年12月26日
许可协议