留下点什么

临兵斗者皆阵列在前



Ld.Phoenix( 维他命C) @ 2004-11-29 08:58

c++ 的11个要点基础知识
下面的这些要点是对所有的C++程序员都适用的。我之所以说它们是最重要
的,是因为这些要点中提到的是你通常在C++书中或网站上无法找到的。如:指向
成员的指针,这是许多资料中都不愿提到的地方,也是经常出错的地方,甚至是对
一些高级的C++程序员也是如此。
  这里的要点不仅仅是解释怎样写出更好的代码,更多的是展现出语言规则里面
的东西。很显然,它们对C++程序员来说是永久的好资料。我相信这一篇文章会使
你收获不小。

  首先,我把一些由不同层次的C++程序员经常问的问题归到一起。我惊奇的发
现有很多是有经验的程序员都还没意识到 .h 符号是否还应该出现在标准头文件
中。


要点1: <iostream.h> 还是 <iostream>?

  很多C++程序员还在使用<iostream.h>而不是用更新的标准的<iostream>库。
这两者都有什么不同呢?首先,5年前我们就开始反对把.h符号继续用在标准的头
文件中。继续使用过时的规则可不是个好的方法。从功能性的角度来讲,
<iostream>包含了一系列模板化的I/O类,相反地<iostream.h>只仅仅是支持字符
流。另外,输入输出流的C++标准规范接口在一些微妙的细节上都已改进,因此,
<iostream>和<iostream.h>在接口和执行上都是不同的。最后,<iostream>的各组
成都是以STL的形式声明的,然而<iostream.h>的各组成都是声明成全局型的。

  因为这些实质上的不同,你不能在一个程序中混淆使用这两个库。做为一种习
惯,在新的代码中一般使用<iostream>,但如果你处理的是过去编写的代码,为了
继承可以用继续用<iostream.h>旧保持代码的一致性。  


要点2:用引用传递参数时应注意的地方  

  在用引用传递参数时,最好把引用声明为const类型。这样做的好处是:告诉
程序不能修改这个参数。在下面的这个例子中函数f()就是传递的引用:
 
void f(const int & i);
int main()
{
 f(2); /* OK */
}
 
  这个程序传递一个参数2给f()。在运行时,C++创建一个值为2的int类型的临
时变量,并传递它的引用给f().这个临时变量和它的引用从f()被调用开始被创建
并存在直到函数返回。返回时,就被马上删除。注意,如果我们不在引用前加上
const限定词,则函数f()可能会更改它参数的值,更可能会使程序产生意想不到的
行为。所以,别忘了const。

  这个要点也适用于用户定义的对象。你可以给临时对象也加上引用如果是
const类型:
 
struct A{};
void f(const A& a);
int main()
{
 f(A()); // OK,传递的是一个临时A的const引用
}  
 

要点3:“逗号分离”表达形式

 “逗号分离”表达形式是从C继承来的,使用在for-和while-循环中。当然,这
条语法规则被认为是不直观的。首先,我们来看看什么是“逗号分离”表达形式。

  一个表达式由一个或多个其它表达式构成,由逗号分开,如:
 
 if(++x, --y, cin.good()) //三个表达式  
  这个if条件包含了三个由逗号分离的表达式。C++会计算每个表达式,但完整
的“逗号分离”表达式的结果是最右边表达式的值。因此,仅当cin.good()返回
true时,if条件的值才是true。下面是另一个例子:  
int j=10;  
int i=0;
while( ++i, --j)
{
 //直到j=0时,循环结束,在循环时,i不断自加
}  

要点4,使用全局对象的构造函数在程序启动前调用函数

  有一些应用程序需要在主程序启动前调用其它函数。如:转态过程函数、登记
功能函数都是必须在实际程序运行前被调用的。最简单的办法是通过一个全局对象
的构造函数来调用这些函数。因为全局对象都是在主程序开始前被构造,这些函数
都将会在main()之前返回结果。如:  
class Logger
{

 public:
 Logger()  
  {  
   activate_log();//译者注:在构造函数中调用你需要先运行的函数
  }
};
Logger log; //一个全局实例

int main()
{
 record * prec=read_log();//译者注:读取log文件数据
 //.. 程序代码
}

 
  全局对象log在main()运行之前被构造,log调用了函数activate_log()。从
而,当main()开始执行时,它就可以从log文件中读取数据。


  毫无疑问地,在C++编程中内存管理是最复杂和最容易出现bug的地方。直接访
问原始内存、动态分配存储和最大限度的发挥C++指令效率,都使你必须尽力避免

有关内存的bug。
  
要点5:避免使用复杂构造的指向函数的指针

  指向函数的指针是C++中可读性最差的语法之一。你能告诉我下面语句的意思
吗?  
 
void (*p[10]) (void (*)());  
  P是一个“由10个指针构成的指向一个返回void类型且指向另一个无返回和无
运算的函数的数组”。这个麻烦的语法真是让人难以辨认,不是吗?你其实可以简
单的通过typedef来声明相当于上面语句的函数。首先,使用typedef声明“指向一
个无返回和无运算的函数的指针”:  
typedef void (*pfv)();  
  接着,声明“另一个指向无返回且使用pfv的函数指针”:  
typedef void (*pf_taking_pfv) (pfv);  
  现在,声明一个由10个上面这样的指针构成的数组:  
pf_taking_pfv p[10];  
  与void (*p[10]) (void (*)())达到同样效果。但这样是不是更具有可读性
了!  

要点6:指向成员的指针

  一个类有两种基本的成员:函数成员和数据成员。同样的,指向成员的指针也
