决定了。。回家!

我决定了。。回家。。。闷死我了。。自己一个人。。哀。。吃饭也吃不好。。睡觉也睡不好。。。

我要回家。。回家。。下午就走。。把我闷坏了可就不好玩了。。。

昨天下了雨。。天儿也不是很热。。。回家看老娘去。。

Coreutils 的内容

安装的程序: basename, cat, chgrp, chmod, chown, chroot, cksum, comm, cp, csplit, cut, date, dd, df, dir, dircolors, dirname, du, echo, env, expand, expr, factor, false, fmt, fold, groups, head, hostid, hostname, id, install, join, link, ln, logname, ls, md5sum, mkdir, mkfifo, mknod, mv, nice, nl, nohup, od, paste, pathchk, pinky, pr, printenv, printf, ptx, pwd, readlink, rm, rmdir, seq, sha1sum, shred, sleep, sort, split, stat, stty, sum, sync, tac, tail, tee, test, touch, tr, true, tsort, tty, uname, unexpand, uniq, unlink, users, vdir, wc, who, whoami, yes

简要描述

basename

去掉文件名中的目录和后缀

cat

把文本文件的内容发送到标准输出

chgrp

改变文件和目录属组,属组可以使用组名或者组识别号表示

chmod

改变文件和目录的权限,权限可以使用符号或者八进制两种表达方式

chown

改变文件和目录的所有权(包括用户和/或组)

chroot

使用特定的目录作为执行某个命令或者交互 shell 的根目录(/)。在多数系统中,只有 root 用户能运行这个命令

cksum

输出指定的每个文件的CRC(循环冗余校验)校验和与字节数

comm

一行一行对两个已经排序的文件进行比较,在第三列中显示同一行是否相同

cp

复制文件

csplit

把一个文件按照给定的模式或者行号分成几块

cut

从指定的文件中提取特定的列送到标准输出

date

以特定的格式显示当前时间,或者设置系统日期

dd

以可选块长度复制文件,默认情况下从标准输入设备输出到标准输出设备。复制过程中,还可以对文件进行一些转换

df

显示参数中的文件所在分区磁盘空间的使用情况,如果没有给出文件参数就显示所有已经安装的文件系统的可用空间数量。

dir

列出给定目录的内容 (同 ls 命令)

dircolors

设置 LS_COLOR 环境变量(用来改变 ls 及相关工具默认颜色组合)

dirname

显示从文件名去掉非目录后缀之后的内容

du

显示参数使用的磁盘空间的数量,对于参数为目录还会列出每个子目录磁盘空间占用情况。

echo

显示给定字符串或变量值

env

在一个被修改的环境中运行一个程序

expand

把 tab 转换为空格符

expr

执行表达式计算

factor

输出所有指定整数的质因数

false

返回一个不成功或者逻辑假的结果

fmt

重新格式化指定文件的段落

fold

断开指定文件(默认是标准输入)较长的行,在屏幕上显示

groups

显示一个用户所在的组

head

显示每个指定文件的前几行(默认是10)

hostid

以16进制方式,显示当前主机的数字标志符

hostname

显示或设置主机名

id

显示某个用户或者当前用户的真实和有效的 UID、GID 。

install

复制文件,设置它们的权限,如果可能还设置拥有它们的用户和组

join

合并两个文件的行

link

创建从指定文件到指定名称的硬链接

ln

创建文件之间的硬/软(符号)连接

logname

显示当前用户的登录名

ls

列出指定目录的所有内容。缺省是将文件和子目录按字母顺序排列。

md5sum

显示或者校验 MD5 校验码。

mkdir

建立目录,使用给定的参数作为目录名。

mkfifo

以给定的参数作为名字建立FIFO(又叫”命名管道”)文件。

mknod

使用给出的文件名,建立一个设备节点,也就是:FIFO、字符特殊文件(special file)或者块特殊文件(special file)。

mv

根据所给参数的不同,把文件或者目录移动到另外的目录或者将其改名

nice

修改某个进程的调度优先级

nl

把每个指定文件的内容写到标准输出,在每行加上行号

nohup

使某个命令不被挂起,并将输出重定向到一个日志文件。

od

以数字方式显示指定文件的内容,默认为八进制。

paste

将字段连接在一起,在字段之间自动插入分割符,默认的分割符是 Tab 。

pathchk

检查文件名是否是有效的或者是可移植的

pinky

一个轻量级的 finger 客户端,用来得到某个用户的信息。

pr

将文件分成适当大小的页送到打印机

printenv

显示环境变量

printf

根据给定的参数格式化输出数据,与 C 语言中的该函数相似。

ptx

为指定的文件提供一个排序索引

pwd

显示当前工作目录

readlink

显示指定符号链接的值

rm

删除文件或者目录

rmdir

删除目录(目录必需为空)

seq

以指定的步长输出一个数列

sha1sum

显示或校验 160 位的 SHA1 校验码

shred

安全删除一个文件,重写其占用的磁盘空间,使其无法恢复。

sleep

延迟一段时间

sort

对文件进行排序

split

把文件分成固定大小(字节或行数)的片断

stat

显示文件或者文件系统的状态

stty

改变和显示终端行的设置

sum

显示指定文件的校验和及块数

sync

刷新文件系统缓冲区,使磁盘和内存的数据同步。

tac

逆向显示指定的文件,最后一行在最前。

tail

显示每个指定文件的最后几行(默认是10)。

tee

从标准输入读取数据,输出到标准输出和指定的文件。

test

检查文件类型,以及进行变量的比较。

touch

把参数指定的文件的访问和修改时间改为当前的时间。如果文件不存在,它就建立一个空文件。

tr

从标准输入读入正文,对字符进行转换、压缩或者删除,然后写到标准输出

true

返回一个成功或者逻辑真的结果

tsort

对给定的文件进行拓扑排序

tty

显示标准输出设备连接终端的文件名

uname

打印系统信息

unexpand

把空格符转换成 tab

uniq

抛弃指定文件或者标准输入中内容重复的行

unlink

删除指定文件

users

显示在当前主机登录的用户名

vdir

ls -l

wc

统计文件中包含的字节数、单词数、行数

who

显示有哪些用户登录

whoami

打印当前用户的有效用户标志符

yes

重复输出”y“字符,直到被杀死。

学吧。这么多命令呢。。呵

SIM复制原理

看了一些关于拷卡的内容,遗憾的是,似乎 05 下半年和以后的移动卡都是 V2 加密的了,用现有的拷卡软件没戏拷出来。虽然一堆厂商都声称在开发 V2 的XX软件,不过我觉得就是扯淡。


一篇 blog 不能写太短,那就简单讲讲刚看的关于 GSM 加密的内容吧:


GSM 的加密系统里面大致涉及三种算法,A3,A5,A8,这些并不特定指代什么算法,只是给出算法的输入和输出规范,以及对算法的要求,GSM 对于每种算法各有一个范例实现,理论上并没有限制大家使用哪种算法。但是世界上的设备商和运营商都是很懒得沟通的,看到既然有了范例实现,就都拿来用了,于是全世界的 SIM 卡被XX了都一样拷法。


说到这里就不能不简单介绍一下 SIM 卡, SIM 卡是一种智能卡片,里面有个非常简单的 CPU 和一点 NVRAM,可以存储和读出数据,还可以进行一些运算。卡里面有很多内容,不过我只介绍和加密相关的。每张 SIM 卡里面一般都存着一个全球唯一的标志号,叫做 IMSI,这个是用来唯一标识你 SIM 卡的,手机在开机时候会从卡里面读出这个号发给移动网络,移动那里有一个很大的数据库,描述了 IMSI 和手机号的对应关系,于是网络就知道你的手机号是多少了(如果你手机卡丢了去补,新补来的卡 IMSI 和原有的不同,而移动数据库那里将你原来的手机号指向新的 IMSI,旧的卡就再也不能用了)除了 IMSI ,还有 16 个字节的密钥数据,这个数据是无法通过 SIM 卡的接口读出的,通常称为 Ki, Ki 在移动网络那边也保存了一份。


在手机登录移动网络的时候,移动网络会产生一个 16 字节的随机数据(通常称为 RAND)发给手机,手机将这个数据发给 SIM 卡, SIM 卡用自己的密钥 Ki 和 RAND 做运算以后,生成一个 4 字节的应答(SRES)发回给手机,并转发给移动网络,与此同时,移动网络也进行了相同算法的运算,移动网络会比较一下这两个结果是否相同,相同就表明这个卡是我发出来的,允许其登录。这个验证算法在 GSM 规范里面叫做 A3,m = 128 bit, k = 128 bit, c=32 bit,很显然,这个算法要求已知 m 和 k 可以很简单的算出 c ,但是已知 m 和 c 却很难算出 k 。A3 算法是做在 SIM 卡里面的,因此如果运营商想更换加密算法,他只要发行自己的 SIM 卡,让自己的基站和 SIM 卡都使用相同的算法就可以了,手机完全不用换。


在移动网络发送 RAND 过来的时候,手机还会让 SIM 卡对 RAND 和 Ki 计算出另一个密钥以供全程通信加密使用,这个密钥的长度是 64 bits, 通常叫做 Kc, 生成 Kc 的算法是 A8 ,因为 A3 和 A8 接受的输入完全相同,所以实现者偷了个懒,用一个算法同时生成 SRES 和 Kc 。


在通信过程中的加密就是用 Kc 了,这个算法叫做 A5 ,因为 A5 的加密量很巨大,而且 SIM 卡的速度很慢,因此所有通信过程中的加密都是在手机上面完成的,这样一来,除非天下所有 GSM 手机都至少支持一种相同的 A5 算法,否则就没法漫游了,这时候运营商和设备商的懒惰又体现出来了,全世界目前只有一种通用的 A5 算法,没有其他的,这个算法就是和 Kc 的 8 字节序列进行简单的循环 XOR,再和报文序号做个减法。


上面只是简单的介绍 GSM 的加密通信过程,实际上 GSM 的操作比这个还要复杂一些,比如除了第一次登录时候用真正的 IMSI ,之后都是用商定的临时标识 TMSI ,不过这个不是今天讨论的重点。


下面就来说说为啥手机卡可以被复制。


从前面的介绍里面我们知道,要完成一次登录过程,IMSI 和 Ki 是必不可少的,A3 算法也需要知道,这其中, IMSI 是直接可读的,但是 A3 算法和存在你的卡里面的数据,都是不知道的,手机只是简单的把 RAND 给 SIM 卡, SIM 卡把算好的数据返回。实际设备中使用的 A3 算法被作为高级商业机密保护起来。但是世界上没有不透风的墙,在 1998 还是 1999 年的时候,有人从哪里偷到了几页纸的相关文档,然后把这文档输入了电脑。后来这个文档落到了加州伯克力几个教授手里面。这个文档里面缺少一些东西,而且还有写错的地方,这几个牛教授们拿一个 SIM 卡比对了一阵子,把缺的补上了,错的也给修正了,于是这个算法就成为了世人皆知的秘密。这个算法又被叫做 Comp128 ,他同时生成 SRES 和 Kc ,代码在这个文件里面。
http://kangkang.org/wordpress/wp-content/uploads/2006/06/a3a8.txt


