[C#/WMI] 직접 혹은 C#으로 맥주소 변경하는 방법

MAC Address?

맥주소는 네트워크 인터페이스(랜카드 등)를 식별하는 용도 등으로 사용되는 고유한 식별 문자열입니다. 사실 깊게 들어가면 위 설명만으로는 많이 부족하지만, 이 글의 목적은 맥주소를 설명하는 것이 아니니 위키 링크로 대체하도록 하겠습니다.

맥 주소를 변경하는 목적

사람마다 수많은 각기 다른 목적이 있겠으나, 맥 주소의 특성상 ISP에서 맥주소가 바뀌면 다른 사용자로 인식해 새로운 아이피를 할당해줍니다. 즉, 아이피를 강제로 변경하는 용도로 쓰는 것이 가장 많지 않을까 싶네요.
저같은 경우엔 웹 어플리케이션이 수많은 아이피에 대해 각기 다른 응답을 잘 보내주는지 테스트하는 용도로 사용했습니다.

맥주소 변경하기

직접 변경하기

당연하게도, 직접 맥주소를 변경할 수 있습니다. 제가 아는 방법은 두 가지가 있는데, 첫번째는 장치 관리자의 네트워크 인터페이스 속성에서 MacAddress를 다른 문자열로 할당하는 방법입니다.

장치 관리자에서 맥주소를 변경하는 화면
그러나 위 방법은 일부 맥주소 변경 항목이 없는 랜카드에서는 사용할 수 없습니다. 그래서 찾은 방법이 레지스트리 변경입니다.
레지스트리 변경으로 맥주소 변경 (윈도우 10 기준)
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class{4d36e972-e325-11ce-bfc1-08002be10318} 키로 이동합니다. 그럼 아래와 같은 하위 키들이 보입니다.

4자리 숫자로 이뤄진 하위 키 목록
더 밑으로 내리면 Configuration, Properties라는 하위 키가 있으나, 지금 중요한건 오로지 4자리 숫자로 이뤄진 하위 키입니다. 이 하위 키들은 각각 현재 컴퓨터에 설치돼있는 네트워크 인터페이스에 대응되는 인덱스입니다. 여기서 현재 인터넷에 연결돼있는 네트워크 인터페이스와 대응되는 키를 찾으면 됩니다.
어떻게 찾냐고요?
각각 하위 키를 클릭해보시면 알게 됩니다.
하위 키를 클릭하시면 위와 같이 DriverDesc라는 문자열 값이 존재합니다. 이 값이 자신의 현재 인터넷에 연결된 네트워크 카드의 설명과 동일한 키를 찾으면 됩니다.
제 경우엔 Killer E2200 Gigabit Ehternet Controller이고,
0002 키에 있네요.
찾았으면 이 다음부턴 간단합니다. 일단 해당 서브키(제 경우 0002)의 값들 중 NetworkAddress 문자열 값을 원하는 맥주소로 수정하고, 해당 키의 하위에 Ndi\Params\NetworkAddress 키의 기본값을 동일한 맥주소로 수정해줍니다.
주의할 점은, 맥주소는 어떤 구분자도 없어야 합니다.
예를 들어, FC-02-AA-AF-C0-DD가 ipconfig에서 확인한 맥주소이고, 이걸 FC-02-AA-AF-C0-DE로 변경하고자 한다면 FC02AAAFC0DE라고 입력해야합니다. 중간에 구분자는 입력하면 변경되지 않습니다.

0002키의 NetworkAddress를 변경한 화면

0002\Ndi\Params\NetworkAddress 키의 기본값을 동일하게 변경한 화면
여기까지 하셨으면 이제 네트워크 어댑터를 사용 안 함 -> 사용함으로 변경해주시면 맥주소 변경이 끝납니다. 반드시 사용 안 함 -> 사용함으로 해줘야 변경사항이 적용됩니다.

C#으로 변경하기

자, 손수 변경은 끝냈으니 이제 코딩을 해볼까요? 이 글의 제목은 C#으로 변경하기이고, 작성도 C# 기준으로 되었지만 .Net Framework를 사용하는 언어라면 모두 호환됩니다. 해당 언어에 맞게 적절한 수정은 해야겠지만요.
아무튼, 가 봅시다.
레지스트리 변경을 통한 맥주소 변경을 자동화하기
제가 하고싶은건, 거추장스러운 창이 뜨지 않고, 이 프로그램이 돌아가면서도 저는 키보드와 마우스를 자유롭게 쓸 수 있어야 합니다. 즉, 마우스/키보드 매크로 없이 자동화를 하고싶은 겁니다. 때문에, 레지스트리 변경을 통한 맥주소 변경 방법을 채택했습니다.
그럼 일단, 현재 인터넷에 연결된 NIC를 가져오는 코드부터 짜볼까요?
private static NetworkInterface GetCurrentOnlineNetworkInterface()
{
    //현재 PC의 모든 네트워크 인터페이스에 대해 루프. foreach문을 써도 상관없음.
    for (var i = 0; i < NetworkInterface.GetAllNetworkInterfaces().Length; i++)
    {
        var ni = NetworkInterface.GetAllNetworkInterfaces()[i];

        // 필터링 조건.
        if (ni.OperationalStatus == OperationalStatus.Up && // 현재 사용중
            ni.NetworkInterfaceType != NetworkInterfaceType.Loopback && // 루프백이 아님
            ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel && // 터널이 아님
            ni.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 && // 무선이 아님. (무선 랜을 사용하시는 분은 이 조건을 삭제하시면 됩니다)
            !ni.Name.ToLower().Contains("loopback")) // 사용자 정의 필터링 조건. 저같은 경우, Wireshark를 설치할 때 같이 설치된 가상NIC가 loopback이라는 이름이 들어가있어서 추가한 조건입니다.
            return ni;
    }

    return null;
}
현재 PC의 모든 네트워크 인터페이스를 가져와 필터링을 거쳐 현재 인터넷에 연결된 NIC를 가져오는 코드를 짰습니다. 저기에 뭔가 조건을 더 추가하고싶으시다면 if문에 원하는 조건을 더 추가하시면 됩니다.
그럼 NIC를 가져왔으니, 사용자에게 현재 맥주소를 알려줘봅시다. 변경 전과 후의 맥주소를 비교해볼 수 있도록요.
Console.WriteLine("Previous MAC: " + GetCurrentOnlineNetworkInterface().GetPhysicalAddress());
자, 변경 전 맥주소도 알려줬으니 이제 실제로 맥주소를 변경해야겠죠? 별 거 없습니다. 레지스트리 변경 과정을 그대~로 코드로 옮긴 뒤, 네트워크 어댑터를 껐다 키는것도 고대~로 코드로 구현하면 됩니다.
우선, 레지스트리 편집 부분입니다. 이 코드는 예시이므로, 하드코딩하였습니다.
var targetInterface = GetCurrentOnlineNetworkInterface(); // 현재 인터넷에 연결된 NIC를 변수에 할당

Console.WriteLine("Previous MAC: " + targetInterface.GetPhysicalAddress()); // 사용자에게 변경 전 MAC주소를 알려줌

var guid = targetInterface.Id; // 랜카드의 고유 식별자 문자열을 가져옴

// HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318 키를 엶
using (var reg = Registry.LocalMachine.OpenSubKey("SYSTEM").OpenSubKey("CurrentControlSet").OpenSubKey("Control").OpenSubKey("Class").OpenSubKey("{4d36e972-e325-11ce-bfc1-08002be10318}"))
{
    // 모든 하위 키를 가져옴. 0000, 0001, 0002, ..., Configuration, Properties
    var subKeyNames = reg.GetSubKeyNames();

    foreach (var subKeyName in subKeyNames)
    {
        // 숫자 4자리로 되어있지 않은 하위 키는 건너뜀
        if (!Regex.IsMatch(subKeyName, @"\d{4}"))
            continue;

        // 숫자 4자리로 된 하위 키를 쓰기 위해 엶
        using (var subKey = reg.OpenSubKey(subKeyName, true))
        {
            // 해당 하위 키의 NetCfgInstanceId 값을 읽어옴.
            var instanceId = subKey.GetValue("NetCfgInstanceId");

            // 인스턴스 아이디의 값이 현재 인터넷에 연결된 NIC의 GUID값과 같다면 맥주소 변경 시작
            if (instanceId?.Equals(guid) == true)
            {
                // 숫자 4자리 키의 NetworkAddress 문자열 값을 원하는 맥주소로 설정
                subKey.SetValue("NetworkAddress", "FCAA1472FBD2");

                // .\Ndi\Params\NetworkAddress 키를 쓰기 위해 엶
                using (var networkAddressKey = subKey.OpenSubKey("Ndi", true).OpenSubKey("Params", true).OpenSubKey("NetworkAddress", true))
                {
                    // 해당 키의 기본값을 원하는 맥주소로 설정
                    networkAddressKey.SetValue(string.Empty, "FCAA1472FBD2"); //FC-AA-14-72-FB-D2
                    // 키가 새로 생성됐거나 값이 없을 경우를 상정해 필수값 생성
                    networkAddressKey.SetValue("Param Desc", "Network Address");
                }
            }
        }
    }
}
자, 이렇게 레지스트리 편집 부분이 끝났습니다. 정확히 손으로 레지스트리 변경 작업을 한 것과 동일한 결과물이 나오게 됩니다. 그럼 이제 남은건?
네. 그렇습니다. NIC를 사용안함 -> 사용함으로 변경해 변경된 맥주소가 적용되게 하는 일이죠.
이 작업엔 WMI가 쓰입니다. WMI 없이 하려면 불필요하게 콘솔창이 떠야되거나, 마우스 매크로 혹은 키보드 매크로를 사용해야하거든요. 거추장스럽습니다.
아무튼, WMI를 사용해 사용 안 함 -> 사용함으로 바꾸는 방법입니다.
// 현재 등록된 NIC중 GUID가 인터넷에 연결된 GUID와 일치하는 NIC의 정보를 긁어옴
var wmiQuery = new SelectQuery($"SELECT * FROM Win32_NetworkAdapter WHERE GUID='{guid}'");

var searchProcedure = new ManagementObjectSearcher(wmiQuery);

// 어차피 하나밖에 없으므로, foreach 대신 searchProcedure.Get()[0]을 써도 됨.
foreach (var item in searchProcedure.Get())
{
    var obj = (ManagementObject) item;

    // 사용 안 함 설정
    obj.InvokeMethod("Disable", null);

    // 확실히 사용 안 함 설정이 될 때까지 대기.
    // 사용 안 함으로 제대로 설정됐다면 해당 메서드의 리턴값이 null이 됨.
    // 왜냐? 현재 온라인 상태의 인터넷에 연결된 NIC가 없으므로.
    while (GetCurrentOnlineNetworkInterface() != null)
        Thread.Sleep(1000);

    // 확실히 사용 안 함이 되었다면, 이제 다시 사용함으로 설정.
    obj.InvokeMethod("Enable", null);

    // 마찬가지로 사용함으로 될 때까지 대기.
    while (GetCurrentOnlineNetworkInterface() == null)
        Thread.Sleep(1000);
}
간단하죠? 대기하는 부분은 여러가지 방법으로 쓸 수 있습니다. 예를 들면 핑이라던지요. 그러나 저같은 경우엔 무선랜카드도 돌아가고 있기 때문에 유선랜카드가 비활성화된다고 핑이 안 날아가진 않기에 보다 확실한 방법으로 처리한 것입니다. 취향에 맞게 코딩하세요.
자, 그럼 전체 코드를 볼까요?
using Microsoft.Win32;
using System.Management;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using System.Threading;

using Console = System.Console;

namespace ChangeMACTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            var targetInterface = GetCurrentOnlineNetworkInterface();

            Console.WriteLine("Previous MAC: " + targetInterface.GetPhysicalAddress());

            var guid = targetInterface.Id;

            using (var reg = Registry.LocalMachine.OpenSubKey("SYSTEM").OpenSubKey("CurrentControlSet").OpenSubKey("Control").OpenSubKey("Class").OpenSubKey("{4d36e972-e325-11ce-bfc1-08002be10318}"))
            {
                var subKeyNames = reg.GetSubKeyNames();
                foreach (var subKeyName in subKeyNames)
                {
                    if (!Regex.IsMatch(subKeyName, @"\d{4}"))
                        continue;

                    using (var subKey = reg.OpenSubKey(subKeyName, true))
                    {
                        var instanceId = subKey.GetValue("NetCfgInstanceId");

                        if (instanceId?.Equals(guid) == true)
                        {
                            subKey.SetValue("NetworkAddress", "FCAA1472FBD2"); //FC-AA-14-72-FB-D2

                            using (var networkAddressKey = subKey.OpenSubKey("Ndi", true).OpenSubKey("Params", true).OpenSubKey("NetworkAddress", true))
                            {
                                networkAddressKey.SetValue(string.Empty, "FCAA1472FBD2"); //FC-AA-14-72-FB-D2
                                networkAddressKey.SetValue("Param Desc", "Network Address");
                            }
                        }
                    }
                }
            }

            var wmiQuery = new SelectQuery($"SELECT * FROM Win32_NetworkAdapter WHERE GUID='{guid}'");

            var searchProcedure = new ManagementObjectSearcher(wmiQuery);

            foreach (var item in searchProcedure.Get())
            {
                var obj = (ManagementObject) item;

                obj.InvokeMethod("Disable", null);

                while (GetCurrentOnlineNetworkInterface() != null)
                    Thread.Sleep(1000);

                obj.InvokeMethod("Enable", null);

                while (GetCurrentOnlineNetworkInterface() == null)
                    Thread.Sleep(1000);
            }

            Console.WriteLine("Current MAC: " + GetCurrentOnlineNetworkInterface().GetPhysicalAddress());

            Console.ReadLine();
        }

        private static NetworkInterface GetCurrentOnlineNetworkInterface()
        {
            for (var i = 0; i < NetworkInterface.GetAllNetworkInterfaces().Length; i++)
            {
                var ni = NetworkInterface.GetAllNetworkInterfaces()[i];

                if (ni.OperationalStatus == OperationalStatus.Up &&
                    ni.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
                    ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel &&
                    ni.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 &&
                    !ni.Name.ToLower().Contains("loopback") &&
                    !ni.Name.Contains("SAMSUNG"))
                    return ni;
            }

            return null;
        }
    }
}
짜잔. 도움이 되셨다면 좋아요와 구ㄷ.. 댓글 부탁드립니다. 댓글로 뭔가 부족한것 같다면 애드블럭 끄고 광고나 한 번 클릭해주세요. 요즘 배가 많이 고픕니다.

댓글

이 블로그의 인기 게시물

C# 남아도는 메모리에도 불구하고 OutOfMemoryException이 발생한다면?

MySQL 데이터 타입과 Java 데이터 타입 비교/매칭

테일즈위버 OST 전곡 모음