‘壹’ 写一个学生信息的管理系统,用C语言和数据结构,我想请问一下基本的框架怎么写请高手指点!!!
不知道你要的信息管理功能都要求实现什么功能,只是我前两天做的,可以给你参考一下,用结构做的,其中统计功能是我做的,其他部分是老师编好的
/*预编译命令*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
/*自定义数据类型*/
typedef struct student{
char xh[10];//学号
char xm[15];//姓名
int cj[3];//3门课程的成绩
}STU,*STUP;
typedef struct stulist{
STUP elem;//结构体指针变量
int listsize;//容量
int length;//长度
}SqList;
/*自定义函数说明*/
int initSqlist(SqList &L,int n);
int insertSqlist(SqList &L,STU x,int i);
void creatSqlist(SqList &L,int m);
int deleteSqlist(SqList &L,int i);
int locateSqlist(SqList L,char *xhp);
void disp(SqList L);
int menu();
void tongji(int bh,SqList &m);
/*函数定义*/
/*菜单函数*/
int menu(){
int num;
while(1){
system("cls");//清屏
printf(" ------------学生成绩管理系统-------------\n\n");
printf(" 1--创建学生成绩表 2--插入学生成绩\n\n");
printf(" 3--删除学生成绩 4--按学号查找\n\n");
printf(" 5--显示学生成绩表 6--成绩统计\n\n");
printf(" 0--退出\n\n");
printf(" -----------------------------------------\n");
printf("请输入0-6:");
scanf("%d",&num);
fflush(stdin);
if(num<0||num>6){
printf("重新选择!按任意键继续!");
getch();}
else
break;
}
return num;
}
/*初始化函数*/
int initSqlist(SqList &L,int n){
L.elem=new STU[n];
if(L.elem==NULL){
printf("申请空间失败!\n");
exit(0);
}
L.listsize=n;
L.length=0;
return 1;
}
/*插入函数*/
int insertSqlist(SqList &L,STU x,int i){
int j;
if(i>L.listsize){
printf("溢出!\n");
printf("重新选择!按任意键继续!");
getch();
return 0;
}
if(i<=0||i>L.length+1){
printf("位置不合理!\n");
printf("重新选择!按任意键继续!");
getch();
return 0;
}
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j];
L.elem[i-1]=x;
L.length++;
return 1;
}
/*创建函数*/
void creatSqlist(SqList &L,int m){
int i,j; STU x;
for(i=1;i<=m;i++){
printf("请输入第%d个学生的数据:\n",i);
printf("学号:");
scanf("%s",x.xh);
for(j=0;j<L.length;j++){
if(strcmp(x.xh,L.elem[j].xh)==0){
printf("该学号已存在,请重新输入:");
scanf("%s",x.xh);
}
}
printf("姓名:");
scanf("%s",x.xm);
printf("高数 C语言 英语(以空格隔开):");
scanf("%d%d%d",&x.cj[0],&x.cj[1],&x.cj[2]);
insertSqlist(L,x,i);
}
fflush(stdin);
}
/*显示函数*/
void disp(SqList L){
int i;
printf("%10s%15s%7s%7s%7s\n","学号","姓名","高数","C语言","英语");
for(i=0;i<L.length;i++)
printf("%10s%15s%7d%7d%7d\n",L.elem[i].xh,L.elem[i].xm,
L.elem[i].cj[0],L.elem[i].cj[1],L.elem[i].cj[2]);
printf("按任意键继续!");
getch();
}
/*删除函数*/
int deleteSqlist(SqList &L,int i){
int j;
if(i<=0||i>L.length){
printf("位置不合理,请重新输入!");
getch();
return 0;
}
for(j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j];
L.length--;
return 1;
}
/*查找函数*/
int locateSqlist(SqList L,char *xhp){
int i;
for(i=0;i<L.length;i++)
if(strcmp(xhp,L.elem[i].xh)==0)break;
if(i<L.length)return i+1;
else return 0;
}
/*统计函数*/
void tongji(int bh,SqList &m){
int i,max,min;
double sum;
switch(bh){
case 1: if(m.length<2){
printf("当前人数少于2个,无法完成统计,按任意键继续:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[0];
}
max=m.elem[0].cj[0];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[0])
max=m.elem[i].cj[0];
}
min=m.elem[0].cj[0];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[0])
min=m.elem[i].cj[0];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意键继续:");
getch();
break;
case 2: if(m.length<2){
printf("当前人数少于2个,无法完成统计,按任意键继续:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[1];
}
max=m.elem[0].cj[1];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[1])
max=m.elem[i].cj[1];
}
min=m.elem[0].cj[1];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[1])
min=m.elem[i].cj[1];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意键继续:");
getch();
break;
case 3: if(m.length<2){
printf("当前人数少于2个,无法完成统计,按任意键继续:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[2];
}
max=m.elem[0].cj[2];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[2])
max=m.elem[i].cj[2];
}
min=m.elem[0].cj[2];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[2])
min=m.elem[i].cj[2];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意键继续:");
getch();
break;
}
}
/*主函数*/
void main(){
int menunum,n,m,pos,i; SqList L; STU x; char xh[10];
while(1){
menunum=menu();
switch(menunum){
case 0: exit(0);
case 1: printf("请输入学生总人数:");
scanf("%d",&n);
if(initSqlist(L,n)){
printf("请输入现在需要创建的学生人数:");
scanf("%d",&m);
creatSqlist(L,m);
}
break;
case 2: printf("\n请输入待插入学生的数据:\n");
printf("学号:");
scanf("%s",x.xh);
for(i=0;i<L.length;i++){
if(strcmp(x.xh,L.elem[i].xh)==0){
printf("该学号已存在,请重新输入:");
scanf("%s",x.xh);
}
}
printf("姓名:");
scanf("%s",x.xm);
printf("高数 C语言 英语:");
scanf("%d%d%d",&x.cj[0],&x.cj[1],&x.cj[2]);
while(1){
printf("请输入待插入学生的位置(1-%d):",L.length+1);
scanf("%d",&pos);
if(insertSqlist(L,x,pos))break;
}
break;
case 3: while(1){
printf("请输入待删除学生的位置(1-%d):",L.length);
scanf("%d",&pos);
if(deleteSqlist(L,pos))break;
}
break;
case 4: printf("请输入要查找的学号:");
scanf("%s",xh);
pos=locateSqlist(L,xh);
if(pos!=0){
printf("该学生的成绩如下:\n");
printf("%10s%15s%7s%7s%7s\n","学号","姓名","高数","C语言","英语");
printf("%10s%15s%7d%7d%7d\n",L.elem[pos-1].xh,L.elem[pos-1].xm,
L.elem[pos-1].cj[0],L.elem[pos-1].cj[1],L.elem[pos-1].cj[2]);
}
else
printf("该学生的成绩不存在\n");
printf("按任意键继续!");
getch();
break;
case 5: disp(L);
break;
case 6: int bianhao1;
printf("请输入需要统计的课程编号1-3:\n\n");
printf("1--高数 2--C语言 3--英语 \n\n");
scanf("%d",&bianhao1);
fflush(stdin);
while(bianhao1<1||bianhao1>3){
printf("编号错误,请重新输入:\n");
scanf("%d",&bianhao1);
}
tongji(bianhao1,L);
}//switch
}//while
}
‘贰’ 论文框架怎么写
1、摘要 写论文大纲首先要明确论文的格式,最重要的是先要把摘要弄好,论文摘要就是要明确写出论文的主旨是什么,用三百至五百个字描述清楚论文的大概内容,再用几个词总结出来。
2、目录 论文大纲还需要一个很重要的内容就是目录,目录一定要清晰。
3、前言 论文大纲还要有的内容就是前言,交代写论文的目的以及选题背景。
4、选题背景及方法 详细具体的写出论文的背景和研究方法。
5、归纳出现的问题 把在做论文时发现的问题写出来,再写出解决问题的方法,做到发现问题就要解决问题。
6、提出的建议 论文大纲承接上部分所出现的问题就是提出的建议,因为能够发现问题说明有一定的判断能力,但是能够实际的提出建议,才能看出具体解决问题的能力。基本结构的写作更多是对文章的规划,可以避免很多写作中的常见问题,如字数超过规定字数,或者写不够规定字数,对写作是很有益处的
‘叁’ 如何编译CNTK框架
如何编译CNTK框架
现阶段不可能。
.NET 下你用的所有 CLR 方法也好类也好都是封装在一个个 .NET Framework 的 DLL 里的,程序运行时必须这些文件,除非你一个 CLR 类和方法都没用过(那不就是纯 C 语言而且是只有标准库的 C 了么)。
有些类似于 Remote Soft's Salamander .NET Native Compiler、Infralution Globalizer 这些工具,也不是把代码编译成机器语言了,而是把需要的类库等直接写入了 PE,会导致程序体积大大增加,而且只支持 .NET 2.0,最重要的是还是收费软件。
微软自己倒是新推出了个 .NET Native,不过可惜的是目前只支持 .NET 4.5 下的 Windows 8 App 程序。
‘肆’ 如何写一个编译时注解框架
1、注解
我们日常使用的很多开源库都有注解的实现,主要有运行时注解和编译时注解两种。
运行时注解:主要作用就是得到注解的信息
Retrofit:
调用
@GET("/users/{username}")
User getUser(@Path("username") String username);
定义
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
String value();
}
编译时注解:主要作用动态生成代码
Butter Knife
调用
@InjectView(R.id.user)
EditText username;
定义
@Retention(CLASS)
@Target(FIELD)
public @interface InjectView {
int value();
}
2、编译时注解
要实现编译时注解需要3步:
1、定义注解(关于注解的定义可以参考下面引用的博客)
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Seriable
{
}
2、编写注解解析器
@SupportedAnnotationTypes("annotation.Seriable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectProcessor extends AbstractProcessor {
@Override
public boolean process(Set annotations,
RoundEnvironment roundEnv) {
for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
if(ele.getKind() == ElementKind.FIELD){
//todo
}
return true;
}
@SupportedAnnotationTypes,定义要支持注解的完整路径,也可以通过getSupportedAnnotationTypes方法来定义
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(InjectView.class.getCanonicalName());
return types;
}
@SupportedSourceVersion(SourceVersion.RELEASE_6)表示支持的jdk的版本
3、创建制定文件resources/META-INF/services/javax.annotation.processing.Processor,并填写注解解析器的类路径,这样在编译的时候就能自动找到解析器
看上去实现编译时注解还是很容易的,但是真要完整的实现一个类似Butter Knife的框架,这还只是开始。
Butter Knife是专注View的注入,在使用注解的类编译后,查看编译后的class文件,会发现多出了文件,如:
SimpleActivity$$ViewInjector.java
SimpleActivity$$ViewInjector.java,就是通过编译时注解动态创建出来的,查看SimpleActivity$$ViewInjector.java的内容
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife;
import android.view.View;
import butterknife.ButterKnife.Finder;
public class SimpleActivity$$ViewInjector {
public static void inject(Finder finder, final com.example.butterknife.SimpleActivity target, Object source) {
View view;
view = finder.findRequiredView(source, 2131230759, "field 'title'");
target.title = (android.widget.TextView) view;
view = finder.findRequiredView(source, 2131230783, "field 'subtitle'");
target.subtitle = (android.widget.TextView) view;
view = finder.findRequiredView(source, 2131230784, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = (android.widget.Button) view;
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override
public void doClick(
android.view.View p0
) {
target.sayHello();
}
});
view.setOnLongClickListener(
new android.view.View.OnLongClickListener() {
@Override
public boolean onLongClick(
android.view.View p0
) {
return target.sayGetOffMe();
}
});
view = finder.findRequiredView(source, 2131230785, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = (android.widget.ListView) view;
((android.widget.AdapterView) view).setOnItemClickListener(
new android.widget.AdapterView.OnItemClickListener() {
@Override
public void onItemClick(
android.widget.AdapterView p0,
android.view.View p1,
int p2,
long p3
) {
target.onItemClick(p2);
}
});
view = finder.findRequiredView(source, 2131230786, "field 'footer'");
target.footer = (android.widget.TextView) view;
}
public static void reset(com.example.butterknife.SimpleActivity target) {
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
}
}
inject方法进行初始化,reset进行释放。inject都是调用Finder的方法与android系统的findViewById等方法很像,再来看Finder类,只截取部分。
public enum Finder {
public T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who);
if (view == null) {
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
return view;
}
public T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}
@Override protected View findView(Object source, int id) {
return ((View) source).findViewById(id);
}
@SuppressWarnings("unchecked") // That's the point.
public T castView(View view, int id, String who) {
try {
return (T) view;
} catch (ClassCastException e) {
if (who == null) {
throw new AssertionError();
}
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
findRequiredView方法实际上就是我们常用findViewById的实现,其动态帮我们添加了这些实现。view注入的原理我们就清楚了。
虽然编译时创建了这个类,运行的时候如何使用这个类呢,这里我用自己实现的一个类来描述
public class XlViewInjector {
static final Map, AbstractInjector> INJECTORS = new LinkedHashMap, AbstractInjector>();
public static void inject(Activity activity){
AbstractInjector injector = findInjector(activity);
injector.inject(Finder.ACTIVITY, activity, activity);
}
public static void inject(Object target, View view){
AbstractInjector injector = findInjector(target);
injector.inject(Finder.VIEW, target, view);
}
private static AbstractInjector findInjector(Object target){
Class clazz = target.getClass();
AbstractInjector injector = INJECTORS.get(clazz);
if(injector == null){
try{
Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY);
injector = (AbstractInjector) injectorClazz.newInstance();
INJECTORS.put(clazz, injector);
}catch(Exception e){
e.printStackTrace();
}
}
return injector;
}
}
XlViewInjector与ButterKnife,比如调用时我们都会执行XlViewInjector.inject方法,通过传入目标类的名称获得封装后的类实例就是SimpleActivity$$ViewInjector.java,再调用它的inject,来初始化各个view。
总结一下整个实现的流程:
1、通过编译时注解动态创建了一个包装类,在这个类中已解析了注解,实现了获取view、设置监听等代码。
2、执行时调用XlViewInjector.inject(object)方法,实例化object类对应的包装类,并执行他的初始化方法inject;
因此我们也能明白为什么XlViewInjector.inject(object)方法一定要在setContentView之后执行。
3、实现注解框架时的坑
解析有用到android api,因此需要创建Android工程,但是android library并没有javax的一些功能,
在eclipse环境下,右键build-path add library把jdk加进来
在android studio下,需要先创建java Library功能,实现与view无关的解析,再创建一个android library功能引用这个工程并实现余下的解析。
‘伍’ 如何编译生成xposedbridge
installer3.0版本之后,传统的xposed框架的使用方法是从官网上下载xposed installer.apk以及xposed-arm-sdk22.zip包。然后具体的使用方法是将xposed installer.apk安装到手机,然后在手机上进入recovery模式,将下载的xposed-arm-sdk22.zip刷入手机。完成之后xposed installer才可以正常使用。本文写了另一种xposed框架的使用办法:编译xposed源码,生成xposed相关的应用程序和so库等文件,然后将这些应用程序和so库文件集成到android系统中,重新打包生成镜像文件,烧入手机。
文档主要用于xposed源码的编译以及xposed的集成工作。在编译过程中,主要是通过Xposed Tools进行编译;在集成过程中,将xposed的编译之后生成的文件替换到android系统中,打包之后重新生成镜像文件,将镜像文件刷入到手机查看日志信息来判断是否成功。
由于在集成过程中是将xposed的编译生成文件打包到android5.1系统中重新生成镜像文件,然后将镜像文件烧入到nexus5手机,所以需要对android源码进行编译,并在执行lunch选择对应源码代号时选择nexus5对应的android源码代号。
环境配置
‘陆’ 写出一个汇编语言的框架程序,内容2个段,数据段和代码段
呵呵,建议你下了解一下杀毒软件的工作机制。
金山,360,瑞星等杀毒软件,都是驱动保护的,bat因为比它高层,所以做不到关闭杀毒软件。否则连批处理都可轻易攻破,那早就有无数病毒泛滥了。只有汇编语言这样的底层语言才可以,(其实C语言也能做到)。当然,bat也不是不行,可以用bat汇编,叫做ASCIICoding技术,挺难的。2007年以前国内还没有一个会的(除非会但不说,那我当然就不知道了)。也建议你学习Rootkit Hook技术,可以读一读这篇文章。
瘟神的尾行--Rootkit技术发展史
作者:小金
一. 无法驱逐的“助手”
网管小张正在手忙脚乱的寻找他的手工杀毒工具包,因为他在安装一个网管工具的时候无意中走了神,点击了“下一步”按钮后才惊觉安装程序的界面里一个不引人注目的角落里写着“安装CNNIC网络实名”这一行小字,而且最开头部分有一个小小的勾。于是着名的“中国网民的得力助手”便理所当然的在他的机器里安了家。
心里把厂商骂了十八遍的小张终于翻出了他外出修机时最得意的工具IceSword和超级巡警,果然在进程列表和SSDT列表里发现了红色警报,小张笑了笑,对付这些一般用户无法卸载的恶意流氓,自己可谓经验丰富了,当下便三下五除二的把CNNIC的进程给终结了,SSDT也给恢复了初始状态,然后小张去删除注册表启动项——突然发出的一个错误提示声音把小张吓了一跳,再定睛一看,他的笑容凝固了:“删除项时出错”。不会吧?小张急忙去删除CNNIC目录,结果彻底愣在了那里,系统弹出的错误提示很明确的告诉他,“无法删除文件,文件可能正在被使用”。怎么回事?小张一下子没了头绪……
达尔文的进化论告诉我们,“物竞天择,适者生存”,同样,在这安全与入侵的网络世界里,也在进行着这样一场选择的过程……
二. 被AIDS纠缠的互联网
。。。。。。。。。
。。。。。。。。。。。。。
。。。。。。。。。。。。。。(网络长度限制。。。想看全篇自己搜吧)
三. 结语
虽然到处都在提倡和谐网络的普及,但是,“健康上网”仅仅是指代那些黄赌毒而已吗?在利益面前,开发者的正义感越发渺小起来,我们的网络世界,是被瘟神紧紧跟随着的。技术的斗争越发激烈,但是用户的电脑知识是不会跟着时代发展而自动填充的,最终,大众上网的人民成了这一切技术较量的受害者。
这个荒谬的发展方向,何时才能休止呢?
还有这篇:
对大多数的Windows开发者来说,如何在Win32系统中对API函数的调用进行拦截一直是项极富挑战性的课题,因为这将是对你所掌握的计算机知识较为全面的考验,尤其是一些在如今使用RAD进行软件开发时并不常用的知识,这包括了操作系统原理、汇编语言甚至是机器指令(听上去真是有点恐怖,不过这是事实)。
当前广泛使用的Windows操作系统中,像Win 9x和Win NT/2K,都提供了一种比较稳健的机制来使得各个进程的内存地址空间之间是相互独立,也就是说一个进程中的某个有效的内存地址对另一个进程来说是无意义的,这种内存保护措施大大增加了系统的稳定性。不过,这也使得进行系统级的API拦截的工作的难度也大大加大了。
当然,我这里所指的是比较文雅的拦截方式,通过修改可执行文件在内存中的映像中有关代码,实现对API调用的动态拦截;而不是采用比较暴力的方式,直接对可执行文件的磁盘存储中机器代码进行改写。
二、API钩子系统一般框架
通常,我们把拦截API的调用的这个过程称为是安装一个API钩子(API Hook)。一个API钩子基本是由两个模块组成:一个是钩子服务器(Hook Server)模块,一般为EXE的形式;一个是钩子驱动器(Hook Driver)模块,一般为DLL的形式。
钩子服务器主要负责向目标进程注入钩子驱动器,使得钩子驱动器运行在目标进程的地址空间中,这是关键的第一步,而钩子驱动器则负责实际的API拦截处理工作,以便在我们所关心的API函数调用的之前或之后能做一些我们所希望的工作。一个比较常见的API钩子的例子就是一些实时翻译软件(像金山词霸)中必备的的功能:屏幕抓词。它主要是对一些Win32 API中的GDI函数进行了拦截,获取它们的输入参数中的字符串,然后在自己的窗口中显示出来。
针对上述关于API钩子的两个部分,有以下两点需要我们重点考虑的: 选用何种DLL注入技术,以及采用何种API拦截机制。
三、注入技术的选用
由于在Win32系统中各个进程的地址是互相独立的,因此我们无法在一个进程中对另一个进程的代码进行有效的修改,但如果你要完成API钩子的工作又必须如此。因此,我们必须采取某种独特的手段,使得API钩子(准确的说是钩子驱动器)能够成为目标进程中的一部分,才有较大的可能来对目标进程数据和代码进行有控制的修改。
通常可采用的几种注入方式:
1.利用注册表
如果我们准备拦截的进程连接了User32.dll,也就是使用了 User32.dll中的API(一般图形界面的应用程序都是符合这个条件),那么就可以简单把你的钩子驱动器DLL的名字作为值添加在下面注册表的键下: HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs 值的形式可以为单个DLL的文件名,或者是一组DLL的文件名,相邻的名称之间用逗号或空格间隔。所有由该值标识的DLL将在符合条件的应用程序启动的时候装载。这是一个操作系统内建的机制,相对其他方式来说危险性较小,但它也有一些比较明显的缺点:该方法仅适用于NT/2K操作系统,显然看看键的名称就可以明白;如果需要激活或停止钩子的注入,只有重新启动Windows,这个就似乎太不方便了;最后一点也很显然,不能用此方法向没有使用User32.dll的应用程序注入DLL,例如控制台应用程序等。另外,不管是否为你所希望,钩子DLL将注入每一个GUI应用程序,这将导致整个系统性能的下降!
2.建立系统范围的Windows钩子
要向某个进程注入DLL,一个十分普遍也是比较简单的方法就是建立在标准的Windows钩子的基础上。Windows钩子一般是在DLL中实现的,这是一个全局性的Windows钩子的基本要求,这也很符合我们的需要。当我们成功地调用SetWindowsHookEx函数之后,便在系统中安装了某种类型的消息钩子,这个钩子可以是针对某个进程,也可以是针对系统中的所有进程。一旦某个进程中产生了该类型的消息,操作系统会自动把该钩子所在的DLL映像到该进程的地址空间中,从而使得消息回调函数(在 SetWindowsHookEx的参数中指定)能够对此消息进行适当的处理,在这里,我们所感兴趣的当然不是对消息进行什么处理,因此在消息回调函数中只需把消息钩子向后传递就可以了,但是我们所需的DLL已经成功地注入了目标进程的地址空间,从而可以完成后续工作。
我们知道,不同的进程之间是不能直接共享数据的,因为它们活动在不同的地址空间中。但在Windows钩子 DLL中,有一些数据,例如Windows钩子句柄HHook,这是由SetWindowsHookEx函数返回值得到的,并且作为参数将在 CallNextHookEx函数和UnhookWindoesHookEx函数中使用,显然使用SetWindowsHookEx函数的进程和使用 CallNextHookEx函数的进程一般不会是同一个进程,因此我们必须能够使句柄在所有的地址空间中都是有效的有意义的,也就是说,它的值必须必须在这些钩子DLL所挂钩的进程之间是共享的。为了达到这个目的,我们就应该把它存储在一个共享的数据区域中。
在VC++中我们可以采用预编译指令#pragma data_seg在DLL文件中创建一个新的段,并且在DEF文件中把该段的属性设置为"shared",这样就建立了一个共享数据段。对于使用 Delphi的人来说就没有这么幸运了:没有类似的比较简单的方法(或许是有的,但我没有找到)。不过我们还是可以利用内存映像技术来申请使用一块各进程可以共享的内存区域,主要是利用了CreateFileMapping和MapViewOfFile这两个函数,这倒是一个通用的方法,适合所有的开发语言,只要它能直接或间接的使用Windows的API。
在Borland的BCB中有一个指令#pragma codeseg与VC++中的#pragma data_seg指令有点类似,应该也能起到一样的作用,但我试了一下,没有没有效果,而且BCB的联机帮助中对此也提到的不多,不知怎样才能正确的使用(或许是另外一个指令,呵呵)。
一旦钩子DLL加载进入目标进程的地址空间后,在我们调用UnHookWindowsHookEx函数之前是无法使它停止工作的,除非目标进程关闭。
这种DLL注入方式有两个优点: 这种机制在Win 9x/Me和Win NT/2K中都是得到支持的,预计在以后的版本中也将得到支持;钩子DLL可以在不需要的时候,可由我们主动的调用 UnHookWindowsHookEx来卸载,比起使用注册表的机制来说方便了许多。尽管这是一种相当简洁明了的方法,但它也有一些显而易见的缺点:首先值得我们注意的是,Windows钩子将会降低整个系统的性能,因为它额外增加了系统在消息处理方面的时间;其次,只有当目标进程准备接受某种消息时,钩子所在的DLL才会被系统映射到该进程的地址空间中,钩子才能真正开始发挥作用,因此如果我们要对某些进程的整个生命周期内的API调用情况进行监控,用这种方法显然会遗漏某些API的调用 。
3.使用 CreateRemoteThread函数
在我看来这是一个相当棒的方法,然而不幸的是,CreateRemoteThread这个函数只能在Win NT/2K系统中才得到支持,虽然在Win 9x中这个API也能被安全的调用而不出错,但它除了返回一个空值之外什么也不做。该注入过程也十分简单:我们知道,任何一个进程都可以使用 LoadLibrary来动态地加载一个DLL。但问题是,我们如何让目标进程(可能正在运行中)在我们的控制下来加载我们的钩子DLL(也就是钩子驱动器)呢?有一个API函数CreateRemoteThread,通过它可在一个进程中可建立并运行一个远程的线程--这个好像和注入没什么关系嘛?往下看!
调用该API需要指定一个线程函数指针作为参数,该线程函数的原型如下: Function ThreadProc(lpParam: Pointer): DWORD,我们再来看一下LoadLibrary的函数原型: Function LoadLibrary(lpFileName: PChar): HMole。发现了吧!这两个函数原型几乎是一样的(其实返回值是否相同关系不大,因为我们是无法得到远程线程函数的返回值的),这种类似使得我们可以把直接把LoadLibrary当做线程函数来使用,从而在目标进程中加载钩子DLL。
与此类似,当我们需要卸载钩子DLL时,也可以FreeLibrary作为线程函数来使用,在目标进程中卸载钩子DLL,一切看来是十分的简洁方便。通过调用GetProcAddress函数,我们可以得到LoadLibrary函数的地址。由于 LoadLibrary是Kernel32中的函数,而这个系统DLL的映射地址对每一个进程来说都是相同的,因此LoadLibrary函数的地址也是如此。这点将确保我们能把该函数的地址作为一个有效的参数传递给CreateRemoteThread使用。 FreeLibrary也是一样的。
AddrOfLoadLibrary := GetProcAddress(GetMoleHandle(‘Kernel32.dll'), ‘LoadLibrary');
HRemoteThread := CreateRemoteThread(HTargetProcess, nil, 0, AddrOfLoadLibrary, HookDllName, 0, nil);
要使用CreateRemoteThread,我们需要目标进程的句柄作为参数。当我们用 OpenProcess函数来得到进程的句柄时,通常是希望对此进程有全权的存取操作,也就是以PROCESS_ALL_ACCESS为标志打开进程。但对于一些系统级的进程,直接这样显然是不行的,只能返回一个的空句柄(值为零)。为此,我们必须把自己设置为拥有调试级的特权,这样将具有最大的存取权限,从而使得我们能对这些系统级的进程也可以进行一些必要的操作。
4.通过BHO来注入DLL
有时,我们想要注入DLL的对象仅仅是Internet Explorer,很幸运,Windows操作系统为我们提供了一个简单的归档方法(这保证了它的可靠性!)―― 利用Browser Helper Objects(BHO)。一个BHO是一个在 DLL中实现的COM对象,它主要实现了一个IObjectWithSite接口,而每当IE运行时,它会自动加载所有实现了该接口的COM对象。
四、拦截机制
在钩子应用的系统级别方面,有两类API拦截的机制――内核级的拦截和用户级的拦截。内核级的钩子主要是通过一个内核模式的驱动程序来实现,显然它的功能应该最为强大,能捕捉到系统活动的任何细节,但难度也较大,不在本文的探讨范围之内(尤其对我这个使用Delphi的人来说,还没涉足这块领域,因此也无法探讨,呵呵)。
而用户级的钩子则通常是在普通的DLL中实现整个API的拦截工作,这才是此次重点关注的。拦截API函数的调用,一般可有以下几种方法:
1. 代理DLL(特洛伊木马
一个容易想到的可行的方法是用一个同名的DLL去替换原先那个输出我们准备拦截的API所在的DLL。当然代理DLL也要和原来的一样,输出所有函数。但如果想到DLL中可能输出了上百个函数,我们就应该明白这种方法的效率是不高的,估计是要累死人的。另外,我们还不得不考虑DLL的版本问题,很是麻烦。
2.改写执行代码
有许多拦截的方法是基于可执行代码的改写,其中一个就是改变在CALL指令中使用的函数地址,这种方法有些难度,也比较容易出错。它的基本思路是检索出在内存中所有你所要拦截的API的CALL指令,然后把原先的地址改成为你自己提供的函数的地址。
另外一种代码改写的方法的实现方法更为复杂,它的主要的实现步骤是先找到原先的API函数的地址,然后把该函数开始的几个字节用一个JMP指令代替(有时还不得不改用一个INT指令),从而使得对该API函数的调用能够转向我们自己的函数调用。实现这种方法要牵涉到一系列压栈和出栈这样的较底层的操作,显然对我们的汇编语言和操作系统底层方面的知识是一种考验。这个方法倒和很多文件型病毒的感染机制相类似。
3.以调试器的身份进行拦截
另一个可选的方法是在目标函数中安置一个调试断点,使得进程运行到此处就进入调试状态。然而这样一些问题也随之而来,其中较主要的是调试异常的产生将把进程中所有的线程都挂起。它也需要一个额外的调试模块来处理所有的异常,整个进程将一直在调试状态下运行,直至它运行结束。
4.改写PE文件的输入地址表
这种方法主要得益于现如今Windows系统中所使用的可执行文件(包括EXE文件和DLL文件)的良好结构――PE文件格式(Portable Executable File Format),因此它相当稳健,又简单易行。要理解这种方法是如何运作的,首先你得对PE文件格式有所理解。
一个PE文件的结构大致如下所示:一般PE文件一开始是一段DOS程序,当你的程序在不支持Windows的环境中运行时,它就会显示"This Program cannot be run in DOS mode"这样的警告语句;接着这个DOS文件头,就开始真正的PE文件内容了,首先是一段称为"IMAGE_NT_HEADER"的数据,其中是许多关于整个PE文件的消息,在这段数据的尾端是一个称为Data Directory的数据表,通过它能快速定位一些PE文件中段(section)的地址;在这段数据之后,则是一个"IMAGE_SECTION_HEADER"的列表,其中的每一项都详细描述了后面一个段的相关信息;接着它就是PE文件中最主要的段数据了,执行代码、数据和资源等等信息就分别存放在这些段中。
在所有的这些段里,有一个被称为".idata"的段(输入数据段)值得我们去注意,该段中包含着一些被称为输入地址表(IAT,Import Address Table)的数据列表,每个用隐式方式加载的API所在的DLL都有一个IAT与之对应,同时一个API的地址也与IAT中一项相对应。当一个应用程序加载到内存中后,针对每一个API函数调用,相应的产生如下的汇编指令:
JMP DWORD PTR [XXXXXXXX]
如果在VC++中使用了_delcspec(import),那么相应的指令就成为:
CALL DWORD PTR [XXXXXXXX]。
不管怎样,上述方括号中的总是一个地址,指向了输入地址表中一个项,是一个DWORD,而正是这个 DWORD才是API函数在内存中的真正地址。因此我们要想拦截一个API的调用,只要简单的把那个DWORD改为我们自己的函数的地址,那么所有关于这个API的调用将转到我们自己的函数中去,拦截工作也就宣告顺利的成功了。这里要注意的是,自定义的函数的调用约定应该是API的调用约定,也就是 stdcall,而Delphi中默认的调用约定是register,它们在参数的传递方法等方面存在着较大的区别。
另外,自定义的函数的参数形式一般来讲和原先的API函数是相同的,不过这也不是必须的,而且这样的话在有些时候也会出现一些问题,我在后面将会提到。因此要拦截API的调用,首先我们就要得到相应的IAT的地址。系统把一个进程模块加载到内存中,其实就是把 PE文件几乎是原封不动的映射到进程的地址空间中去,而模块句柄HMole实际上就是模块映像在内存中的地址,PE文件中一些数据项的地址,都是相对于这个地址的偏移量,因此被称为相对虚拟地址(RVA,Relative Virtual Address)。
于是我们就可以从HMole开始,经过一系列的地址偏移而得到IAT的地址。不过我这里有一个简单的方法,它使用了一个现有的API函数ImageDirectoryEntryToData,它帮助我们在定位IAT时能少走几步,省得把偏移地址弄错了,走上弯路。不过纯粹使用RVA从HMole开始来定位IAT的地址其实并不麻烦,而且这样还更有助于我们对PE文件的结构的了解。上面提到的那个API 函数是在DbgHelp.dll中输出的(这是从Win 2K才开始有的,在这之前是由ImageHlp.dll提供的),有关这个函数的详细介绍可参见MSDN。
在找到IAT之后,我们只需在其中遍历,找到我们需要的API地址,然后用我们自己的函数地址去覆盖它,下面给出一段对应的源码:
procere RedirectApiCall; var ImportDesc:PIMAGE_IMPORT_DESCRIPTOR; FirstThunk:PIMAGE_THUNK_DATA32; sz:DWORD;
begin
//得到一个输入描述结构列表的首地址,每个DLL都对应一个这样的结构 ImportDesc:=ImageDirectoryEntryToData(Pointer(HTargetMole), true, IMAGE_DIRECTORY_ENTRY_IMPORT, sz);
while Pointer(ImportDesc.Name)<>nil do
begin //判断是否是所需的DLL输入描述
if StrIComp(PChar(DllName),PChar(HTargetMole+ImportDesc.Name))=0 then begin
//得到IAT的首地址
FirstThunk:=PIMAGE_THUNK_DATA32(HTargetMole+ImportDesc.FirstThunk);
while FirstThunk.Func<>nil do
begin
if FirstThunk.Func=OldAddressOfAPI then
begin
//找到了匹配的API地址 ......
//改写API的地址
break;
end;
Inc(FirstThunk);
end;
end;
Inc(ImportDesc);
end;
end;
最后有一点要指出,如果我们手工执行钩子DLL的退出目标进程,那么在退出前应该把函数调用地址改回原先的地址,也就是API的真正地址,因为一旦你的DLL退出了,改写的新的地址将指向一个毫无意义的内存区域,如果此时目标进程再使用这个函数显然会出现一个非法操作。
五、替换函数的编写
前面关键的两步做完了,一个API钩子基本上也就完成了。不过还有一些相关的东西需要我们研究一番的,包括怎样做一个替换函数。 下面是一个做替换函数的步骤: 首先,不失一般性,我们先假设有这样的一个API函数,它的原型如下:
function SomeAPI(param1: Pchar;param2: Integer): DWORD;
接着再建立一个与之有相同参数和返回值的函数类型:
type FuncType= function (param1: Pchar;param2: Integer): DWORD;
然后我们把SomeAPI函数的地址存放在OldAddress指针中。接着我们就可以着手写替换函数的代码了:
function DummyFunc(param1: Pchar;param2: Integer): DWORD; begin ......
//做一些调用前的操作
//调用被替换的函数,当然也可以不调用
result := FuncType(OldAddress) (param1 , param2);
//做一些调用后的操作
end;
我们再把这个函数的地址保存到NewAddress中,接着用这地址覆盖掉原先API的地址。这样当目标进程调用该API的时候,实际上是调用了我们自己的函数,在其中我们可以做一些操作,然后在调用原先的API函数,结果就像什么也没发生过一样。当然,我们也可以改变输入参数的值,甚至是屏蔽调这个API函数的调用。
尽管上述方法是可行的,但有一个明显的不足:这种替换函数的制作方法不具有通用性,只能针对少量的函数。如果只有几个API要拦截,那么只需照上述说的重复做几次就行了。但如果有各种各样的API要处理,它们的参数个数和类型以及返回值的类型是各不相同的,仍然采用这种方法就太没效率了。
的确是的,上面给出的只是一个最简单最容易想到的方法,只是一个替换函数的基本构架。正如我前面所提到的,替换函数的与原先的API函数的参数类型不必相同,一般的我们可以设计一个没有调用参数也没有返回值的函数,通过一定的技巧,使它能适应各种各样的API 函数调用,不过这得要求你对汇编语言有一定的了解。
首先,我们来看一下执行到一个函数体内前的系统堆栈情况(这里函数的调用方式为stdcall),函数的调用参数是按照从右到左的顺序压入堆栈的(堆栈是由高端向低端发展的),同时还压入了一个函数返回地址。在进入函数之前,ESP正指向返回地址。因此,我们只要从ESP+4开始就可以取得这个函数的调用参数了,每取一个参数递增4。另外,当从函数中返回时,一般在EAX中存放函数的返回值。
了解了上述知识,我们就可以设计如下的一个比较通用的替换函数,它利用了Delphi的内嵌式汇编语言的特性。
Procere DummyFunc;
asm add esp,4 mov eax,esp//得到第一个参数
mov eax,esp+4//得到第二个参数 ......
//做一些处理,这里要保证esp在这之后恢复原样
call OldAddress //调用原先的API函数 ......
//做一些其它的事情
end;
当然,这个替换函数还是比较简单的,你可以在其中调用一些纯粹用OP语言写的函数或过程,去完成一些更复杂的操作(要是都用汇编来完成,那可得把你忙死了),不过应该把这些函数的调用方式统一设置为stdcall方式,这使它们只利用堆栈来传递参数,因此你也只需时刻掌握好堆栈的变化情况就行了。如果你直接把上述汇编代码所对应的机器指令存放在一个字节数组中,然后把数组的地址当作函数地址来使用,效果是一样的。
六、后记
做一个API钩子的确是件不容易的事情,尤其对我这个使用Delphi的人来说,为了解决某个问题,经常在OP、C++和汇编语言的资料中东查西找,在程序调试中还不时的发生一些意想不到的事情,弄的自己是手忙脚乱。不过,好歹总算做出了一个 API钩子的雏形,还是令自己十分的高兴,对计算机系统方面的知识也掌握了不少,受益非浅。当初在写这篇文章之前,我只是想翻译一篇从网上Down下来的英文资料(网址为 ,文章名叫"API Hook Revealed",示例源代码是用VC++写的,这里不得不佩服老外的水平,文章写得很有深度,而且每个细节都讲的十分详细)。
‘柒’ 论文框架怎么写
论文一般的组成部分包括以下内容:题名、作者、摘要、关键词、正文、参考文献和附录等部分组成。
其中部分组成(例如附录)可有可无。
论文题目要求准确、简练、醒目、新颖。
目录是论文中主要段落的简表。(短篇论文不必列目录)
内容提要是文章主要内容的摘录,要求短、精、完整。
关键词是从论文的题名、提要和正文中选取出来的,是对表述论文的中心内容有实质意义的词汇。关键词是用作计算机系统标引论文内容特征的词语,便于信息系统汇集,以供读者检索。每篇论文一般选取3-8个词汇作为关键词,另起一行,排在“提要”的左下方。
主题词是经过规范化的词,在确定主题词时,要对论文进行主题分析,依照标引和组配规则转换成主题词表中的规范词语。(参见《汉语主题词表》和《世界汉语主题词表》)。
论文正文
(1)引言:引言又称前言、序言和导言,用在论文的开头。引言一般要概括地写出作者意图,说明选题的目的和意义, 并指出论文写作的范围。引言要短小精悍、紧扣主题。
(2)论文正文:正文是论文的主体,正文应包括论点、论据、论证过程和结论。主体部分包括以下内容:
a.提出问题-论点;
b.分析问题-论据和论证;
c.解决问题-论证方法与步骤;
d.结论。
参考文献
一篇论文的参考文献是将论文在研究和写作中可参考或引证的主要文献资料,列于论文的末尾。参考文献应另起一页,标注方式按《GB7714-87文后参考文献着录规则》进行。
论文装订
论文的有关部分全部抄清完了,经过检查,再没有什么问题,把它装成册,再加上封面。论文的封面要朴素大方,要写出论文的题目、学校、科系、指导教师姓名、作者姓名、完成年月日。论文的题目的作者姓名一定要写在表皮上,不要写里面的补页上。
学位论文
学位申请者为申请学位而提出撰写的学术论文叫学位论文。这种论文是考核申请者能否被授予学位的重要条件。
学位申请者如果能通过规定的课程考试,而论文的审查和答辩合格,那么就给予学位。如果说学位申请者的课程考试通过了,但论文在答辩时被评为不合格,那么就不会授予他学位。
有资格申请学位并为申请学位所写的那篇毕业论文就称为学位论文,学士学位论文。学士学位论文既是学位论文又是毕业论文。
学术论文
中华人民共和国国家标准VDC 001.81、GB 7713-87号文件给学术论文的定义为:
学术论文是某一学术课题在实验性、理论性或观测性上具有新的科学研究成果或创新见解的知识和科学记录;或是某种已知原理应用于实际中取得新进展的科学总结,用以提供学术会议上宣读、交流或讨论;或在学术刊物上发表;或作其他用途的书面文件。
在社会科学领域,人们通常把表达科研成果的论文称为学术论文。
学术论文具有四大特点:①学术性 ②科学性 ③创造性 ④理论性
‘捌’ 论文结构框架怎么写
1、题目:题目是论文内容的概括,向读者说明研究的主要问题。一个好的学术论文题目应当是准确概括论文内容,文字简练、新颖,范围明确,便于分类的
2、前言:前言又或者序言、导言、绪论,写在正文之前,用于说明写作目的、问题的提出、研究的意义等。
3、正文:正文部分占全文大部分篇幅。这部分必须对研究内容进行全面的阐述和论证。写作时以观点为轴心,贯穿全文用材料说明观点,使观点与材料相统一,用观点去表现主题,使观点与主题相一致。
4、结论:结论是经反复研究后形成的总体论点。结论应指出所得的结果是否支持假设,或指出哪些问题已经解决了,还有什么问题尚待进一步探讨。
5、参考文献:这部分包括参考的文章、书目等,附在论文的末尾。
论文结构框架,主要包括题目、摘要、关键词、引言、正文、致谢、参考文献。
(1)题目的写作技巧
①题目应简明、确切,不要太长太笼统;
②题目可省去定冠词和不定冠词;
③题目中不应列入非公知公用的符号、代号,以及数学公式、化学 结构式等。
(2)摘要的写作技巧
①使用短而简单的句子,表达要准确、简洁、清楚;
②注意表述的逻辑性,尽量使用指示性的词语来表达论文的不同部 分(层次);
③不应出现公式、图表、参考文献的序号;
④用过去时态叙述自己的工作,用现在时态叙述自己的结论;
⑤尽量用主动语态代替被动语态。
(3)关键词的写作技巧
①论文所属科学名称
②成果名称
③所用方法名称
④研究对象
⑤便于文献检索利用的名称
(4)引言的写作技巧
①采取适当的方式强调研究中最重要的发现或贡献,让读者顺着逻 辑的演进阅读论文。
②解释或定义专门术语或缩写词,以帮助编辑、审稿人和读者阅读 稿件。
③适当地使用“I”,“We”或“Our”,以明确地指示自己的工作。
④叙述前人工作的欠缺以强调自己研究的创新时,应慎重且留有余地。
(5)正文的写作技巧
①思路清晰,逻辑性强,层次清晰。
②引用已有的方法及结论要标明所出文献及其编号。
③推导及论证过程简洁而准确,实验数据及结论准确无误。
④力求避免中国式英语和论证思路,多参看外文文献及相关外文教材。
(6)致谢的写作技巧
致谢是对整个过程中给予帮助的个人或团体的感谢,内容应尽量具体、用词要恰当、格式要遵从拟投稿期刊的习惯和相关规定。
(7)参考文献的写作技巧
①参考文献要精选;
②参考文献的所在期刊、出版的年月及卷期要准确无误,确保与文中引用参考文献的一一对应,其书写格式应参考