??xml version="1.0" encoding="utf-8" standalone="yes"?> 在计机E序或文本编辑中Qhardcode(q个词比hard code用v来要频繁一?是指可变变量用一个固定值来代替的方法。用q种Ҏ~译后,如果以后需要更Ҏ变量非常困难了。大部分E序语言里,可以一个固定数值定义ؓ一个标讎ͼ然后用这个特D标记来取代变量名称。当标记名称改变Ӟ变量名不变,q样Q当重新~译整个E序Ӟ所有变量都不再是固定|q样更Ҏ的实C改变变量的目的。尽通过~辑器的查找替换功能也能实现整个变量名称的替换,但也很有可能出现多换或者少换的情况Q而在计算机程序中QQ何小错误的出现都是不可饶恕的。最好的Ҏ是单独ؓ变量名划分空_来实现这U变化,如同前面说的那P需要改变的变量名暂时用一个定义好的标记名U来代替是一U很好的Ҏ。通常情况下,都应该避免用hardcodeҎ。
有时也用hardcode来Ş定w些非帔R学的语言Q比如C或者C++语言Q相对的Q用softcode来Ş容象VBq类单好用的E序语言? 很好理解,windows操作pȝ使用消息处理机制,那么,我们所设计的程序如何才能分辨和处理pȝ中的各种消息?q就是消息分器的作? 单来?消息分流器就是一D代?在我的讲qC,分7重来循序渐进的介l它.从最初的W?重到最成熟的第7?它的样子会有很大的变??实现的功能都是一L,所不同?仅仅是变得更加简lŞ? E序开始时?会是main函数,然后会生成初始的H口,同时会调用WndProc函数.q是一个自定义的函?名字也会有变?但其功能是一L,是q行消息分流?WndProc函数如下: LRESULT CALLBACK WndProc (HWND hwnd, UINT msg,WPARAM wParam, LPARAM lParam) //...... return DefWindowProc(hwnd, msg, wParam, lParam); } q其?hwnd是窗口的句柄,msg是系l发送来的消息的名字.wParam和lParam则是随消息一起发送来的消息参? WndProc函数使用了消息分器Q下面把消息分流器的内容解释一下: 一重,当不同的消息出现Ӟ在其中写入相应的E序语句卛_?br>LRESULT CALLBACK WndProc (HWND hwnd, UINT msg,WPARAM wParam, LPARAM lParam) case WM_PAINT: case WM_DESTROY: 二重Q运用三个消息分器q行处理?br>LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_PAINT: case WM_DESTROY: #define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L) #define HANDLE_WM_PAINT(hwnd, wParam, lParam, fn) ((fn)(hwnd), 0L) #define HANDLE_WM_DESTROYCLIPBOARD(hwnd, wParam, lParam, fn) ((fn)(hwnd), 0L) 0L是表Cintcd的变量,其数gؓ0?br>intcdӞ可在后面加l或者L(写和大写Ş? LRESULT是一个系l的数据cdQ其定义如下Q?br>typedef LONG_PTR LRESULT; LONG_PTR也是一个系l的数据cdQ其定义如下Q?br>#if defined(_WIN64) 那么(LRESULT)-1L的实质ƈ不是减法Q而是((LRESULT)(-1L))Q即强制cd转换 三重Q把消息分流器的宏定义代换回去,成了下面的样子 case WM_PAINT: case WM_DESTROY: 四重Q?br>LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) HANDLE_MSG也是一个宏Q它在windowsx.h中定义,如下Q?br>#define HANDLE_MSG(hwnd, message, fn) case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn)) q个宏要做的是Ҏ不同的messageQ?#用来q接前后的字W串Q,把自?#8220;变成”相应的HANDLE_XXXXMESSAGE形式的宏Q再通过相应的宏来执行消息处理代码?br>说白了,是把message的消息做为替换,##是一个替换的标志?br>如果没有##Q就成了HANDLE_message了,q样Q宏是不会被代换的?br>如果单独一个,则会代换Q如hwnd和fn?/p>
比如实际代码中写入: 以上四重Q是消息分离器的基本使用Q但Q这不完_消息分离器主要应用在对话框消息处理中?br>q里Q窗口子cd是我们经怋用的手段Q这也可以通过消息分流器实玎ͼ W五?br>LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 但是Q这其中有错误,因ؓ有的消息,需要单独处理。单独处理的消息列表见SetDlgMsgResult宏?/p>
W六?br>q点问题,q就需要用到SetDlgMsgResult(hwnd, msg, result)宏? LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_COMMAND: q个宏定义如下: Z表述清楚Q所以用了此格式Q这是一个三表辑ּQ首先对消息cdq行考察?/p>
如果对话框过E处理的消息恰y回特定g的一个,则如实返回resultQ?br>不要被前面的BOOL蒙蔽QBOOL在头文g中的定义实际上是一个int型, q样Q我们的Cls_OnInitDialogp够正的q回它的BOOLgQ?br>而Cls_OnCommand在处理之后,也可以由后面的逗号表达式正的q回一个TRUE表示消息已处理?/p>
W七?br>我们q可以把case也包含进来,成了如下的样子?/p>
LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) chHANDLE_DLGMSG是牛人定义的一个宏Q它把case也包含进来了?br>#define chHANDLE_DLGMSG(hwnd, message, fn) case (message): return (SetDlgMsgResult(hwnd, uMsg, HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) q样Q程序中的语?br> switch (uMsg) p译成: case (WM_SIZE) case (WM_COMMAND) q样,消息分流?׃l完? 1、插入器(<<) 2、析取器(>>) 在C++中,Ҏ件的操作是通过stream的子c?a name=0>fstream(file stream)来实现的Q所以,要用q种方式操作文gQ就必须加入头文?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstream.h。下面就把此cȝ文g操作q程一一道来?/p>
一、打开文g void open(const char* filename,int mode,int access); 参数Q?/p>
filenameQ 要打开的文件名 ios::appQ 以追加的方式打开文g 打开文g的属性取值是Q?/p>
0Q普通文Ӟ打开讉K 例如Q以二进?/strong>输入方式打开文gc:config.sys fstream file1; 如果open函数只有文g名一个参敎ͼ则是以读/写普通文件打开Q即Q?/p>
file1.open("c:\config.sys");<=>file1.open("c:\config.sys",ios::in|ios::out,0); 另外Q?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstreamq有和open()一L构造函敎ͼ对于上例Q在定义的时侯就可以打开文g了: fstream file1("c:\config.sys"); 特别提出的是Q?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstream有两个子c:ifstream(input file stream)和ofstream(outpu file stream)Qifstream默认以输入方式打开文gQ而ofstream默认以输出方式打开文g?/p>
ifstream file2("c:\pdos.def");//以输入方式打开文g 所以,在实际应用中Q根据需要的不同Q选择不同的类来定义:如果想以输入方式打开Q就用ifstream来定义;如果想以输出方式打开Q就用ofstream来定义;如果想以输入/输出方式来打开Q就?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstream来定义?/p>
二、关闭文?br> 打开的文件用完成后一定要关闭Q?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstream提供了成员函数close()来完成此操作Q如Qfile1.close();把file1相连的文件关闭?/p>
三、读写文?br> d文g分ؓ文本文g?strong style="COLOR: black; BACKGROUND-COLOR: #99ff99">二进?/strong>文g?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">dQ对于文本文件的d比较单,用插入器和析取器可以了Q而对?strong style="COLOR: black; BACKGROUND-COLOR: #99ff99">二进?/strong>?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">dp复杂些,下要pl的介绍q两U方?/p>
1、文本文件的d file2<<"I Love You";//向文件写入字W串"I Love You" q种方式q有一U简单的格式化能力,比如可以指定输出?6q制{等Q具体的格式有以下一?/p>
操纵W?功能 输入/输出 比如要把123当作十六q制输出Qfile1< 2?strong style="COLOR: black; BACKGROUND-COLOR: #99ff99">二进?/strong>文g的读?br>①put() ②get() 一U就是和put()对应的Ş式:ifstream &get(char &ch);功能是从中d一个字W,l果保存在引用ch中,如果到文件尾Q返回空字符。如file2.get(x);表示从文件中d一个字W,q把d的字W保存在x中?/p>
另一U重载Ş式的原型是: int get();q种形式是从中q回一个字W,如果到达文g,q回EOFQ如x=file2.get();和上例功能是一L?/p>
q有一UŞ式的原型是:ifstream &get(char *buf,int num,char delim=' ')Q这UŞ式把字符d?buf 指向的数l,直到d?num 个字W或遇到了由 delim 指定的字W,如果没?delim q个参数Q将使用~省值换行符' '。例如: file2.get(str1,127,'A');//从文件中d字符到字W串str1Q当遇到字符'A'?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">d?27个字W时l止?/p>
③读写数据块 read(unsigned char *buf,int num); read()从文件中d num 个字W到 buf 指向的缓存中Q如果在q未d num 个字W时到了文件尾Q可以用成员函数 int gcount();来取得实?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">d的字W数Q?write() 从buf 指向的缓存写 num 个字W到文g中,值得注意的是~存的类型是 unsigned char *Q有时可能需要类型{换?/p>
例: unsigned char str1[]="I Love You"; 四、检EOF 例: if(in.eof())ShowMessage("已经到达文g!"); 五、文件定?br> 和C的文件操作方式不同的是,C++ I/Opȝ理两个与一个文件相联系的指针。一个是L针,它说明输入操作在文g中的位置Q另一个是写指针,它下ơ写操作的位|。每ơ执行输入或输出Ӟ相应的指针自动变化。所以,C++的文件定位分位置和写位置的定位,对应的成员函数是 seekg()?seekp()Qseekg()是设|读位置Qseekp是设|写位置。它们最通用的Ş式如下: istream &seekg(streamoff offset,seek_dir origin); streamoff定义?iostream.h 中,定义有偏U量 offset 所能取得的最大|seek_dir 表示Ud的基准位|,是一个有以下值的枚DQ? ios::begQ 文g开? 例: file1.seekg(1234,ios::cur);//把文件的L针从当前位置向后U?234个字?br> file2.seekp(1234,ios::beg);//把文件的写指针从文g开头向后移1234个字?
80x87的指令系l?br />点处理单元FPUh自己的指令系l,共有几十UQҎ令,可以分成传送、算术运、超函数、比较、FPU控制{类。QҎ令归属于ESC指oQ其?位的操作码都?1011bQ它的指令助记符均以F开头?br />1. 点传送类指o
采用点指o的汇~语aE序格式Q与整数指o源程序格式是cM的,但有以下几点需要注意:
点寄存? 首先Q至有一点可以肯定,那就是ANSI
C保证l构体中各字D在内存中出现的位置是随它们的声明顺序依ơ递增的,q且W一个字D늚首地址{于整个l构体实例的首地址。比如有q样一个结构体Q?br /> assert(p < q); q时Q有朋友可能会问:"标准是否规定盔R字段在内存中也相?"?
唔,对不PANSI C没有做出保证Q?/font>你的E序在Q何时候都不应该依赖这个假设。那q是否意味着我们永远无法勑Zq更清晰更精的l构体内存布局图?哦,当然不是。不q先让我们从q个问题中暂时抽w,x一下另一个重要问题————内存对齐?/font>
许多实际的计机pȝ对基本类型数据在内存中存攄位置有限Ӟ它们会要求这些数据的首地址的值是?/font>个数k(通常它ؓ4?)的倍数Q这是所谓的内存寚wQ而这个k则被UCؓ该数据类型的寚w模数(alignment modulus)。当一U类型S的对齐模C另一U类型T的对齐模数的比值是大于1的整敎ͼ我们qcdS的对齐要?/font>比T?严格)Q而称T比S?宽松)。这U强制的要求一来简化了处理器与内存之间传输pȝ的设计,二来可以提升d数据的速度。比如这么一U处理器Q它每次d内存的时候都从某?倍数的地址开始,一ơ读出或写入8?/font>字节的数据,假如软g能保证doublecd的数据都?倍数地址开始,那么L写一个doublecd数据只需?/font>一ơ内存操作。否则,我们可能需要两ơ内存操作才能完成这个动作,因ؓ数据或许恰好横跨在两个符合对?/font>要求?字节内存块上。某些处理器在数据不满寚w要求的情况下可能会出错,但是Intel的IA32架构的处理器则不数据是否对齐都能正工作。不qIntel奉劝大家Q如果想提升性能Q那么所有的E序数据都应该尽可能?/font>寚w。Win32q_下的微YC~译?cl.exe for 80x86)在默认情况下采用如下的对齐规?
M基本数据cdT的对齐模数就是T的大,即sizeof(T)。比如对于doublecd(8字节)Q就要求该类型数据的地址L8的倍数Q而charcd数据(1字节)则可以从M一个地址开始。Linux下的GCC奉行的是另外一套规?在资料中查得Qƈ未验证,如错误请指正):M2字节大小(包括单字节吗?)的数据类?比如short)的对齐模数是2Q而其它所有超q?字节的数据类?比如long,double)都以4为对齐模数?/font> 现在回到我们兛_的struct上来。ANSI
C规定一U结构类型的大小是它所有字D늚大小以及字段之间或字D?/font>N的填充区大小之和。嗯Q填充区Q对Q这是Z使结构体字段满内存寚w要求而额外分配给l构体的I?/font>间。那么结构体本n有什么对齐要求吗Q有的,ANSI
C标准规定l构体类型的寚w要求不能比它所有字D中要求最严格的那个宽松,可以更严?但此非强制要求,VC7.1׃仅是让它们一样严?。我们来看一个例?以下所有试验的环境是Intel Celeron 2.4G + WIN2000 PRO +
vc7.1Q内存对齐编译选项?默认"Q即不指?Zp?/font>/pack选项): typedef struct ms1
假设MS1按如下方式内存布局(本文所有示意图中的内存地址从左臛_递增):
因ؓMS1中有最强对齐要求的是b字段(int)Q所以根据编译器的对齐规则以及ANSI C标准QMS1对象的首?/font>址一定是4(intcd的对齐模?的倍数。那么上q内存布局中的b字段能满intcd的对齐要求吗Q嗯Q当然不能。如果你是编译器Q你会如何y妙安排来满CPU的癖好呢Q呵呵,l过1毫秒的艰苦思考,你一定得Z如下的方案:
_______________________________________
q个Ҏ在a与b之间多分配了3个填?padding)字节Q这样当整个struct对象首地址满4字节的对齐要?/font>Ӟb字段也一定能满int型的4字节寚w规定。那么sizeof(MS1)昄应该是8Q而b字段相对于结构体首地址的偏Ud?。非常好理解Q对吗?现在我们把MS1中的字段交换一下顺? typedef struct ms2 或许你认为MS2比MS1的情况要单,它的布局应该是
_______________________
因ؓMS2对象同样要满?字节寚w规定Q而此时a的地址与结构体的首地址相等Q所以它一定也?字节寚w。嗯Q分析得有道理,可是却不全面。让我们来考虑一下定义一个MS2cd的数l会出现什么问题。C标准保证Q?/font>Mcd(包括自定义结构类?的数l所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话_数组各元素之间不会有I隙。按照上面的ҎQ一个MS2数组array的布局是: |<- array[1]
->|<- array[2] ->|<- array[3] ..... __________________________________________________________
当数l首地址?字节寚wӞarray[1].a也是4字节寚wQ可是array[2].a呢?array[3].a ....呢??/font>见这U方案在定义l构体数l时无法让数l中所有元素的字段都满_齐规定,必须修改成如下Ş?
___________________________________
现在无论是定义一个单独的MS2变量q是MS2数组Q均能保证所有元素的所有字D都满寚w规定。那?/font>sizeof(MS2)仍然?Q而a的偏UMؓ0Qb的偏UL4?/font>
好的Q现在你已经掌握了结构体内存布局的基本准则,试分析一个稍微复杂点的类型吧?/font> typedef struct ms3 我想你一定能得出如下正确的布局? typedef struct ms4
MS3中内存要求最严格的字D|cQ那么MS3cd数据的对齐模数就与double的一??)Qa字段后面应填?/font>7个字节,因此MS4的布局应该? 昄Qsizeof(MS4){于24Qb的偏Uȝ??/font>
在实际开发中Q我们可以通过指定/Zp~译选项来更改编译器的对齐规则。比如指?Zpn(VC7.1中n可以?????6)是告诉~译器最大对齐模数是n。在q种情况下,所有小于等于n字节的基本数据类型的寚w?/font>则与默认的一P但是大于n个字节的数据cd的对齐模数被限制为n。事实上QVC7.1的默认对齐选项q当于/Zp8。仔l看看MSDN对这个选项的描qͼ会发现它郑重告诫了程序员不要在MIPS和Alphaq_上用/Zp1?Zp2?/font>,也不要在16位^C指定/Zp4?Zp8(xZ么?)。改变编译器的对齐选项Q对照程序运行结果重新分?/font>上面4U结构体的内存布局是一个很好的复习?/font>
Cq里Q我们可以回{本文提出的最后一个问题了。结构体的内存布局依赖于CPU、操作系l、编译器及编译时的对齐选项Q而你的程序可能需要运行在多种q_上,你的源代码可能要被不同的人用不同的编译器~译(?/font>想你为别人提供一个开放源码的?Q那么除非绝对必需Q否则你的程序永q也不要依赖q些诡异的内存布局。顺便说一下,如果一个程序中的两个模块是用不同的寚w选项分别~译的,那么它很可能会生一些非常微妙的?/font>误。如果你的程序确实有很难理解的行为,不防仔细查一下各个模块的~译选项?/font>
思考题:请分析下面几U结构体在你的^C的内存布局Qƈ试着L一U合理安排字D声明顺序的Ҏ以尽量节省内存空间?/font> A. struct P1 { int a;
char b; int c; char d; }; 参考资? ?】《深入理解计机pȝ(修订?》,
hardcode----编码?br>所谓硬~码QhardcodeQ就是把一个本来应该(可以Q写到配|信息中的信息直接在E序代码中写M?
例如Q写了一个收发邮件的E序Q用户名Q密码,服务器地址{最好做成外部配|,
但是如果直接写死在程序代码中Q每ơ改信息旉要重新编译了……
q种E序不好l护?nbsp;
一般懒的程序员或者初学者这U程序量较大?br>
原文链接Q?a >http://blog.csdn.net/hopkins9961629/archive/2006/01/25/588184.aspx
{
{
switch(msg)
{
case WM_CREATE:
// ...
return 0;
// ...
return 0;
//...
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
{
switch(msg)
{
case WM_CREATE:
return HANDLE_WM_CREATE(hwnd, wParam, lParam, Cls_OnCreate);
return HANDLE_WM_PAINT(hwnd, wParam, lParam, Cls_OnPaint);
return HANDLE_WM_DESTROY(hwnd, wParam, lParam, Cls_OnDestroy);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
q里的HANDLE_WM_CREATEQHANDLE_WM_PAINTQHANDLE_WM_DESTROY是消息分流器?br>与消息不同之处就是在前面增加?#8220;HANDLE_”字符Qwindows的消息分器是q样的模栗?br>它的本质是宏定义?br>其中的四个参数有三个都是从本函数的入口参C直接得到的,即ؓhwnd, wParam, lParam?br>只有W四的参数是表明调用的函数?br>消息分流器是在winowsx.h文g中定义的。由此,可以看出W四个参数是调用的函敎ͼ其定义如下:
表明无符hӞ可在后面加u或者U(写和大写Ş?
floatcdӞ可在后面加f或者F(写和大写Ş?
例如Q?br>128u 1024UL 1L 8Lu 3.14159F 0.1f
typedef __int64 LONG_PTR;
#else
typedef long LONG_PTR;
#endif
由此可见QLRESULT的实质就?4的longcd的变?/p>
LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
return Cls_OnCreate(hwnd, (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L;
// 如果处理了消息,则Cls_OnCreate应返回TRUEQ导致WndProcq回0Q否则Cls_OnCreateq回FALSEQ导致WndProcq回-1Q?/p>
return Cls_OnPaint(hwnd), 0L;
// 逗号表达式;Cls_OnPaint是voidcdQ这里返?Q?
return Cls_OnDestroy(hwnd), 0L; // 同Cls_OnPaint
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
在逗号表达式,C++会计每个表辑ּQ但完整的逗号表达式的l果是最双表达式的倹{?br>所以,会return 0?br>然后Q就可以手动的编写各个处理函CQCls_OnCreateQCls_OnPaintQWM_DESTROY?/p>
{
switch(msg)
{
HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);
HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate)
则经q{换就变成Q?br>case (WM_CREATE): return HANDLE_WM_CREATE((hwnd), (wParam), (lParam), (Cls_OnCreate))
q与二重一模一栗?/p>
{
switch(msg)
{
HANDLE_MSG(hwnd, WM_INITDIALO , Cls_OnInitDialog); // 不能直接使用HANDLE_MSG?br> HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand); // 不能直接使用HANDLE_MSG?br> }
return false;
}
׃是窗口子cdQ所以,最后,q回的是falseQ以表明Q如果没有约定响应的消息Q?br>则返回父亲窗口falseQ如果有Q则q回tureQ这是与前四重不同的地方?br>一般情况下Q对话框q程函数应该在处理了消息的情况下q回TRUEQ如果没有处理,则返回FALSE?br>如果对话框过E返回了FALSEQ那么对话框理器ؓq条消息准备默认的对话操作?/p>
{
switch(msg)
{
case WM_INITDIALO:
return (SetDlgMsgResult(hwnd, Msg, HANDLE_WM_INITDIALO((hwnd), (wParam), (lParam), (fn)));
return (SetDlgMsgResult(hwnd, Msg, HANDLE_WM_COMMAND((hwnd), (wParam), (lParam), (fn)));
}
return false;
}
q里Q就用直接用CW二重的消息分流器,而抛弃了其他?/p>
#define SetDlgMsgResult(hwnd, msg, result)
(
(
(msg) == WM_CTLCOLORMSGBOX ||
(msg) == WM_CTLCOLOREDIT ||
(msg) == WM_CTLCOLORLISTBOX ||
(msg) == WM_CTLCOLORBTN ||
(msg) == WM_CTLCOLORDLG ||
(msg) == WM_CTLCOLORSCROLLBAR ||
(msg) == WM_CTLCOLORSTATIC ||
(msg) == WM_COMPAREITEM ||
(msg) == WM_VKEYTOITEM ||
(msg) == WM_CHARTOITEM ||
(msg) == WM_QUERYDRAGICON ||
(msg) == WM_INITDIALOG
) ?
(BOOL)(result) :
(SetWindowLongPtr((hwnd), DWLP_MSGRESULT, (LPARAM)(LRESULT)(result)), TRUE)
)
一旦需要返回非TRUE或FALSE的其他|照样可以Q?/p>
{
switch(msg)
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Cls_OnCommand);
}
return false;
}
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_SIZE, Dlg_OnSize);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
switch (uMsg)
{
case (WM_INITDIALOG):
return (SetDlgMsgResult(hwnd, uMsg, HANDLE_WM_INITDIALOG((hwnd), (wParam), (lParam), (Dlg_OnInitDialog))));
return (SetDlgMsgResult(hwnd, uMsg, HANDLE_WM_SIZE((hwnd), (wParam), (lParam), (Dlg_OnSize))));
return (SetDlgMsgResult(hwnd, uMsg, HANDLE_WM_COMMAND((hwnd), (wParam), (lParam), (Dlg_OnCommand))));
}
向流输出数据。比如说pȝ有一个默认的标准输出?cout)Q一般情况下是指的昄器,所以,cout<<"Write Stdout"<<' ';pC把字符?Write Stdout"和换行字W?' ')输出到标准输出流?/p>
从流中输入数据。比如说pȝ有一个默认的标准输入?cin)Q一般情况下是指的键盘Q所以,cin>>x;pCZ标准输入中d一个指定类?卛_量x的类?的数据?/p>
?strong style="COLOR: black; BACKGROUND-COLOR: #ffff66">fstreamcMQ有一个成员函数open()Q就是用来打开文g的,其原型是Q?/p>
modeQ 要打开文g的方?
accessQ 打开文g的属?br>打开文g的方式在cios(是所有流式I/Ocȝ基类)中定义,常用的值如下:
ios::ateQ 文g打开后定位到文g,ios:app包含有此属?
ios::binaryQ? ?a name=2>二进?/strong>方式打开文gQ缺省的方式是文本方式。两U方式的区别见前?
ios::inQ 文g以输入方式打开
ios::outQ 文g以输出方式打开
ios::nocreateQ?不徏立文Ӟ所以文件不存在时打开p|
ios::noreplaceQ不覆盖文gQ所以打开文g时如果文件存在失?
ios::truncQ 如果文g存在Q把文g长度设ؓ0
可以?#8220;?#8221;把以上属性连接v来,如ios::out|ios::binary
1Q只L?
2Q隐含文?
4Q系l文?
可以?#8220;?#8221;或?#8220;+”把以上属性连接v?Q如3?|2是以只d隐含属性打开文g?/p>
file1.open("c:\config.sys",ios::binary|ios::in,0);
ofstream file3("c:\x.123");//以输出方式打开文g
文本文g的读写很单:用插入器(<<)向文件输出;用析取器(>>)从文件输入。假设file1是以输入方式打开Qfile2以输出打开。示例如下:
int i;
file1>>i;//从文件输入一个整数倹{?
dec 格式化ؓ十进制数值数?输入和输?
endl 输出一个换行符q刷新此?输出
ends 输出一个空字符 输出
hex 格式化ؓ十六q制数值数?输入和输?
oct 格式化ؓ八进制数值数?输入和输?
setpxecision(int p) 讄点数的_ֺ位数 输出
put()函数向流写入一个字W,其原型是ofstream &put(char ch)Q用也比较单,如file1.put('c');是向流写一个字W?c'?
get()函数比较灉|Q有3U常用的重蝲形式Q?/p>
要读?strong style="COLOR: black; BACKGROUND-COLOR: #99ff99">二进?/strong>数据块,使用成员函数read()和write()成员函数Q它们原型如下:
write(const unsigned char *buf,int num);
int n[5];
ifstream in("xxx.xxx");
ofstream out("yyy.yyy");
out.write(str1,strlen(str1));//把字W串str1全部写到yyy.yyy?br> in.read((unsigned char*)n,sizeof(n));//从xxx.xxx?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">d指定个整敎ͼ注意cd转换
in.close();out.close();
成员函数eof()用来是否到达文件尾Q如果到达文件尾q回?|否则q回0。原型是int eof();
ostream &seekp(streamoff offset,seek_dir origin);
ios::curQ 文g当前位置
ios::endQ 文gl尾
q两个函C般用?strong style="COLOR: black; BACKGROUND-COLOR: #99ff99">二进?/strong>文gQ因为文本文件会因ؓpȝ对字W的解释而可能与预想的g同?/p>
什么是0Day
0Day的概忉|早用于Y件和游戏破解Q属于非盈利性和非商业化的组l行为,其基本内涉|“x?#8221;。Warez被许多h误认为是一个最大的软g破解l织Q而实际上QWarez如黑客一P只是一U行为?Day也是。当时的0Day是指在正版Y件或游戏发布的当天甚至之前,发布附带着序列h者解密器的破解版Q让使用者可以不用付费就能长期用。因此,虽然Warez?Dday都是反盗版的重要打击对象Q却同时受到免费使用者和业内同行的推崇。尽Warez?Day的拥护者对以此而谋利的盗版商不齿,但商业利益的驱动q是破解行为的商业化推C高峰。而眼下的0DayQ正在对信息安全产生来严重的威胁?br> 信息安全意义上的0Day是指在安全补丁发布前而被了解和掌握的漏洞信息?br> 2005q?2?日,几乎影响Windows所有操作系l的WMF漏洞在网上公开Q虽然微软在8天后提前发布了安全补丁(微Y的惯例是在每月的W一个周二)Q但在q?天内出现了二癑֤个利用此漏洞的攻击脚本。漏z信息的公开加速了软g生企业的安全补丁更新进E,减少了恶意程序的危害E度。但如果是不公开?Day呢?WMF漏洞公开之前Q又有多h已经利用了它Q是否有很多0Day一直在U密传Q例如,l全球网l带来巨大危害的“冲击?#8221;?#8220;震荡?#8221;q两U病毒,如果它们的漏z信息没有公开Q自然也没有这两种病毒的生。反q来惻I有什么理p为眼下不存在cM的有着重大安全隐患的漏z呢Q(Dtloginq程溢出漏洞?002q被发现Q?004q公布。)
看不见的才是最可怕的Q这是0Day的真正威胁?br> 不可避免?Day
信息价值的飞速提升,互联|在全球的普及,数字l济的广泛应用,q一切都刺激着信息安全市场的不断扩大,软g破解、口令解密、间谍Y件、木马病毒全部都从早期的仅做研究和向他h炫耀的目的{化ؓU商业利益的q作Qƈq速地传播开来,从操作系l到数据库,从应用Y件到W三方程序和插gQ再到遍布全球的漏洞发布中心Q看看它们当中有多少0Day存在Q可以毫不夸张的_在安全补丁程序发布之前,所有的漏洞信息都是0DayQ但是从未发布过安全补丁的Y件是否就意味着它们当中不存?Day呢?
有h_“每一个稍兯模的应用软g都可能存?Day?#8221;没错Q从理论上讲Q漏z必定存在,只是未发现Q而I补措施永q滞后而已?br> 只要用户方不独自开发操作系l或应用E序Q或者说只要使用W三方的软gQ?Day的出现就是迟早的事,无论你是使用数据库还是网站管理^収ͼ无论你是使用媒体播放器还是绘囑ַP即便是专职安全防护的软gE序本nQ都会出现安全漏z,q已是不争的事实Q但最可怕的不是漏洞存在的先天性,而是0Day的不可预知性?br> 从开源的角度上来_Linux更容易比闭源代码的Windows存在更多?Day。那些自以ؓ使用着安全操作pȝ的hQ迟早会?Dayd弄得哑口无言。而微软呢Q远有IIS和IEQ近有WMF和ExcelQ由于其操作pȝ应用的广泛性,如今已是补丁加补丁,更新再更斎ͼ最新操作系lVista竟然含有几万行的问题代码。尚未发行,已是满目疮痍Q谁又能保证微Y的源代码没有丝毫泄露呢?
0Day走向何方
来多的破解者和黑客们,已经把目光从率先发布漏洞信息的荣誉感转变到利用这些漏z而得到的l济利益上,互联|到处充斥着C万计的充满入侉|情的脚本子Q更不用说那些以H取信息业的商业间谍和情报h员了。于是,0Day有了市场?br> 国外两年前就有了0Day的网上交易,黑客们通过|上报h出售手中未公开的漏z信息,一个操作系l或数据库的q程溢出源码可以卖到上千元甚至更高Q而国内的黑客同行Q前不久也在|上建立了一个专门出售入늨序号UC国第一0Day的网站,管cM的提供黑客工L|站很多Q但此网站与其它|站的区别在?Day的特性十分明显:h较高的攻ȝ序的d对象Q还没有相应的安全补丁,也就是说q样的攻ȝ序很可能h一d中的效果。这个网站成立不久便在搜索引擎中消失了,也许已经关闭Q也许{入地下。但不管怎样Q?Day带来的潜在经利益不可抹杀Q而其来对信息安全的影响以及危害也绝不能轻视?br> 软g上的0Day几乎不可避免Q那g呢?g是否存在0DayQ答案无疑是肯定的。近q来Q思科路由器漏z频J出玎ͼ今年2月,已被曝光的某知名|络产品漏洞至今仍未被修复。对于那些基于IP协议q接|络的\由、网兟뀁交换机Q哪个电信运营商敢百分之癑֜ 保证自己的网l设备万无一失?Q?005q??1日,全国过二十个城市的互联|出现群发性故障;同年7?2日,北京20万ADSL用户断网Q?br> 早在两年前,q的首席技术官提互联|不能承受与日俱增的使用者这一xQ当Ӟ被很多h认ؓ是无E之谈。今q?月,在美国的商业圆桌会议上,包括惠普公司、IBM公司、Sun公司、通用汽R公司、家得宝公司和可口可乐公司等在内?60个企业代表呼吁政府要对发生大规模|络故障的可能性做好准备工?#8230;…
当今的互联网Q病毒、蠕虫、僵网l、间谍Y件、DDoS犹如z水般泛滥,所有的q一切都或多或少C0Day走过Q可以预,在不q的来Q互联网瘫痪l不遥远?br> 那么政府理者、电信运营商、安全厂商,q有全世界的互联|用P面对0DayQ我们准备好了吗Q?
**我自q了看q片文章Q还不错Q程序也可以~译出来**
文章作者:kiki
信息来源Q邪恶八q制信息安全团队Q?/font>
www.eviloctal.com
Q?br />本文章首?/font>
黑色岸U网l安全技术论?/font>
Q后由kiki友情提交到邪恶八q制信息安全团队
拦截api的技术有很多U,大体分ؓ用户层和内核层的拦截Q这里只说说用户层的拦截Q而用户层也分多种Q修改PE文g导入表,直接修改要拦截的api的内存(从开始到最后,使程序蟩转到指定的地址执行Q.不过大部分原理都是修改程序流E,使之跌{C要执行的地方Q然后再q回到原地址Q原来api的功能必还能实玎ͼ否则拦截失M用了Q修Ҏ件导入表的方法的~点是如果用L序动态加载(使用LoadLibrary和GetProcAddress函数Q,拦截变得复杂一些.所以这里介l一下第二种ҎQ直接修改apiQ当然不是全局的.Q后面会说到Q?br />
需要了解的一些知识:
Q.windows内存的结构属性和q程地址I间
Q.函数堆栈的一些知?br />
一Qwin2000和xp的内存结构和q程地址I间
windows采用4GBq_虚拟地址I间的做法。即每个q程单独拥有4GB的地址I间。每个进E只能访问自qq?GB的虚拟空_而对于其他进E的地址I间则是不可见的。这样保证了q程的安全性和E_性。但是,q?GB的空间是一个虚拟空_在用之前,我们必须先保留一D虚拟地址Q然后再D虚拟地址提交物理存储器。可是我们的内存大部分都q没?GBQ那么这4GB的地址I间是如何实现的呢?事实上windows采用的内存映这U方法,x物理盘当作内存来用,比如我们打开一个可执行文g的时候,操作pȝ会ؓ我们开辟这?GB的地址I间Q?x00000000--0xffffffff。其?x00000000--0x7fffffff是属于用户层的空?0x80000000--0xffffffff则属于共享内核方式分区,主要是操作系l的U程调度Q内存管理,文gpȝ支持Q网l支持和所有设备驱动程序。对于用户层的进E,q些地址I间是不可访问的。Q何访问都导致一个错误。开辟这4GB的虚拟地址I间之后Q系l会把磁盘上的执行文件映到q程的地址I间中去(一般是在地址0x00400000Q可以通过修改~译选项来修改这个地址)而一个进E运行所需要的动态库文g则一般从0x10000000开始加载。但是如果所有的动态库都加载到q个位置肯定会引起冲H。因此必d一些可能引起冲H的dll~译旉C改基地址。但是对于所有的操作pȝ所提供的动态库windows已经定义好了映射在指定的位置。这个位|会随着版本的不同而会有所改变Q不q对于同一台机器上的映地址来说都是一L。即在aq程里映的kernel32.dll的地址和在q程b里的kernel32.dll的地址是一L。对于文件映是一U特D的方式Q得程序不需要进行磁盘i/op对磁盘文件进行操作,而且支持多种保护属性。对于一个被映射的文Ӟ主要是用CreateFileMapping函数Q利用他我们可以讑֮一些读写属?PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY.W一参数指定只能对该映射文gq行L作。Q何写操作导致内存访问错误。第二个参数则指明可以对映射文gq行d。这时候,MҎ件的d都是直接操作文g的。而对于第三个参数PAGE_WRITECOPY思义是写入时拷贝,M向这D内存写入的操作(因ؓ文g是映到q程地址I间的,对这D늩间的dq当于Ҏ件进行的直接d)都将被系l捕Pq新在你的虚拟地址I间重新保留q分配一D内存,你所写入的一切东襉K在q里Q而且你原先的指向映射文g的内存地址也会实际指向q段重新分配的内存,于是在进E结束后Q映文件内容ƈ没有改变Q只是在q行期间在那D늧有拷贝的内存里面存在着你修改的内容。windowsq程q行所需要映的一些系ldll是以这U方式映的Q比如常用的ntdll.dll,kernel32.dll,gdi32.dll.几乎所有的q程都会加蝲q三个动态库。如果你在一个进E里修改q个映射文g的内容,q不会媄响到其他的进E用他们。你所修改的只是在本进E的地址I间之内的。事实上原始文gq没有被改变?br />q样Q在后面的修改系lapi的时候,实际是修改q些动态库地址内的内容。前面说到这不是修改全局api是q个原因Q因Z们都是以写入时拷贝的方式来映的。不q这已经_了,windows提供?个强大的内存操作函数ReadProcessMemory和WriteProcessMemory.利用q两个函数我们就可以随便对Q意进E的L用户地址I间q行d了。但是,现在有一个问题,我们该写什么,说了半天Q怎么实现跌{呢?现在来看一个简单的例子Q?br />MessageBox(NULL, "World", "Hello", 0);
我们在执行这条语句的时候,调用了系lapi MessageBoxQ实际上在程序中我没有定义UNICODE宏,pȝ调用的是MessageBox的ANSI版本MessageBoxA,q个函数是由user32.dll导出的。下面是执行q条语句的汇~代码:
0040102A push 0
0040102C push offset string "Hello" (0041f024)
00401031 push offset string "World" (0041f01c)
00401036 push 0
00401038 call dword ptr [__imp__MessageBoxA@16 (0042428c)]
前面四条指o分别为参数压栈,因ؓMessageBoxA是__stdcall调用U定Q所以参数是从右往左压栈的。最后再CALL 0x0042428c
看看0042428cq段内存的|
0042428C 0B 05 D5 77 00 00 00
可以看到q个?x77d5050b,正是user32.dll导出函数MessageBoxA的入口地址?br />
q是0x77D5050B处的内容Q?
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
理论上只要改变api入口和出口的M机器码,都可以拦截该api。这里我选择最单的修改ҎQ直接修改api入口的前十个字节来实现蟩转。ؓ什么是十字节呢Q其实修改多字节都没有关系Q只要实C函数的蟩转之后,你能把他们恢复ƈ让他l箋q行才是最重要的。在CPU的指令里Q有几条指o可以改变E序的流E:JMPQCALLQINTQRETQRETFQIRET{指令。这里我选择CALL指oQ因Z是以函数调用的方式来实现跌{的,q样可以带一些你需要的参数。到q里Q我该说说函数的堆栈了?br />
ȝQwindowsq程所需要的动态库文g都是以写入时拯的方式映到q程地址I间中的。这P我们只能拦截指定的进E。修改目标进E地址I间中的指定api的入口和出口地址之间的Q意数据,使之跌{到我们的拦截代码中去Q然后再恢复q些字节Q之能利工作?br />
二:函数堆栈的一些知?br />
正如前面所看到MessageBoxA函数执行之前的汇~代码,首先四个参数压栈,然后CALL MessageBoxAQ这时候我们的U程堆栈看v来应该是q样的:
| | <---ESP
|q回地址|
|参数1|
|参数2|
|参数3|
|参数4|
|.. |
我们再看MessageBoxA的汇~代码,
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
注意到堆栈的操作有PUSH ebp,q是保存当前的基址指针Q以便一会儿恢复堆栈后返回调用线E时使用Q然后再有mov ebp,esp是把当前esp的DlebpQ这时候我们就可以使用 ebp+偏移 来表C堆栈中的数据,比如参数1可以表C成[ebp+8]Q返回地址可以表C成[ebp+4]..如果我们在拦截的时候要对这些参数和q回地址做Q何处理,可以用这U方法。如果这个时候函数有局部变量的话,通过减小ESP的值的方式来ؓ之分配空间。接下来是保存一些寄存器QEDI,ESI,EBX.要注意的是,函数堆栈是反方向生长的。这时候堆栈的样子Q?br />|....|
|EDI| <---ESP
|ESI|
|EBX|
|局部变量|
|EBP |
|q回地址|
|参数1|
|参数2|
|参数3|
|参数4|
|.. |
在函数返回的时候,由函数自w来q行堆栈的清理,q时候清理的序和开始入栈的序恰恰相反Q类似的汇编代码可能是这LQ?br />
pop edi
pop esi
pop ebx
add esp, 4
pop ebp
ret 0010
先恢复那些寄存器的|然后通过增加ESP的值的方式来释攑ֱ部变量。这里可以用mov esp, ebp来实现清I所有局部变量和其他一些空闲分配空间。接着函数会恢复EBP的|利用指oPOP EBP来恢复该寄存器的倹{接着函数q行ret 0010q个指o。该指o的意思是Q函数把控制权交l当前栈的地址的指令,同时清理堆栈?6字节的参数。如果函数有q回值的话,那在EAX寄存器中保存着当前函数的返回倹{如果是__cdecl调用方式Q则执行ret指oQ对于堆栈参数的处理交给调用U程d。如wsprintf函数?br />
q个时候堆栈又恢复了原来的样子。线E得以l往下执?..
在拦截api的过E之中一个重要的d是保证堆栈的正性。你要理清每一步堆栈中发生了什么?br />
三:形成思\
呵呵Q不知道你现在脑h不是有什么想法。怎么d现拦截一个apiQ?br /> q里l出一个思\Q事实上拦截的方法真的很多,理清了一个,其他的也容易了。而且上面所说的2个关键知识,也可以以另外的Ş式来利用?br /> 我以拦截CreateFileq个apiZ子来单说下这个思\吧:
首先Q既然我们要拦截q个api应该知道这个函数在内存中的位置吧,臛_需要知道从哪儿入口。CreateFileq个函数是由kernel32.dllq个动态库导出的。我们可以用下面的Ҏ来获取他映射到内存中的地址Q?br /> HMODULE hkernel32 = LoadLibrary("Kernel32.dll");
PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA");
q就可以得到createfile的地址了,注意q里是获取的createfile的ansic版本。对于UNICODE版本的则获取CreateFileW。这时dwCreateFile的值就是他的地址了。对于其他进E中的createfile函数也是q个地址Q前面说qwindows指定了他提供的所有的dll文g的加载地址?br />
接下来,我们该想办法实现跌{了。最单的Ҏ是修改q个api入口处的代码了。但是我们该修改多少呢?修改的内容ؓ什么呢Q前面说q我们可以用CALL的方式来实现跌{Q这U方法的好处是可以ؓ你的拦截函数提供一个或者多个参数。这里只要一个参数就_了。带参数的函数调用的汇编代码是什么样子呢Q前面也已经说了Q类g调用MessageBoxA时的代码Q?br />
PUSH 参数地址
CALL 函数入口地址(q里Z个偏Ud址)
执行q?条指令就能蟩转到你要拦截的函CQ但是我们该修改成什么呢。首先,我们需要知道这2条指令的长度和具体的机器代码的倹{其中PUSH对应0x68Q而CALL指o对应的机器码?xE8,而后面的则分别对应拦截函数的参数地址和函数的地址。注意第一个是一个直接的地址Q而第二个则是一个相对地址。当然你也可以?xFF0x15q个CALL指o来进行直接地址的蟩转?br />下面是计算q?个地址的gQ?br />对于参数和函C的地址Q要分情况而定Q对于对本进E中api的拦截,则直接取地址可以了。对于参敎ͼ可以先定义一个参数变量,然后取变量地址ok了?br />如果是想拦截其他q程中的apiQ则必须使用其他一些方法,最典型的方法是利用VirtualAllocEx函数来在其他q程中申请和提交内存I间。然后用WriteProcessMemory来分别把函数体和参数分别写入甌和分配的内存I间中去。然后再生成要修改的数据Q最后用WriteProcessMemory来修改api入口Q把入口的前10字节修改为刚刚生成的跌{数据。比如在q程q程中你写入的参数和函数体的内存地址分别?x00010000?x00011000,则生成的跌{数据?68 00 00 01 00 E8 00 10 01 00(PUSH 00010000 CALL 00011000),q样E序q行createfile函数的时候将会先q行PUSH 00010000 CALL 00011000Q这样就辑ֈ了蟩转的目的。此L们应该时L意堆栈的状态,对于CreateFile?br />HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
可以看到其有7个参敎ͼ于是在调用之前,堆栈应该已经被压入了q?个参敎ͼ堆栈的样子:
|....| <---ESP
|createfile执行后的下一条指令地址|
|参数1|
|参数2|
|参数3|
|参数4|
|参数5|
|参数6|
|参数7|
|..|
q是执行到我们的跌{语句QPUSH 00010000,于是堆栈又变了:
|....| <---ESP
|00010000|
|createfile执行后的下一条指令地址|
|参数1|
|参数2|
|参数3|
|参数4|
|参数5|
|参数6|
|参数7|
|..|
接着执行CALL 00011000,堆栈变ؓQ?br />|...| <---ESP
|api入口之后的第11个字节的指o的地址|
|00010000|
|createfile执行后的下一条指令地址|
|参数1|
|参数2|
|参数3|
|参数4|
|参数5|
|参数6|
|参数7|
|..|
接下来就C我们的拦截函C拉,当然Q函数肯定也会做一些类似动作,把EBP压栈Qؓ局部变量分配空间等。这时候堆栈的样子又变了:
|EDI| <---ESP
|ESI|
|EBX|
|局部变量|
|EBP| <---EBP
|api入口之后的第11个字节的指o的地址|
|00010000|
|createfile执行后的下一条指令地址|
|参数1|
|参数2|
|参数3|
|参数4|
|参数5|
|参数6|
|参数7|
|..|
q时候,你想做什么就情地做吧,获取参数信息Qg~执行CreateFile函数{等。拿获取打开文g句柄的名字来说吧Q文件名是第一个参敎ͼ前面说过我们可以用[EBP+8]来获取参敎ͼ但是对照上面的堆栈ŞӞ中间又加了另外一些数据,所以我们用[EBP+16]来获取第一个参数的地址。比如:
char* PFileName = NULL;
__asm{
MOV EAX,[EBP+16]
MOV [szFileName], EAX
}
比如我们用一个messagebox来弹Z个信息,说明该程序即打开一个某谋\径的文g句柄。但是有一个要注意的是Q如果你x截远E进E的话,对于那个拦截函数中所使用到的M函数或者以M形式的相对地址的调用都要停止。因为每个进E中的地址分配都是独立的,比如上面的CALL MessageBoxAҎ直接地址的调用。对于用messageboxQ我们应该定义一个函数指针,然后把这个指针的Dgؓuser32.dll中导函数的直接地址。然后利用这个指针来q行函数调用。对于messagebox函数的调用可以这P在源E序中定义一个参数结构体Q参C包含一个导出函数的地址,把这个地址设ؓMessageBoxA的直接地址Q获取地址的方法就不说了。然后把q个参数传给拦截函数Q就可以使用拉。这也是利用一个参数的原因。类g码如下:
typedef struct _RemoteParam {
DWORD dwMessageBox;
} RemoteParam, * PRemoteParam;
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);//定义一个函数指?br />
//拦截函数
void HookCreateFile(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;//获取参数地址
char* PFileName = NULL;//定义一个指?br />__asm{
MOV EAX,[EBP+16]
MOV [szFileName], EAX //把CreateFileW一个参数的|文g的\径的地址? //lszFileName
}
//?义一个函数指?br />PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINFORMATION |MB_OK);
//输出要打开的文件的路径
//.....
}
对于你要使用的其他函敎ͼ都是使用同样的方式,利用q个参数来传递我们要传递的函数的绝对地址Q然后定义这个函数指针,可以用了?br />
好了Q接下来我们该让被拦截的api正常工作了,q个不难Q把他原来的数据恢复一下就可以了。那入口?0个字节。我们在改写他们的时候应该保存一下,然后也把他放在参C传递给拦截函数Q呵呵,参数的作用可多了。接着我们可以用WriteProcessMemory函数来恢复这个api的入口了Q代码如下:
PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess;
PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;
if(!pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnConnect,
(LPCVOID)pRP->szOldCode,
10,
NULL))
pfnMessageBox(NULL, pRP->szModuleName1, pRP->szModuleName2, MB_ICONINFORMATION | MB_OK);
其中q些函数指针的定义和上面的类伹{?br />而参C的szoldcode则是在源E序中在修改api之前保存好,然后传给拦截函数Q在源程序中是用ReadProcessMemory函数来获取他的前10个字节的Q?br />ReadProcessMemory(GetCurrentProcess(),
(LPCVOID)RParam.dwCreateFile,
oldcode,
10,
&dwPid)
strcat((char*)RParam.szOldCode, (char*)oldcode);
接下来如果你ql保持对该api的拦截,则又该用WriteProcessMemory 来修改入口了Q跟前面的恢复入口是一LQ只不过把szOldCode换成了szNewCode了而已。这样你又能对CreateFilel箋拦截了?br />
好了Q接下来该进行堆栈的清理了,也许你还要做点其他事情,管做去。但是清理堆栈是必须要做的,在函数结束的时候,因ؓ在我们放任api恢复执行之后Q他又return 到我们的函数中来了,q个时候的堆栈是什么样子呢Q?br />|EDI| <---ESP
|ESI|
|EBX|
|局部变量|
|EBP| <---EBP
|api入口之后的第11个字节的指o的地址|
|00010000|
|createfile执行后的下一条指令地址|
|参数1|
|参数2|
|参数3|
|参数4|
|参数5|
|参数6|
|参数7|
|..|
我们的目标是把返回D录下来放到EAX寄存器中去,把返回地址记录下来Q同时把堆栈恢复成原来的样子?br />首先我们恢复那些寄存器的|接着释放局部变量,可以用mov esp, ebp.因ؓ我们不清楚具体的局部变量分配了多少I间。所以用这个方法?br />
__asm
{POP EDI
POP ESI
POP EBX //恢复那些寄存?br />MOV EDX, [NextIpAddr]//把返回地址攑ֈEDX中,因ؓ待会? //EBX被恢复后Q线E中的所有局部变量就不能正常使用了?br />
MOV EAX, [RetValue]//q回值放到EAX中,当然也可以修改这个返回?br />MOV ESP, EBP//清理局部变?br />POP EBP//恢复EBP的?br />ADD ESP, 28H //清理参数和返回地址Q注意一?7+1+1+1)*4
PUSH EDX //把返回地址压栈Q这h中就只有q一个返回地址了,q回之后? //q?br />RET
}
q样Q一切就完成了,堆栈恢复了应该有的状态,而你x截的也拦截到了?br />
四:后记
拦截的方式多U多P不过大体的思\却都相同。要时刻注意你要拦截的函数的堆栈状态以及在拦截函数中的Ҏ据的引用和函数的调用Q地址问题Q?br />
//////////////////////////////////////////////////////////////////////
附录Q一个拦截CreateFile函数的简单实?br />//////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <windows.h>
#include <Psapi.h>
#pragma comment(lib, "psapi.lib")
typedef struct _RemoteParam {
DWORD dwCreateFile;
DWORD dwMessageBox;
DWORD dwGetCurrentProcess;
DWORD dwWriteProcessMemory;
unsigned char szOldCode[10];
DWORD FunAddr;
} RemoteParam, * PRemoteParam;
typedef HANDLE (__stdcall * PFN_CREATEFILE)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);
typedef BOOL (__stdcall * PFN_WRITEPROCESSMEMORY)(HANDLE,LPVOID,LPCVOID,SIZE_T,SIZE_T*);
typedef HANDLE (__stdcall * PFN_GETCURRENTPROCESS)(void);
#define PROCESSNUM 128
#define MYMESSAGEBOX "MessageBoxW"
#define MYCREATEFILE "CreateFileW"
void HookCreateFile(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;
DWORD NextIpAddr = 0;
DWORD dwParamaAddr = 0;
HANDLE RetFpHdl = INVALID_HANDLE_VALUE;
LPCTSTR lpFileName;
DWORD dwDesiredAccess;
DWORD dwShareMode;
LPSECURITY_ATTRIBUTES lpSecurityAttributes;
DWORD dwCreationDisposition;
DWORD dwFlagsAndAttributes;
HANDLE hTemplateFile;
PFN_CREATEFILE pfnCreatefile = (PFN_CREATEFILE)pRP->dwCreateFile;
__asm
{
MOV EAX,[EBP+8]
MOV [dwParamaAddr], EAX
MOV EAX,[EBP+12]
MOV [NextIpAddr], EAX
MOV EAX,[EBP+16]
MOV [lpFileName], EAX
MOV EAX,[EBP+20]
MOV [dwDesiredAccess],EAX
MOV EAX,[EBP+24]
MOV [dwShareMode],EAX
MOV EAX,[EBP+28]
MOV [lpSecurityAttributes],EAX
MOV EAX,[EBP+32]
MOV [dwCreationDisposition],EAX
MOV EAX,[EBP+36]
MOV [dwFlagsAndAttributes],EAX
MOV EAX,[EBP+40]
MOV [hTemplateFile],EAX
}
PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
int allowFlag = pfnMessageBox(NULL, lpFileName, NULL, MB_ICONINFORMATION | MB_YESNO);
if(allowFlag == IDYES)
{
unsigned char szNewCode[10];
int PramaAddr = (int)dwParamaAddr;
szNewCode[4] = PramaAddr>>24;
szNewCode[3] = (PramaAddr<<8)>>24;
szNewCode[2] = (PramaAddr<<16)>>24;
szNewCode[1] = (PramaAddr<<24)>>24;
szNewCode[0] = 0x68;
int funaddr = (int)pRP->FunAddr - (int)pfnCreatefile - 10 ;
szNewCode[9] = funaddr>>24;
szNewCode[8] = (funaddr<<8)>>24;
szNewCode[7] = (funaddr<<16)>>24;
szNewCode[6] = (funaddr<<24)>>24;
szNewCode[5] = 0xE8;
PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess;
PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;
pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnCreatefile,
(LPCVOID)pRP->szOldCode,
10,
NULL);
RetFpHdl = pfnCreatefile(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnCreatefile,
(LPCVOID)szNewCode,
10,
NULL);
}
__asm
{POP EDI
POP ESI
POP EBX
MOV EDX, [NextIpAddr]
MOV EAX, [RetFpHdl]
MOV ESP, EBP
POP EBP
ADD ESP, 28H
PUSH EDX
RET
}
}
BOOL AdjustProcessPrivileges(LPCSTR szPrivilegesName)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
{
return FALSE;
}
if(!LookupPrivilegeValue(NULL,szPrivilegesName,
&tkp.Privileges[0].Luid))
{
CloseHandle(hToken);
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken,FALSE,&tkp,sizeof(tkp),NULL,NULL))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
void printProcessNameByPid( DWORD ProcessId )
{
HANDLE pHd;
HMODULE pHmod;
char ProcessName[MAX_PATH] = "unknown";
DWORD cbNeeded;
pHd = OpenProcess( PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, ProcessId );
if(pHd == NULL)
return;
if(!EnumProcessModules( pHd, &pHmod, sizeof(pHmod), &cbNeeded))
return;
if(!GetModuleFileNameEx( pHd, pHmod, ProcessName, MAX_PATH))
return;
printf( "%d\t%s\n", ProcessId, ProcessName);
CloseHandle( pHd );
return;
}
int main(void)
{
if(!AdjustProcessPrivileges(SE_DEBUG_NAME))
{
printf("AdjustProcessPrivileges Error!\n");
return -1;
}
DWORD Pids[PROCESSNUM];
DWORD dwProcessNum = 0;
if(!EnumProcesses(Pids, sizeof(Pids), &dwProcessNum))
{
printf("EnumProcess Error!\n");
return -1;
}
for( DWORD num = 0; num < (dwProcessNum / sizeof(DWORD)); num++)
printProcessNameByPid(Pids[num]);
printf("\nAll %d processes running. \n", dwProcessNum / sizeof(DWORD));
DWORD dwPid = 0;
printf("\n误入要拦截的进Eid:");
scanf("%d", &dwPid);
HANDLE hTargetProcess = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwPid);
if(hTargetProcess == NULL)
{
printf("OpenProcess Error!\n");
return -1;
}
DWORD dwFunAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, 8192,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if((LPVOID)dwFunAddr == NULL)
{
printf("甌U程内存p|!\n");
CloseHandle(hTargetProcess);
return -1;
}
DWORD dwPramaAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, sizeof(RemoteParam),
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if((LPVOID)dwPramaAddr == NULL)
{
printf("甌参数内存p|!\n");
CloseHandle(hTargetProcess);
return -1;
}
printf("\nU程内存地址:%.8x\n"
"参数内存地址:%.8x\n",
dwFunAddr, dwPramaAddr);
RemoteParam RParam;
ZeroMemory(&RParam, sizeof(RParam));
HMODULE hKernel32 = LoadLibrary("kernel32.dll");
HMODULE hUser32 = LoadLibrary("user32.dll");
RParam.dwCreateFile = (DWORD)GetProcAddress(hKernel32, MYCREATEFILE);
RParam.dwGetCurrentProcess = (DWORD)GetProcAddress(hKernel32, "GetCurrentProcess");
RParam.dwWriteProcessMemory = (DWORD)GetProcAddress(hKernel32, "WriteProcessMemory");
RParam.dwMessageBox = (DWORD)GetProcAddress(hUser32, MYMESSAGEBOX);
unsigned char oldcode[10];
unsigned char newcode[10];
int praadd = (int)dwPramaAddr;
int threadadd = (int)dwFunAddr;
newcode[4] = praadd>>24;
newcode[3] = (praadd<<8)>>24;
newcode[2] = (praadd<<16)>>24;
newcode[1] = (praadd<<24)>>24;
newcode[0] = 0x68;
int offsetaddr = threadadd - (int)RParam.dwCreateFile - 10 ;
newcode[9] = offsetaddr>>24;
newcode[8] = (offsetaddr<<8)>>24;
newcode[7] = (offsetaddr<<16)>>24;
newcode[6] = (offsetaddr<<24)>>24;
newcode[5] = 0xE8;
printf("NewCode:");
for(int j = 0; j < 10; j++)
printf("0x%.2x ",newcode[j]);
printf("\n\n");
if(!ReadProcessMemory(GetCurrentProcess(),
(LPCVOID)RParam.dwCreateFile,
oldcode,
10,
&dwPid))
{
printf("read error");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
strcat((char*)RParam.szOldCode, (char*)oldcode);
RParam.FunAddr = dwFunAddr;
printf(
"RParam.dwCreateFile:%.8x\n"
"RParam.dwMessageBox:%.8x\n"
"RParam.dwGetCurrentProcess:%.8x\n"
"RParam.dwWriteProcessMemory:%.8x\n"
"RParam.FunAddr:%.8x\n",
RParam.dwCreateFile,
RParam.dwMessageBox,
RParam.dwGetCurrentProcess,
RParam.dwWriteProcessMemory,
RParam.FunAddr);
printf("RParam.szOldCode:");
for( int i = 0; i< 10; i++)
printf("0x%.2x ", RParam.szOldCode[i]);
printf("\n");
if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwFunAddr, (LPVOID)&HookCreateFile, 8192, &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwPramaAddr, (LPVOID)&RParam, sizeof(RemoteParam), &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
if(!WriteProcessMemory(hTargetProcess, (LPVOID)RParam.dwCreateFile, (LPVOID)newcode, 10, &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
printf("\nThat's all, good luck :)\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return 0;
}
点数据格式:
在计机中,实数的QҎ式(Floating-Point FormatQ所C,分成指数、有效数字和W号位三个部分?br />· W号QSignQ——表C数据的正负Q在最高有效位QMSBQ。负数的W号位ؓ1Q正数的W号??br />· 指数QExponentQ——也被称为阶码,表示数据?为底的幂。指数采用偏UȝQBiased ExponentQ表C,恒ؓ整数?br />· 有效数字QSignificandQ——表C数据的有效数字Q反映数据的_ֺ。有效数字一般采用规格化QNormalizedQŞ式,是一个纯数Q所以也被称为尾敎ͼMantissaQ、小数或分数QFractionQ?br /> 80x87支持三种点数据cdQ单_ֺ、双_ֺ和扩展精度;它们的长度依ơؓ32?4?0位,???0个字节;
· 单精度QҎQ?2位短实数Q——由1位符受?位指数?3位有效数l成?br />· 双精度QҎQ?4位长实数Q——由1位符受?1位指数?2位有效数l成?br />· 扩展_ֺ点敎ͼ80位时实敎ͼ——由1位符受?5位指数?4位有效数l成。很多计机中ƈ没有80位扩展精度这U数据类型,80x87 FPU主要在内部用它存贮中间l果Q以保证最l数值的_ֺ?/p>
点数据传送指令完成主存与栈顶st(0)、数据寄存器st(i)与栈之间的点格式数据的传送。QҎ据寄存器是一个首接的堆栈Q所以它的数据传送实际上是对堆栈的操作,有些要改变堆栈指针TOPQ即修改当前栈顶?br />2. 术q算cL?br /> q类点指o实现点数?6/32位整数的加、减、乘、除q算Q它们支持的d方式相同。这l指令还包括有关术q算的指令,例如求绝对倹{取整等?br />3. 越函数cL?br /> 点指o集中包含有进行三角函数、指数和Ҏq算的指令?br />4. 点比较cL?br /> 点比较指o比较栈顶数据与指定的源操作数Q比较结果通过点状态寄存器反映?br />5. FPU控制cL?br /> FPU控制cL令用于控制和Q点处理单元FPU的状态及操作方式?/p>
· 使用FPU选择伪指?br /> ׃汇编E序MASM默认只识?086指oQ所以要加上.8087 / .287 / .387{伪指o选择汇编点指oQ有Ӟq要加上相应?238/.386{伪指o?br />· 定义点数据
数据定义伪指令dd(dword) / dq(qword) / dt(tbyte)依次说明32/64/80位数据;它们可以用于定义单精度、双_ֺ和扩展精度QҎ。ؓ了区别于整数定义QMASM 6.11采用REAL4、REAL8、REAL10定义单、双、扩展精度QҎQ但不能出现U整敎ͼ其实Q整数后面补个小数点可以了Q。相应的数据属性依ơ是dword、qword、tbyte。另外,实常数可以用E表示10的幂?br />· 初始化Q点处理单?
每当执行一个新的Q点程序时Q第一条指令都应该是初始化FPU的指令finit。该指o清除点数据寄存器栈和异常,为程序提供一个“干净”的初始状态。否则,遗留在Q点寄存器栈中的数据可能会产生堆栈溢出。另一斚wQQҎ令程序段l束Q也最好清IQҎ据寄存器?/p>
点执行环境的寄存器主要?个通用数据寄存器和几个专用寄存器,它们是状态寄存器、控制寄存器、标记寄存器{?br />8个QҎ据寄存器QFPU Data RegisterQ,~号FPR0 ~ FPR7。每个Q点寄存器都是80位的Q以扩展_ֺ格式存储数据。当其他cd数据压入数据寄存器时QPFU自动转换成扩展精度;相反Q数据寄存器的数据取出时Q系l也会自动{换成要求的数据类型?br /> 8个QҎ据寄存器l成首尾相接的堆栈,当前栈顶ST(0)指向的FPRxq态寄存器中TOP字段指明。数据寄存器不采用随机存取,而是按照“后q先出”的堆栈原则工作Qƈ且首@环。向数据寄存器传送(LoadQ数据时是入栈Q堆栈指针TOP先减1Q再数据压入栈寄存器Q从数据寄存器取出(StoreQ数据时是出栈Q先栈寄存器数据弹出Q再修改堆栈指针使TOP?。Q点寄存器栈还有首@环相q的特点。例如,若当前栈TOP=0Q即ST(0) = PFR0Q,那么Q入栈操作后׃TOP=7Q即使ST(0) = PFR7Q,数据被压入PFR7。所以,点数据寄存器常常被UCؓ点数据栈?br /> Z表明点数据寄存器中数据的性质Q对应每个FPR寄存器,都有一?位的标记QTagQ位Q这8个标记tag0 ~ tag7l成一?6位的标记寄存器?/p>
当在C中定义了一个结构类型时Q它的大是否等于各字段(field)大小之和Q编译器如何在内存中放|这些字D?ANSI
C对结构体的内存布局有什么要求?而我们的E序又能否依赖这U布局Q这些问题或许对不少朋友来说q有Ҏp,那么本文p着探究它们背后的秘密?/font>
struct vector{int x,y,z;} s;
int *p,*q,*r;
struct vector
*ps;
p = &s.x;
q = &s.y;
r = &s.z;
ps =
&s;
assert(p < r);
assert(q < r);
assert((int*)ps == p);
// 上述断言一定不会失?/font>
{
char a;
int b;
} MS1;
_____________________________
| | |
| a | b |
| | |
+---------------------------+
Bytes: 1 4
|
|\\\\\\\\\\\| |
| a |\\padding\\| b
|
| |\\\\\\\\\\\| |
+-------------------------------------+
Bytes: 1 3
4
{
int a;
char b;
} MS2;
| | |
|
a | b |
| | |
+---------------------+
Bytes: 4 1
|
| | | |
| a | b | a | b
|.............
| | | |
|
+----------------------------------------------------------
Bytes:
4 1 4 1
| |
|\\\\\\\\\\\|
| a | b |\\padding\\|
| | |\\\\\\\\\\\|
+---------------------------------+
Bytes: 4 1
3
{
char a;
short b;
double c;
}
MS3;
padding
|
_____v_________________________________
| |\|
|\\\\\\\\\| |
| a |\| b |\padding\| c
|
| |\| |\\\\\\\\\| |
+-------------------------------------+
Bytes: 1 1 2 4
8
sizeof(short){于2Qb字段应从偶数地址开始,所以a的后面填充一个字节,而sizeof(double){于8Qc字段要从8倍数地址开始,前面的a、b字段加上填充字节已经?
bytesQ所以b后面再填?个字节就可以保证c字段的对齐要求了。sizeof(MS3){于16Qb的偏UL2Qc的偏UL8。接着看看l构体中字段q是l构cd的情?
{
char a;
MS3 b;
} MS4;
_______________________________________
|
|\\\\\\\\\\\| |
| a |\\padding\\| b
|
| |\\\\\\\\\\\| |
+-------------------------------------+
Bytes: 1 7
16
B. struct P2 { int
a; char b; char c; int d; };
C. struct P3 {
short a[3]; char b[3]; };
D. struct P4 { short
a[3]; char *b[3]; };
E. struct P5 { struct P2
*a; char b; struct P1 a[2]; };
(?Randal E.Bryant; David O'HallaronQ?br /> (?龚奕?雯春,
中国电力出版C,2004
?】《C: A Reference Manual?影印?Q?br />
(?Samuel P.Harbison; Guy L.SteeleQ?br /> 人民邮电出版C,2003
~译出来的结构体怎么占用内存的?/span>
padding
为编译器填充的,不理解的可以看看我前面发的翻译的关于数据寚w?/span>
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
Example 2
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?/span>