随笔-17  评论-64  文章-79  trackbacks-1

首先要感谢 jzyhummel( 刀锋 [ 篱影 ]) 兄弟 提供的部分资料 ^_^

 

   网卡地址这个概念有点混淆不清。因为实际上有两个地址, mac 地址和物理地址 , 一般说网卡地址我是指物理地址,不知道别人怎么看 ? 物理地址指的是网卡上的存放地址的 ROM 里的地址 ,mac 地址是这块卡工作的时候用的地址 , 一般情况下这两个地址是一样的 , 所以很多人都混用了 , 甚至不知道有区别   -_-#

      网卡工作的时候,一个以太网帧 60 1514 (不包括 crc), 帧的 crc 是网卡自动加的 , 前导码是自动加的。网卡目的地址和源地址是驱动程序加的 . 所以实际上网卡工作的时候用什么地址作为工作地址完全是由驱动程序决定的 :) 因此,我们完全可以在不改变网卡的物理地址的情况下用软件方法改变具体的网卡的工作地址 .

     MAC 地址一般保存在注册表里 , 可以修改 , 于是就有下面连个问题 :

    1 ,可以肆无忌弹的盗用 ip

      2 ,可以破一些垃圾加密软件 ...

    很多软件是通过网卡地址加密的,这里面有两种不同。有些做的比较好的是通过物理地址加密。有些是通过工作地址加密 . 通过工作地址加密的像通过 guidgen netbios 等方法得到的地址都是 mac 地址。一般都可以用该方法破解。通过物理地址加密的有点难破,但是也不是没有办法。

 

取得系统中网卡 MAC 地址的三种方法

作者 : QinJia

 

第一种方法使用 Microsoft Netbios API 这是一套通过 Winsock 提供底层网络支持的命令。使用 Netbios 的最大缺点是您必须在系统中安装了 Netbios 服务 ( 如果您在 windows 网络中启用了文件共享的话,这就不是问题了 ) 。除此此外,这种方法又快又准确。

 

Netbios API 只包括了一个函数,就叫做 Netbios 。这个函数使用网络控制块 (network control block) 结构作为参数,这个结构告诉函数要做什么。结构的定义如下:

typedef struct _NCB {

UCHAR ncb_command;

UCHAR ncb_retcode;

UCHAR ncb_lsn;

UCHAR ncb_num;

PUCHAR ncb_buffer;

WORD ncb_length;

UCHAR ncb_callname[NCBNAMSZ];

UCHAR ncb_name[NCBNAMSZ];

UCHAR ncb_rto;

UCHAR ncb_sto;

void (CALLBACK *ncb_post) (struct _NCB *);

UCHAR ncb_lana_num;

UCHAR ncb_cmd_cplt;

#ifdef _WIN64

UCHAR ncb_reserve[18];

#else

UCHAR ncb_reserve[10];

#endif

HANDLE ncb_event;

} NCB, *PNCB;

 

重点在于 ncb_command 成员。这个成员告诉 Netbios 该作什么。我们使用三个命令来探测 MAC 地址。他们在 MSDN 的定义如下:

命令描述:

NCBENUM Windows NT/2000: 列举系统中网卡的数量。使用此命令后, ncb_buffer 成员指向由 LANA_ENUM 结构填充的缓冲区。

NCBENUM 不是标准的 NetBIOS 3.0 命令。

 

NCBRESET 重置网卡。网卡在接受新的 NCB 命令之前必须重置。

NCBASTAT 接受本地或远程接口卡的状态。使用此命令后, ncb_buffer 成员指向由 ADAPTER_STATUS 结构填充的缓冲区,随后是 NAME_BUFFER 结构的数组。

 

下面就是取得您系统 MAC 地址的步骤:

1 》列举所有的接口卡。

2 》重置每块卡以取得它的正确信息。

3 》查询接口卡,取得 MAC 地址并生成标准的冒号分隔格式。

 

下面就是实例源程序。

 

#include <windows.h>

#include <stdlib.h>

#include <stdio.h>

#include <iostream>

#include <string>

 

using namespace std;

#define bzero(thing,sz) memset(thing,0,sz)

 

bool GetAdapterInfo(int adapter_num, string &mac_addr)