有两种:指向函数成员的指针和指向数据成员的指针。后则其实并不常用,因为类
一般是不含有公共数据成员的,仅当用在继承用C写的代码时协调结构(struct)和
类(class)时才会用到。

  指向成员的指针是C++语法中最难以理解的构造之一,但是这也是一个C++最强
大的特性。它可以让你调用一个类的函数成员而不必知道这个函数的名字。这一个
非常敏捷的调用工具。同样的,你也可以通过使用指向数据成员的指针来检查并改
变这个数据而不必知道它的成员名字。

  指向数据成员的指针

  尽管刚开始时,指向成员的指针的语法会使你有一点点的迷惑,但你不久会发
现它其实同普通的指针差不多,只不过是*号的前面多了::符号和类的名字,例:
定义一个指向int型的指针:

 
int * pi;   
  定义一个指向为int型的类的数据成员:  
int A::*pmi; //pmi是指向类A的一个int型的成员  
  你可以这样初始化它:  
class A
{
 public:
 int num;
 int x;
};
int A::*pmi = & A::num;   
  上面的代码是声明一个指向类A的一个int型的num成员并将它初始化为这个num
成员的地址.通过在pmi前面加上*你就可以使用和更改类A的num成员的值:  
A a1, a2;
int n=a1.*pmi; //把a1.num赋值给n
a1.*pmi=5; // 把5赋值给a1.num  
a2.*pmi=6; // 把6赋值给6a2.num  
 
  如果你定义了一个指向类A的指针,那么上面的操作你必须用 ->*操作符代
替:  
A * pa=new A;
int n=pa->*pmi;  
pa->*pmi=5;   

  指向函数成员的指针

  它由函数成员所返回的数据类型构成,类名后跟上::符号、指针名和函数的参
数列表。举个例子:一个指向类A的函数成员(该函数返回int类型)的指针:
 
class A  
{
 public:
 int func ();  
};  
int (A::*pmf) ();  
 
  上面的定义也就是说pmf是一个指向类A的函数成员func()的指针.实际上,这
个指针和一个普通的指向函数的指针没什么不同,只是它包含了类的名字和::符
号。你可以在在任何使用*pmf的地方调用这个函数  
func():
pmf=&A::func;
A a;
(a.*pmf)(); //调用a.func()  
  如果你先定义了一个指向对象的指针,那么上面的操作要用->*代替:  
A *pa=&a;
(pa->*pmf)(); //调用pa->func()  
  指向函数成员的指针要考虑多态性。所以,当你通过指针调用一个虚函数成员
时,这个调用将会被动态回收。另一个需要注意的地方,你不能取一个类的构造函
数和析构函数的地址。

要点7、避免产生内存碎片


  经常会有这样的情况:你的应用程序每运行一次时就因为程序自身缺陷而产生
内存漏洞而泄漏内存,而你又在周期性地重复着你的程序,结果可想而知,它也会
使系统崩溃。但怎样做才能预防呢?首先,尽量少使用动态内存。在大多数情况
下,你可能使用静态或自动存储或者是STL容器。第二,尽量分配大块的内存而不
是一次只分配少量内存。举个例子:一次分配一个数组实例所需的内存,而不是一
次只分配一个数组元素的内存。

要点8、是delete还是delete[]

  在程序员中有个荒诞的说法:使用delete来代替delete[]删除数组类型时是可
以的!
  举个例子吧:
 
 int *p=new int[10];
 delete p; //错误,应该是:delete[] p  
  上面的程序是完全错误的。事实上,在一个平台上使用delete代替delete[]的
应用程序也许不会造成系统崩溃,但那纯粹是运气。你不能保证你的应用程序是不
是会在另一个编译器上编译,在另一个平台上运行,所以还是请使用delete[]。

要点9、优化成员的排列

  一个类的大小可以被下面的方式改变:
 
struct A

{
 bool a;
 int b;
 bool c;
}; //sizeof (A) == 12
 
  在我的电脑上sizeof (A) 等于12。这个结果可能会让你吃惊,因为A的成员总
数是6个字节:1+4+1个字节。那另6字节是哪儿来的?编译器在每个bool成员后面
都插入了3个填充字节以保证每个成员都是按4字节排列,以便分界。你可以减少A
的大小,通过以下方式:
 
struct B
{
 bool a;
 bool c;
 int b;
}; // sizeof (B) == 8
 
  这一次,编译器只在成员c后插入了2个字节。因为b占了4个字节,所以就很自
然地把它当作一个字的形式排列,而a和c的大小1+1=2,再加上2个字节就刚好按两
个字的形式排列B。   

要点10、为什么继承一个没有虚析构函数的类是危险的?

  一个没有虚析构函数的类意味着不能做为一个基类。如std::string,  
std::complex, 和 std::vector 都是这样的。为什么继承一个没有虚析构函数的
类是危险的?当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指
针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你delete
一个这样的类时,C++就不会调用析构函数链。举个例子说明:
 
class A
{
 public:
 ~A() // 不是虚函数
 {
 // ...
 }
};  
class B: public A //错; A没有虚析构函数
{
 public:
 ~B()
 {
 // ...
 }
};

int main()
{
 A * p = new B; //看上去是对的
 delete p; //错,B的析构函没有被调用
}


 

要点11、以友元类声明嵌套的类

  当你以友元类声明一个嵌套的类时,把友元声明放在嵌套类声明的后面,而不
前面。
 
class A  
{
 private:
 int i;
 public:
 class B //嵌套类声明在前
 {
  public:
  B(A & a) { a.i=0;};  
 };
 friend class B;//友元类声明
};
 
  如果你把友元类声明放在声明嵌套类的前面,编译器将抛弃友元类后的其它声
明。   __________________
━━━━━━━━━━       


 
Ld.Phoenix( 维他命C) @ 2004-11-29 08:48

也来说说广州电信招聘规则

出处:PConline

作者:PCjob  

  看到大家都在问中国电信的招聘情况,我也忍不住手痒,写一下我自己对中国电信的看法,和一些我知道的内幕消息。中国电信各个地方各自为政互不往来,所以各个地方之间情况各有不同。广东电信无疑是中国电信最赚钱的一个。当然广州电信更不用说,是广东电信企业中的老大哥。

  所以在毕业生日渐增多的情况下,广州电信已经是上选。中国电信的企业的通病也是国企的通病,论资排辈非常严重,外行领导内行的情况少不了,很多优秀的人才都把中国电信当作跳板,出国的出国,跳槽的跳槽,当老板的当老板。

  自然一些技术一般,善于玩弄权术的人自然就沉淀下来。如果你是比较懒,得过且过的人,中国电信是你最好的地方。据说,广州电信人力资源部选人,第一看外表(特别是女孩子),第二看户口(广州户口优先),第三才看专业技术(特别出众的不要),广州电信需要的人,一小部分从事市场营销(当然要漂亮的了),大部分人从事维护工作(线路、服务器等)、还有部分从事开发维护(即开发一些应用系统,和这些软件系统的维护),所以强调的是“维护”,不需要什么特别的创新,要的是老老实实的维护,不出事故就行。所以能力特别强的并不是十分受欢迎。

  还有就是大家认为电信企业都是高科技企业,用的都是尖端科技,其实并不是都象大家想象的那样,不错中国电信用的设备都是世界一流的电信设备,但是管理比普通的国企好不了多少,内部的计算机网络,乱七八糟,没有一点规划。

  手工的报表满天飞,重复的工作做了一次又一次,DOS,foxbase还在跑。各个分局各自为政互不来往,不要说资源共享,甚至为了各自的利益,你争我夺。在广州电信搞技术的人不需很专业,只要知道大概的简单操作,出了问题找厂商。所以在广州电信里不要期望学到什么高新技术。不要以为这样的工作轻松简单,杂事非常多,还要经常加班,休假两天以上要分局领导批示,越是节假日越紧张。如果自己不积极挤时间学习,等同学聚会时,别听不懂老同学说的话。不过,自己学习要记得保密,否则就会被小人散播你要跳槽,出国之类的谣言。在广州电信,多半是党员,积极入党是必然选择,否则升职就...

  现在研究生一大把,小小本科生更不再话下,再怎么厉害,没有后台没背景干上四五年,最多赏个小班长干干(管三五个人)广州电信的前景跟中国电信一样,王小二过年,一年不如一年。2000年整个电信业增长首次低于GDP的增长率,2001可能更糟糕,整个中国电信可能出现负增长,过去几十万的项目打个招呼就能批钱,现在连报销出差的飞机票都要广州电信的局长亲自签字。最近有消息说,广东省电信公司将各个市局的财政大权收上来,撤销所有各个基层单位银行帐号,即各基层分局不再有发钱的权利。中国电信比起联通,移动来说,技术基础可能要好一点,但并好不了多少。但是,国企不光靠技术,更重要的是靠后台。没有后台,只有任人宰割。现在电信的员工出来都不敢说自己是电信的,大家对中国电信都有一种鄙视的态度,多数人认为中国电信的人都是懒得不得了,只会收钱,不会干活。每年华工到广州电信的都有不少,有几个真正自豪的说他是中国电信的?

  再看看网通,移动,联通,甚至是铁通,只要有后台,不怕你做不到,就怕你不敢想。说道大家最关心的收入问题,这个问题谁也说不清楚,就算是招人的人力资源部也不一定知道你以后能拿多少工资。各个分局差距非常大,当官的和普通的相差也非常大。好的分局10k都不只,当官的更是无法计算。电信每月的工资单上的数字越小,职工越高兴,说明扣的税少,也就是发的钱多,入局一年的工资单上标准工资不到2000/月,主要靠奖金,奖金多少就要看分局和职位。奖金一般跟完成任务量有关,以前电信垄断,为了完成所谓的超额任务不惜成本,反正国家支持,现在不同了,任务完成的越多,亏损越大,再大的家业也顶不住,这就是为什么电信业务增长迅速下降的原因了。

  所以有着远大理想、心怀大志的同学想进入电信工作可要考虑考虑清楚了。




 
Ld.Phoenix( 维他命C) @ 2004-11-28 12:37

带颜色文字的列表框
一个能显示带颜色文字的列表框类,类名是CColorListBox,它的基类是MFC类中的CListBox,它和类CListBox类不一样,它允许能在列表框中显示有颜色的文字,在该类中的addstring()和insertstring()就干这个的,比如你可以用下面的方法:

    m_clistbox.addstring("hey it's red!",RGB(255,0,0));
    m_clistbox.insertstring(index,"hi i'm green",RGB(0,255,0));

   它的使用是很简单的,你只需要用资源编辑器就够了。

   首先,在对话框中添加一个列表框,你得把对话框的属性该一下ower draw:选fixed。当然你的列表框中还应该有字符串(要不然怎么有带颜色的文字呢?)

   然后,还要把有关于这个类的头文件放到你的工程中去。还有下面的语句也是要注意的,

下面是一部分代码:

#include "colorlistbox.h"  // better be in stdafx.h
#include "mydialog.h"

......

bool cmydialog::oninitdialog()
{
   cdialog::oninitdialog();

   // sublclass listbox so that our ccolorlistbox receives windows    events
   m_clistbox.subclassdlgitem(idc_clistbox, this); // idc_clistbox is the listbox's resource id

   .....
}
 下面是头文件和那个CPP文件,全部代码如下:

//以下是头文件
#if !defined(AFX_COLORLISTBOX_H__5529A6B1_584A_11D2_A41A_006097BD277B__INCLUDED_)
#define AFX_COLORLISTBOX_H__5529A6B1_584A_11D2_A41A_006097BD277B__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

//*************************************************************
// ColorListBox.h : header file
//
// MFC ListBox with optional color
//
// Version: 1.0    01/10/1998 (c)Patrice Godard
//
//**************************************************************

/////////////////////////////////////////////////////////////////////////////
// CColorListBox window

class CColorListBox : public CListBox
{
// Construction
public:
   CColorListBox();

// Attributes
public:

// Operations
public:
   int AddString( LPCTSTR lpszItem);
   int AddString( LPCTSTR lpszItem, COLORREF rgb);
   int InsertString( int nIndex, LPCTSTR lpszItem, COLORREF rgb);
// Overrides
   // ClassWizard generated virtual function overrides
   //{{AFX_VIRTUAL(CColorListBox)
   public:
   virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
   //}}AFX_VIRTUAL

// Implementation
public:
   virtual ~CColorListBox();

   // Generated message map functions
protected:
   //{{AFX_MSG(CColorListBox)
   //}}AFX_MSG

   DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_COLORLISTBOX_H__5529A6B1_584A_11D2_A41A_006097BD277B__INCLUDED_)

//以下是实现文件
//*************************************************************
// ColorListBox.cpp : implementation file
//
// MFC ListBox with optional color
//
// Version: 1.0    01/10/1998 (c)Patrice Godard
//
//**************************************************************

#include "ColorListBox.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CColorListBox

CColorListBox::CColorListBox()
{
}

CColorListBox::~CColorListBox()
{
}


BEGIN_MESSAGE_MAP(CColorListBox, CListBox)
   //{{AFX_MSG_MAP(CColorListBox)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CColorListBox message handlers



void CColorListBox::DrawItem(LPDRAWITEMSTRUCT lpdis)
{
   if (lpdis->itemID < 0)
       return;

   COLORREF cvText;
   COLORREF cvBack;
   CString itemString;

   if ((lpdis->itemState & ODS_SELECTED) &&    // if item has been selected
       (lpdis->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
           DrawFocusRect(lpdis->hDC, &lpdis->rcItem);
   

   if (!(lpdis->itemState & ODS_SELECTED) &&    // if item has been deselected
       (lpdis->itemAction & ODA_SELECT))
           DrawFocusRect(lpdis->hDC, &lpdis->rcItem);
   

   if(lpdis->itemData)        // if color information is present
           cvText = SetTextColor(lpdis->hDC, lpdis->itemData);
       else     // if no color information, use default system colors
           cvText = SetTextColor(lpdis->hDC, GetSysColor((lpdis->itemState & ODS_SELECTED)
       ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
   
   // always use system colors for background
   cvBack = SetBkColor(lpdis->hDC, GetSysColor((lpdis->itemState & ODS_SELECTED)
       ? COLOR_HIGHLIGHT : COLOR_WINDOW));

   // get and display item text
   GetText(lpdis->itemID, itemString );
   DrawText(lpdis->hDC, itemString, -1, &lpdis->rcItem, DT_LEFT | DT_SINGLELINE);

   // restore DC colors
   SetTextColor(lpdis->hDC, cvText);
   SetBkColor(lpdis->hDC, cvBack);
}


//***********************************************
// original AddString() method
//
// purpose: Add a string to the listbox
//
// parameters:
//        lpszItem: pointer to item text
//
// remarks:
//        provided because CListBox::AddString is
//        NOT virtual
//
// return:    item index
//***********************************************
int CColorListBox::AddString( LPCTSTR lpszItem)
{
   return ((CListBox*)this)->AddString(lpszItem);
}


//***********************************************
// new AddString() method
//
// purpose: Add a string to the listbox
//
// parameters:
//        lpszItem: pointer to item text
//             rgb: text color as a COLORREF
//
// return:    item index
//***********************************************
int CColorListBox::AddString( LPCTSTR lpszItem,COLORREF rgb )
{
   int item = AddString(lpszItem);
   if(item >=0)
       SetItemData(item,rgb);
   return item;
}


//***********************************************
// new InsertString() method
//
// purpose: Insert a string to the listbox
//
// parameters:
//          nIndex: index of inserted item
//        lpszItem: pointer to item text
//             rgb: text color as a COLORREF
//
// return:    item index
//***********************************************
int CColorListBox::InsertString( int nIndex, LPCTSTR lpszItem, COLORREF rgb)
{
   int item = ((CListBox*)this)->InsertString(nIndex,lpszItem);
   if(item >=0)
       SetItemData(item,rgb);
   return item;

}
效果如下:



  希望这能起到抛砖引玉的作用,有技术的读者能把它给扩展了。


 
Ld.Phoenix( 维他命C) @ 2004-11-28 12:29

不规则对话框的又一实现
作者:河北唐山 吕建欣

本文示例代码:


代码运行效果图如下:





此不规则对话框的实现应用了一个重要的位图对话框类——CBitmapDialog ,通过灵活运用,可以实现任意形状的对话框。

一、CBitmapDialog 简介:

CBitmapDialog从CDialog派生并封装了根据位图形成任意形状对话框的功能,类的实现也非常简单,主要是一些加载位图、设置透明色等操作,最关键的函数是MakeWindowRgn, 它根据位图生成所需的区域对象, 然后调用SetWindowRgn将窗口设置成需要的形状,MakeWindowRgn函数的具体代码请参考本文配套的代码。

二、CBitmapDialog类的使用

下面介绍CBitmapDialog类的使用过程:
1. 运行VC,建立基于对话框的应用程序 BitmapDlg。设置对话框的属性,去掉边框和标题栏。
2. 插入位图。(在插入->资源->选 Bitmap)
3. 在CBitmapDialogDlg 的BOOL CBmpDlgDlg::OnInitDialog() 中添加如下代码:

LoadBitmap(IDB_BITMAP1);//加载位图
SetTransparent (TRUE);
SetTransColor (RGB(0,0,0));//去掉形状的颜色设置
SetStaticTransparent (TRUE);
SetClickAnywhereMove (TRUE);//鼠标是否能拖动窗体。

4. 将 CBitmapDialogDlg 的基类改为 CBitmapDialog,并将更改相应的消息映射。

请大家都来试一试吧!


 
Ld.Phoenix( 维他命C) @ 2004-11-28 12:25

程序自删除方法总结

程序的自删除早已经不是什么新鲜的话题了,对于各位大虾来说是更是比较容易的事情,但想想自己刚学时遇到的种种错误,我觉得有必要把自己所知道的各种方法总结一下,希望对新手的学习能够有所帮助。
程序的自删除广泛用于反安装程序最后的自删除(环保呀!),当然更多见于木马、病毒首次安装的自动销毁^*^,至于用于何种用途就看你自己啦!
经典自删除
说到程序的自删除就不能不说由 Gary Nebbett 等大虾所写的代码,经典之作呀!代码采用C语言内嵌汇编asm:
在Win9x下只要先对exe本身句柄执行FreeLibrary操作即可解除exe IMAGE在内存的映射,随后就可以通过调用DeleteFile来删除自身文件。
Win9x下的代码如下[selfkill-9x.c]:
#include "windows.h"
int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;
module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);
__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push FreeLibrary
ret
}
return 0;
}

在WinNT/2K下则需要先调用CloseHandle关闭exe文件本身对应的IMAGE的句柄HANDLE[硬编码为4],然后调用UnmapViewOfFile解除了另外一个对应IMAGE的HANDLE,并且解除了程序本身在内存的映射对象,最后就可以用DeleteFile删除自身啦!(注意:本方法不适用于WinXP!)

WinNT/2K下的代码如下[selfkill-nt.c]:
#include "windows.h"
int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;
module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);
CloseHandle((HANDLE)4);
__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push UnmapViewOfFile
ret
}
return 0;
}


把上面用于Win9x及WinNT/2K下的代码综合起来,即把两种平台用到的API代码全部执行一遍,虽然在一种平台上可能会有几个API运行失败,有几个API会运行成功,但最后的结果exe程序文件在退出前就删除了自身!

Win9x和WinNT/2K下的代码如下[selfkill-9x+nt.c]:
#include "windows.h"
int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;
module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);
CloseHandle((HANDLE)4);

__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push module
push UnmapViewOfFile
push FreeLibrary
ret
}
return 0;
}

  因为我自己在学习Win32下的汇编[MASM32],所以重新用汇编写了一遍,但结果却发现每次都执行失败,显示如图一的错误,

=========== 在此插入图一 ==============

通过反汇编比较发现原来由于MASM32编译器对API调用的编码和C编译器的不同,导致使用FreeLibrary或UnmapViewOfFile解除程序在内存的映射后,调用DeleteFile时又引用IMAGE映射地址内的代码[JMP DeleteFile],导致读内存执行错误。
错误分析
普通程序进行API调用时,编译器会将一个API调用语句编译为几个参数压栈指令后跟一条间接调用语句(这是指Microsoft编译器,Borland编译器使用JMP DWORD PTR [XXXXXXXXh])形式如下:

push arg1
push arg2
……
call dword ptr[XXXXXXXXh]
地址XXXXXXXXh在程序映像的导入(Import Section)段中,当程序被加载运行时,由装入器负责向里面添入API函数的地址;

一:用MASM32编译的程序其API函数调用格式为:
Call capi;
……
……
……
capi:
jmp dword ptr[XXXXXXXX];XXXXXXXX中存放着所调用的API函数真正地址

其中jmp dword ptr[XXXXXXXX]指令是由“编译器”在程序所有代码的后面自动加上的这样调用的好处是当多次调用同一API时可以减少代码体积,〈呵呵:)个人观点!

二:用C编译的程序其API函数调用格式为:
Call dword ptr [XXXXXXXX];XXXXXXXX地址中存放着所调用的API函数真正地址

正是由于上面API函数调用格式不同导致用MASM32编译的程序自删除失败,因为当调用UnmapViewOfFile后其中代码段的jmp dword ptr[XXXXXXXX]指令所处的代码节变成了不可读,后面的DeleteFile这个API的执行就会失败,程序出错!所以我们如果用MASM32编译这种自删除程序时,应该把push DeleteFile指令改为:

mov eax,DeleteFile
;取jmp dword ptr[XXXXXXXX]指令地址,机器码FF25XXXXXXXX
inc eax
inc eax
mov eax,dword ptr[eax]
push dword ptr[eax]
这样才是把DeleteFile的真正地址放入堆栈,当然用动态获取API也行,但不如这样代码少,下面是我改好的MASM32代码[selfkill9x-nt.asm]:

.386
.model flat, stdcall
option casemap :none

include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
start:
mov ebp, esp
invoke GetModuleHandle,NULL ;获取自身模块句柄
mov ebx,eax
invoke GetModuleFileName,ebx,ebp,MAX_PATH ;获取自身路径
invoke CloseHandle,4 ;关闭exe文件本身对应的IMAGE的句柄[硬编码为4]
push 0;ExitProcess的参数
push 0
push ebp;DeleteFile的参数
mov eax,ExitProcess
inc eax
inc eax
mov eax,dword ptr[eax]
push dword ptr[eax];pushExitProcess