光有了算法还是不能够得到在 SIM 卡里面保存的 Ki, 理论上面是可以把 SIM 卡拆了,然后把芯片接到特殊设备上面来读出 Ki ,但是这个听起来就像用小刀在硬盘上面刻操作系统一样不靠谱。于是很多有志之士就开始了对 Comp128 算法的攻击,在一开始大家想到的肯定是穷举,不过这个 GSM 的设计者也想到了,SIM 卡里面有个逻辑是一共只能查询 2^16 次左右,之后卡会自杀,让XX者啥都得不到。因此研究者们试图在可以接受的次数之内通过构造特定明文和分析输出秘文来分析出 Ki 的值,结果还真被大家发现出来了一些。在下面这个 pdf 里面有一些相关的内容介绍,IBM 的一个小组甚至用 6 次查询就可以彻底解出 Ki,当然现在外面卖的那种拷卡器肯定没有这么牛,但是看表现似乎都可以在几分钟之内XX 。
http://kangkang.org/wordpress/wp … Brumley-comp128.pdf


随着时间的推移,针对 Comp128 的XX算法越来越成熟,商用的卡复制设备也越来越多,运营商们终于坐不住了。很多运营商都开始发行 Comp128 v2 加密算法的卡了。这其中就包括中国移动,我看了一下论坛上面的帖子,大部分都是在反映 05 年的新卡基本都没法用 simscan 之类软件读出 Ki 。Comp128 v2 算法是 GSM 协会在 v1 被攻破以后,迅速在 v1 上面修改得来的结果,据说比较好的解决了 v1 算法中的弱点,当然,这个算法像 v1 一样,还是不公布于众。。而且到现在也没有人公布出来。这样一来,基本就没法解了。现在网上面很多拷卡设备厂商说的正在研发 v2 解码,我觉得基本是扯淡,这个既要有足够内线,能从设备商那里盗窃到 v2 的算法库或者从其他位置盗窃到文档,还要有足够数学实力,能够找出算法漏洞。就凭这些作坊企业那还差得远点。

手机SIM卡揭密

手机是什么?几乎所有人都知道答案:就是那特烧钱、特不保值的玩意!SIM卡是什么?估计许多人无法给出一个比较完整的答案,只知道是一张小小的卡片!但是您也许不知道手机离开了这张小小的SIM卡是几乎做不了工作的,除了在特殊情况下用户可以通过不带SIM卡的呼叫网络许可的专用紧急号码,比如110,119等等……下面让我们走近SIM卡,一起来揭开SIM卡的面纱,看看它的真实“面目”。
一、SIM卡名词解释
SIM卡(Subscriber Identity Module),即用户识别卡,它是一张符合GSM规范的“智慧卡”,SIM卡有大小之分,大卡尺寸54mmx84mm(约为名片大小),小卡尺寸为25mmx15mm(比普通邮票还小)。其实“大卡”上面真正起作用的是它上面的那张“小卡”,“小卡”上起作用的部分只有小指甲盖那么大。目前国内流行样式是“小卡”,小卡也可以换成“大卡”(有个卡托即可)。“大卡”和“小卡”分别适用于不同类型的GSM移动电话,早期的机型如摩托罗拉GC87C、308C等手机用的是“大卡”,现在新出的机型基本上都是用“小卡”。SIM卡可以插入任何一部符合GSM规范的移动电话中,“实现电话号码随卡不随机的功能”,而通话费则自动计入持卡用户的帐单上,与手机无关。
二、SIM卡密码剖析
(1)PIN码(Personal Identity Number):个人识别码,也叫PIN1码,长4位,由用户自己设定,是属于SIM卡的密码,用来保护SIM卡的安全,初始状态是不激活的。启动该功能后,每次用户重新开机后,GSM系统就要和手机之间进行自动鉴别,判断SIM卡的合法性,即和手机对“口令”,只有在系统认可后,才为该用户提供服务。
(2)PIN2码:PIN2码也是SIM卡的密码,它跟网络的计费和SIM卡内部资料的修改有关。手机上的“计费”功能需要PIN2码支持。GSM协议支持手机随时查询已通话的支出,但国内移动局和联通都未开通此项业务,所以PIN2码用户都不知道。
(3)PUK(PIN Unblocking Key):PUK码是解PIN码的万能锁,每张SIM卡有各自对应的PUK码,长8位,可以交由用户自己管理,也可以由网络运营商自己控制。目前国内移动局基本都已开通查询PUK的业务,用户可以自己管理PUK码。
三、SIM卡使用FAQ
(1)SIM卡的日常使用中该注意什么?
一是请勿将卡弯曲,卡上的金属芯片更应小心保护,保持金属芯片清洁(可用酒精棉球轻擦),避免沾染尘埃及化学物品;二是为保护金属芯片,请避免经常将SIM卡从手机中抽出;请勿将SIM卡置于超过85度或低于-35度的环境中;在取出或放入SIM卡前,请先关闭手机电源;三是最好不要用手去触摸那些触点,以防止静电损坏。
(2)SIM卡被锁怎么办?
导致这种锁卡现象的发生原因一般都是用户在启动PIN码保护功能后不慎将PIN码忘记,在错误的输入三次PIN码后SIM自动上锁,手机无法接入网络,提示要求输入PUK码。此时若您不知道PUK码,那么请不要再尝试输入PIN码了,请携带有关凭证(比如手机使用证,俗称无委证、身份证)和手机到移动局或者联通的营业厅去解开(免费)。若您输入10次错误的PIN码,那么SIM卡的自杀程序将自动启动,将SIM烧毁,这样您将需要花费人民币100元来重新办理一张新的SIM卡。
(3)SIM卡遗失或者被窃后该做什么?
当您遭遇到这种情况后,请立即携带有关证件到移动局或者联通营业厅去申请挂失,避免SIM卡被盗用,给您造成经济损失,并重新补办一张新的SIM卡。
(4)SIM卡插入手机后开机出现出错信息怎么办?
一是出现手机插入SIM卡开机后无任何反应或插入SIM卡显示出错(Bad Card/SIM Error)时,表示可能:SIM卡开关不良、接触不良、或使用废卡均会出现这样的问题。如果换新卡后故障仍然存在,那么故障一般发生在SIM卡供电部分。在SIM卡插座的供电端、时钟端、数据端,开机瞬间可用示波器观察到读卡信号,如无此信号,应为SIM卡供电开关周边电阻电容元件与卡脱焊问题。二是SIM卡在一部手机上可以用,在另一部手机上不能用,可能是在手机中已经设置“网络限制”和“用户限制”功能。可以通过16部网络控制码(NCK)、用户控制码(SPCK)启动该手机的限制功能,这种故障需要网络运营商解决,有时是SIM卡供电偏低或接触不良造成。
四、SIM卡知识进阶
(一)SIM卡内保存的数据可以归纳为以下四种类型:
(1)由SIM卡生产厂商存入的系统原始数据。
(2)由GSM网络运营部门或者其他经营部门在将卡发放给用户时注入的网络参数和用户数据。包括:
*鉴权和加密信息Ki(Kc算法输入参数之一:密匙号);
*国际移动用户号(IMSI);
*A3:IMSI认证算法;
*A5:加密密匙生成算法;
*A8:密匙(Kc)生成前,用户密匙(Kc)生成算法;
(3)由用户自己存入的数据。比如,短消息、固定拨号,缩位拨号,性能参数,话费记数等。
(4)用户在用卡过程中自动存入和更新的网络接续和用户信息类数据。包括最近一次位置登记时的手机所在位置区识别号(LAI),设置的周期性位置更新间隔时间,临时移动用户号(TMSI)等。
这些数据都存放在各自的目录项内,第一类数据放在根目录,当电源开启后首先进入根目录,再根据指令进入相关的子目录,每种目录极其内部的数据域均有各自的识别码保护,只有经过核对判别以后才能对数据域中的数据进行查询,读出和更新。上面第一类数据通常属永久性的数据,由SIM卡生产厂商注入以后无法更改,第二类数据只有网络运行部门的专门机构才允许查阅和更新,再第三、四类数据中的大部分允许用户利用任何手机对其进行读/写操作。
(二)SIM卡结构
(1)SIM卡能够储存多少电话号码取决于卡的EEPROM的容量(有2K、3K、8K容量),若有8KB的存储容量,可供储存以下信息:
*100组电话号码及其对应的性名文字;
*15组短信息(Short Message);
*25组以上最近拨出的号码;
*4位SIM卡密码(PIN)。
表1:几种主要的SIM卡的结构数据
(2)SIM卡是带有微处理器的芯片卡,内有5个模块,每个模块对应一个功能:CPU(8位)、程序存储器ROM(6-16kbit)、工作存储器RAM(128-256kbit)、数据存储器EEPROM(2-8kbit)和串行通信单元,这5个模块集成在一块集成电路中。SIM卡在与手机连接时,最少需要5个连接线:
*电源(Vcc)
*时钟(CLK)
*数据I/Q口(Data)
*复位(RST)
*接地端(GND)
(3)电源开关时,SIM卡电气性能为:当开启电源期间,按以下次序激活各触点:RST低电平状态;Vcc加电;I/O口处于接收状态;Vpp加电;提供稳定的时钟信号。当关闭电源时,按如下次序工作:RST低电平状态;CLK低电平状态;Vpp去电;I/O口低电平状态;Vcc掉电。
(4)SIM卡背面上20位数字所代表的含义如下:
*前6位(898600):是中国的代号;
*第7位:业务接入号,对应于135、136、137、138、139中的5、6、8、9;
*第8位:SIM卡的功能位:一般为0,现在的预付费SIM卡为I;
*第9、10位:各省的编码;
*11、12位:年号;
*13位:供应商代码;
*14-19位:用户识别码;
*20位:校验位。
厂商SIM卡型号 中央处理器 ROM RAM EEPROM
摩托罗拉SC21 8位 6kbit 128kbit 3kbit
Sc27 8位 12kbit 240kbit 3kbit
Sc28 8位 16kbit 240kbit 8kbit
日立H8/3101 8/16位 10kbit 256kbit 8kbit
Thomson ST16612 8位 6kbit 128kbit 2kbit
ST16 8位 16kbit 256kbit 8kbit

LFS中的Bzip2-1.0.3

Bzip2 包含了对文件进行压缩和解压缩的工具,对于文本文件,bzip2 比传统的 gzip 拥有更高压缩比。

bunzip2

解压使用 bzip2 压缩的文件

bzcat

解压缩指定的文件到标准输出

bzcmp

对 bzip2 压缩的文件运行 cmp 命令

bzdiff