{

// 重置网卡,以便我们可以查询

NCB Ncb;

memset(&Ncb, 0, sizeof(Ncb));

Ncb.ncb_command = NCBRESET;

Ncb.ncb_lana_num = adapter_num;

if (Netbios(&Ncb) != NRC_GOODRET) {

mac_addr = "bad (NCBRESET): ";

mac_addr += string(Ncb.ncb_retcode);

return false;

}

 

// 准备取得接口卡的状态块

bzero(&Ncb,sizeof(Ncb);

Ncb.ncb_command = NCBASTAT;

Ncb.ncb_lana_num = adapter_num;

strcpy((char *) Ncb.ncb_callname, "*");

struct ASTAT

{

ADAPTER_STATUS adapt;

NAME_BUFFER NameBuff[30];

} Adapter;

bzero(&Adapter,sizeof(Adapter));

Ncb.ncb_buffer = (unsigned char *)&Adapter;

Ncb.ncb_length = sizeof(Adapter);

 

// 取得网卡的信息,并且如果网卡正常工作的话,返回标准的冒号分隔格式。

if (Netbios(&Ncb) == 0)

{

char acMAC[18];

sprintf(acMAC, "%02X:%02X:%02X:%02X:%02X:%02X",

int (Adapter.adapt.adapter_address[0]),

int (Adapter.adapt.adapter_address[1]),

int (Adapter.adapt.adapter_address[2]),

int (Adapter.adapt.adapter_address[3]),

int (Adapter.adapt.adapter_address[4]),

int (Adapter.adapt.adapter_address[5]));

mac_addr = acMAC;

return true;

}

else

{

mac_addr = "bad (NCBASTAT): ";

mac_addr += string(Ncb.ncb_retcode);

return false;

}

}

 

int main()

{

// 取得网卡列表

LANA_ENUM AdapterList;

NCB Ncb;

memset(&Ncb, 0, sizeof(NCB));

Ncb.ncb_command = NCBENUM;

Ncb.ncb_buffer = (unsigned char *)&AdapterList;

Ncb.ncb_length = sizeof(AdapterList);

Netbios(&Ncb);

 

// 取得本地以太网卡的地址

string mac_addr;

for (int i = 0; i < AdapterList.length - 1; ++i)

{

if (GetAdapterInfo(AdapterList.lana, mac_addr))

{

cout << "Adapter " << int (AdapterList.lana) <<

"'s MAC is " << mac_addr << endl;

}

else

{

cerr << "Failed to get MAC address! Do you" << endl;

cerr << "have the NetBIOS protocol installed?" << endl;

break;

}

}

 

return 0;

}

 

第二种方法-使用 COM GUID API

这种方法使用 COM API 创建一个 GUID( 全局唯一标识符 ) 并从那里继承 MAC 地址。 GUID 通常用来标识 COM 组件以及系统中的其他对象。它们是由 MAC 地址 ( 结合其他东西 ) 计算得来的,表面上 MAC 地址就包含在其中。我说表面上是因为事实上并没有包含。

我提供这种方法更多的是为了作为反面教材。您也许用这种方法能够得到 MAC 地址,但有时候您只会得到随机的十六进制数值。

下面的例子十分简单,无需多讲。我们使用 CoCreateGuid 创建 GUID ,并将最后六个字节放入字符串中。它们可能是 MAC 地址,但并不是必然的。

 

#include <windows.h>

#include <iostream>

#include <conio.h>

 

using namespace std;

 

int main()

{

cout << "MAC address is: ";

 

// COM 要求一个 UUID 。如果机器中有以太网卡,

// UUID 最后的六个字节 (Data4 2 7 字节 ) 应该是本地以太网卡的 MAC 地址。

GUID uuid;

CoCreateGuid(&uuid);

// Spit the address out

char mac_addr[18];

sprintf(mac_addr,"%02X:%02X:%02X:%02X:%02X:%02X",

uuid.Data4[2],uuid.Data4[3],uuid.Data4[4],

uuid.Data4[5],uuid.Data4[6],uuid.Data4[7]);

cout << mac_addr << endl;

getch();

return 0;

}

 

 

第三种方法- 使用 SNMP 扩展 API

我要讨论的第三种方法是使用 Windows SNMP( 简单网络管理协议 ) 扩展来取得 MAC 地址。在我的经验里,这个协议很简单。代码也是直勾勾的向前的。基本步骤和 Netbios 相同:

1 》取得网卡列表

2 》查询每块卡的类型和 MAC 地址

3 》保存当前网卡

我个人对 SNMP 了解不多,但如我刚刚所言,代码十分清楚。

 

#include <snmp.h>

#include <conio.h>

#include <stdio.h>

 

typedef bool(WINAPI * pSnmpExtensionInit) (

IN DWORD dwTimeZeroReference,

OUT HANDLE * hPollForTrapEvent,

OUT AsnObjectIdentifier * supportedView);

 

typedef bool(WINAPI * pSnmpExtensionTrap) (

OUT AsnObjectIdentifier * enterprise,

OUT AsnInteger * genericTrap,

OUT AsnInteger * specificTrap,

OUT AsnTimeticks * timeStamp,

OUT RFC1157VarBindList * variableBindings);

 

typedef bool(WINAPI * pSnmpExtensionQuery) (

IN BYTE requestType,

IN OUT RFC1157VarBindList * variableBindings,

OUT AsnInteger * errorStatus,

OUT AsnInteger * errorIndex);

 

typedef bool(WINAPI * pSnmpExtensionInitEx) (

OUT AsnObjectIdentifier * supportedView);

 

void main()

{

HINSTANCE m_hInst;

pSnmpExtensionInit m_Init;

pSnmpExtensionInitEx m_InitEx;

pSnmpExtensionQuery m_Query;

pSnmpExtensionTrap m_Trap;

HANDLE PollForTrapEvent;

AsnObjectIdentifier SupportedView;

UINT OID_ifEntryType[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 3};

UINT OID_ifEntryNum[] = {1, 3, 6, 1, 2, 1, 2, 1};

UINT OID_ipMACEntAddr[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 6};

AsnObjectIdentifier MIB_ifMACEntAddr =

{ sizeof(OID_ipMACEntAddr) sizeof(UINT), OID_ipMACEntAddr };

AsnObjectIdentifier MIB_ifEntryType =

{sizeof(OID_ifEntryType) sizeof(UINT), OID_ifEntryType};

AsnObjectIdentifier MIB_ifEntryNum =

{sizeof(OID_ifEntryNum) sizeof(UINT), OID_ifEntryNum};

RFC1157VarBindList varBindList;

RFC1157VarBind varBind[2];

AsnInteger errorStatus;

AsnInteger errorIndex;

AsnObjectIdentifier MIB_NULL = {0, 0};

int ret;

int dtmp;

int i = 0, j = 0;

bool found = false;

char TempEthernet[13];

m_Init = NULL;

m_InitEx = NULL;

m_Query = NULL;

m_Trap = NULL;

 

/* 载入 SNMP DLL 并取得实例句柄 */

m_hInst = LoadLibrary("inetmib1.dll");

if (m_hInst < (HINSTANCE) HINSTANCE_ERROR)

{

m_hInst = NULL;

return;

}

m_Init =

(pSnmpExtensionInit) GetProcAddress(m_hInst, "SnmpExtensionInit");

m_InitEx =

(pSnmpExtensionInitEx) GetProcAddress(m_hInst,

"SnmpExtensionInitEx");

m_Query =

(pSnmpExtensionQuery) GetProcAddress(m_hInst,

"SnmpExtensionQuery");

m_Trap =

(pSnmpExtensionTrap) GetProcAddress(m_hInst, "SnmpExtensionTrap");

m_Init(GetTickCount(), &PollForTrapEvent, &SupportedView);

 

/* 初始化用来接收 m_Query 查询结果的变量列表 */

varBindList.list = varBind;

varBind[0].name = MIB_NULL;

varBind[1].name = MIB_NULL;

 

/* OID 中拷贝并查找接口表中的入口数量 */

varBindList.len = 1; /* Only retrieving one item */

SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryNum);

ret =

m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,

&errorIndex);

