第二章:编译和链接_2
第二章:编译和链接_2编译器做了什么从直观来说,编译器就是将高级语言翻译成机器语言的一个工具。编译器的存在可以让程序员更加关注程序本身的逻辑,而尽量少考虑计算机本身的限制,如字长,内存大小,通信方式,存储方式。
编译过程一般可以分为6步, 扫描,语法分析,语义分析,源代码优化,代码生成和目标代码优化
2.2.1 词法分析首先源代码被输入到 扫描器,扫描器的任务很简单,它只是简单地进行词法分析,运用一种类似于 有限状态机的算法可以很轻松地将源代码的字符序列分割成一系列的 记号(token)。
词法分析产生的记号一般可以分为以下几类: 关键字,标识符,字面量(包含数字,字符串)和特殊符号(如加号,等号)。
在识别记号的同时,扫描器也完成了其他的工作。比如将标识符放到符号表,将数字,字符串常量存放到文字表中,以备后面的步骤使用。
有一个叫 lex的程序可以实现词法扫描。
2.2.2 语法分析接下来 语法分析器将对由扫描器产生的记号进行语法分析,从而产生 语法树。整个过程采用了 上下文无关语法的分析手段,语法分析器产生的语法树就是以表达式为结点的树。
正如前面的词法分析有lex一样,语法分析有 ...
第二章:编译和链接_1
第二章:编译和链接_12.1 被隐藏了的过程
当我们用GCC编译文件的时候有很多步骤被隐藏了,GCC编译的过程可以分为四个步骤,分别是 预处理,编译,汇编,链接
2.1.1 预编译使用命令来让编译器只执行预编译
123$gcc -E hello.c -o hello.i或者$gcc hello.c > hello.i
预编译的主要过程是处理那些源代码中的以“#”开头的预编译指令。比如“#include”,“#define”等,主要处理规则如下:
将所有的“#define”删除,并且展开所有的宏定义。
处理所有条件预编译指令,比如“#if”,“#ifdef”,“#elif’,”#else”,”#endif”
处理“#include”预编译指令,将被包含的文件插入到该预编译的位置。这个过程是递归的,也就是说被包含的文件还可能包含其他文件。
删除所有的注释“//”和”/**/“。
添加行号和文件名标识,比如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
保留所有的#pra ...
第一章:计算机的软硬件基本结构—Q&A
第一章:计算机的软硬件基本结构—Q&A1.1 从hello World说起问题
程序为什么要被编译了以后才可以运行?
A:
编程语言是程序员的语言,但是不是计算机能够理解的语言,计算机能够理解的语言只有机器码,也就是二进制的0和1。所以程序需要被编译才能运行。
编译器在把C语言程序转换为可以执行的机器码的过程中做了什么,怎么做的?
A:
编译分为四个步骤,分别是预处理,编译,汇编,链接。预处理首先对以#开头的预编译命令处理。删除所有注释。编译会经过一系列的词法分析,语法分析,语义分析,优化后产生相应的汇编代码文件。汇编负责将汇编文件转换为机器可以执行的指令。链接负责将我们程序里引用的库函数和我们写的程序进行链接。
最后编译出来的可执行文件里面是是什么?除了机器码还有什么?他们怎么存放的,怎么组织?
A:
这个问题不太会,等学完后来填坑。
#include<stdio.h>是什么意思?将stdio.h包含进来意味着什么?C语言库又是什么?它怎么实现的?
A:
#include<stdio.h>是将stdio.h这个库函数给引入进来。include的效 ...
第一章:计算机的软硬件基本结构_6
第一章:计算机的软硬件基本结构—61.6 众人拾柴火焰高1.6.1 线程基础
在现代软件系统中,线程和进程一样重要。特别是随着CPU频率增长开始出现停滞,而开始向多核发展。多线程,作为实现软件并发执行的一个重要的方法,也开始具有越来越重要的地位。
什么是线程 线程(Thread),有时候被称为 轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
通常意义上,一个进程由一个到多个线程组成,各个线程间共享程序的内存空间(包括代码段,数据段,堆等)以及一些进程级的资源(打开文件和信号)
大多数软件应用中,线程数量都不只一个。多个线程可以互不干扰地并发执行,并共享进程的全局变量和堆的数据。和单线程对比,多线程具有的优势为:
某个操作可能会陷入长时间的等待,等待的线程会进入睡眠状态,无法继续执行,多线程可以有效利用等待的时间。
某个操作会消耗大量的时间(通常是计算),如果只有一个线程,程序和用户之间的交互会中断。多线程可以让一个线程负责交互,另一个线程负责计算。
程序逻辑本身就需要并发 ...
第一章:计算机的软硬件基本结构_5
第一章:计算机的软硬件基本结构_51.5 内存不够怎么办
操作系统的多任务功能使得CPU能够在多个进程之间很好的共享,从进程角度好像它独占了CPU而不用考虑与其他进程分享CPU。
为了更有效的利用硬件资源,我们必须同时运行多个程序,正如前面的多道程序,分时系统和多任务一样,当我们能够同时运行多个程序时,CPU的利用率较高。但是就会有一个很明显的问题, 如何将计算机上有限的物理内存分配给多个程序使用。
在分配内存时会出现的问题。
地址空间不隔离 所有程序直接访问物理地址,程序所使用的内存空间不是相互隔离的。恶意的程序可以很容易改写其他程序的内存数据,以到达破坏的目的。
内存使用效率低 由于没有有效的内存管理机制,通常我们正在运行程序A的时候要是想运行程序B,但是这个时候内存空间其实已经不够用了,于是计算机便会将程序A换出到磁盘或者更多来释放足够的空间来运行程序B。中间有大量的数据在换入换出,导致效率低下。
程序运行的地址不确定 因为程序每次需要装入运行时,我们需要给他从内存中分配一块足够大的空闲区域。这个区域的位置是不确定的。这给程序的编写造成了一定的麻烦,因为 ...
第一章:计算机的软硬件基本结构_4
第一章:计算机的软硬件基本结构_41.4 操作系统做什么
操作系统的一个功能是提供抽象的接口,另一个主要功能是管理硬件资源。
1.4.1 不要让CPU打盹在计算机发展的早期,CPU资源十分昂贵,当CPU运行一个程序的时候,该程序需要读写磁盘,CPU就空闲下来了。这极大的浪费了CPU的资源。于是人们编写了一个监控程序,当某个程序暂时无须使用CPU时,监控程序就把另外正在等待CPU资源的程序启动,使得CPU能充分利用起来。这被称为 多道程序。但是这种程序的调度策略太过粗糙。程序之间不分轻重缓急。
稍微改进了一下,程序运行模式就变成了一种协作的模式。每个程序运行一段时间以后都主动让出CPu给其他程序,使得一段时间内每个程序都有机会运行一小段时间。这种程序协作模式叫做 分时系统。但是这种方式如果有一个非常耗时的计算在运行,那就会一直霸占着计算机。
于是出现了更先进的操作系统模式,这便是我们熟悉的 多任务系统,操作系统接管了所有硬件资源,并且本身运行在一个受硬件保护的级别。所有应用程序都以 进程的方式运行在比操作系统权限更低的级别。每个进程都有自己独立的地址空间,使得进程之间的地址空间相互隔离 ...
第一章:计算机的软硬件基本结构_3
第一章:计算机的软硬件基本结构_31.3 站得高,望得远系统软件可以分为两块,一块是平台性的,比如操作系统内核,驱动程序,运行库,和数以千计的系统工具。另一块是用于程序开发的,比如编译器,汇编器,链接器等开发工具和开发库。
计算机系统软件体系结构采用一种层的结构,有人说过一句名言:
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决!
系统软件体系结构中,各种软件的位置如下图所示。
每个层次之间相互通过共同的通信协议来通信,我们一般将其称为 接口,接口下面那层是接口的提供者,由他定义接口;接口上面那层是借接口的使用者,它使用该接口来实现所需要的功能。
我们的软件体系中,位于最上层的应用程序,比如我们平时用到的网络浏览器,多媒体播放器,图片浏览器等。从整天层次来看,开发工具和应用程序是属于一个同一个层次的,因为他们都使用一个接口,那就是操作系统 应用程序编程接口(API)应用程序接口的提供者是运行库。
运行库使用操作系统提供的系统调用接口,系统调用接口在实现中往往以 软件中断的方式提供,比如Linux使用的0x80号中断作为系统调用接口,Windos使用0x2E号中断作为 ...
第一章:计算机的软硬件基本结构_2
第一章:计算机的软硬件基本结构1.2 万变不离其宗
计算机是个非常广泛的概念,包括大到要装数层楼的超级计算机,也有小到手上拿的手机上的嵌入式芯片都可以被称为计算机。
撇开计算机硬件中复杂的各种设备,芯片,以及外围接口的等,站在软件开发者的角度去看,我们只需要抓住硬件的几个关键部件,对于系统应用开发者来说,最为关键的部件分别是,中央处理器CPU,内存和I/O控制芯片,对于普通应用程序开发者来说,他们只需要关心CPU以外其他的都不需要,对于一些高级平台的开发者来说(如java,.NET或脚本语言开发者),连 CPU都不需要关心。因为平台为他们提供了一个通用的抽象的计算机,他们只需要关心这个抽象的计算机就可以了。
早期的计算机没有复杂的图形功能,只需要一个 总线(BUS)来连接CPU,内存和I/O设备。
后来由于CPU核心频率的提升,导致内存跟不上CPU的速度,于是产生了与内存频率一致的 系统总线,而CPU采用倍频的方式与系统总线进行通信。后面随着3D游戏和多媒体的发展,使得图形芯片需要跟CPU和内存之间大量交换数据,为了协调CPU,内存和高速的图形设备,人们专门设计 ...
符号修饰与函数签名
符号符号修饰与函数签名约在20世纪70年代以前,编译器编译源代码产生目标文件的时候,符号名与相应的变量和函数的名字是一样的。
但是现在库和目标文件越来越多,如果我们想要使用一个库的时候,我们就不能使用库中定义的函数和变量。否则就会有符号名冲突。为了防止符号名冲突,UNIX下的C语言就规定,C语言源代码文件中的所有全局变量和函数经过编译后,相对应的符号名前加上下划线”_”。
这种简单而原始的方法确实能够解决符号冲突的概率,但是当程序很大时,不同模块由多个部门开发,他们之间的命名规范如果不严格,则很有可能导致冲突。于是像C++这样的后来设计的语言开始考虑到了这个问题,增加了 名称空间(Namespace)的方法来解决多模块的符号冲突问题。
随着时间的推移,很多操作系统和编译器被完全重写,符号冲突问题不是那么明显了,现在的Linux下的GCC编译器中,默认情况下已经去掉了在C语言符号中加”_”的方式。只有GCC在windows平台下的版本(mingw,cygwin)和 Visual C++ 编译器会在C语言符号前加”_“。
C++符号修饰众所周知,强大而又复杂的C++拥有类,继承,虚机制 ...
祥云杯2022-unexploitable - wp
祥云杯2022-unexploitable - wp题目分析分析一下文件,发现只有一个read函数来读取数据,但是给的空间非常大,基本上是随便溢出了。但是还是出了点小问题,刚好这是我第一道爆破的题,记录一下,以后要是忘了还可以回来看看
exp看栈上返回地址的后面第二位是libc_start_main一定偏移的地址,于是我们可以通过部分覆盖法来将该地址覆盖为one_gaget的地址,看师傅们的wp
有用vsyacall来使返回地址滑到libc那里去的
也有通过返回到push ebp之后的地址使返回地址的上一位被弹出的
好像还能用ret2_dl_runtime来做(还没有自己试过)
前两种方法都是爆破法来求解,但是爆破的概率有点小,需要爆破三位。全凭运气了,概率为4096分之一。但还是写一下。最近发现爆破法用得挺多的。
通过ret_push_after 的exp在下面
12345678910111213141516171819202122from pwn import *context.log_level = "debug"global pdef pwn(): ...