对 bzip2 压缩的文件运行 diff 命令

bzgrep

对 bzip2 压缩的文件运行 grep 命令

bzegrep

对 bzip2 压缩的文件运行 egrep 命令

bzfgrep

对 bzip2 压缩的文件运行 fgrep 命令

bzip2

使用 Burrows-Wheeler 块排列文本压缩算法和霍夫曼编码来压缩文件。压缩比要大于 gzip 工具使用的基于”Lempel-Ziv“的压缩算法(如 gzip 格式),接近 PPM 统计压缩算法族的压缩比。

bzip2recover

试图从被破坏的 bzip2 文件中恢复数据

bzless

对 bzip2 压缩的文件运行 less 命令

bzmore

对 bzip2 压缩的文件运行 more 命令

libbz2*

利用 Burrows-Wheeler 算法,实现无损块顺序数据压缩的库文件。

if ( test ! -d /tools/bin ) ; then mkdir -p /tools/bin ; fi

if ( test ! -d /tools/lib ) ; then mkdir -p /tools/lib ; fi

if ( test ! -d /tools/man ) ; then mkdir -p /tools/man ; fi

if ( test ! -d /tools/man/man1 ) ; then mkdir -p /tools/man/man1 ; fi

if ( test ! -d /tools/include ) ; then mkdir -p /tools/include ; fi

cp -f bzip2 /tools/bin/bzip2

cp -f bzip2 /tools/bin/bunzip2

cp -f bzip2 /tools/bin/bzcat

cp -f bzip2recover /tools/bin/bzip2recover

chmod a+x /tools/bin/bzip2

chmod a+x /tools/bin/bunzip2

chmod a+x /tools/bin/bzcat

chmod a+x /tools/bin/bzip2recover

cp -f bzip2.1 /tools/man/man1

chmod a+r /tools/man/man1/bzip2.1

cp -f bzlib.h /tools/include

chmod a+r /tools/include/bzlib.h

cp -f libbz2.a /tools/lib

chmod a+r /tools/lib/libbz2.a

cp -f bzgrep /tools/bin/bzgrep

ln /tools/bin/bzgrep /tools/bin/bzegrep

ln /tools/bin/bzgrep /tools/bin/bzfgrep

chmod a+x /tools/bin/bzgrep

cp -f bzmore /tools/bin/bzmore

ln /tools/bin/bzmore /tools/bin/bzless

chmod a+x /tools/bin/bzmore

cp -f bzdiff /tools/bin/bzdiff

ln /tools/bin/bzdiff /tools/bin/bzcmp

chmod a+x /tools/bin/bzdiff

cp -f bzgrep.1 bzmore.1 bzdiff.1 /tools/man/man1

chmod a+r /tools/man/man1/bzgrep.1

chmod a+r /tools/man/man1/bzmore.1

chmod a+r /tools/man/man1/bzdiff.1

echo “.so man1/bzgrep.1″ > /tools/man/man1/bzegrep.1

echo “.so man1/bzgrep.1″ > /tools/man/man1/bzfgrep.1

echo “.so man1/bzmore.1″ > /tools/man/man1/bzless.1

echo “.so man1/bzdiff.1″ > /tools/man/man1/bzcmp.1

这是make install 以后的信息。

看这几句:

cp -f bzip2 /tools/bin/bzip2

cp -f bzip2 /tools/bin/bunzip2

cp -f bzip2 /tools/bin/bzcat

bzip2,bunzip2,bzcat其实都是bzip2副本

但是运行的时候会产生不同一效果,很神奇,看这个:

[root@hacker tmp]# bzip2

bzip2: I won’t write compressed data to a terminal.

bzip2: For help, type: `bzip2 –help’.

[root@hacker tmp]# bunzip2

bunzip2: I won’t read compressed data from a terminal.

bunzip2: For help, type: `bunzip2 –help’.



[root@hacker tmp]# bzcat

bzcat: I won’t read compressed data from a terminal.

bzcat: For help, type: `bzcat –help’.

虽然三个用的都是一个文件,不过文件名不同,效果不同。

我认为程序应该是判断自己的文件名来选择不同的函数运行。很有意思。。呵。

看用法吧。

bzip2 文件名 ;即被压缩

bunzip2 文件名.bz2 ;即被解压缩

bzip2 -d 等价于bunzip2

-f参数强制覆盖已存在的文件

Bash的陷阱

感谢fcicq,他的new 30 days系列为我们带来了不少好文章。

今天想分析的是这篇Bash Pitfalls, 介绍了一些bash编程中的经典错误。fcicq说可能不适合初学者,而我认为,正是bash编程的初学者才应该好好阅读一下这篇文章。

下面就逐个分析一下这篇文章中提到的错误。不是完全的翻译,有些没用的话就略过了,有些地方则加了些注释。

1. for i in `ls *.mp3`

常见的错误写法:

for i in `ls *.mp3`; do     # Wrong!

为什么错误呢?因为for…in语句是按照空白来分词的,包含空格的文件名会被拆成多个词。如遇到 01 – Don’t Eat the Yellow Snow.mp3 时,i的值会依次取 01,-,Don’t,等等。

用双引号也不行,它会将ls *.mp3的全部结果当成一个词来处理。

for i in "`ls *.mp3`"; do   # Wrong!

正确的写法是

for i in *.mp3; do

2. cp $file $target

这句话基本上正确,但同样有空格分词的问题。所以应当用双引号:

cp "$file" "$target"

但是如果凑巧文件名以 – 开头,这个文件名会被 cp 当作命令行选项来处理,依旧很头疼。可以试试下面这个。

cp -- "$file" "$target"

运气差点的再碰上一个不支持 — 选项的系统,那只能用下面的方法了:使每个变量都以目录开头。

for i in ./*.mp3; do
  cp "$i" /target
  ...

3. [ $foo = “bar” ]

当$foo为空时,上面的命令就变成了

[ = "bar" ]

类似地,当$foo包含空格时:

[ multiple words here = "bar" ]

两者都会出错。所以应当用双引号将变量括起来:

[ "$foo" = bar ]      # 几乎完美了。

但是!当$foo以 – 开头时依然会有问题。在较新的bash中你可以用下面的方法来代替,[[ 关键字能正确处理空白、空格、带横线等问题。

[[ $foo = bar ]]      # 正确

旧版本bash中可以用这个技巧(虽然不好理解):

[ x"$foo" = xbar ]    # 正确

或者干脆把变量放在右边,因为 [ 命令的等号右边即使是空白或是横线开头,依然能正常工作。(Java编程风格中也有类似的做法,虽然目的不一样。)

[ bar = "$foo" ]      # 正确

4. cd `dirname “$f”`

同样也存在空格问题。那么加上引号吧。

cd "`dirname "$f"`"

问题来了,是不是写错了?由于双引号的嵌套,你会认为`dirname 是第一个字符串,`是第二个字符串。错了,那是C语言。在bash中,命令替换(反引号“中的内容)里面的双引号会被正确地匹配到一起,不用特意去转义。

$()语法也相同,如下面的写法是正确的。

cd "$(dirname "$f")"

5. [ “$foo” = bar && “$bar” = foo ]

[ 中不能使用 && 符号!因为 [ 的实质是 test 命令,&& 会把这一行分成两个命令的。应该用以下的写法。

[ bar = "$foo" -a foo = "$bar" ]       # Right!
[ bar = "$foo" ] && [ foo = "$bar" ]   # Also right!
[[ $foo = bar && $bar = foo ]]         # Also right!

6. [[ $foo > 7 ]]

很可惜 [[ 只适用于字符串,不能做数字比较。数字比较应当这样写:

(( $foo > 7 ))

或者用经典的写法:

[ $foo -gt 7 ]

但上述使用 -gt 的写法有个问题,那就是当 $foo 不是数字时就会出错。你必须做好类型检验。

这样写也行。

[[ $foo -gt 7 ]]

7. grep foo bar | while read line; do ((count++) ); done

由于格式问题,标题中我多加了一个空格。实际的代码应该是这样的:

grep foo bar | while read line; do ((count++)); done         # 错误!

这行代码数出bar文件中包含foo的行数,虽然很麻烦(等同于grep -c foo bar或者 grep foo bar | wc -l)。乍一看没有问题,但执行之后count变量却没有值。因为管道中的每个命令都放到一个新的子shell中执行,所以子shell中定义的count变量无法传递出来。

8. if [grep foo myfile]

初学者常犯的错误,就是将 if 语句后面的 [ 当作if语法的一部分。实际上它是一个命令,相当于 test 命令,而不是 if 语法。这一点C程序员特别应当注意。

if 会将 if 到 then 之间的所有命令的返回值当作判断条件。因此上面的语句应当写成

if grep foo myfile > /dev/null; then

9. if [bar=”$foo”]

同样,[ 是个命令,不是 if 语句的一部分,所以要注意空格。

if [ bar = "$foo" ]

10. if [ [ a = b ] && [ c = d ] ]

同样的问题,[ 不是 if 语句的一部分,当然也不是改变逻辑判断的括号。它是一个命令。可能C程序员比较容易犯这个错误?

if [ a = b ] && [ c = d ]        # 正确

11. cat file | sed s/foo/bar/ > file

不能在同一条管道操作中同时读写一个文件。根据管道的实现方式,file要么被截断成0字节,要么会无限增长直到填满整个硬盘。如果想改变原文件的内容,只能先将输出写到临时文件中再用mv命令。

sed 's/foo/bar/g' file > tmpfile && mv tmpfile file

12. echo $foo

这句话还有什么错误码?一般来说是正确的,但下面的例子就有问题了。

MSG="Please enter a file name of the form *.zip"
echo $MSG         # 错误!

如果恰巧当前目录下有zip文件,就会显示成

Please enter a file name of the form freenfss.zip lw35nfss.zip

所以即使是echo也别忘记给变量加引号。

13. $foo=bar

变量赋值时无需加 $ 符号——这不是Perl或PHP。

14. foo = bar

变量赋值时等号两侧不能加空格——这不是C语言。

15. echo <<EOF

here document是个好东西,它可以输出成段的文字而不用加引号也不用考虑换行符的处理问题。不过here document输出时应当使用cat而不是echo。

# This is wrong:
echo &lt;&lt;EOF
Hello world
EOF

# This is right:
cat &lt;&lt;EOF
Hello world
EOF

16. su -c ‘some command’

原文的意思是,这条基本上正确,但使用者的目的是要将 -c ‘some command’ 传给shell。而恰好 su 有个 -c 参数,所以su 只会将 ‘some command’ 传给shell。所以应该这么写:

su root -c 'some command'

但是在我的平台上,man su 的结果中关于 -c 的解释为

-c, --commmand=COMMAND
            pass a single COMMAND to the shell with -c

也就是说,-c ‘some command’ 同样会将 -c ‘some command’ 这样一个字符串传递给shell,和这条就不符合了。不管怎样,先将这一条写在这里吧。

17. cd /foo; bar

cd有可能会出错,出错后 bar 命令就会在你预想不到的目录里执行了。所以一定要记得判断cd的返回值。

cd /foo &amp;&amp; bar

如果你要根据cd的返回值执行多条命令,可以用 ||。

cd /foo || exit 1;
bar
baz

关于目录的一点题外话,假设你要在shell程序中频繁变换工作目录,如下面的代码:

find ... -type d | while read subdir; do
  cd "$subdir" &amp;&amp; whatever &amp;&amp; ... &amp;&amp; cd -
done

不如这样写:

find ... -type d | while read subdir; do
  (cd "$subdir" &amp;&amp; whatever &amp;&amp; ...)
done

括号会强制启动一个子shell,这样在这个子shell中改变工作目录不会影响父shell(执行这个脚本的shell),就可以省掉cd – 的麻烦。

你也可以灵活运用 pushd、popd、dirs 等命令来控制工作目录。

18. [ bar == “$foo” ]

[ 命令中不能用 ==,应当写成

[ bar = "$foo" ] &amp;&amp; echo yes
[[ bar == $foo ]] &amp;&amp; echo yes

19. for i in {1..10}; do ./something &; done

& 后面不应该再放 ; ,因为 & 已经起到了语句分隔符的作用,无需再用;。

for i in {1..10}; do ./something &amp; done

20. cmd1 && cmd2 || cmd3

有人喜欢用这种格式来代替 if…then…else 结构,但其实并不完全一样。如果cmd2返回一个非真值,那么cmd3则会被执行。所以还是老老实实地用 if cmd1; then cmd2; else cmd3 为好。

21. UTF-8的BOM(Byte-Order Marks)问题

UTF-8编码可以在文件开头用几个字节来表示编码的字节顺序,这几个字节称为BOM。但Unix格式的UTF-8编码不需要BOM。多余的BOM会影响shell解析,特别是开头的 #!/bin/sh 之类的指令将会无法识别。

MS-DOS格式的换行符(CRLF)也存在同样的问题。如果你将shell程序保存成DOS格式,脚本就无法执行了。

$ ./dos
-bash: ./dos: /bin/sh^M: bad interpreter: No such file or directory

22. echo “Hello World!”

交互执行这条命令会产生以下的错误:

-bash: !": event not found

因为 !” 会被当作命令行历史替换的符号来处理。不过在shell脚本中没有这样的问题。

不幸的是,你无法使用转义符来转义!:

$ echo "hi!"
hi!

解决方案之一,使用单引号,即

$ echo 'Hello, world!'

如果你必须使用双引号,可以试试通过 set +H 来取消命令行历史替换。

set +H
echo "Hello, world!"

23. for arg in $*

$*表示所有命令行参数,所以你可能想这样写来逐个处理参数,但参数中包含空格时就会失败。如:

#!/bin/bash
# Incorrect version
for x in $*; do
  echo "parameter: '$x'"
done

$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg'
parameter: '1'
parameter: 'arg2'
parameter: 'arg3'

正确的方法是使用 “$@”。

#!/bin/bash
# Correct version
for x in "$@"; do
  echo "parameter: '$x'"
done

$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg 1'
parameter: 'arg2'
parameter: 'arg3'

在 bash 的手册中对 $* 和 $@ 的说明如下:

*    Expands to the positional parameters, starting from one.
     When the expansion occurs within double quotes, it
     expands to a single word with the value of each parameter
     separated by the first character of the IFS special variable.
     That is, "$*" is equivalent to "$1c$2c...",
@    Expands to the positional parameters, starting from one.
     When the expansion occurs within double quotes, each
     parameter expands to a separate word.  That  is,  "$@"
     is equivalent to "$1" "$2" ...

可见,不加引号时 $* 和 $@ 是相同的,但”$*” 会被扩展成一个字符串,而 “$@” 会被扩展成每一个参数。

24. function foo()

在bash中没有问题,但其他shell中有可能出错。不要把 function 和括号一起使用。最为保险的做法是使用括号,即

foo() {
  ...
}

Ncurses介绍

Ncurses介绍

[ncurses]

摘要:

Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库

什么是Ncurses?

您希望您的程序有一个彩色的界面吗?Ncurses是一个能提供基于文本终端窗口功能的动态库. Ncurses可以:

  • 只要您喜欢,您可以使用整个屏幕
  • 创建和管理一个窗口
  • 使用8种不同的彩色
  • 为您的程序提供鼠标支持
  • 使用键盘上的功能键



Ncurses可以在任何遵循ANSI/POSIX标准的UNIX系统上运行,除此之外,它还可以从系统数据库中检测终端的属性, 并且自动进行调整,提供一个不受终端约束的接口.因此,Ncurses可以在不同的系统平台和不同的终端上工作的非常好.

mc工具集就是一个用ncurses写的很好的例子,而且在终端上系统核心配置的界面同样是用ncurses编写的. 下面就是它们的截图:

[Midnight Commander]

[kernel config]

哪里可以下载?

Ncurses是基于GNU/Linux发展的,请访问 http://www.gnu.org/software/ncurses/以获得最新的更新版本或者其他详细信息以及相关链接 .

基础知识

为了能够使用ncurses库,您必须在您的源程序中将curses.h包括(include)进来,而且在编译的需要与它连接起来. 在gcc中您可以使用参数-lcurses进行编译.

在使用ncurses的时候,您有必要了解它的基础数据结构.它有一个WINDOW结构,从名字就很容易知道,它是用来描述 您创建的窗体的,所有ncurse库中的函数都带有一个WINDOW指针参数.

在ncurses中使用最多的组件是窗体.即使您没有创建自己的窗体,当前屏幕会认为是您自己的窗体. 如同标准输入输出系统提供给屏幕的文件描述符stdout一样(假设没有管道转向),ncurses提供一个 WINDOW指针stdscr做相同工作.除了stdscr外,ncurses还定义了一个WINDOW指针curscr. 和stdscr描述当前屏幕一样,curscr描述当前在库中定义的屏幕,您可以带着”他们有什么区别?”这个问题继续阅读.

为了在您的程序中使用ncurses的函数和变量,您必须首先调用initscr函数(初始化工作),它会给一些变量比如 stdscr,curscr等分配内存,并且让ncurses库处于准备使用状态,换句话说,所有ncurses函数必须跟在initscr后面. 同样的约定,您在结束使用ncurses后,应该使用endwin来释放所有ncurses使用的内存.在使用endwin后,您将不能在使用 任何ncurses的函数,除非您再一次调用initscr函数.

在initscr和endwin之间,请不要使用标准输入输出库的函数输出结果到屏幕上,否则,您会看到屏幕会被您的输出 弄的乱七八糟,这可不是您期望的结果.当ncurses处在激活状态时,请使用它自己的函数来把结果输出到屏幕.在调用initscr之前或者 endwin之后,您就可以随便使用了.

刷新屏幕:refresh

WINDOW结构不会经常保持同一高度宽度以及在窗体中的位置,但是会保持在窗体中的内容.当您向窗体写入数据时, 会改变窗体中的内容,但并不意味着在屏幕中会立即显示出来,要更新屏幕内容,必须调用refresh或者wrefres函数.

这里介绍了stdscr和curscr两者之间的区别.curscr保存着当前屏幕的内容,在调用ncurse的输出函数后,stdscr和curscr可能会有不同的内容, 如果您想在最近一次屏幕内容改变后让stdscr和curscr保持一致,您必须使用refresh函数.换句话说, refresh是唯一一个处理curscr的函数.千万不要弄混淆了curscr和stdscr,应该在refresh函数中更新curscr中的内容

refresh有一个能尽可能快的更新屏幕的机制,当调用refresh时,它只更新窗体中内容改变的行,这节省了CPU的处理时间 ,防止程序往屏幕上写相同的信息(译者注:在屏幕的同一位置不用重新显示同样的内容.)这种机制就是为什么同时使用ncurses的函数 和标准的输入输出函数会造成屏幕内容错位的原因.当调用ncurses的输出函数时,它会设置一个标志,能让refresh 知道是哪一行改变了.但是您调用标准输入输出函数时,就不会产生这种结果.

refresh和wrefresh其实做了同样的事情.wrefresh需要一个WINDOW的指针参数,它仅仅刷新该窗体的内容. refresh()等同于wrefresh(stdscr).我在后面会提到,和wrefresh一样,ncursers的许多函数都有许多这种为stdscr定义的宏函数.

定义一个新的窗体

下面我们来谈谈能定义新窗体的subwin和newwin函数.他们都需要一个来定义新窗体的高度,宽度以及 左上角位置的参数,并且返回一个WINDOW指针来指向该窗体.您可以使用它来作为wrefresh的参数或者一些其他我将要 谈到的函数.

您可能会问:”如果他们做同样的事情,为什么要有两个函数?”,您是对的,他们之间有一些细微的差别.subwin创建一个 窗体的子窗体,它将继承了父窗体的所有属性.但如果在子窗体中改变了这些属性的值,它将不会影响父窗体.

除此之外,父窗体和子窗体之间还有一些联系.父窗体和子窗体中的内容将彼此共享,彼此影响.换句话说, 在父窗口和子窗体重叠的区域的字符会被任意一个窗体改变.如果父窗体写入了数据到这块区域,子窗体中这块区域同样 改变了,反之也是如此.

和subwin不同的是,newwin创建一个独有的窗体.这样的窗体,在没有他们的子窗体之前,是不会和其他窗体共享 任何文本数据的.使用subwin的好处是可以使用较少的内存就可以方便的共享字符数据了.但是如果您担心窗体数据会互相影响 那么就应该使用newwin.

您可以创建任意多层的子窗体,每一个子窗体又可以有它自己的子窗体,但是一定要记住,窗体的字符内容是被两个以上 的窗体共享的.

当您调用完您定义的窗体后,您可以使用delwin函数来删除该窗体.我建议您使用man pages来得到这些函数的详细参数.

向窗体写数据和从窗体读数据

我们谈到了stdscr,curscr,以及刷新屏幕和定义一个新窗体,但是我们怎样向一个窗体写入数据?我们怎样从一个窗体中读入数据?

实现以上目的函数如同标准输入输出库中的一些函数一样,我们使用printw来替换printf输出内容,scanw替换scanf接受输入, addch替换putc或者putchar,getch替换getc或者getchar.他们用法一样,仅仅名字不同,类似的,addstr可以用来向窗体 写入一个字符串,getstr用来从窗体中读入一个字符串.所有这些函数都是以一个”w”字母开头,后面再跟上函数的 字, 如果需要操作另外一个窗体内容,第一个参数必须是该窗体的WINDOWS结构指针,举个例子,printw(…)和wprintw(stdscr,…) 是相同的,就如同refresh()和wrefresh(stdscr)一样.

如果要写这些函数的详细说明,这篇文章将会变的很长.要得到他们的 述,原型以及返回值或者其他信息,man pages是一个不错的选择. 我建议您对照man pages检查您使用的 一个函数.他们提供了详细和非常有用的信息.在这篇文章的最后一节,我提供了 一个示例程序,可以当作是一个ncurses函数的使用指南.

物理指针和逻辑指针

在讲完写入数据和从窗体读出数据后,我们需要解释一下物理指针和逻辑指针 物理指针是一个常用指针,它只有一个,从另一个方面讲,逻辑指针属于ncurses窗体, 每一个窗体都只有一个物理指针,但是他们可以有多个逻辑指针.

当窗体准备写入和读出的时候,逻辑指针会指向窗体中将要进行操作的区域.因此, 通过移动逻辑指针,您可以任何时候向窗体中的任意位置写入数据.这个是区别与标准输入输出库的优势之处.

移动逻辑指针的函数是move或者另外一个您非常容易猜出来的函数wmove.move是wmove的一个宏函数,专门用来处理 stdscr的.

另外一个需要确认的是物理指针和逻辑指针的协作关系,物理指针的位置将会在一段写入程序后无效,但是我们通过可以 通过WINDOW结构的_leave标志定位它.如果设置了_leave标志,在写操作结束后,逻辑指针将会移动到物理指针指向窗体中最后写入的区域. 如果没有设置_leave位,在写操作结束后,物理指针将返回到逻辑指针指向窗体的第一个字符写入位置._leave标志是由leaveok函数控制的.

移动物理指针的函数是mvcur,不象其他的函数,mvcur在不用等待refresh动作就会立即生效.如果您想隐藏物理指针, 您可以使用curs_set函数,使用man pages来获得详细信息.

同样存在一些宏函数简化了上述的移动和写入等函数.您可以在addch,addstr,printw,getch,getstr,scanw等函数 的man pages页得到更多的解释.

清除窗体

当我们向窗体写完内容后,我们怎么样清除窗体,行和字符?

在ncurses中,清除意味着用空白字符填充整块区域,整行或者整个窗体的内容. 下面我介绍的函数将会使用空白字符填充必要的区域,达到我们清屏的目的.

首先我们谈到能清楚字符和行的函数,delch和wdelch能删除掉窗体逻辑指针指向的字符,下一个字符和一直到行末的字符都会左移 一个位置.deleteln和wdeleteln能删除掉逻辑指针指向的行,并且上移下一行.

clrteol和wclrtoeol能清除掉从逻辑指针指向位置右边字符开始到行末的所有字符.clrtbot和wclrtobot 首先清除掉从逻辑指针所在位置右边字符开始到行末的所有字符,接着删除下面所有行.

除了这些,还有一些函数能清除整个屏幕和窗体.有两种方法可以清除掉整个屏幕.第一个是先用空白字符填充 屏幕所有区域,然后再调用refresh函数.另外一种方法是用固定的终端控制字符清除.第一种方法比较慢,因为它需要重写 当前屏幕.第二种能迅速清除整个屏幕内容.

erase和werase用空白字符替换窗体的文本字符,在下一次调用refresh后屏幕内容将会被清除掉.但是如果窗体 需要清掉整个屏幕, 这将一个比较苯的办法.您可以使用上面讲的第一种方法来完成.当窗体需要被清除的是一个屏幕那么宽, 您可以使用下面讲的函数来非常好的完成您的任务.

在涉及到其他函数之前,我们先来讨论一下_clear标志位.如果设置了该标志,那么它会存在WINDOW结构中. 当调用它时,它会用refresh来发送控制代码到终端,refresh检查窗体的宽度是否是屏幕的宽度(使用_FULLWIN标志位). 如果是的话,它将用内置的终端方法刷新屏幕,它将写入除了空白字符外的文本字符到屏幕,这是一种非常快速清屏的方法. 为什么仅仅当窗体的宽度和屏幕宽度相等时才用内置的终端方法清屏呢?那是因为控制终端代码不仅仅只清除窗体自身 ,它还可以清除当前屏幕._clear标志位由clearok函数控制.

函数clear和wclear被用来清除和屏幕宽度一样的窗体内容.实际上,这些函数等同与使用werase和clearok. 首先,它用空白字符填充窗体的文本字符.接着,设置_clear标志位,如果窗体宽度和屏幕宽度一样,就使用内置的终端方法 清屏,如果不一样就用空白字符填充窗体所有区域再用refresh刷新.

总而言之,如果您知道窗体的宽度和屏幕宽度一样,就使用clear或者wclear,这个速度将非常快.如果窗体宽度 不是和屏幕宽度一样,那么使用wclear和werase将没有任何分别.

使用颜色

您在屏幕上看到的颜色其实都是颜色对,因为每一个区域都有一个背景色和一个前景色.使用ncurses显示彩色 意味着您定义自己的颜色对并且将这些颜色对写入到窗体.

如同使用ncurses函数必须先调用initscr一样,start_color需要首先调用以初始化色素. 您用来定义自己的颜色对的函数是init_pair,当您使用它定义了一个颜色对后,它将会和您在函数中的设置的第一个参数联系起来. 在程序中,无论您什么时候需要用该颜色对,您只需用COLOR_PAIR调用该参数就可以了.

除了定义颜色对,您还必须使用函数来保证写入的使用是用不同的颜色对,attron和wattron可以满足您的要求. 使用这些函数将会用您选择的颜色对写入数据到相应的屏幕上,直到调用了attroff或者wattroff函数.

bkgd和wbkgd函数可以改变相应的整个窗体的颜色对,调用时,它将会改变窗体所有区域的前景色和背景色.也就 是说,在下一个刷新动作前,窗体上所有的区域将会使用新的颜色对重写.

使用刚才提到的那些函数man pages来得到详细的关于颜色资料和信息.

窗体的边框

您可以给您的程序里面的窗体一个很好看的边框,在库中有一个box宏函数可以替您做到这一点,和其他函数所 不同的是,没有wbox函数.box需要一个WINDOW指针来作为参数.

您可以在box的man pages页轻松获得详细的帮助,这里有一些需要注意的是,给一个窗体设置边框其实只是 在窗体的相应边框区域写入了一些字符.如果您在边框区域一下写如了某些数据,边框将会被中断. 解决的办法就是在您在原始窗体里面再建一个子窗体,将原始窗体放入到边框里面然后使用里面的子窗体作为需要的输入数据窗体.

功能键

为了能够使用功能键,必须在我们需要接受输入的窗体中设置_use_keypad标志位,keypad是一个能设置 _use_keypad值的函数,当您设置了_use_keypad后,您就可以使用键盘的功能键(快捷键),如同普通输入一样.

在这里,如果您想使用getch来作个简单接受数据输入的例子,您需要注意的是要将数据赋给整形变量(int)而不是 字符型(char).这是因为整形变量能容纳的功能键比字符型更多.您不需要知道这些功能键的值,您只需要使用库中定义的 宏名就可以了,在getch的man page中有这些数值的列表.

范例

我们将来分析一个非常简单实用的程序.在这个程序中,将使用ncurses定义菜单,菜单中的 一个选择项都会被证明选种. 这个程序比较有意思的一面就是使用了ncurses的窗体来达到菜单效果.您可以看下面的屏幕截图.

[example program]

程序开始和普通一样,包括进去了一个头文件.接着我们定义了回车键和escape键的ASCII码值.

#include &lt;curses.h&gt;
#include &lt;stdlib.h&gt;

#define ENTER 10
#define ESCAPE 27

当程序的时候,下面的函数会被调用.它首先调用initscr初始化指针接着调用start_color来显示彩色. 整个程序中所使用的颜色对会在后面定义.调用curs_set(0)会屏蔽掉物理指针.noecho()将终止键盘上的输入会在屏幕上显示出来. 您可以使用noecho函数控制键盘输入进来的字符,只允许需要的字符显示.echo()将会屏蔽掉这种效果. 接着的函数keypad设置了可以在stdscr中接受键盘的功能键(快捷键),我们需要在后面的程序中定义F1,F2以及移动的光标键.

void init_curses()
{
    initscr();
    start_color();
    init_pair(1,COLOR_WHITE,COLOR_BLUE);
    init_pair(2,COLOR_BLUE,COLOR_WHITE);
    init_pair(3,COLOR_RED,COLOR_WHITE);
    curs_set(0);
    noecho();
    keypad(stdscr,TRUE);
}

下面定义的这个函数定义了一个显示在屏幕最顶部的菜单栏, 您可以看下面的main段程序,它看上去好象只是屏幕最顶部的一行,其实实际上是stdscr窗体的一个子窗体,该子窗体只有 一行.下面的程序将指向该子窗体的指针作为它的参数,首先改变它的背景色,接着定义菜单的 字,我们使用waddstr定义菜单 的 字.需要注意的是wattron调用了另外一个不同的颜色对(序号3)以取代缺省的颜色对(序号2).记住2号颜色对在最开始就 由wbkgd设置成缺省的颜色对了.wattroff函数可以让我们切换到缺省的颜色对状态.

void draw_menubar(WINDOW *menubar)
{
    wbkgd(menubar,COLOR_PAIR(2));
    waddstr(menubar,"Menu1");
    wattron(menubar,COLOR_PAIR(3));
    waddstr(menubar,"(F1)");
    wattroff(menubar,COLOR_PAIR(3));
    wmove(menubar,0,20);
    waddstr(menubar,"Menu2");
    wattron(menubar,COLOR_PAIR(3));
    waddstr(menubar,"(F2)");
    wattroff(menubar,COLOR_PAIR(3));
}

下一个函数显示了当按下F1或者F2键显示的菜单,定义了一个在蓝色背景上 菜单栏颜色一样的白色背景窗体,我们不希望这个新窗口会被显示在背景色上的字覆盖掉.它们应该停留在那里直到 关闭了菜单.这就是为什么菜单窗体不能定义为stdscr的子窗体,下面会提到,窗体items[0]是用newwin函数定义的, 其他8个窗体则都是定义成items[0]窗体的子窗体.这里的items[0]被用来绘制一个围绕在菜单旁边的边框,其他的 窗体则用来显示菜单中选中的单元.同样的,他们不会覆盖掉菜单上的边框.为了区别选中和没选中的状态,有必要让 选中的单元背景色和其他的不一样.这就是这个函数中倒数第三句的作用了,菜单中的第一个单元背景色和其他的不一样, 这是因为菜单弹出来后,第一个单元是选中状态.

WINDOW **draw_menu(int start_col)
{
    int i;
    WINDOW **items;
    items=(WINDOW **)malloc(9*sizeof(WINDOW *));

    items[0]=newwin(10,19,1,start_col);
    wbkgd(items[0],COLOR_PAIR(2));
    box(items[0],ACS_VLINE,ACS_HLINE);
    items[1]=subwin(items[0],1,17,2,start_col+1);
    items[2]=subwin(items[0],1,17,3,start_col+1);
    items[3]=subwin(items[0],1,17,4,start_col+1);
    items[4]=subwin(items[0],1,17,5,start_col+1);
    items[5]=subwin(items[0],1,17,6,start_col+1);
    items[6]=subwin(items[0],1,17,7,start_col+1);
    items[7]=subwin(items[0],1,17,8,start_col+1);
    items[8]=subwin(items[0],1,17,9,start_col+1);
    for (i=1;i&lt;9;i++)
        wprintw(items[i],"Item%d",i);
    wbkgd(items[1],COLOR_PAIR(1));
    wrefresh(items[0]);
    return items;
}

下面这个函数简单的删除了上面函数定义的菜单窗体.它首先用delwin函数删除窗体, 接着释放items指针的内存单元.

void delete_menu(WINDOW **items,int count)
{
    int i;
    for (i=0;i&lt;count;i++)
        delwin(items[i]);
    free(items);
}

scroll_menu函数允许我们在菜单选择项上上下移动,它通过getch读取键盘上的键值,如果按下了键盘上的上移或者下移方向键, 菜单选择项的上一个项或者下一个项被选中.回忆一下刚才所讲的,选中项的背景色将会和没选中的不一样.如果是向左或者向右 的方向键,当前菜单将会关闭,另一个菜单打开.如果按下了回车键,则返回选中的单元值.如果按下了ESC键,菜单将会被关闭,并且没有任何选择项 ,下面的函数忽略了其他的输入键.getch能从键盘上读取键值,这是因为我们在程序开始使用了keypad(stdscr,TRUE) 并且将返回值赋给一个int型变量而不是char型变量,这是因为int型变量能表示比char型更大的值.

int scroll_menu(WINDOW **items,int count,int menu_start_col)
{
    int key;
    int selected=0;
    while (1) {
        key=getch();
        if (key==KEY_DOWN || key==KEY_UP) {
            wbkgd(items[selected+1],COLOR_PAIR(2));
            wnoutrefresh(items[selected+1]);
            if (key==KEY_DOWN) {
                selected=(selected+1) % count;
            } else {
                selected=(selected+count-1) % count;
            }
            wbkgd(items[selected+1],COLOR_PAIR(1));
            wnoutrefresh(items[selected+1]);
            doupdate();
        } else if (key==KEY_LEFT || key==KEY_RIGHT) {
            delete_menu(items,count+1);
            touchwin(stdscr);
            refresh();
            items=draw_menu(20-menu_start_col);
            return scroll_menu(items,8,20-menu_start_col);
        } else if (key==ESCAPE) {
            return -1;
        } else if (key==ENTER) {
            return selected;
        }
    }
}

最后就是我们的main部分了.它使用了上面所有我们所讲述和编写的函数来使程序合适的工作. 它同样通过getch读取键值来判断F1或者F2是否按下了,并且用draw_menu来在相应的菜单窗体上绘制菜单. 接着调用scroll_menu函数让用户选择某一个菜单,当scroll_menu返回后,它删除菜单窗体并且显示所选择的单元内容 在信息栏里.

我必须提到的是函数touchwin.如果在菜单关闭后没有调用touchwin而立即刷新,那么最后打开的菜单将一直停留在 屏幕上.这是因为在调用refresh时,menu函数根本就没有完全改变stdscr的内容.它没有重新写入数据到stdscr上, 因为它以为窗体内容没有改变.touchwin函数设置了所有WINDOW结构中的标志位,它通知refresh刷新窗体中所有的行, 值都改变了,这样在下一次刷新整个窗体时,即使窗体内容没有改变也要重新写入一次.在菜单关闭后,选择的菜单信息会一直停留在 stdscr上面.菜单没有在stdscr上写数据,因为它是开了一个新的子窗口.

int main()
{
    int key;
    WINDOW *menubar,*messagebar;

    init_curses();

    bkgd(COLOR_PAIR(1));
    menubar=subwin(stdscr,1,80,0,0);
    messagebar=subwin(stdscr,1,79,23,1);
    draw_menubar(menubar);
    move(2,1);
    printw("Press F1 or F2 to open the menus. ");
    printw("ESC quits.");
    refresh();

    do {
        int selected_item;
        WINDOW **menu_items;
        key=getch();
        werase(messagebar);
        wrefresh(messagebar);
        if (key==KEY_F(1)) {
            menu_items=draw_menu(0);
            selected_item=scroll_menu(menu_items,8,0);
            delete_menu(menu_items,9);
            if (selected_item&lt;0)
                wprintw(messagebar,"You haven't selected any item.");
            else
                wprintw(messagebar,
                  "You have selected menu item %d.",selected_item+1);
            touchwin(stdscr);
            refresh();
        } else if (key==KEY_F(2)) {
            menu_items=draw_menu(20);
            selected_item=scroll_menu(menu_items,8,20);
            delete_menu(menu_items,9);
            if (selected_item&lt;0)
                wprintw(messagebar,"You haven't selected any item.");
            else
                wprintw(messagebar,
                  "You have selected menu item %d.",selected_item+1);
            touchwin(stdscr);
            refresh();
        }
    } while (key!=ESCAPE);

    delwin(menubar);
    delwin(messagebar);
    endwin();
    return 0;
}

如果您拷贝了代码到一个文件,假设名字是example.c,并且移走了我所有的注释,您可以用下面这个方法编译:

gcc -Wall example.c -o example -lcurses

为了测试程序,您可以在参考一章里下载该程序.

总结

我谈到了很多关于ncurses的基础知识,应该足够用来给您的程序创建一个很好看的界面.还有许多方便的功能 在这里都没有提及,您可以在我经常问到的几个函数的man pages里面找到很多有用的信息.读完了后,您将回明白 我这里提到的东西和内容仅仅是一个介绍而已.

SCSI硬盘详细介绍

很多找我买SCSI的朋友都反映是用坏了几个IDE硬盘才考虑买我的SCSI盘的,仔细一想,我们每个人不都是有过在不长的时间内用坏过至少1。2个IDE硬盘的经历吗?尤其是网吧服务器用的硬盘。想来买这几个IDE盘的钱也够买上1,2个SCSI盘了。所以SCSI硬盘的第一个好处就是比IDE寿命长几倍,


SCSI的意思是指小型计算机接口方式,普通计算机只要增加一个PCI的SCSI卡就可以随意接驳SCSI盘了,如果是要用SCSI盘做C盘启动,只要在主办的CMOS设置里把第一启动设置成SCSI就可以了。对主办和其他周边没什么特别要求。也不影响其他IDE设备的同时使用。一个双通的SCSI就可以接驳多达30个同速度的盘,一个单通160的SCSI卡可以接15个同速度的盘。而彼此不影响速度,且只占用一个IRQ。和普通IDE硬盘比较总结几点优势:


1.SCSI可支持多个设备,SCSI-2(Fast SCSI)最多可接7个SCSI设备,ULTRA2和ULTRA3的双通可接30个SCSI设备。单通到15个盘。也就是说,所有的设备只需占用一个IRQ,同时SCSI还支持相当广的设备,如CD-ROM、DVD、CDR、硬盘、磁带机、扫描仪等。


2.SCSI还允许在对一个设备传输数据的同时,另一个设备对其进行数据查找。这就可以在多任务操作系统如Linux、Windows NT中获得更高的性能。


3.SCSI占用CPU极低,确实在多任务系统中占有着明显的优势。由于SCSI卡本身带有CPU,可处理一切SCSI设备的事务,在工作时主机CPU只要向SCSI卡发出工作指令,SCSI卡就会自己进行工作,工作结束后返回工作结果给CPU, 在整个过程中,CPU均可以进行自身工作。


4.SCSI设备还具有智能化,SCSI卡自己可对CPU指令进行排队,这样就提高了工作效率。在多任务时硬盘会在当前磁头位置,将邻近的任务先完成,再逐一进行处理。


5.现在最快的SCSI总线有320MB/s的带宽,这要求使用一个64位的66MHz(166MHZ)的PCI插槽,但在普通PC机中也能达到的最大速度为133MB/s,


(以上1-5点是转贴的,对方错误的地方我做了些修改)


6。现在高端SCSI盘,10K/15K的单盘速度测试已经接近或超过2个7200转主流IDE硬盘RAIDO的方式了。但SCSI盘也分档次,一般型号越新越快,噪音和温度也控制越好。比如SEAGATE做的豹子系列以代数来分,有豹子1-7代,型号尾数代表代数,比如ST318705LC代表豹子5代18G,C代表80针服务器热拔查,如果是W结尾代表是68针。最新一万五千转的STX15 型号也是一样,比如ST336752LW 代表36G X15 2代。豹子以前的盘是酷鱼系列7200转的SCSI盘。FUJITSU做的SCSI硬盘以速度快而安静又低温而闻名。外表较SEAGATE温柔,有亲和力,符合她的特点。主流型号以英文字母最后一位的排序为档次区分,和SEAGATE略有不同。MAN318MC代表MAN18G 80针,如果是MAN318MP代表是18G 68针。先有MAH,再有MAJ,后有MAN,最后有MAM。MAN和MAM是最新高端盘,配备8MB缓冲,其中MAM是15K转的,和X15II相当。


7。较新的SCSI硬盘在接口方式上只有种——68针和80针(老式盘有50针的方式,这点早期玩SCSI的最清楚,有朋友还在用)。80针的设计原理是直接提供给服务器的热拔插模组的,支持热拔插。68针是面向普通PC用户的,不支持直接热拔插。80针的盘同样可以用到在普通台式机上(这也是我是使用和推广的方式),只需要增加一个80转68的LVD转接头就可以了,偶尔停电或不小心热拔插了也不用担心。转接头一定要质量好的,否则即使是U160的盘也变成40MB了,当然质量和价格是成正比的。普通头子也有,是SE方式的40MB,从外观上往往普通SE的转接头和LVD的转接头没太大区别,唯一可靠的办法是接上盘后使用,2个判断指标,一是看显示的速度,二是看测试的数据。有些人担心通过转接会影响速度,但实际测试结果是一样的。


8。SCSI和IDE比在设置上稍微复杂点,如果一个通道上接2个以上的盘就需要分配ID了,就象几兄弟要排队。不然就一个盘都识别不了。80针盘的跳线法是在转接头上进行的。68针的盘是在硬盘盘体上的跳线上进行的。如果只接一个硬盘。就不需要进行跳线的。IDO是默认是启动的盘,其他ID是非启动盘。SCSI通道要默认占用一个ID7。SCSI通道都会自动终结,不需要另外终结也可以教稳定使用。但在SCSI盘这头给于一个终结信号是稳妥可靠的,所以高档LVD线揽都有多头,在末端配备终结器,分固定终结器和活动终结器。


9。所谓LVD是指低电压传输方式,因为要实现80/160的理论速度,必须是LVD方式,英文这样写的:Low Voltage Differential ,LVD分80MB和160MB。LVD是以2条信号线为一组进行信号传输的,其中一条处于高电位,另一条处于低电位。想让接收信号的一方识别电位的改变,就必须对两条线分别施加高、低电压。这种方式在双方都受到干扰时,能够将干扰部分互相抵消而保持正常的数据传输,同时还扩大了信号的可识别临界范围。但LVD方式发挥作用的前提,是必须保证两条线与干扰源的距离相等,所以两条信号线必须捆绑在一起(就象局域网中使用的双绞线一样)。真正的LVD用扁平线应该是双绞型的,至于有些U160扁平线纯粹是出于节约成本的目的。使用它后,尽管也有可能正常工作,但将无法保障25m的可接线长度。


10。双U160LVD通道SCSI和单U160LVD通道SCSI的区别:双160是2个160并行通道,可每个通道接拨1-15个U160盘,而单U160卡是一个U160通道,另一个是40MB的通道。2着不是一个档次的,一般双通卡比单通卡要贵不少。


11。说了这样多,好像比较复杂,其实简单的说就是要用LVD的SCSI卡 + LVD的SCSI盘 + LVD的线 就可以组成你理想中的快速,稳定,耐用的SCSI系统,尤其做音频,视频和非编的人非得使用SCSI盘才能保证不掉桢。使用IDE来非编是件很痛苦甚至是无法完成的事。


12。那么普通玩家用SCSI的有哪些突出的可以感受到的好处呢?

A。在移动拷贝数据的时候,可以明显感觉到比IDE反应快。举例说明:拷贝一个460MB的文件到SCSI盘只花费约10多秒的时间。

B。我们都知道做系统用的C盘有着频繁的数据查找和读写,产生碎片和不连续空间,实践表明IDE盘在系统使用时间久了(比如一个月不整理碎片和空间)就会明显感觉系统响应时间变慢,而SCSI做系统盘之后这样的感觉几乎不再有啦!

C。可以同时做很多事情,有没有试过同时听MP3,玩Q3/CS,后台还在开2,3个拷贝窗口和数个IE窗口呢?比较明显的感觉是在分区间拷贝大文件时,SCSI的优势和速度就表现得淋漓尽侄了。

D。不过要说明的是,如果单纯从装系统所用的时间或启动系统所用的时间上来比较SCSI和IDE的优劣,那你得到的结果会让你失望,因为往往SCSI比7200转的IDE盘在这点上要慢一点,除非是最新的15K盘。那些仅仅注重这2点的人建议不要购买SCSI。我们的做法一般都是拿SCSI盘做系统,同时使用IDE做备份盘。当然你如果钱多抛弃IDE盘也没人指责你,SCSI虽然比IDE贵了不少,但物有所值,尤其适合重载工作的场合,比如网吧。何况现在全新的二手SCSI设备大约只有行货一半。呵呵。

13。购买SCSI盘要注意区分老型号的厚盘或老型号的薄盘,这类盘价格便宜往往让你心动。即使是薄盘也会让你失望。淘汰盘有部分是厚盘,相当于2个IDE堆砌起来还要高点,有的虽然也是U160接口,4MB缓存,但速度就别指望很快了。尤其是噪音,我见过IBM那种U160厚盘,在已经有思想准备的情况下还是给吓了一大跳,那个噪音估计比美国的长弓阿伯奇战斗直升机的声音有大没小,整个桌面都在震动,好在价格便宜,36G的他只卖360元。说到噪音,FUJITSU在这方面是老大!他的MAJ/MAN/MAM系列的工作噪音和安静的IDE硬盘一样,很多时候我分辨不出究竟是IDE的声音还是SCSI盘的声音。新型SCSI都和IDE硬盘一样大小厚薄的,不同的是SCSI的外观比较眩!电路板更是IDE没法比的,多了缓存芯片,CPU,大量的高档坦电等。其次是品牌的考虑,做SCSI的厂家并不多,时间久了,形成了对SCSI优劣的一个心里定势。希捷,富士通,昆腾,日立是主要的大制造商,惠普,康柏,戴尔等其他品牌的服务器SCSI盘多为他们的OEM盘。其中以希捷和富士通的最多,他们是老大。IBM自己有做SCSI盘,典型代表是ULTRA STAR DDYS系列,在性能和温度噪音控制上并不如SCSI的老大,坏盘率也比较高点。



以上总结纯属个人体会和经验。有不对之处请指教,相互探讨,我的目的就是要“把高高在上的SCSI普及到老百姓家~!人人都来感受SCSI的魅力” ^^



一片双通160卡+ 4个ESERVER豹子 18G的工作时的状态,里面同时有其他PCI设备包括声卡显卡等,还有个30G的IDE硬盘,所用的主办不过是片用SD内存的VIA KT133A的主办。但这样的SCSI系统比用P4 2。4GA + 两个7200转的IDE RAIDO方式还强劲!


安装SCSI的必备知识和驱动下载:[推荐]


开篇特别提示:普通用户严禁使用SCSI卡自身的BIOS里自带的FORMAT 功能,它是属于低级格式化,使用后99%以上的SCSI盘不可恢复!切忌!切记!


A. 第一次插上卡接好盘后,开机应该有SCSI卡的BIOS检测信息和提示BIOS设置快捷键,一般不需要进入BIOS设置,默认的就是最佳的. 除非你要用SCSI的VERIFY功能检测硬盘是否有物理坏道的时候.


B. 检测搜索15个ID后(如果是双通道卡就会先后搜索2个通道的15个ID),能正确识别出盘的型号,容量,同步速率,带宽模式.


C. 第一次上SCSI做系统前,我们的做法一般是先接好卡,盘,进入以前的老系统,推荐的系统是WINDOWS 2000/XP,在2K/XP里找到卡的驱动后,用磁盘管理器进行分区和快速格式化. (WIN2K有个强大的磁盘管理器,位置在:在”我的电脑”上点右键—–>管理—–>磁盘管理.


特别提示: 有不少朋友反映说在系统里找不到新装的SCSI盘,其实原因很简单,不是盘坏了,也不是系统不认盘,是因为系统要经过磁盘的签名和标记后才可以显示在资源管理器里.这些都是要在磁盘管理起里进行的.


D. 如果是要用SCSI做系统启动,主要在用2K/XP的磁盘管理器快速分区和格式化后记住要激活下要做启动盘的C区,这个原理和DOS下的FDISK一样,需要SET ACTIVE.


E. 除ADAPTEC的产品外,其他如LSI53C1010 INITIO或QLOGIC的 SCSI卡在装2K或XP时,第一次重起后,需要按F6 键,按提示放入事前作好的SCSI的驱动软盘.否则就会出现满屏的蓝屏提示:Inaccessible unknow harddrive……


F: 假如你买了一片不同型号的新卡想直接把SCSI系统盘接到新卡上,不能直接把老卡拔掉后接新卡.会出现蓝屏2K/XP系统会CRASH掉.除非是重装系统.最省事的办法是,老卡老系统不动,把新卡也插上进系统,等系统认出新SCSI卡的驱动后,再关机,把SCSI线接到新卡上,拔掉老SCSI卡,这样你就不需要重装系统而升级到新的SCSI卡了!


这里特别提供我所卖出去的几种主要的SCSI的驱动下载连接:


A.建邦TERAM DC390U3D LSI53C1010-33 双通道160卡/ LSI895U3/LSI53C1010-66:

http://support.megaraid.com/support/dlcenter1/w2k1010.zip (2K)
http://support.megaraid.com/support/dlcenter1/95981010.zip (98SE/ME)


B.LSI53C895 80M单通道80M卡:

http://support.megaraid.com/support/dlcenter1/w2k895a.zip (2K)
http://support.megaraid.com/support/dlcenter1/win896.zip (98SE/ME)
http://support.megaraid.com/support/dlcenter1/nt896.zip (NT4。0)


C。 RIOWORKVIER SCQ 双通160卡:

http://download.qlogic.com/drivers/3825/1216W2K72111.exe
http://download.qlogic.com/drivers/2194/qla12160_win9x_v1.06.zip
http://download.qlogic.com/drivers/2196/qla12160_me_v1.06.zip
http://download.qlogic.com/drivers/3865/1216W2K_XP72111.exe
http://download.qlogic.com/drivers/2663/qla12160_nt_720.zip


D. AHA卡一般自认,也可以自己下,所有的:
http://www.adaptec.com/worldwide … romPage=driverindex


E.AHA-3200S FOR 2K DRIVERS:

http://www.adaptec.com/worldwide … _win2k_drv_v303.exe


F。INITIO 80M LVD卡:
http://www.initio.com/drivers/a100win2k.zip(for 2K)

http://www.initio.com/drivers/a100dsk1.zip(for 95/98/nt)


备注:检测SCSI盘常用的有EZSCSI这个软件。不容易下到。买我硬盘的朋友可以免费提供。或到下面地址下载,但不知道能放多久:
http://www.cnhdd.com/down_view.asp?id=53

OllyDBG 入门系列(三)-函数参考

标 题: 【原创】OllyDBG 入门系列(三)-函数参考
作 者: CCDebuger
时 间: 2006-02-14,23:34
链 接: http://bbs.pediy.com/showthread.php?t=21330


OllyDBG 入门系列(三)-函数参考


作者:CCDebuger


现在进入第三篇,这一篇我们重点讲解怎样使用 OllyDBG 中的函数参考(即名称参考)功能。仍然选择 crackmes.cjb.net 镜像打包中的一个名称为 CrackHead 的crackme。老规矩,先运行一下这个程序看看:
http://bbs.pediy.com/upload/2006/4/image/2_crackme_gui.gif

呵,竟然没找到输入注册码的地方!别急,我们点一下程序上的那个菜单“Shit”(真是 Shit 啊,呵呵),在下拉菜单中选“Try It”,会来到如下界面:
http://bbs.pediy.com/upload/2006/4/image/2_crackme_check.gif

我们点一下那个“Check It”按钮试一下,哦,竟然没反应!我再输个“78787878”试试,还是没反应。再试试输入字母或其它字符,输不进去。由此判断注册码应该都是数字,只有输入正确的注册码才有动静。用 PEiD 检测一下,结果为 MASM32 / TASM32,怪不得程序比较小。信息收集的差不多了,现在关掉这个程序,我们用 OllyDBG 载入,按 F9 键直接让它运行起来,依次点击上面图中所说的菜单,使被调试程序显示如上面的第二个图。先不要点那个“Check It”按钮,保留上图的状态。现在我们没有什么字串好参考了,我们就在 API 函数上下断点,来让被调试程序中断在我们希望的地方。我们在 OllyDBG 的反汇编窗口中右击鼠标,在弹出菜单中选择 查找->当前模块中的名称 (标签),或者我们通过按 CTR+N 组合键也可以达到同样的效果(注意在进行此操作时要在 OllyDBG 中保证是在当前被调试程序的领空,我在第一篇中已经介绍了领空的概念,如我这里调试这个程序时 OllyDBG 的标题栏显示的就是“[CPU – 主线程, 模块 – CrackHea]”,这表明我们当前在被调试程序的领空)。通过上面的操作后会弹出一个对话框,如图:
http://bbs.pediy.com/upload/2006/4/image/2_name.gif

对于这样的编辑框中输注册码的程序我们要设断点首选的 API 函数就是 GetDlgItemText 及 GetWindowText。每个函数都有两个版本,一个是 ASCII 版,在函数后添加一个 A 表示,如 GetDlgItemTextA,另一个是 UNICODE 版,在函数后添加一个 W 表示。如 GetDlgItemTextW。对于编译为 UNCODE 版的程序可能在 Win98 下不能运行,因为 Win98 并非是完全支持 UNICODE 的系统。而 NT 系统则从底层支持 UNICODE,它可以在操作系统内对字串进行转换,同时支持 ASCII 和 UNICODE 版本函数的调用。一般我们打开的程序看到的调用都是 ASCII 类型的函数,以“A”结尾。又跑题了,呵呵。现在回到我们调试的程序上来,我们现在就是要找一下我们调试的程序有没有调用 GetDlgItemTextA 或 GetWindowTextA 函数。还好,找到一个 GetWindowTextA。在这个函数上右击,在弹出菜单上选择“在每个参考上设置断点”,我们会在 OllyDBG 窗口最下面的那个状态栏里看到“已设置 2 个断点”。另一种方法就是那个 GetWindowTextA 函数上右击,在弹出菜单上选择“查找输入函数参考”(或者按回车键),将会出现下面的对话框:
http://bbs.pediy.com/upload/2006/4/image/2_ref_getw.gif

看上图,我们可以把两条都设上断点。这个程序只需在第一条指令设断点就可以了。好,我们现在按前面提到的第一条方法,就是“在每个参考上设置断点”,这样上图中的两条指令都会设上断点。断点设好后我们转到我们调试的程序上来,现在我们在被我们调试的程序上点击那个“Check It”按钮,被 OllyDBG 断下:


00401323 |. E8 4C010000          CALL <JMP.&USER32.GetWindowTextA>            ; GetWindowTextA

00401328 |. E8 A5000000          CALL CrackHea.004013D2                       ; 关键,要按F7键跟进去

0040132D |. 3BC6                 CMP EAX,ESI                                  ; 比较

0040132F |. 75 42                JNZ SHORT CrackHea.00401373                  ; 不等则完蛋

00401331 |. EB 2C                JMP SHORT CrackHea.0040135F

00401333 |. 4E 6F 77 20 7>       ASCII “Now write a keyg”

00401343 |. 65 6E 20 61 6>       ASCII “en and tut and y”

00401353 |. 6F 75 27 72 6>       ASCII “ou’re done.”,0

0040135F |> 6A 00                PUSH 0                                       ; Style = MB_OK|MB_APPLMODAL

00401361 |. 68 0F304000          PUSH CrackHea.0040300F                       ; Title = “Crudd’s Crack Head”

00401366 |. 68 33134000          PUSH CrackHea.00401333                       ; Text = “Now write a keygen and tut and you’re done.”

0040136B |. FF75 08              PUSH DWORD PTR SS:[EBP+8]                    ; hOwner

0040136E |. E8 19010000          CALL <JMP.&USER32.MessageBoxA>               ; MessageBoxA


从上面的代码,我们很容易看出 00401328 地址处的 CALL CrackHea.004013D2 是关键,必须仔细跟踪。而注册成功则会显示一个对话框,标题是“Crudd’s Crack Head”,对话框显示的内容是“Now write a keygen and tut and you’re done.”现在我按一下 F8,准备步进到 00401328 地址处的那条 CALL CrackHea.004013D2 指令后再按 F7 键跟进去。等等,怎么回事?怎么按一下 F8 键跑到这来了:


00401474 $- FF25 2C204000       JMP DWORD PTR DS:[<&USER32.GetWindowText>     ; USER32.GetWindowTextA

0040147A $- FF25 30204000       JMP DWORD PTR DS:[<&USER32.LoadCursorA>]      ; USER32.LoadCursorA

00401480 $- FF25 1C204000       JMP DWORD PTR DS:[<&USER32.LoadIconA>]        ; USER32.LoadIconA

00401486 $- FF25 20204000       JMP DWORD PTR DS:[<&USER32.LoadMenuA>]        ; USER32.LoadMenuA

0040148C $- FF25 24204000       JMP DWORD PTR DS:[<&USER32.MessageBoxA>]      ; USER32.MessageBoxA


原来是跳到另一个断点了。这个断点我们不需要,按一下 F2 键删掉它吧。删掉 00401474 地址处的断点后,我再按 F8 键,呵,完了,跑到 User32.dll 的领空了。看一下 OllyDBG 的标题栏:“[CPU – 主线程, 模块 – USER32],跑到系统领空了,OllyDBG 反汇编窗口中显示代码是这样:


77D3213C 6A 0C                  PUSH 0C

77D3213E 68 A021D377            PUSH USER32.77D321A0

77D32143 E8 7864FEFF            CALL USER32.77D185C0


怎么办?别急,我们按一下 ALT+F9 组合键,呵,回来了:


00401328 |. E8 A5000000         CALL CrackHea.004013D2                      ; 关键,要按F7键跟进去

0040132D |. 3BC6                CMP EAX,ESI                                 ; 比较

0040132F |. 75 42               JNZ SHORT CrackHea.00401373                 ; 不等则完蛋


光标停在 00401328 地址处的那条指令上。现在我们按 F7 键跟进:


004013D2 /$ 56                 PUSH ESI                                     ; ESI入栈

004013D3 |. 33C0               XOR EAX,EAX                                  ; EAX清零

004013D5 |. 8D35 C4334000      LEA ESI,DWORD PTR DS:[4033C4]                ; 把注册码框中的数值送到ESI

004013DB |. 33C9               XOR ECX,ECX                                  ; ECX清零

004013DD |. 33D2               XOR EDX,EDX                                  ; EDX清零

004013DF |. 8A06               MOV AL,BYTE PTR DS:[ESI]                     ; 把注册码中的每个字符送到AL

004013E1 |. 46                 INC ESI                                      ; 指针加1,指向下一个字符

004013E2 |. 3C 2D              CMP AL,2D                                    ; 把取得的字符与16进制值为2D的字符(即“-”)比较,这里主要用于判断输入的是不是负数

004013E4 |. 75 08              JNZ SHORT CrackHea.004013EE                  ; 不等则跳

004013E6 |. BA FFFFFFFF        MOV EDX,-1                                   ; 如果输入的是负数,则把-1送到EDX,即16进制FFFFFFFF

004013EB |. 8A06               MOV AL,BYTE PTR DS:[ESI]                     ; 取“-”号后的第一个字符

004013ED |. 46                 INC ESI                                      ; 指针加1,指向再下一个字符

004013EE |> EB 0B              JMP SHORT CrackHea.004013FB

004013F0 |> 2C 30              SUB AL,30                                    ; 每位字符减16进制的30,因为这里都是数字,如1的ASCII码是“31H”,减30H后为1,即我们平时看到的数值

004013F2 |. 8D0C89             LEA ECX,DWORD PTR DS:[ECX+ECX*4]             ; 把前面运算后保存在ECX中的结果乘5再送到ECX

004013F5 |. 8D0C48             LEA ECX,DWORD PTR DS:[EAX+ECX*2]             ; 每位字符运算后的值与2倍上一位字符运算后值相加后送ECX

004013F8 |. 8A06               MOV AL,BYTE PTR DS:[ESI]                     ; 取下一个字符

004013FA |. 46                 INC ESI                                      ; 指针加1,指向再下一个字符

004013FB |> 0AC0               OR AL,AL

004013FD |.^ 75 F1             JNZ SHORT CrackHea.004013F0                  ; 上面一条和这一条指令主要是用来判断是否已把用户输入的注册码计算完

004013FF |. 8D040A             LEA EAX,DWORD PTR DS:[EDX+ECX]               ; 把EDX中的值与经过上面运算后的ECX中值相加送到EAX

00401402 |. 33C2               XOR EAX,EDX                                  ; 把EAX与EDX异或。如果我们输入的是负数,则此处功能就是把EAX中的值取反

00401404 |. 5E                 POP ESI                                      ; ESI出栈。看到这条和下一条指令,我们要考虑一下这个ESI的值是哪里运算得出的呢?

00401405 |. 81F6 53757A79      XOR ESI,797A7553                             ; 把ESI中的值与797A7553H异或

0040140B . C3                 RETN



这里留下了一个问题:那个 ESI 寄存器中的值是从哪运算出来的?先不管这里,我们接着按 F8 键往下走,来到 0040140B 地址处的那条 RETN 指令(这里可以通过在调试选项的“命令”标签中勾选“使用 RET 代替 RETN”来更改返回指令的显示方式),再按一下 F8,我们就走出 00401328 地址处的那个 CALL 了。现在我们回到了这里:


0040132D |. 3BC6              CMP EAX,ESI                                   ; 比较

0040132F |. 75 42             JNZ SHORT CrackHea.00401373                   ; 不等则完蛋


光标停在了 0040132D 地址处的那条指令上。根据前面的分析,我们知道 EAX 中存放的是我们输入的注册码经过计算后的值。我们来看一下信息窗口:


ESI=E6B5F2F9

EAX=FF439EBE


左键选择信息窗口中的 ESI=E6B5F2F9,再按右键,在弹出菜单上选“修改寄存器”,我们会看到这样一个窗口:
http://bbs.pediy.com/upload/2006/4/image/2_mod_esi.gif

可能你的显示跟我不一样,因为这个 crackme 中已经说了每个机器的序列号不一样。关掉上面的窗口,再对信息窗口中的 EAX=FF439EBE 做同样操作:
http://bbs.pediy.com/upload/2006/4/image/2_mod_eax.gif

由上图我们知道了原来前面分析的对我们输入的注册码进行处理后的结果就是把字符格式转为数字格式。我们原来输入的是字串“12345666”,现在转换为了数字 12345666。这下就很清楚了,随便在上面那个修改 ESI 图中显示的有符号或无符号编辑框中复制一个,粘贴到我们调试的程序中的编辑框中试一下:
http://bbs.pediy.com/upload/2006/4/image/2_success.gif

呵呵,成功了。且慢高兴,这个 crackme 是要求写出注册机的。我们先不要求写注册机,但注册的算法我们要搞清楚。还记得我在前面说到的那个 ESI 寄存器值的问题吗?现在看看我们上面的分析,其实对做注册机来说是没有多少帮助的。要搞清注册算法,必须知道上面那个 ESI 寄存器值是如何产生的,这弄清楚后才能真正清楚这个 crackme 算法。今天就先说到这里,关于如何追出 ESI 寄存器的值我就留到下一篇 OllyDBG 入门系列(四)-内存断点 中再讲吧。