printf("# of adapters in this system : %in",

varBind[0].value.asnValue.number);

varBindList.len = 2;

 

/* 拷贝 OID ifType -接口类型 */

SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryType);

 

/* 拷贝 OID ifPhysAddress -物理地址 */

SNMP_oidcpy(&varBind[1].name, &MIB_ifMACEntAddr);

 

do

{

 

/* 提交查询,结果将载入 varBindList

可以预料这个循环调用的次数和系统中的接口卡数量相等 */

ret =

m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,

&errorIndex);

if (!ret)

ret = 1;

else

/* 确认正确的返回类型 */

ret = SNMP_oidncmp(&varBind[0].name, &MIB_ifEntryType,

MIB_ifEntryType.idLength);

 if (!ret) {

j++;

dtmp = varBind[0].value.asnValue.number;

printf("Interface #%i type : %in", j, dtmp);

 

/* Type 6 describes ethernet interfaces */

if (dtmp == 6)

{

 

/* 确认我们已经在此取得地址 */

ret =

SNMP_oidncmp(&varBind[1].name, &MIB_ifMACEntAddr,

MIB_ifMACEntAddr.idLength);

if ((!ret) && (varBind[1].value.asnValue.address.stream != NULL))

{

if((varBind[1].value.asnValue.address.stream[0] == 0x44)

&& (varBind[1].value.asnValue.address.stream[1] == 0x45)

&& (varBind[1].value.asnValue.address.stream[2] == 0x53)

&& (varBind[1].value.asnValue.address.stream[3] == 0x54)

&& (varBind[1].value.asnValue.address.stream[4] == 0x00))

{

/* 忽略所有的拨号网络接口卡 */

printf("Interface #%i is a DUN adaptern", j);

continue;

}

if ((varBind[1].value.asnValue.address.stream[0] == 0x00)

&& (varBind[1].value.asnValue.address.stream[1] == 0x00)

&& (varBind[1].value.asnValue.address.stream[2] == 0x00)

&& (varBind[1].value.asnValue.address.stream[3] == 0x00)

&& (varBind[1].value.asnValue.address.stream[4] == 0x00)

&& (varBind[1].value.asnValue.address.stream[5] == 0x00))

{

/* 忽略由其他的网络接口卡返回的 NULL 地址 */

printf("Interface #%i is a NULL addressn", j);

continue;

}

sprintf(TempEthernet, "%02x%02x%02x%02x%02x%02x",

varBind[1].value.asnValue.address.stream[0],

varBind[1].value.asnValue.address.stream[1],

varBind[1].value.asnValue.address.stream[2],

varBind[1].value.asnValue.address.stream[3],

varBind[1].value.asnValue.address.stream[4],

varBind[1].value.asnValue.address.stream[5]);

printf("MAC Address of interface #%i: %sn", j, TempEthernet);}

}

}

} while (!ret); /* 发生错误终止。 */

