UCAS CTF

如何使用 shell

Author: 凉凉

本文将介绍关于如何使用 shell 以及对应相关的一堆工具链, 完成本文的阅读与学习, 你将学会:

行文风格与符号注记

基本的 shell 使用以及快捷键

简单的工具链的编译与构建

在这一节, 将会有两个例子:

  1. 编译一个 C 的项目;
  2. 自己组织一个 C 的项目并编译

编译一个 C 的项目 Make, Cmake, AutoConf, …

说明: 为什么我们在谈及 "项目" 的时候需要谈到编译工具链?

当我们的工具的代码量并不大的情况下, 比如只有一个 main 函数的简单计算器, 谈及编译的时候往往是一个简单的 CC 命令即可完成的事情:

gcc -o calculator calculator.c

但是真实情况下, 譬如我们引用或调包了一个库, 比方说 webview, 在编译的时候, 我们就需要将这个调用的库引用过来:

gcc -o webview_calculator webview_calculator.c -framework WebKit -ldl -...

注: 这里只是一个示意, 实际上有一大堆 link 和 compile 的参数.

那么, 为什么会这么复杂?

可以从一个更加简单的例子来看, 假设我们有一个不同系统下不同表现的函数:

// simple.c
#include "stdio.h"

int main() {

#ifdef __MAC_OS_X__
  printf("This is compiled for macOS\n");
#endif

#ifdef __LINUX__
  printf("This is comipled for Linux\n");
#endif

}

我们在编译的时候可以使用 -D<macro_name>(=macro_value) 的形式来定义宏, 于是对于上面的代码, 我们可以为 macOS 编译:

> gcc -o simple simple.c -D__MAC_OS_X__ && ./simple
This is compiled for macOS

当然, 实际上并不是使用 __MAC_OS_X__ 这样的标记手动指定编译的系统, 而是通过 Pre-defined Compiler Macros 中说明的宏进行自动检测.

我们完全可以替换上文中的代码 __MAC_OS_X__ 中的 printf, 变成在另一个文件中定义的一个函数, 比如 attack_mac_os.c 中的 attack_mac_os, 对于 __LINUX__ 也同样可以有 attack_linux.c 中的 attack_linux 函数. 这样我们可以选择对于不同的编译对象, 只编译部分对应的代码, 从而使得我们的程序即小巧, 又具有可移植性.

那么如何实现这一操作呢?

假设我们的 attack_mac_os.c 文件如下:

// attack_mac_os.c
#include "stdio.h"

void attack_mac_os() {
  printf("Haha, your macOS is being cracked. \n");
}

而主函数文件如下:

// main.c

#ifdef __APPLE__
void attack_mac_os();
#endif

int main() {
#ifdef __APPLE__
  attack_mac_os();
#endif
}

因为 gcc 一次编译一个文件, 所以我们需要先编译 attack_mac_os (等其他文件), 即在编译完 main.c 的所有依赖后, 再编译 main.c 文件:

> gcc -o attack_mac_os.o attack_mac_os.c -c
> gcc -o simple main.c attack_mac_os.o
> ./simple
Haha, your macOS is being cracked.

在这中间发生了什么? (简单但是并不准确的解释)

  • main.c 中, 我们定义了一个 void attack_mac_os(), 这是为了告诉编译器, 有一个不接受输入参数, 也不返回参数的函数, 名字叫作 attack_mac_os.
  • 尽管编译器可能并不知道这个函数的具体定义是什么, 但是它确实会保留这个符号.
  • 在第一个 gcc 编译的时候, 我们编译了具体定义 attack_mac_os 这个函数的文件, 但是通过添加一个 -c 的参数, 使得编译器在 c​ompilation 阶段就停止;
  • 在第二个 gcc 编译的时候, 我们将所有的结果都合并 (link) 在一起, 那个被保留的符号 attack_mac_os 在这个时候也会找到自己的定义.

那么对于一个较大的项目, 我们想要编译一个结果, 肯定不会像这样手动去解决各种编译顺序和依赖, 最简单的方式就是写一个 Shell 脚本, 描述编译的全过程并进行自动化处理; 但是缺点就是 Shell 脚本可维护性较差, 所以渐渐地会有人想出如何替代, 或者如何有更好的自动化管理工具, 于是就有了 Make, CMake 之类的东西.

autogen.sh, configure: 以 mg 为例

  1. 获得源码:

    git clone https://github.com/troglobit/mg.git && cd mg
    
  2. 阅读安装手册:

    less README.md
    

    按照 README.md 编译. 请尝试修改不同的 ./configure 参数, 看看效果如何?

    请注意 --prefix 会将文件 “安装” (移动) 到哪里? 如果你不是 root 用户, 该将文件移动到哪里?

    如何将安装的文件清除掉? (make uninstall)

练习: 尝试编译其他的一些项目, 可以选择的有 neovim, emacs

(注: 虽然我很想添加 vscode, 但是鉴于其并非完全开源, 你从微软官方下载的编译后的内容实际和你用源码编译出来的东西并不相同, 所以没有必要去尝试. )

CMake

这里并不详细介绍, 只是简单提一嘴.

你可以把 CMake 看作是 autoconf 流一样的 Makefile 生成器, 但是 CMake 的语法更加 “简洁” 一些. 其功能就是根据 CMake 的规定, 会展开成一个 Makefile (cmake -B build 会进行一个配置).

在配置完后, 你完全可以使用 make 那一套流程进行编译/安装等等.

自己组织一个 C 的项目并编译 Make

简单的 Linux 规范与约定