push ebx;UnmapViewOfFile的参数
mov eax,DeleteFile
inc eax
inc eax
mov eax,dword ptr[eax]
push dword ptr[eax];pushDeleteFile
push ebx;FreeLibrary的参数
mov eax,UnmapViewOfFile
inc eax
inc eax
mov eax,dword ptr[eax]
push dword ptr[eax];pushUnmapViewOfFile
push FreeLibrary;FreeLibrary不用改因为调用它时代码节还可以读
ret
endstart

远程线程插入自删除
远程线程插入如今广泛用于木马和病毒的自我保护及隐蔽自身,同样我们也可以把它用在程序的自删除。

其中所插入的删除自身的远程线程的代码如下:
KREMOTE_CODE_START equ this byte
call @F
@@:
pop ebx
sub ebx,offset @B ;线程代码重定位
push 500
call [ebx+_lpselfkillSleep] ;休眠0.5秒
lea eax,[ebx+offset _selfkillselfname]
push eax
call [ebx+_lpselfkillDeleteFile] ;删除程序文件
ret

_lpselfkillSleep dd?; Sleep的硬编码地址
_lpselfkillDeleteFile dd?; DeleteFile的硬编码地址
_selfkillselfname: ; 程序自身文件名,主程序内生成写入

KREMOTE_CODE_END equ this byte
KREMOTE_CODE_LENGTH equ offset KREMOTE_CODE_END - offset KREMOTE_CODE_START

主程序中使用GetProcAddress来获取Sleep和DeleteFile的硬编码地址后写入上面,并用GetModuleFileName获取自身路径存入_selfkillselfname处,供远程线程使用。

Win9x下的用于在KERNEL32.DLL中建立远程线程代码如下:
Kernel32 db"KERNEL32.DLL",0
SzCreateKernelThread db 'CreateKernelThread',0
_RemoteCode9Xproc@_RmCodeStart,@_RmCodeLen
local lpThreadID
local lpCreateKernelThread
local hProcess
invoke GetModuleHandle,addr Kernel32
mov ebx,eax
invoke GetProcAddress,ebx,offset szCreateKernelThread
mov lpCreateKernelThread,eax ;取得CreateKernelThread的地址
; _findProcess是一个根据名称查找进程PID的函数过程,详细代码见[selfkill-R9x.asm]
invoke _findProcess,offset Kernel32 ;查找KERNEL32.DLL进程
.if eax
invoke OpenProcess,PROCESS_ALL_ACCESS,TRUE,eax
mov hProcess,eax
invoke WriteProcessMemory,eax,80001100h,@_RmCodeStart,@_RmCodeLen,NULL
.if eax
xor eax,eax
lea ecx,lpThreadID
push ecx
push eax
push eax
push 80001100h
push eax
push eax
call lpCreateKernelThread ;创建KERNEL32.DLL线程
.endif
invokeCloseHandle,hProcess
.endif
ret
_RemoteCode9Xendp
函数的调用格式为:
push KREMOTE_CODE_LENGTH+MAX_PATH ;代码长度
push offset REMOTE_CODE ;代码地址
call _RemoteCode9X
[注意:这里不使用
invoke _RemoteCode9X,offset REMOTE_CODE,KREMOTE_CODE_LENGTH+MAX_PATH
来调用函数,因为我测试时发现invoke调用会使KREMOTE_CODE_LENGTH+MAX_PATH的值变大!也许是编译器的一个BUG?]

在_RemoteCode9X中首先使用GetProcAddress获得CreateKernelThread这个用于在KERNEL32.DLL中建立远程线程的API地址[CreateKernelThread的参数和CreateThread类似,但有一点不同为lpStartAddress参数(线程开始执行的地址)处于KERNEL32.DLL进程中!],然后调用_findProcess过程查找KERNEL32.DLL进程的PID,随后以全部的权限打开此进程,并用WriteProcessMemory把代码写入到KERNEL32.DLL进程80001100h开始的空间内[之所以选择80001100h是因为此处有大段可能未使用得内存00h,这样就不用像中国黑客那样进入0环啦!],最后调用CreateKernelThread创建KERNEL32.DLL线程来删除自身!(Win9x下的远程线程插入自删除完整代码见selfkill-R9x.asm!)


Win2K/XP下的用于建立远程线程的代码如下:
;用于在explorer.exe进程中插入远程线程
szDesktopClassdb'Progman',0
szDesktopWindowdb'Program Manager',0
_RemoteCode2KXPproc @_RmCodeStart,@_RmCodeLen
local @hRmCodeMemory
local @hselfkillProcessID
local @hselfkillProcess
;查找文件管理器窗口并获取进程ID,然后打开进程
invoke FindWindow,addr szDesktopClass , addr szDesktopWindow
lea ecx , @hselfkillProcessID
invoke GetWindowThreadProcessId , eax,ecx
invoke OpenProcess, PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or PROCESS_VM_WRITE , FALSE , @hselfkillProcessID
mov @hselfkillProcess , eax
;在进程中分配空间并将写入远程代码,建立远程线程
invoke VirtualAllocEx , @hselfkillProcess , NULL , @_RmCodeLen , MEM_COMMIT , PAGE_EXECUTE_READWRITE
.ifeax
mov@hRmCodeMemory,eax
invoke WriteProcessMemory,@hselfkillProcess,eax,@_RmCodeStart,@_RmCodeLen,NULL
xor eax,eax
invokeCreateRemoteThread,@hselfkillProcess,eax,eax,@hRmCodeMemory,eax,eax,eax
invokeCloseHandle,eax
.endif
invokeCloseHandle,@hselfkillProcess
ret
_RemoteCode2KXPendp
函数的调用格式和_RemoteCode9X相同!