getch();

 

FreeLibrary(m_hInst);

/* 解除绑定 */

SNMP_FreeVarBind(&varBind[0]);

SNMP_FreeVarBind(&varBind[1]);

}

 

 

得到物理地址的方法对于不同的网卡是不同的,不过都是操作 io 端口,端口的具体地址要看具体芯片的说明书。加密软件要得到物理地址,不能用这个方法。一般来说,是在核心态用 NDISREQUEST 来得到的。这里提供一个应用层的方法。

要扯到 NDISREQUEST ,就要扯远了,还是打住吧 ...

ndis 规范中说明,网卡驱动程序支持 ioctl_ndis_query_stats 接口:

参数如下:

OID_802_3_PERMANENT_ADDRESS :物理地址

OID_802_3_CURRENT_ADDRESS   mac 地址

于是我们的方法就得到了。

首先,看看注册表,找一找网卡有几块,分别是什么设备名。

具体位置和 os 有关, 2000 下在 hlm\software\microsoft\windows nt\current version \networkcards 。然后 createfile(devicename,...) 注意,要用 linkname, 因此

还要加上 "////.//device//".

然后 deviceiocontrol(hmac,IOCTL_NDIS_QUERY_STATS,

OID_802_3_PERMANENT_ADDRESS/OID_802_3_CURRENT_ADDRESS...)

具体的情况可以参看 ddk 下的

OID_802_3_CURRENT_ADDRESS 条目。

于是我们就得到了 mac 地址和物理地址。

 

 

--------------------------------------------------------------------------------

jzyhummel( 刀锋 [ 篱影 ]) : 关于如何修改 mac 地址 , 没有太多的讲的必要 , 反正和注册表有关 , 下面帖一点如何修改网卡物理地址的东西 :

 

可以直接从 eeprom 读取或修改(如: 8029AS 采用的是 93c 46 芯片)

不过网卡的芯片不同,读取得方式不同。

如读取 8029AS eeprom MAC OFFSET 0001h ;实际 ADDRESS I/O+01

MAC 存入在 EEPROM ADDRESS 1h,2h,3h ;

如读取 8139C L )的 EEPROM(93c46) OFFSET 0050h; 实际 I/O+0x50

MAC 存入在 EEPROM 中, ADDRESS 7h,8h,9h 中;

系统实际使用的 MAC 放在 8139chin register 中,偏移地址是 0000h,0001h,0002h,0003h,0004h,0005h

posted on 2007-02-16 15:54 飞鸟 阅读(485) 评论(0)  编辑  收藏 所属分类: VC

只有注册用户登录后才能发表评论。


网站导航: