diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
index eb412b6fc..62eb9a578 100644
--- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
+++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
@@ -484,6 +484,9 @@ public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe
 	public synthetic fun kick (Ljava/lang/String;)Lkotlin/Unit;
 	public fun kick (Ljava/lang/String;)V
 	public abstract fun kick (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public synthetic fun modifyAdmin (Z)Lkotlin/Unit;
+	public fun modifyAdmin (Z)V
+	public abstract fun modifyAdmin (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun nudge ()Lnet/mamoe/mirai/message/action/MemberNudge;
 	public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
 	public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index e145bd15f..7def6a2d2 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -484,6 +484,9 @@ public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe
 	public synthetic fun kick (Ljava/lang/String;)Lkotlin/Unit;
 	public fun kick (Ljava/lang/String;)V
 	public abstract fun kick (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public synthetic fun modifyAdmin (Z)Lkotlin/Unit;
+	public fun modifyAdmin (Z)V
+	public abstract fun modifyAdmin (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun nudge ()Lnet/mamoe/mirai/message/action/MemberNudge;
 	public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
 	public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
diff --git a/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt
index 55fdf4a2a..e47892cea 100644
--- a/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt
+++ b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt
@@ -108,6 +108,18 @@ public interface NormalMember : Member {
      */
     public suspend fun kick(message: String)
 
+    /**
+     * 给予或移除群成员的管理员权限。
+     *
+     * 此操作需要 Bot 为群主 [MemberPermission.OWNER]
+     *
+     * @param operation true 为给予
+     *
+     * @see MemberPermissionChangeEvent 群成员权限变更事件
+     * @throws PermissionDeniedException 无权限修改时抛出
+     */
+    public suspend fun modifyAdmin(operation: Boolean)
+
     /**
      * 向群成员发送消息.
      * 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息.
diff --git a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
index 9b9efe668..80aff92f3 100644
--- a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
@@ -17,7 +17,6 @@ import kotlinx.coroutines.*
 import net.mamoe.mirai.LowLevelApi
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.MemberInfo
-import net.mamoe.mirai.event.broadcast
 import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.internal.message.OnlineMessageSourceToTempImpl
 import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
@@ -46,7 +45,7 @@ internal class NormalMemberImpl constructor(
 
     override fun toString(): String = "NormalMember($id)"
 
-    private val handler by lazy { GroupTempSendMessageHandler(this) }
+    private val handler: GroupTempSendMessageHandler by lazy { GroupTempSendMessageHandler(this) }
 
     @Suppress("DuplicatedCode")
     override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember> {
@@ -183,6 +182,43 @@ internal class NormalMemberImpl constructor(
             MemberLeaveEvent.Kick(this@NormalMemberImpl, null).broadcastWithBot(bot)
         }
     }
+
+    override suspend fun modifyAdmin(operation: Boolean) {
+        checkBotPermissionHighest("modifyAdmin")
+
+        val origin = this@NormalMemberImpl.permission
+        val new = if (operation) {
+            MemberPermission.ADMINISTRATOR
+        } else {
+            MemberPermission.MEMBER
+        }
+
+        if (origin == new) return
+
+        bot.network.run {
+            val resp: TroopManagement.ModifyAdmin.Response = TroopManagement.ModifyAdmin(
+                client = bot.client,
+                member = this@NormalMemberImpl,
+                operation = operation
+            ).sendAndExpect()
+
+            check(resp.success) {
+                "Failed to modify admin, cause: ${resp.msg}"
+            }
+
+            this@NormalMemberImpl.permission = new
+
+            MemberPermissionChangeEvent(this@NormalMemberImpl, origin, new).broadcastWithBot(bot)
+        }
+    }
+}
+
+internal fun Member.checkBotPermissionHighest(operationName: String) {
+    check(group.botPermission == MemberPermission.OWNER) {
+        throw PermissionDeniedException(
+            "`$operationName` operation requires the OWNER permission, while bot has ${group.botPermission}"
+        )
+    }
 }
 
 internal fun Member.checkBotPermissionHigherThanThis(operationName: String) {
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt
index 8218c853e..c83611f92 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt
@@ -145,6 +145,7 @@ internal object KnownPacketFactories {
         TroopManagement.Mute,
         TroopManagement.GroupOperation,
         TroopManagement.GetTroopConfig,
+        TroopManagement.ModifyAdmin,
         //  TroopManagement.GetGroupInfo,
         TroopManagement.EditGroupNametag,
         TroopManagement.Kick,
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
index fb42c0986..cac566716 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
@@ -30,7 +30,6 @@ import net.mamoe.mirai.internal.utils.io.serialization.*
 import net.mamoe.mirai.utils.daysToSeconds
 
 internal class TroopManagement {
-
     internal object Mute : OutgoingPacketFactory<Mute.Response>("OidbSvc.0x570_8") {
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
             //屁用没有
@@ -377,4 +376,47 @@ internal class TroopManagement {
         }
 
     }
+
+    internal object ModifyAdmin : OutgoingPacketFactory<ModifyAdmin.Response>("OidbSvc.0x55c_1") {
+        data class Response(val success: Boolean, val msg: String) : Packet {
+            override fun toString(): String {
+                return "TroopManagement.ModifyAdmin.Response(success=${success}, msg=${msg})"
+            }
+        }
+
+        /**
+         * @param operation: true is add
+         */
+        operator fun invoke(
+            client: QQAndroidClient,
+            member: Member,
+            operation: Boolean
+        ): OutgoingPacket {
+            return buildOutgoingUniPacket(client) {
+                writeProtoBuf(
+                    OidbSso.OIDBSSOPkg.serializer(),
+                    OidbSso.OIDBSSOPkg(
+                        command = 1372,
+                        serviceType = 1,
+                        bodybuffer = buildPacket {
+                            writeInt(member.group.id.toInt())
+                            writeInt(member.id.toInt())
+                            writeByte(if (operation) 1 else 0)
+                        }.readBytes()
+                    )
+                )
+            }
+        }
+
+        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ModifyAdmin.Response {
+            val stupidPacket = readProtoBuf(OidbSso.OIDBSSOPkg.serializer())
+            return stupidPacket.run {
+                ModifyAdmin.Response(
+                    this.result == 0,
+                    this.errorMsg
+                )
+            }
+        }
+
+    }
 }
\ No newline at end of file