上面的函数_RemoteCode2KXP首先调用FindWindow和GetWindowThreadProcessId来获得explorer.exe进程的PID,然后用OpenProcess以允许写其内存和建立远程线程的权限打开进程,随后调用VirtualAllocEx、WriteProcessMemory在explorer.exe申请内存写入代码,最后使用CreateRemoteThread建立远程线程并运行。(Win2K/XP下的远程线程插入自删除完整代码见selfkill-Rnt.asm!)
批处理文件的自删除
我们知道在批处理文件中可以使用 %x来获取传递给批处理的参数,而%0获得的则是自身的路径,用del %0就可以删除实现批处理文件的自删除。
我们可以把这个小技巧运用在自己的程序当中,程序中调用批处理文件删除自身,达到自删除的目的。
生成的相应的批处理文件如下:
@echo off
:selfkill
attrib -a -r -s -h "c:\selfkill-bat.exe"
del "c:\selfkill-bat.exe"
if exist "c:\selfkill-bat.exe" goto selfkill
del %0
我对其进行了修改,首先用@echo off来关闭输出信息,这样可以使批处理文件运行完后的DOS窗口自动关闭,然后使用attrib修改文件属性,防止自身是只读、隐藏、系统属性时,无法使用批处理来删除,程序名称使用双引号引起来,防止路径中有空格出现。[用批处理文件删除程序自身示例代码见selfkill-bat.asm]
示例中在固定位置生成的批处理文件“c:\Autoexce.bat”,而不在当前目录生成,是为了防止自身所在目录路径中包含空格,导致批处理无法运行,生成批处理后使用WinExec隐蔽运行,不显示DOS 窗口。
DOS虚拟机下的自删除
这个方法乃好友“抑郁天使”所提供的(感谢!),代码如下:
#include <stdio.h>
int main(int argc,char *argv[])
{
unlink(argv[0]);
return 0;
}
unlink相信学 C语言的朋友比较熟悉吧,就是删除指定文件,使用TC2.0把上面代码编译为dos下16位的程序,执行看看,是不是在闪出一个dos 窗口后,程序不见啦?!

我们再把上面的程序改写一下,使其可以接受参数:
#include <stdio.h>
int main(int argc,char *argv[])
{
sleep(1); //休眠1秒
if(argc==2)
unlink(argv[1]); //删除程序(参数一)
unlink(argv[0]); //删除自身
return 0;
}

通过对其反汇编分析,结合测试,这个自删除的原因应该为DOS下的程序在Windows下是通过虚拟机执行[Win2000下为16位MS-DOS子系统(NTVDM CPU)ntvdm.exe,Win98下应该是Winoa386.mod]的,而当DOS程序在虚拟机下执行时,因为已被虚拟机读入内存,也相当于是解释执行的(类似脚本的执行),所以当DOS程序加载后系统并没有对其进行保护,所以可以在执行中被删除,你可以用如下方法来验证!
使用DEBUG建立一个死循环的DOS下的COM程序,命令如下:
debug
-a
0B22:0100 jmp 100
0B22:0102
-r cx
CX 0000
:02
-n dos16.com
-w
Writing 00002 bytes
-q

运行生成的dos16.com,会产生一个DOS窗口,你手工删除dos16.com下,成功没?^*^
上面的C代码生成的程序太大,用起来麻烦,给你来个汇编的,同样采用DEBUG生成:
-a
0B22:0100 mov si,120
0B22:0103 mov dx,si
0B22:0105 mov ax,4301
0B22:0108 xor cx,cx
0B22:010A int 21
0B22:010C mov ah,41
0B22:010E int 21
0B22:0110 cmp al,5
0B22:0112 je 103
0B22:0114 lodsb
0B22:0115 or al,al
0B22:0117 jne 114
0B22:0119 cmp byte ptr [si],0
0B22:011C jne 103
0B22:011E int 20
0B22:0120 db 'kill.com',0
0B22:0129 db 'selfkill.exe',0,0
0B22:0137
-r cx
CX 0000
:37
-n kill.com
-w
Writing 00037 bytes
-q

