diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs
index 33de5e67a1b54947daa884494b272765a0cca39e..3c721a5b56b4f9b5e519ab377fa1f7d6bd3bb5c7 100644
--- a/src-tauri/src/cmds.rs
+++ b/src-tauri/src/cmds.rs
@@ -175,6 +175,15 @@ pub async fn restart_sidecar() -> CmdResult {
     wrap_err!(CoreManager::global().run_core().await)
 }
 
+#[tauri::command]
+pub fn grant_permission(core: String) -> CmdResult {
+    #[cfg(target_os = "macos")]
+    return wrap_err!(manager::grant_permission(core));
+
+    #[cfg(not(target_os = "macos"))]
+    return Err("Unsupported target");
+}
+
 /// get the system proxy
 #[tauri::command]
 pub fn get_sys_proxy() -> CmdResult<Mapping> {
diff --git a/src-tauri/src/core/manager.rs b/src-tauri/src/core/manager.rs
new file mode 100644
index 0000000000000000000000000000000000000000..250dc5cfb2b522848d13f6a533d66836545a0c54
--- /dev/null
+++ b/src-tauri/src/core/manager.rs
@@ -0,0 +1,37 @@
+/// 给clash内核的tun模式授权
+#[cfg(any(target_os = "macos", target_os = "linux"))]
+pub fn grant_permission(core: String) -> anyhow::Result<()> {
+    use std::process::Command;
+    use tauri::utils::platform::current_exe;
+
+    let path = current_exe()?.with_file_name(core).canonicalize()?;
+    let path = path.display();
+
+    log::debug!("grant_permission path: {path}");
+
+    #[cfg(target_os = "macos")]
+    let output = {
+        let shell = format!("chown root:admin {path}\nchmod +sx {path}");
+        let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
+        Command::new("osascript")
+            .args(vec!["-e", &command])
+            .output()?
+    };
+
+    #[cfg(target_os = "linux")]
+    let output = {
+        let shell = format!("setcap cap_net_bind_service,cap_net_admin=+ep {path}");
+        Command::new("sudo")
+            .arg("sh")
+            .arg("-c")
+            .arg(shell)
+            .output()?
+    };
+
+    if output.status.success() {
+        Ok(())
+    } else {
+        let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
+        anyhow::bail!("{stderr}");
+    }
+}
diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs
index 18655abb8a345a514fb865bcfa06b7d6c6528bcd..4221721f1ee35ae8128c1f84d7881318ad94393f 100644
--- a/src-tauri/src/core/mod.rs
+++ b/src-tauri/src/core/mod.rs
@@ -3,6 +3,7 @@ mod core;
 pub mod handle;
 pub mod hotkey;
 pub mod logger;
+pub mod manager;
 pub mod sysopt;
 pub mod timer;
 pub mod tray;
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index a3ff8665d55f2fca3642359303d4ca10b7f0d5f0..6d0f79eb3e4590a0bf8253f7e6bb7fb09969f081 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -36,6 +36,7 @@ fn main() -> std::io::Result<()> {
             cmds::open_core_dir,
             // cmds::kill_sidecar,
             cmds::restart_sidecar,
+            cmds::grant_permission,
             // clash
             cmds::get_clash_info,
             cmds::get_clash_logs,
diff --git a/src/components/setting/mods/clash-core-viewer.tsx b/src/components/setting/mods/clash-core-viewer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8d05898423e84b031dbf8740c8a4a4ff229e5f98
--- /dev/null
+++ b/src/components/setting/mods/clash-core-viewer.tsx
@@ -0,0 +1,106 @@
+import { mutate } from "swr";
+import { forwardRef, useImperativeHandle, useState } from "react";
+import { BaseDialog, DialogRef, Notice } from "@/components/base";
+import { useTranslation } from "react-i18next";
+import { useVerge } from "@/hooks/use-verge";
+import { useLockFn } from "ahooks";
+import { Lock } from "@mui/icons-material";
+import { IconButton, List, ListItemButton, ListItemText } from "@mui/material";
+import { changeClashCore } from "@/services/cmds";
+import { closeAllConnections } from "@/services/api";
+import { grantPermission } from "@/services/cmds";
+import getSystem from "@/utils/get-system";
+
+const VALID_CORE = [
+  { name: "Clash", core: "clash" },
+  { name: "Clash Meta", core: "clash-meta" },
+];
+
+const OS = getSystem();
+
+export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
+  const { t } = useTranslation();
+
+  const { verge, mutateVerge } = useVerge();
+
+  const [open, setOpen] = useState(false);
+
+  useImperativeHandle(ref, () => ({
+    open: () => setOpen(true),
+    close: () => setOpen(false),
+  }));
+
+  const { clash_core = "clash" } = verge ?? {};
+
+  const onCoreChange = useLockFn(async (core: string) => {
+    if (core === clash_core) return;
+
+    try {
+      closeAllConnections();
+      await changeClashCore(core);
+      mutateVerge();
+      setTimeout(() => {
+        mutate("getClashConfig");
+        mutate("getVersion");
+      }, 100);
+      Notice.success(`Successfully switch to ${core}`, 1000);
+    } catch (err: any) {
+      Notice.error(err?.message || err.toString());
+    }
+  });
+
+  const onGrant = useLockFn(async (core: string) => {
+    try {
+      await grantPermission(core);
+      Notice.success(`Successfully grant permission to ${core}`, 1000);
+    } catch (err: any) {
+      Notice.error(err?.message || err.toString());
+    }
+  });
+
+  return (
+    <BaseDialog
+      open={open}
+      title={t("Clash Core")}
+      contentSx={{
+        pb: 0,
+        width: 320,
+        height: 200,
+        overflowY: "auto",
+        userSelect: "text",
+        marginTop: "-8px",
+      }}
+      disableOk
+      cancelBtn={t("Back")}
+      onClose={() => setOpen(false)}
+      onCancel={() => setOpen(false)}
+    >
+      <List component="nav">
+        {VALID_CORE.map((each) => (
+          <ListItemButton
+            key={each.core}
+            selected={each.core === clash_core}
+            onClick={() => onCoreChange(each.core)}
+          >
+            <ListItemText primary={each.name} secondary={`/${each.core}`} />
+
+            {(OS === "macos" || OS === "linux") && (
+              <IconButton
+                color="inherit"
+                size="small"
+                edge="end"
+                onClick={(e) => {
+                  e.preventDefault();
+                  e.stopPropagation();
+                  onGrant(each.core);
+                }}
+              >
+                <Lock fontSize="inherit" />
+              </IconButton>
+            )}
+          </ListItemButton>
+        ))}
+      </List>
+    </BaseDialog>
+  );
+});
diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx
index a3af39797bdf8b89a9ded69cfaf6ff25350ff9c8..19f77ca6120cdadd88467d9862ebfd8c17cc1ca9 100644
--- a/src/components/setting/setting-clash.tsx
+++ b/src/components/setting/setting-clash.tsx
@@ -8,16 +8,16 @@ import {
   Typography,
   IconButton,
 } from "@mui/material";
