文件md5计算小工具
呵呵,这是我在公司花了一两个小时做的一个内部用的小工具,最近公司的游戏开放下载了,竟然把这个内部用的小工具放出来下载,给外部人员用来计算游戏的md5值-_-!晕死了,这样连版权,公司名等信息都没有写的东西就这样放出来了。。。。呵呵,因为是自己做的东西,拿过来,留作纪念:)
在老地方http://groups.google.com/group/jiutianfile/
呵呵,这是我在公司花了一两个小时做的一个内部用的小工具,最近公司的游戏开放下载了,竟然把这个内部用的小工具放出来下载,给外部人员用来计算游戏的md5值-_-!晕死了,这样连版权,公司名等信息都没有写的东西就这样放出来了。。。。呵呵,因为是自己做的东西,拿过来,留作纪念:)
在老地方http://groups.google.com/group/jiutianfile/
郁闷坏了,在一再确定程序没有问题的前提下,我放弃了原有Ubuntu服务器版的稳定配置(配置了很久啊,当然也许就是那个时候鼓捣的配置,弄得程序跑不成功),昨天重新安了个Ubuntu的桌面版,第一件事就是测以前的程序,一次成功-_-!
再次郁闷一下,然后一次手误,将ssh_config配置文件删了,半天找不回来,网上教学的多,真有完整配置文件的少,将ssh,openssh-server卸载了重新安装,还是没有新的配置文件,。。。。。总算找到配置文件后,又要想办法将原有的X11删掉,呵呵,真是麻烦啊。。。。。不过,好歹,网络程序可以跑了,不要再郁闷的Unix网络编程的书,然后在Windows下跑程序了-_-!
write by 九天雁翎(JTianLing) -- www.jtianling.com
从一开始的学习到现在,我的Ubuntu 8.04服务器版本就从来没有正常的跑过一个服务器程序,亏它还是服务器版本的,郁闷的我要死。目前为止的正常的服务器端网络程序都是在Windows下运行成功的,比如第一次那个daytime服务器,都是靠着移植到windows下才能成功,不过不停的调试啊,调试啊,gdb倒是熟悉了不少,还顺便熟悉了一堆的网络调试工具,linux下的netstat,tcpdump,windows下的Wireshark(linux因为没有X window,所以用不了)倒是真熟悉了很多,我都真不知道这样的死Ubuntu对于学习是好是坏了,假如没有碰到这么多问题的话,我不会调试一个那样简单的程序那么多次,不会这样的进一步熟悉那些特别简单的套接口API,真是,郁闷了。
中间发帖求救,有人说是防火墙,我配置了半天也没有反应,直接将ufw,iptables什么的都卸载了,结果还是不行!
最新的调试结果就是netstat显示的确服务器端listen成功了, tcpdump可以看到客户端的发送的SYN包,但是就是服务器不回ACK包,郁闷的客户端所以总是不停的发送几次SYN包,然后connect failed了,奇怪的家伙,我今晚准备下个ubuntu的普通版本试一下,服务器版本可能为了安全做了太多的手脚,我配置linux的能力又那么弱-_-!光学些bash,makefile什么的,对于配置文件的修改实在是不行,不然我也不会为了samba的配置郁闷到死,然后干脆传输文件都直接改用WinSCP,通过SSH传输,而不是通过共享了。唉。。。。。还是公司好,有管理员。
ubuntu普通版本还不行,我就只好换回FC了。。。。刚才看了一下已经到10了-_-!我上一次用的时候还是FC6呢,那时候4,5张盘,现在FC10只有1CD了,最近的操作系统瘦身工程进行的还挺彻底。要知道,连最最老的经典的RH9那时候都是4张盘。
呵呵,其实想起来,看的其实是UNIX的书,弄个真正的UNIX,比如FreeBSD什么的应该也挺不错的,可惜最近工作那个忙的啊。。。。都快吐血了,游戏快要上大规模的内测了,老总,总监催进度催的我一天头是大的,刚开始弄游戏的主逻辑服务器,第二天就要我设计并添加模块-_-!实在是@#%#$^@#$^$%@&$%基本上就那句话,不能上硬着头皮眯着眼睛也要上!唉。。。。所以嘛,还是暂时收收心吧。。。好好把网络编程学好都不错了,连Linux都没有弄转,呵呵,还是老实点好。
以下是我痛苦的调试经历:-_-!
@ubuntu:~$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:13000 *:* LISTEN
@ubuntu:~$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:daytime *:* LISTEN
@ubuntu:~$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:9877 *:* LISTEN
三个不同的程序,都是卡在listen上。。。。
@ubuntu:~/test/testclient$ ./test 127.0.0.1
connet failed.
@ubuntu:~/test/testclient$ ./test 127.0.0.1
connet failed.
可怜的客户端啊。。。一次一次的connet failed
其实它实在不停的尝试的。。。。
一次:
@ubuntu:~$ sudo tcpdump port 9877
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:04:03.173472 IP 192.168.0.158.46269 > localhost.9877: S 1088742785:1088742785(0) win 5840 <mss 1460,sackOK,timestamp 95154 0,nop,wscale 4>
22:04:06.171580 IP 192.168.0.158.46269 > localhost.9877: S 1088742785:1088742785(0) win 5840 <mss 1460,sackOK,timestamp 95904 0,nop,wscale 4>
两次:
@ubuntu:~$ sudo tcpdump port 9877 -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:05:09.022315 IP (tos 0x0, ttl 64, id 16507, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0xef4b (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 111616 0,nop,wscale 4>
22:05:12.019572 IP (tos 0x0, ttl 64, id 16508, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0xec5d (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 112366 0,nop,wscale 4>
22:05:18.019572 IP (tos 0x0, ttl 64, id 16509, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0xe681 (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 113866 0,nop,wscale 4>
22:05:30.019593 IP (tos 0x0, ttl 64, id 16510, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0xdac9 (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 116866 0,nop,wscale 4>
22:05:54.019575 IP (tos 0x0, ttl 64, id 16511, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0xc359 (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 122866 0,nop,wscale 4>
22:06:42.019581 IP (tos 0x0, ttl 64, id 16512, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.46270 > localhost.9877: S, cksum 0x9479 (correct), 2110715003:2110715003(0) win 5840 <mss 1460,sackOK,timestamp 134866 0,nop,wscale 4>
6 packets captured
6 packets received by filter
0 packets dropped by kernel
三次:
@ubuntu:~$ sudo tcpdump port 13000 -vv
[sudo] password for xqh:
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:44:49.480945 IP (tos 0x0, ttl 64, id 10118, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0xebc4 (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 706731 0,nop,wscale 4>
22:44:52.479579 IP (tos 0x0, ttl 64, id 10119, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0xe8d6 (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 707481 0,nop,wscale 4>
22:44:58.479574 IP (tos 0x0, ttl 64, id 10120, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0xe2fa (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 708981 0,nop,wscale 4>
22:45:10.479576 IP (tos 0x0, ttl 64, id 10121, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0xd742 (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 711981 0,nop,wscale 4>
22:45:34.479596 IP (tos 0x0, ttl 64, id 10122, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0xbfd2 (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 717981 0,nop,wscale 4>
22:46:22.479584 IP (tos 0x0, ttl 64, id 10123, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.60205 > localhost.13000: S, cksum 0x90f2 (correct), 793636653:793636653(0) win 5840 <mss 1460,sackOK,timestamp 729981 0,nop,wscale 4>
22:49:22.209124 IP (tos 0x0, ttl 64, id 2573, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0x20c8 (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 774913 0,nop,wscale 4>
22:49:25.207577 IP (tos 0x0, ttl 64, id 2574, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0x1dda (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 775663 0,nop,wscale 4>
22:49:31.207570 IP (tos 0x0, ttl 64, id 2575, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0x17fe (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 777163 0,nop,wscale 4>
22:49:43.207579 IP (tos 0x0, ttl 64, id 2576, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0x0c46 (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 780163 0,nop,wscale 4>
22:50:07.207588 IP (tos 0x0, ttl 64, id 2577, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0xf4d5 (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 786163 0,nop,wscale 4>
22:50:55.207580 IP (tos 0x0, ttl 64, id 2578, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.158.49879 > localhost.13000: S, cksum 0xc5f5 (correct), 778754316:778754316(0) win 5840 <mss 1460,sackOK,timestamp 798163 0,nop,wscale 4>
记住,就算我不改一个字,原《UNIX Network Programming》第一卷第三版一书的源程序(我试过4,5个前面几章的例子了),都是可以编译,可以运行,但是状况都是同样的。。。那就是connect不会成功。-_-!
处于对于原程序的一丝不信任,我自己重写了一些程序,但是几乎同样的程序,在Windows下相当的正常,但是在Ubuntu 8.04服务器版本上,还是同样的状况!!有没有人可以给我个解释啊。。。。。朝闻道夕死可矣。。。。。
举个例子,比如,linux下的两个程序:
服务器端:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/socket.h>
4 #include <sys/time.h>
5 #include <time.h>
6 #include <netinet/in.h>
7 #include <errno.h>
8 #include <string.h>
9 #include <unistd.h>
10
11
12 #define MAXLINE 1000
13 int main(int argc, char* argv[])
14 {
15 int listenfd, connfd;
16 struct sockaddr_in servaddr;
17 //char buff[MAXLINE] = {0};
18 //time_t ticks;
19
20 listenfd = socket(AF_INET, SOCK_STREAM, 0);
21 if(0 == listenfd)
22 {
23 printf("socket Error./n");
24 return -1;
25 }
26
27 memset(&servaddr, 0, sizeof(servaddr));
28 servaddr.sin_family = AF_INET;
29 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
30 servaddr.sin_port = htons(13000); /* daytime server */
31
32 if( 0 != bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
33 {
34 printf("bind error./n");
35 close(listenfd);
36 return -1;
37 }
38
39 if( 0 != listen(listenfd, 10))
40 {
41 printf("listen error./n");
42 close(listenfd);
43 return -1;
44 }
45
46 printf("listen right./n");
47
48 char buffRecv[MAXLINE] = {0};
49 connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
50
51 printf("have accept./n");
52
53 while(true)
54 {
55 int n = 0;
56 if( 0 == (n = read(connfd, buffRecv, MAXLINE)) )
57 {
58 printf("recv failed/n");
59 break;
60 }
61 buffRecv[n] = 0;
62 if( 0 == (n = write(connfd, buffRecv, strlen(buffRecv))) )
63 {
64 printf("send failed./n");
65 break;
66 }
67 fputs(buffRecv, stdout);
68 }
69 close(connfd);
70 exit(0);
71 }
72
客户端:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/socket.h>
4 #include <sys/time.h>
5 #include <time.h>
6 #include <netinet/in.h>
7 #include <errno.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <arpa/inet.h>
11
12 #define MAXLINE 1000
13
14 void str_cli(FILE *fp, int sockfd)
15 {
16 char sendline[MAXLINE] = {0};
17 char recvline[MAXLINE] = {0};
18 while ( (fgets(sendline, MAXLINE, fp)) != NULL)
19 {
20
21 if(0 == send(sockfd, sendline, strlen(sendline), 0))
22 {
23 printf("send failed./n");
24 break ;
25 }
26 }
27 close(sockfd);
28 }
29
30 int main(int argc, char* argv[])
31 {
32 if (argc != 2)
33 {
34 printf("usage: tcpcli <IPaddress>");
35 exit(1);
36 }
37
38 int sockfd = 0;
39 sockfd = socket(AF_INET, SOCK_STREAM, 0);
40
41 struct sockaddr_in servaddr;
42 memset(&servaddr, 0, sizeof(servaddr));
43 servaddr.sin_family = AF_INET;
44 servaddr.sin_addr.s_addr = inet_addr(argv[1]);
45 servaddr.sin_port = htons(13000); /* daytime server */
46
47 if( 0 != connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
48 {
49 printf("connet failed./n");
50 close(sockfd);
51 return -1;
52 }
53
54 str_cli(stdin, sockfd); /* do it all */
55 exit(0);
56 }
57
就这么简单的程序,硬是成功不了,何解?
几乎同样的程序,比如:
服务器端:
#include "Winsock2.h"
//#include "python.h"
//void CallPython(const char* apszPy)
//{
// Py_Initialize();
//
// PyRun_SimpleString(apszPy);
//
// Py_Finalize();
//}
#define MAXLINE 1000
int main(int argc, char **argv)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
// windows下此初始化为必须
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;
}
SOCKET listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(INVALID_SOCKET == listenfd)
{
printf("socket Error: %d",WSAGetLastError());
return -1;
}
ZeroMemory(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, SOMAXCONN);
char buffRecv[MAXLINE] = {0};
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
while(true)
{
int n = 0;
if( 0 == (n = recv(connfd, buffRecv, MAXLINE,0)) )
{
printf("recv failed, Error Code: %d", WSAGetLastError());
break;
}
buffRecv[n] = 0;
// CallPython(buffRecv);
if( 0 == (n = send(connfd, buffRecv, strlen(buffRecv),0)) )
{
printf("send failed, Error Code: %d", WSAGetLastError());
break;
}
fputs(buffRecv, stdout);
}
closesocket(connfd);
}
客户端:
#include "Winsock2.h"
#include "errno.h"
#include "stdlib.h"
#define MAXLINE 1000
void str_cli(FILE *fp, SOCKET sockfd)
{
char sendline[MAXLINE] = {0};
char recvline[MAXLINE] = {0};
while ( (fgets(sendline, MAXLINE, fp)) != NULL)
{
if(SOCKET_ERROR == send(sockfd, sendline, strlen(sendline), 0))
{
printf("send failed, Error Code: %d", WSAGetLastError());
break ;
}
}
closesocket(sockfd);
}
int main(int argc, char **argv)
{
WORD wVersionRequested = 0;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
// windows下此初始化为必须,实际包含加载WinsockDLL的过程
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;
}
SOCKET sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
{
printf("usage: tcpcli <IPaddress>");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
ZeroMemory(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
if( SOCKET_ERROR == connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)))
{
printf("connet failed, Error Code: %d", WSAGetLastError());
closesocket(sockfd);
return -1;
}
str_cli(stdin, sockfd); /* do it all */
system("pause");
exit(0);
}
就是运行的那么正常-_-!给我个解释?。。。。。另外,虽然做了写了好几个服务器了,我最近才知道。。原来。。。recv,send本来就是标准的套接口API。。。。我一直以为这些都是windows扩展的-_-!《Unix Network Programming》书以前只看了前面一点,因为前面用的都是read,write接口来写套接字,所以嘛。。。呵呵,误解了,以前还常喊windows下用的recv,send很奇怪呢-_-!太无知了,所以嘛,还是要好好学习底层知识才行。。。不能太依赖于公司的网络框架了,虽然那个框架写的真是不赖。。。好用的很。。。
有在Ubuntu 8.04版本上成功accept的兄弟,请告知怎么样配置的。。。。
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
人类都有对未知事物的恐惧,map的使用即是其一,因为RB-Tree实在是太过于复杂,相当的复杂,去实现一个就知道我在说什么了,所以,作为一个C++程序员天生的从C继承过来的那种不知道怎么实现的,就不敢用的天性,很多人不敢好好的使用map,因为,我们不知道它是怎么实现的。我这里不是要来讨论其实现,请参考数据结构相关书籍,我这里仅仅是从外部测试来证实其特性,这样花费的功夫是最少的。
首先是map的排序性,因为hash表的混淆,导致很多人认为map不是排序的,实际中我看我们总监都不能肯定这件事情,所以常常仅仅将map作为一个搜索结构,然后另外弄一个list去处理排序并迭代的事情,这样相当于将一个map可以做的事情拆开来做,并且每次增删操作都要同步处理两件事情-_-!
其次是其迭代的速度,因为map的插入速度接近O(logN),删除速度接近O(logN),搜索速度也接近O(logN),所以出于好事不能让其占尽的原则,map总有个缺点吧,迭代速度就是其一,至于慢到什么地步,到了迭代100个元素需要10毫秒的地步?测试数据面前说话。以下测试程序就是见证了这个问题,写这个测试程序,来源于工作中我希望利用map的排序性和multimap的可重复键特性来简化工作,但是开会讨论的结果是因为大家对map,multimap不熟悉而不能确定,以下是确定的过程:
(单位,毫秒,结果来自aqtime5,机器酷睿E2140,需要提醒的是因为用了aqtime的lines处理特性,所以速度会有一定的降低,这里并不是要测试绝对速度,仅仅是与list做比较,假如有人说list的迭代都慢的不能接受,那么map的迭代自然也不能接受)
#include "stdafx.h"
#include <list>
#include <map>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <functional>
using namespace std;
// Hit Count : 1
// Time : 0.00
// Time with Children : 0.01
multimap<int, int, greater<int>> goMMap;
// Hit Count : 1
// Time : 0.00
// Time with Children : 0.00
map<int, int, greater<int>> goMap;
// Hit Count : 1
// Time : 0.00
// Time with Children : 0.01
list<int> goList;
// 测试一,Map的排序,实际是搜索二叉树的前序遍历
// 实际上搜索二叉树本来就是排好序的,只是说到map比较容易和hashmap混淆
// hash表是不排序的,博客上有个哈希表的实现,可以看到,就是通过哈希算法
// 找到一个独特的位置,然后尽快搜索的过程
void TestMapOrder()
// Hit Count : 1
// Time : 0.20
// Time with Children : 35.69
{
// 有序的插入对于平衡搜索二叉树来说是效率最慢的一种
for(int i=0; i < 100; ++i)
{
goMap.insert(make_pair(i, 1));
}
for(map<int, int, greater<int>>::const_iterator lit = goMap.begin();
lit != goMap.end(); ++lit)
{
cout <<lit->first << " ";
}
cout <<endl;
// 有序的插入对于平衡搜索二叉树来说是效率最慢的一种
for(int i=0; i < 100; ++i)
{
goMMap.insert(make_pair(i-1, 1));
goMMap.insert(make_pair(i, 1));
goMMap.insert(make_pair(i+1, 1));
}
for(multimap<int, int, greater<int>>::const_iterator lit = goMMap.begin();
lit != goMMap.end(); ++lit)
{
cout <<lit->first << " ";
}
cout <<endl;
}
#define TEST_TIMES 100
void MapIterate()
// Hit Count : 1
// Time : 2.31
// Time with Children : 2.59
{
int j = 0;
for(int i=0; i < TEST_TIMES; ++i)
{
for(multimap<int, int, greater<int>>::const_iterator lit = goMMap.begin();
lit != goMMap.end(); ++lit)
{
j+=lit->first;
}
}
cout <<j <<endl;
}
void ListIterate()
// Hit Count : 1
// Time : 1.95
// Time with Children : 2.26
{
int j = 0;
for(int i=0; i < TEST_TIMES; ++i)
{
for(list<int>::const_iterator lit = goList.begin();
lit != goList.end(); ++lit)
{
j+=*lit;
}
}
cout <<j <<endl;
}
// 主要是前序遍历迭代时的速度,虽然插入,删除,搜索速度都快才是map的强项
// 但是实际使用中不能忍受太慢的迭代速度,这里和list做比较
void TestMapSpeed()
// Hit Count : 1
// Time : 0.02
// Time with Children : 5.01
{
for(int i=0; i < 100; ++i)
{
goList.push_back(i-1);
goList.push_back(i);
goList.push_back(i+1);
}
MapIterate();
ListIterate();
}
int _tmain(int argc, _TCHAR* argv[])
// Hit Count : 1
// Time : 0.00
// Time with Children : 40.71
{
TestMapOrder();
TestMapSpeed();
return 0;
}
速度在上面已经有输出了,300元素,迭代100次,list花费2.26秒,multimap花费2.59秒,multimap的迭代速度的确比list慢,但是慢的比率实在是不太多,没有到传言中不可接受的地步,下面会做一个加强版的测试,测试在元素非常多(2万)的情况下,multimap的迭代速度降低的影响。
以上程序输出:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73
72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 4
6 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20
19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
100 99 99 98 98 98 97 97 97 96 96 96 95 95 95 94 94 94 93 93 93 92 92 92 91 91 9
1 90 90 90 89 89 89 88 88 88 87 87 87 86 86 86 85 85 85 84 84 84 83 83 83 82 82
82 81 81 81 80 80 80 79 79 79 78 78 78 77 77 77 76 76 76 75 75 75 74 74 74 73 73
73 72 72 72 71 71 71 70 70 70 69 69 69 68 68 68 67 67 67 66 66 66 65 65 65 64 6
4 64 63 63 63 62 62 62 61 61 61 60 60 60 59 59 59 58 58 58 57 57 57 56 56 56 55
55 55 54 54 54 53 53 53 52 52 52 51 51 51 50 50 50 49 49 49 48 48 48 47 47 47 46
46 46 45 45 45 44 44 44 43 43 43 42 42 42 41 41 41 40 40 40 39 39 39 38 38 38 3
7 37 37 36 36 36 35 35 35 34 34 34 33 33 33 32 32 32 31 31 31 30 30 30 29 29 29
28 28 28 27 27 27 26 26 26 25 25 25 24 24 24 23 23 23 22 22 22 21 21 21 20 20 20
19 19 19 18 18 18 17 17 17 16 16 16 15 15 15 14 14 14 13 13 13 12 12 12 11 11 1
1 10 10 10 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 -1
1485000
1485000
有点乱,从前面几排可以看出map是排序的,迭代可以按顺序输出。
后面的也证实,multimap也是同样。
最后的两排证明list,multmap的确经过了同样的迭代。
另外,经过一些反汇编分析的学习后,我不再完全相信VS了,因为其在release版本下有可能进行不可思议的省略优化,比如这里,最后的程序最后可能仅仅是直接保存了1485000并进行输出-_-!呵呵,那也不可能需要2毫秒。。。。看了反汇编代码确定,的确map,list都有完整的迭代过程。
其实从实现上考虑,可以想到,随着元素的增加,list的迭代速度是不会变慢的,但是map作为二叉树,树越大,其遍历的速度会越来越慢,这里为了看看到底会有多慢,将元素的个数扩大到一个实际中有可能会碰到的数值,3万,再来测试迭代速度。
Routine Name Time Time with Children Shared Time Hit Count
MapIterate 1.15 1.57 73.18 1
ListIterate 0.25 0.51 49.40 1
#define TEST_TIMES 100
void MapIterate()
// Hit Count : 1
// Time : 1.15
// Time with Children : 1.57
{
int j = 0;
for(multimap<int, int, greater<int>>::const_iterator lit = goMMap.begin();
lit != goMMap.end(); ++lit)
{
j+=lit->first;
}
cout <<j <<endl;
}
void ListIterate()
// Hit Count : 1
// Time : 0.25
// Time with Children : 0.51
{
int j = 0;
for(list<int>::const_iterator lit = goList.begin();
lit != goList.end(); ++lit)
{
j+=*lit;
}
cout <<j <<endl;
}
3万应该是我们公司游戏中我做的专卖店模块可能碰到的最大值了,这里map的迭代已经比list慢了3倍了,的确是慢的太多,看来这个世界上流言也不是无缘无故的有的,还是略有其事。至于慢3倍的迭代能不能接受,这个就可能得考虑一下了。不用multimap用什么呢?
用一个map来做搜索,然后再维护一个list用来遍历?维护两个容器的复杂度自然要比维护一个容器的复杂度高的多-_-!而且速度快很多吗?需要接受的是添加,删除时候的速度要慢一倍以上,也许还要远大于1倍,问题出在list到底排不排序?不排序,那删除的时候估计够呛,排序?那添加的时候呢?呵呵,map的优势不在于增加元素快,再快也没有list,vector的push_back快,不在于删除元素块,再快也没有list,vector的pop_back快,也不在于搜索速度快,再快也没有排序后的vector,list的find快,从上面看来,map的迭代还是非常慢的,但是map为什么好用呢?甚至成为了构建python,lua等脚本语言中的结构。就在于其所有操作的平均的效率,都不差。
这点就像很多人批评C++一样,高不成低不就,但是还是有人使用C++,就在于同样的特性在使用的时候也可以是比C语言的抽象能力更强,比其他高级语言的速度更快,更接近底层。
write by 九天雁翎(JTianLing) -- www.jtianling.com
其实个人感觉,真正可能会使C++成为历史的是将底层和上层完全分开的潮流,那样才会架空C++的空间。比如,底层与硬件打交道和要求速度非常快的部分交给C,然后上层逻辑全部交给Python等脚本语言,这才是C++最怕的事情。事实上使用过一些如python,mysql,lua的API后就会发现,不仅仅是windows API,事实上世界上大部分应用程序在提供自己的扩展接口时,基本都是C接口,毕竟因为C++的特性,要提供一个兼容的类接口不是那么容易的,这就是世界的格局,C语言几乎不可能从软件世界消失,因为它是如英语一样的世界通用语言,虽然C++的社团不断的希望融入这个世界,mysql也有第3方封装的C++ 接口,lua也有众多C++绑定接口,boost.python库的出现就是更是使C++为Python写扩展更加容易,但是要为世界所接受,仍然任重而道远。
很多人将python,lua等脚本语言称为胶水语言,我个人希望C++将来可以成为连接上层与下层的桥梁语言,可惜的是,这一点的实现已经越来越远,唯一的希望寄托在C++09x标准上了。
write by 九天雁翎(JTianLing) -- www.jtianling.com
学的如此之杂,绝不是我的初衷,但是事实已经形成了,还是按照刚工作时的计划,好好的学习网络技术吧。虽说我已远不是以前服务器组的成员了。。。虽说我其实开发了好几个服务器了,但是那是在公司的框架之上的,连套接字使用的机会都及其之少。。。实在谈不上什么网络编程的经验,那种开发基本上做好的包的映射和MFC的消息机制区别都不大了。
今天是调整心态并转换方向的第一天,本来以为没有什么好说的-_-!但是却给我碰到意外了。
UNPv1中最前面的时间客户端/服务器程序在我的Ubuntu中跑的没有效果-_-!
而且调试的时候都是直接分别在connect和accept上阻塞了,估计是网络可能没有调好,郁闷死了。
于是我将其中的时间服务器程序移植到Windows下了-_-!基本上相当于搬....
更改后源代码如下:
#include <time.h>
#include "Winsock2.h"
#define MAXLINE 1000
int
main(int argc, char **argv)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
// windows下此初始化为必须
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;
}
SOCKET listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(INVALID_SOCKET == listenfd)
{
printf("socket Error: %d",WSAGetLastError());
return -1;
}
ZeroMemory(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, SOMAXCONN);
for ( ; ; ) {
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
ticks = time(NULL);
_snprintf(buff, sizeof(buff), "%.24s/r", ctime(&ticks));
// 相当诡异的write换成了send
send(connfd, buff, strlen(buff),MSG_OOB);
closesocket(connfd);
}
}
然后通过Ubuntu上跑的客户端来连接这台windows上的服务器就好了-_-!莫名其妙。。。。。。可能还是属于我Linux下的配置和管理不太熟悉吧。。。但是运行服务器后,netstat却的确也没有看到服务器有监听端口啊。。。程序又没有问题....无奈了。
对于windows下的套接口使用源程序,有几点想说一下,因为我进入公司当时分配在服务器组,学的第一课是序列化,第二课就是网络基础啦,当时看的也是这本Stevens的经典著作。。。呵呵,都是将Unix环境的,所以当时在公司调试自己的Windows程序,光是因为没有使用WSAStartup初始化就卡了我大半天-_-!
的确是很诡异的用法,MS自己突发奇想加上的东西,我当时看的是UNP。。怎么会知道呢。。。。。那时候光是htonl, htons这种函数需要调用都不知道。。。。也不知道为什么要调用。。。。光是蒙着头乱蒙-_-!还好,现在还懂点皮毛了。呵呵
另外,原有的包装函数我都是直接用原始的函数替换了,也没有加错误判断,不是什么好习惯。仅作学习使用。
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
《通过全局变量和自擦除代码来防Dump 》中我提到了用WriteProcessMemory来完成代码段的自擦除,其实这并不是什么好的手段,因为使用了Win32 API,对于分析你代码的人来说,这是明摆着告诉对方你要干什么,破解手段在原文中也提到了不少。其实,当时我去搜寻WriteMemroy API但是并不存在的时候,我就在想,为什么微软不提供,并且,同样的,有ReadProcessMemory但是有ReadMemory,为什么要使用的时候都需要通过GetCurrentProcess()作为第一参数这样奇怪的形式,这个MS一贯的作风不符。这两个函数只在WinCE中有提供,普通Windows并没有,其实后来仔细想想就想通了,因为读写另外的进程的内存你必须要通过额外API,所以有这样的API给你来使用,但是读写自己的内存根本就不需要别的特殊的API,自己进程访问和改写自己进程中的数据是没有限制的。因此,需要做自擦除的操作仅仅需要memcpy或者memset就可以了!
下面是改进的示例:
1 #include "windows.h"
2 #include "tchar.h"
3
4 void Run()
5 {
6 // begin of the func
7 DWORD ldwBegin = 0;
8 __asm
9 {
10 call $+5
11 pop eax
12 mov ldwBegin, eax
13 }
14 DWORD ldwOldPro = 0;
15 // Must have this step
16 if(!VirtualProtect((void*)ldwBegin, 1000, PAGE_EXECUTE_READWRITE, &ldwOldPro))
17 {
18 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
19 return;
20 }
21
22 MessageBox(NULL, _T("Right"), _T("Hello World"), MB_OK);
23
24 // begin of the func
25 DWORD ldwEnd = 0;
26 __asm
27 {
28 call $+5
29 pop eax
30 mov ldwEnd, eax
31 }
32
33 DWORD ldwFuncLen = ldwEnd - ldwBegin;
34 BYTE *lpbyRand = new BYTE[ldwFuncLen];
35
36 DWORD ldwWritten = 0;
37 memcpy((void*)ldwBegin, lpbyRand, ldwFuncLen);
38
39 delete[] lpbyRand;
40 }
41
42 int main(int argc, char* argv[])
43 {
44 Run();
45 MessageBox(NULL, _T("OK"), _T("OK"), MB_OK);
46
47 return 0;
48 }
49
50
这样做后,就没有办法简单的将memcpy挂上使其无效化来使得擦除无效,而且,从发现你的目的的难度上来讲也更加大一些,毕竟memcpy这种函数使用的频率太高了,你可能因为各种原因使用它,而不仅仅是反Dump的目的。这也加大了分析的难度:)
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
一般而言,你的程序一旦运行起来就没有办法防止Dump了,因为所有的数据都在内存中了,而且,为了更好的Dump完整程序,程序将要启动,还未启动时的Dump,任你程序中有多少防Dump的方法都没有用。这里只能结合两种方式来实现反Dump,其一,程序运行的时候其本身数据并不是完整的,以前我已经讲过方法了, 《让EXE文件不能直接启动的方法以防止直接调试的方法》,只要多用几次这样的方法,比如启动代码无效,中间某段代码也无效,然后通过与启动程序的交互来完成中间代码的修改,只在启动程序通知后才继续运行,以防止错误。这样就没有办法通过在文中介绍的启动时Dump来Dump数据了。对于Dump的作用还不理解的,可以去使用一下LordPE的Dump功能,保证你能够见识到工具的作用性如此之大,并认识到自己程序多么的脆弱。
但是这种方法有个缺陷是,程序一旦运行起来,所有代码段的数据都是正确的了,还是可以Dump出来,有种方式是用某个全局变量来指示是否是Dump出来的数据或者是正常运行的数据。
这里介绍一下:
1
2 #include "stdafx.h"
3 #include "windows.h"
4 #include "tchar.h"
5
6 bool gbDumped = false;
7
8 int main(int argc, char* argv[])
9 {
10 if(!gbDumped)
11 {
12 gbDumped = true;
13 MessageBox(NULL, _T("Right"), _T("Hello World"), MB_OK);
14 }
15 else
16 {
17 MessageBox(NULL, _T("Dumped me?!"), _T("Find you Dumped me!!"), MB_OK);
18 }
19 return 0;
20 }
21
这样只要实在你弹出了对话框后Dump出来的程序其实就是不对的程序,虽然逻辑上将,应该会弹出另外一个对话框,但是实际上对于这样正常流程根本不会走到的地方甚至有可能被编译器所优化,然后导致Dump出来的程序直接崩溃。但是这样的程序可以被跟踪调试,通过找到全局变量并修改实在不是什么很难的问题。
下面再将一个稍微复杂一点的办法,让代码一旦运行起来,程序代码段就被破坏,那么这样运行时的Dump也就更加无效了,并且此种方法还可以防止调试。因为完整的程序已经不存在了。
原理上也很简单,对于那些只会运行一次的代码,直接在运行后将自己在代码段的内容擦除,可以写入任意值来迷惑调试者,效果更佳。
源代码:
1 #include "windows.h"
2 #include "tchar.h"
3
4 void Run()
5 {
6 // begin of the func
7 DWORD ldwBegin = 0;
8 __asm
9 {
10 call $+5
11 pop eax
12 mov ldwBegin, eax
13 }
14 DWORD ldwOldPro = 0;
15 // Must have this step
16 if(!VirtualProtect((void*)0x401000, 1000, PAGE_EXECUTE_READWRITE, &ldwOldPro))
17 {
18 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
19 return;
20 }
21
22 MessageBox(NULL, _T("Right"), _T("Hello World"), MB_OK);
23
24 // begin of the func
25 DWORD ldwEnd = 0;
26 __asm
27 {
28 call $+5
29 pop eax
30 mov ldwEnd, eax
31 }
32
33 DWORD ldwFuncLen = ldwEnd - ldwBegin;
34 BYTE *lpbyRand = new BYTE[ldwFuncLen];
35
36 DWORD ldwWritten = 0;
37 if(!WriteProcessMemory(GetCurrentProcess(), (void*)ldwBegin, lpbyRand, ldwFuncLen, &ldwWritten))
38 {
39 printf( "WriteProcessMemory failed (%d)./n", GetLastError() );
40 }
41 delete[] lpbyRand;
42 }
43
44 int main(int argc, char* argv[])
45 {
46 Run();
47 MessageBox(NULL, _T("OK"), _T("OK"), MB_OK);
48
49 return 0;
50 }
以上代码,在弹出OK后再Dump,下次再进入Run函数会直接报错,因为后来添进去的其实是堆上的随机数值。
这里需要说明的是
call $+5
pop eax
两句内嵌代码的含义是获取当前的EIP,这在壳中用的非常多,我在这里套用了一下。这样ldwBegin就是Run函数的开始EIP,ldwEnd虽然不是函数结束的EIP,但是主体部分已经包括在内了, 达到这样的效果就足够了。
通过上面的两种方式基本上可以预防住运行时的一次性Dump,而且可以将上述方式扩展,将多段擦除,结合以前讲的方式,将多段代码由启动程序来写入,这样无论是启动时的Dump,还是运行时的Dump都不能获取到正确的内容了。
但是破解方式还是有的,比如这种方式,只需要跟踪调试程序,绕过WriteProcessMemroy函数的调用就可以了,或者直接一点,直接将此API函数挂接上并使其无效,无论多少此的自擦除都会无效。
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
因为破解总是不可避免的,所以就应该开着大门让人破解吗?因为外挂总是不可避免,所以就应该开着大门让人做外挂吗?
即便是专业的安全专家,也没有办法做出无法破解的壳,我们是程序员,更加没有办法,所以这就是我们放弃抵抗的理由吗?-_-!
总觉得自己是待宰的羔羊一样。。。。。。。。
不过既然无论怎么样也没有办法避免,的确会不值得用太多时间,太多代价去做这样的无效工作。毕竟作为一个游戏开发公司,最最重要的当然是游戏产品。。。。呵呵,可怜的当然就像是我这样的小程序员罗,虽然是做反外挂的工作,但是却没有得到足够的时间,在最最短的时间内要开发出自己完全不知道的东西,只能是奇迹才可能了-_-!......................
当然,还好的就是老总也没有对我有更高的要求-_-!呵呵,以前是游戏不能直接运行就行。最近的加壳工作仅仅是用个现成的壳就行。。。。。。。唉。。。哪怕这个壳对于我这个初学者来说手动脱壳都不需要半分钟........
这里有个疑问就是,一个游戏的反静态分析,反调试,反dump,加壳的工作到底应该做到什么地步才算是比较合适的?
难道真是按老总说的那样预防住那些完全没有技术的初学者就行了吗?能让那些有兴趣但是没有技术的人知难而退就够了?因为对于真的想破解并分析游戏并做外挂赚钱的人来说,没有什么可以难到他们-_-!
刚开始还以为反外挂工作能让我有时间好好学习学习win32汇编,看来事实上机会还是不多了-_-!又一次的感叹,自己学的是越来越杂了,做游戏就从服务器做到客户端,到工具,光是语言也是从C学到C++,Lua,python,加反复学习的汇编,平台也是windows,Linux兼收。。。。但是好像却很难有时间精通一样技术了.......................唉。。。。最后怎么学到这样了呢-_-!前段时间甚至还有学习Object-C或者JAVA去弄iPhone,Android的想法。。。。
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
游戏中经常需要这样的技术,即让游戏主程序不能直接启动,通过这样的方式,可以在一定程序上达到防止调试的目的,虽然仅仅是最最简单的防止,但是仍然有一定的作用。
这里讲讲这样的技术。。。。。其实研究甚浅。。。。
基本思路有两种,其一就是直接破坏PE头,那么此文件根本无法加载,自然更没有办法加载了。但是我们自己必须的完全模拟系统加载PE文件的过程,代价有点大。所以,虽然此方案更好,但是我没有深入的研究。其二就是仅仅破坏一段有效的代码,只要你在程序必须执行的地方插入了一堆无效数据,程序自然一运行就崩溃。目的达到了。
这里从破坏《从最简单的Win32汇编程序,HelloWorld说起》文中介绍的一个最简单的程序开始。
原程序源代码:
.486 ; create 32 bit code
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
include windows.inc
include masm32.inc
include user32.inc
include kernel32.inc
includelib masm32.lib
includelib user32.lib
includelib kernel32.lib
.data
szCaption db "A MessageBox !",0
szText db "Hello,World !",0
.code
start:
invoke MessageBox,NULL,offset szText,/
offset szCaption,MB_OK
invoke ExitProcess,NULL
end start
反汇编的代码:
00401000 >/$ 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |. 68 00304000 PUSH helloWor.00403000 ; |Title = "A MessageBox !"
00401007 |. 68 0F304000 PUSH helloWor.0040300F ; |Text = "Hello,World !"
0040100C |. 6A 00 PUSH 0 ; |hOwner = NULL
0040100E |. E8 07000000 CALL <JMP.&user32.MessageBoxA> ; /MessageBoxA
00401013 |. 6A 00 PUSH 0 ; /ExitCode = 0
00401015 /. E8 06000000 CALL <JMP.&kernel32.ExitProcess> ; /ExitProcess
0040101A $- FF25 08204000 JMP NEAR DWORD PTR [<&user32.Message>; user32.MessageBoxA
00401020 .- FF25 00204000 JMP NEAR DWORD PTR [<&kernel32.ExitP>; kernel32.ExitProcess
这里我将其前一个字节改为55,那么其前三句将会解析成如下形式:
00401000 >/$ 55 PUSH EBP ; |Title = ""
00401001 |. 0068 00 ADD BYTE PTR [EAX], CH ; |
00401004 |. 3040 00 XOR BYTE PTR [EAX], AL ; |
这是再运行这个程序必然是崩溃的。这里必须要我们自己的程序去修复它,然后再运行它才能运行成功,这里以一个字节的修改为例,其实实际中你愿意改多少,改多少段完全是由你自己决定的。
启动程序基本思路及过程如下,首先用CreateProcess启动刚才修改过的应用程序,但是将其挂起,然后用VirtualProctectEx函数将挂起的进程需要修改的代码段属性设为可写,然后再用WriteProcessMemroy函数将正确的结果写入,然后再通过ResumeThread将挂起的进程运行。这时,就可以通过你的启动程序去启动被破坏的程序了,而正常情况下,被破坏的程序只能是由你的启动程序来启动。
全部启动源代码如下:
1 #include <windows.h>
2 #include <tchar.h>
3
4
5 int main(int argc, char* argv[])
6 {
7 STARTUPINFO si;
8 PROCESS_INFORMATION pi;
9 LPTSTR szCmdline=_tcsdup(TEXT("HelloWorld2"));
10
11 ZeroMemory( &si, sizeof(si) );
12 si.cb = sizeof(si);
13 ZeroMemory( &pi, sizeof(pi) );
14
15 // Start the child process.
16 if( !CreateProcess( NULL, // No module name (use command line)
17 szCmdline, // Command line
18 NULL, // Process handle not inheritable
19 NULL, // Thread handle not inheritable
20 FALSE, // Set handle inheritance to FALSE
21 CREATE_SUSPENDED, // Suspended the process, the key!
22 NULL, // Use parent's environment block
23 NULL, // Use parent's starting directory
24 &si, // Pointer to STARTUPINFO structure
25 &pi ) // Pointer to PROCESS_INFORMATION structure
26 )
27 {
28 printf( "CreateProcess failed (%d)./n", GetLastError() );
29 return -1;
30 }
31
32 DWORD ldwOldPro = 0;
33 if(!VirtualProtectEx(pi.hProcess, (void*)0x401000, 1, PAGE_EXECUTE_READWRITE, &ldwOldPro))
34 {
35 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
36 TerminateProcess(pi.hProcess, -1);
37 // Close process and thread handles.
38 CloseHandle( pi.hProcess );
39 CloseHandle( pi.hThread );
40 return -1;
41 }
42
43 DWORD ldwWritten = 0;
44 BYTE lbt = 0x6A;
45 if(!WriteProcessMemory(pi.hProcess, (void*)0x401000, &lbt, 1, &ldwWritten))
46 {
47 printf( "WriteProcessMemory failed (%d)./n", GetLastError() );
48 TerminateProcess(pi.hProcess, -1);
49 // Close process and thread handles.
50 CloseHandle( pi.hProcess );
51 CloseHandle( pi.hThread );
52 return -1;
53 }
54
55 if(!VirtualProtectEx(pi.hProcess, (void*)0x401000, 1, ldwOldPro, &ldwOldPro))
56 {
57 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
58 TerminateProcess(pi.hProcess, -1);
59 // Close process and thread handles.
60 CloseHandle( pi.hProcess );
61 CloseHandle( pi.hThread );
62 return -1;
63 }
64
65 if(-1==ResumeThread(pi.hThread))
66 {
67 printf( "ResumeThread failed (%d)./n", GetLastError() );
68 TerminateProcess(pi.hProcess, -1);
69 // Close process and thread handles.
70 CloseHandle( pi.hProcess );
71 CloseHandle( pi.hThread );
72 return -1;
73 }
74
75
76
77 // Wait until child process exits.
78 WaitForSingleObject( pi.hProcess, INFINITE );
79
80 // Close process and thread handles.
81 CloseHandle( pi.hProcess );
82 CloseHandle( pi.hThread );
83
84
85 return 0;
86 }
再次说明,这里仅仅是示例,所以仅仅修改了一个字节,假如你改动字节比较多的话,直接通过OD或者SoftIce来调试你的应用程序就没有办法了,当然,假如你的启动程序没有任何防护,是可以先调试你的启动程序的。
但是方法有个完全的破坏方法,那就是Dump。
Dump方法:
因为此例实在是太过于简单,所以在程序运行后,也就是弹出对话框后,再调用LordPE载入进程Dump也完全可以,但是在实际中,可能因为程序在启动时修改该了一些全局的数据,此时Dump会有问题。
正确的Dump步骤应该是在程序恢复运行的一瞬间,也就是将要启动却还未启动的时候。以前在一个程序将要启动却还未启动时Dump有个小技巧,那就是将其前一个字节修改为0xCC,那么,启动的一瞬间就会出现调试中断,然后将OD等调试工具设为默认调试工具,就可以在此时中断进程,并进行Dump,但是在此例中比较特殊,因为第一个字节本来就是由程序写入的,所以你没有办法通过修改文件首字节的办法来完成工作:)而且就我注意到,目前所有可以Dump的程序(也许是我见得不多,http://www.pediy.com/bbshtml/bbs7/pediy7-659-5.htm
一文中介绍的Dump工具我都用过)
都是先遍历进程,然后再Dump的,但是挂起还没有运行的进程它们竟然都检查不出来-_-!这点我很奇怪,我也不知道他们都是用什么来遍历进程的,但是windows的任务管理器就可以遍历出来(Windows的任务管理器其实相当的强悍,以前我做了一个进程占用CPU,内存资源百分比的监视工具,才知道要做好那么多任务不是那么简单的)。
呵呵,可以进行的办法是动态修改首字节,或者自己写一个给出进程ID就可以进行Dump的工具。。。。或者直接修改我比较习惯的LordPE的遍历进程方式,让其可以遍历出挂起的进程。。。。
这里我做个程序用于Dump上述程序。
此程序用于将指定进程ID的进程首字节动态改为CC以出现调试中断,并且将原有的首字节读取出来,并输出,以方便中断出现后,在OD中将其改为原有值,然后Dump。经测试,此方式的确可以做到Dump上述动态修改的挂起进程的目的:)
此程序也起到了很方便的作用:)
源代码如下:
1 #include "windows.h"
2 int main(int argc, char* argv[])
3 {
4 printf("Give me a Process ID: ");
5
6 DWORD ldwPid = 0;
7 scanf("%d", &ldwPid);
8
9 HANDLE lhPro = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
10 FALSE, ldwPid);
11
12 if(lhPro == NULL)
13 {
14 printf( "Error Process ID(%u) or OpenProcess failed (%d)./n",ldwPid, GetLastError() );
15 return -1;
16 }
17
18 DWORD ldwOldPro = 0;
19 if(!VirtualProtectEx(lhPro, (void*)0x401000, 1, PAGE_EXECUTE_READWRITE, &ldwOldPro))
20 {
21 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
22 CloseHandle( lhPro );
23 return -1;
24 }
25
26 DWORD ldwReaded = 0;
27 BYTE lbtFirst = 0;
28 if(!ReadProcessMemory(lhPro, (void*)0x401000, &lbtFirst, 1, &ldwReaded))
29 {
30 printf( "ReadProcessMemory failed (%d)./n", GetLastError() );
31 CloseHandle( lhPro );
32 return -1;
33 }
34
35
36 DWORD ldwWritten = 0;
37 BYTE lbt = 0xcc;
38 if(!WriteProcessMemory(lhPro, (void*)0x401000, &lbt, 1, &ldwWritten))
39 {
40 printf( "WriteProcessMemory failed (%d)./n", GetLastError() );
41 CloseHandle( lhPro );
42 return -1;
43 }
44
45 printf( "First BYTE Changed to CC,and origin first BYTE is %X./n", lbtFirst);
46
47 if(!VirtualProtectEx(lhPro, (void*)0x401000, 1, ldwOldPro, &ldwOldPro))
48 {
49 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
50 CloseHandle( lhPro );
51 return -1;
52 }
53 // Close process handles.
54 CloseHandle( lhPro );
55
56
57
58 return 0;
59 }
因为此程序已经有一定的实用性,方便了目前不是太方便的动态修改头字节为CC以实现Dump动态修改并挂起的进程,为了方便不喜欢编译的兄弟,我将其编译后放到讨论新闻组及文件,名字为DynamicChangeFirstBYTE.exe
write by 九天雁翎(JTianLing) -- www.jtianling.com
write by 九天雁翎(JTianLing) -- www.jtianling.com
个人手动脱壳定义,不是完全不用工具,仅仅是指不用脱壳机,并且手动寻找OEP,恢复IAT的时候使用ImportREC,但是手动找到IAT的位置,不用自动搜寻功能,其实找到了位置后,ImportREC还是做了新添加一个段,拷贝2进制数据,修改PE头中的IAT偏移地址这种工作,因为重复性太高,不手动进行了。
这些自然不是最佳,最快脱壳的方式,仅仅是学习。。。。。
需要用到的工具有OllyDbg(用于调试),LordPE(用于Dump,LordPE的Dump似乎比OllyDbg的Dump插件更稳定,因为好像OlldyDbg的Dump插件还尝试做了一些其他工作),ImportREC(恢复IAT)
其实对于UPX这样著名的壳,网上教程实在是多,所以我没有必要再教大家一次,我不过是利用这个机会,练习练习而已,毕竟UPX定位于压缩壳,脱起来实在是比较容易。
先尝试:
upx203w版本
选择的对象就是发布的upx本身,因为其本身就是用此版本加的壳-_-!
1.找OEP
方法其实有很多种,但是对于UPX壳。。。我发现最简单的方法就是直接向下翻页,找到最后一个00前的内容。。。。。。因为UPX将其他内容全部换成00了,这样的标志太明显了。
我这里最后几条语句是:
0048149D . 61 POPAD
0048149E . 8D4424 80 LEA EAX, DWORD PTR [ESP-80]
004814A2 > 6A 00 PUSH 0
004814A4 . 39C4 CMP ESP, EAX
004814A6 .^ 75 FA JNZ SHORT upx.004814A2
004814A8 . 83EC 80 SUB ESP, -80
004814AB .- E9 60FDF7FF JMP upx.00401210
最后一行的地址其实就是OEP了。。。。。。。。。。设个断,跟过去,直接用LordPE Dump all。。。。。工作已经完成一半,我在学习之前也没有想过会这么简单。
接下来找IAT,找到第一个系统函数调用,
00401218 FF15 E4E24700 CALL NEAR DWORD PTR [47E2E4] ; msvcrt.__set_app_type
__set_app_type,地址就在47E2E4这个地方了,数据窗口跟随地址,看到一堆函数,向前放到0000,在这里
0047E20C 00000000
0047E210 00000000
0047E214 7C835505 kernel32.AddAtomA
0047E218 7C812EAD kernel32.CreateSemaphoreA
向后翻到0000,在这里
0047E3C0 77BED711 msvcrt.strtol
0047E3C4 77C1AECF msvcrt.time
0047E3C8 77BEC9C9 msvcrt.tolower
0047E3CC 00000000
0047E3D0 00000000
然后开启ImortRec,选择正挂起的进程,将左下角的OEP填上刚找到的地址1210,(这里去掉了基址,仅需要RVA就可以了)
在IAT的RVA一栏填上开始地址7E214,长度用7E3C8-7E214得到1B4(这一步仅仅是为了学习的目的,其实按IAT AutoSearch一般也可以得到正确的结果)。
然后点击Get Imports按钮,上面的列表框就有结果了,我这里显示一起都正常。即valid:YES。
此时一切工作接近尾声,点击Fix Dump按钮,选择刚才Dump出来的文件,完成修复。脱壳完成。
这种脱壳的目的仅仅是为了更好的去调试和分析代码,其实并不完善,因为UPX加壳后保留的UPX0,UPX1段名没有改,壳其实还是保留在文件中,这里仅仅是将入口改到了实际入口(OEP),并将数据恢复到了正确的地方。
其实从功能上来讲用原有upx工具的-d 选项来脱壳要好的多:)
目前UPX官方最新版的是upx303w
我还以为其有一定的改进,就实际效果而言,完全没有变化,可能毕竟定位于压缩壳的UPX稳定才是最重要的,所以脱壳基本上没有什么难度,其也没有想做成有什么难度,不然也不会提供自动脱壳功能。。。。。
要说明的一点是新版的壳用Ollydbg的Dump插件,勾上FixIAT选项Dump会出现错误,ImportREC自动搜索IAT的时候大小和位置也会找错,我这里的效果是总是从第一调用的函数开始向后找,手动输入正确的IAT地址和长度就没有任何问题。
最后,UPX是有源码的,很适合作为第一款来脱的壳和第一款用来学习做壳的壳:)但是也给了我们一个提示,不要将无用的代码位置置为00,那样只能是给破解者最好的定位方式。
write by 九天雁翎(JTianLing) -- www.jtianling.com