上面代码就是调用DOS中断INT 21 的41号功能删除自身的,至于Windows下的应用程序如何使用此方法删除自身的完整代码见[selfkill-dos.asm]文件,和批处理的利用方式一样以隐蔽运行方式调用!
脚本自删除
欢乐时光的泛滥,想必很多人对于VBS脚本有所了解啦,由于脚本是解释执行的,所以在运行时可以被删除,也就是说脚本文件删除自身后不影响后面的代码执行。
我们来做个实验,把下面的脚本保存为selfkill.vbs或selfkill.vbe:
Set fso = CreateObject("Scripting.FileSystemObject")
f = fso.DeleteFile(WScript.ScriptName)
WScript.Echo( WScript.ScriptName)
然后运行它,是不是发现selfkill.vbs神奇的消失啦?而后面的对话框却被正常显示出来噢^*^
上面的脚本调用FSO控件,使用WSH中Wscript对象得ScriptName属性,得到脚本自身的文件名,并调用FSO的DeleteFile方法删除自身!
把它稍微改写一下:
On Error Resume Next '防止出现错误
Set fso = CreateObject("Scripting.FileSystemObject")
WScript.Sleep 1000 '将脚本执行挂起1秒
fso.DeleteFile(WScript.ScriptName) '删除脚本自身
If fso.FileExists("c:\selfkill.exe") Then fso.DeleteFile("c:\selfkill.exe") '删除程序
程序就可以动态生成VBS自删除脚本,并调用它删除自身啦,方法同样和批处理文件的自删除相似!需要说明的是由于病毒及蠕虫对脚本的滥用,脚本删除文件时可能会被被误认为恶意代码!
[附自删除js脚本:
try{fso = new ActiveXObject("Scripting.FileSystemObject");
WScript.Sleep(1000);//休眠1秒
fso.DeleteFile(WScript.ScriptName);//删除脚本自身
fso.DeleteFile("c:\selfkill.exe");//删除程序
}catch(e){}
]
当然还有wsf脚本文件,和上面的基本上是一样的!
特殊方式打开文件自删除
这个方法我只在Win2000下当文件处于FAT32(FAT)格式的分区时成功删除,在NTFS分区下并不能成功删除,不知是何原因,所以这个方法也许利用价值很低,但既然写总结,就一并稍微提一下。
代码如下:
[自删除.asm]
.386
.model flat, stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.code
rdb"selfkill.exe",0
main:
;以FILE_FLAG_DELETE_ON_CLOSE方式打开selfkill.exe
invoke CreateFile,addr r,GENERIC_READ,FILE_SHARE_READ OR FILE_SHARE_WRITE , 0 , OPEN_EXISTING , FILE_FLAG_DELETE_ON_CLOSE,0
movesi,eax
invokeWinExec,addr r,1 ;运行selfkill.exe
invokeSleep,500
invokeCloseHandle,esi
invoke ExitProcess, NULL
end main

[selfkill.asm]
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.code
delexedb'自删除.exe',0
start:
invokeSleep,1500
invokeDeleteFile,offset delexe
invokeMessageBox,NULL,offset delexe,offset delexe,MB_OK
invokeExitProcess,NULL
endstart

首先在“自删除.asm”中使用CreateFile以FILE_FLAG_DELETE_ON_CLOSE(文件被关闭后立即被系统自动删除)方式打开selfkill.exe文件,然后运行selfkill.exe,休眠0.5秒后关闭文件(也就是删除selfkill.exe),在“selfkill.asm”中首先休眠1.5秒,然后删除“自删除.exe”。
文件编译后,在Win2000下FAT分区内运行“自删除.exe”,你会发现两个文件全部被自动删除,而对话框却仍然被正常显示出来!
重起系统后自删除
上面所说的方法,都是运行中就把程序直接删除,并不需要重起系统,程序自删除还有下面重起系统后删除自身的几种方法。
一:WININIT.INI 自删除
利用 WININIT.INI 的一些特性,在 WININIT.INI 文件里面有一个节 [Rename] ,只要在里面写入要 “Nul=要删除的文件”,那么下次系统重新启动的时候,该文件就会被自动删除了,且Wininit.ini在每次被系统执行完它其中的命令时就会被系统自动删除。以下是一个Wininit.ini例子:
[rename]
nul=c:\Selfkill.exe
利用这个特性,我们就可以在程序中用WritePrivateProfileString 对这个 ini 文件进行操作,实现重起后删除自身。
二:文件移动自删除
在NT下,文件移动API 函数MoveFileEx,当移动标志指定为参数MOVEFILE_DELAY_UNTIL_REBOOT,目标文件为空的情况下,下次启动系统是会删除指定文件!代码如下:
.386
.model flat, stdcall
option casemap :none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data?
selfname db MAX_PATH dup(?)
.code
start:
invoke GetModuleFileName,NULL,addr selfname,MAX_PATH
;下次启动时删除自身
invoke MoveFileEx,addr selfname,NULL,MOVEFILE_DELAY_UNTIL_REBOOT
invoke ExitProcess,NULL
endstart

通过监测,发现当MoveFileEx以MOVEFILE_DELAY_UNTIL_REBOOT方式运行时,会在注册表中建立如下键值:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
"PendingFileRenameOperations"=hex(7):5c,00,3f,00,3f,00,5c,00,43,00,3a,00,5c,00,\
73,00,65,00,6c,00,66,00,6b,00,69,00,6c,00,6c,00,2e,00,65,00,78,00,65,00,00,\
00,00,00,00,00
PendingFileRenameOperations键值类型为REG_MULTI_SZ,在注册表编辑器中值显示为:\??\c:\selfkill.exe,是Unicode编码格式。
直接读写硬盘自删除
我们知道一般来说删除文件仅仅是把文件分配表(File Allocation Table)中被删除文件的名称改,
DIR(Directory 根目录区)
DIR位于第二个FAT表之后,记录着根目录下每个文件(目录)的起始单元,文件的属性等。定位文件位置时,操作系统根据DIR中的起始单元,结合FAT表就可以知道文件在硬盘中的具体位置和大小了。
在NT和2000下,通过CreateFile来打开需要读写的驱动器,ReadFile、WriteFile来进行磁盘读写。
CreateFile("\.\A:",
GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);

众所周知windows有FAT12,FAT16,FAT32,NTFS等文件格式,而FAT12,FAT16,FAT32文件格式可看作一类,简称FAT格式,而NTFS文件格式又可看作一类
'\.\vwin32''\.\PHYSICALDRIVE0'




 
日历
网志分类
· 所有网志 (122)
· C/C++ (31)
· Windows (5)
· 适用技巧 (4)
· 桌面外设 (8)
· 问题求助 (3)
· 七嘴八舌 (21)
· Java+Dephi等 (2)
· other (9)
· 其他 (3)
· 未分类 (36)
最新的评论
站内搜索
友情链接
· 我的歪酷 非非共享界
· MSDN中文
·
· CSDN社区

订阅 RSS

0029059

歪酷博客

本模版系 歪酷博客Zazamu Studio 授权使用 请尊重知识产权