-import { ArrowForward } from "@mui/icons-material";
+import { ArrowForward, Settings } from "@mui/icons-material";
 import { DialogRef } from "@/components/base";
 import { useClash } from "@/hooks/use-clash";
 import { GuardState } from "./mods/guard-state";
-import { CoreSwitch } from "./mods/core-switch";
 import { WebUIViewer } from "./mods/web-ui-viewer";
 import { ClashFieldViewer } from "./mods/clash-field-viewer";
 import { ClashPortViewer } from "./mods/clash-port-viewer";
 import { ControllerViewer } from "./mods/controller-viewer";
 import { SettingList, SettingItem } from "./mods/setting-comp";
+import { ClashCoreViewer } from "./mods/clash-core-viewer";
 
 interface Props {
   onError: (err: Error) => void;
@@ -39,6 +39,7 @@ const SettingClash = ({ onError }: Props) => {
   const fieldRef = useRef<DialogRef>(null);
   const portRef = useRef<DialogRef>(null);
   const ctrlRef = useRef<DialogRef>(null);
+  const coreRef = useRef<DialogRef>(null);
 
   const onSwitchFormat = (_e: any, value: boolean) => value;
   const onChangeData = (patch: Partial<IConfigData>) => {
@@ -51,6 +52,7 @@ const SettingClash = ({ onError }: Props) => {
       <ClashFieldViewer ref={fieldRef} />
       <ClashPortViewer ref={portRef} />
       <ControllerViewer ref={ctrlRef} />
+      <ClashCoreViewer ref={coreRef} />
 
       <SettingItem label={t("Allow Lan")}>
         <GuardState
@@ -143,7 +145,21 @@ const SettingClash = ({ onError }: Props) => {
         </IconButton>
       </SettingItem>
 
-      <SettingItem label={t("Clash Core")} extra={<CoreSwitch />}>
+      <SettingItem
+        label={t("Clash Core")}
+        extra={
+          <IconButton
+            color="inherit"
+            size="small"
+            onClick={() => coreRef.current?.open()}
+          >
+            <Settings
+              fontSize="inherit"
+              style={{ cursor: "pointer", opacity: 0.75 }}
+            />
+          </IconButton>
+        }
+      >
         <Typography sx={{ py: "7px", pr: 1 }}>{version}</Typography>
       </SettingItem>
     </SettingList>
diff --git a/src/services/cmds.ts b/src/services/cmds.ts
index d6bbca530b1681c583b1a02b4700cd62feaa836b..2bd857e1643b38946da21e4c2c7310831d3fb792 100644
--- a/src/services/cmds.ts
+++ b/src/services/cmds.ts
@@ -127,6 +127,10 @@ export async function restartSidecar() {
   return invoke<void>("restart_sidecar");
 }
 
+export async function grantPermission(core: string) {
+  return invoke<void>("grant_permission", { core });
+}
+
 export async function openAppDir() {
   return invoke<void>("open_app_dir").catch((err) =>
     Notice.error(err?.message || err.toString(), 1500)