IntroductiontoBashScriptingHTB
Working Components
Conditional Execution
条件执行允许我们通过达到不同的条件来控制脚本的流程。这一功能是必不可少的组成部分之一。否则,我们只能一个接一个地执行命令。
在定义各种条件时,我们指定应该为特定值执行哪些函数或代码段。如果我们达到了一个特定的条件,那么只执行该条件的代码,而跳过其他代码。一旦代码部分完成,以下命令将在条件执行之外执行。让我们再看一下脚本的第一部分并分析它。
Script.sh
Code:
|
总而言之,此代码段使用以下组件:
#!/bin/bash- Shebang.if-else-fi- Conditional execution.echo- Prints specific output.$#/$0/$1- Special variables.domain- Variables.
条件执行的条件可以使用变量($#、$0、$1、domain)、值(0)和字符串来定义,我们将在下面的示例中看到。这些值将与我们将在下一节中查看的comparison operators(-eq)进行比较。
Shebang
shebang行始终位于每个脚本的顶部,并且始终以“#!“开头。这一行包含执行脚本的指定解释器(/bin/bash)的路径。我们还可以使用Shebang来定义其他解释器,如Python,Perl等。
Code:
#!/usr/bin/env python |
Code:
#!/usr/bin/env perl |
If-Else-Fi
最基本的编程任务之一是检查不同的条件来处理这些条件。在编程和脚本语言中,条件检查通常有两种不同的形式,if-else condition和case statements。在伪代码中,if条件的含义如下:
Pseudo-Code 伪代码
Code:
if [ the number of given arguments equals 0 ] |
默认情况下,一个If-Else条件只能包含一个“If“,如下例所示。
If-Only.sh
Code: 验证码: bash
|
If-Only.sh - Execution
mikannse7@htb[/htb]$ bash if-only.sh 5 |
If-Only.sh - ExecutionIf-Only.sh执行
mikannse7@htb[/htb]$ bash if-only.sh 12 |
当添加Elif或Else时,我们添加替代项来处理特定的值或状态。如果一个特定的值不适用于第一种情况,它将被其他人捕获。
If-Elif-Else.sh
Code:
|
If-Elif-Else.sh - Execution
mikannse7@htb[/htb]$ bash if-elif-else.sh 5 |
If-Elif-Else.sh - ExecutionIf-Elif-Else.sh执行
mikannse7@htb[/htb]$ bash if-elif-else.sh 12 |
If-Elif-Else.sh - ExecutionIf-Elif-Else.sh执行
mikannse7@htb[/htb]$ bash if-elif-else.sh HTB |
我们可以扩展脚本并指定几个条件。这可能看起来像这样:
Several Conditions - Script.sh 几个条件-Script.sh
Code:
|
这里我们定义了另一个条件(elif [<condition>];then),它打印一行,告诉我们(echo -e "...")我们已经给出了多个参数,并以错误(exit 1)退出程序。
Exercise Script
Code:
|
Arguments, Variables, and Arrays
Arguments
bash脚本的优点是,我们总是可以传递多达9个参数($0-$9)给脚本,而无需将它们分配给变量或为这些变量设置相应的要求。9 arguments因为第一个参数$0是为脚本保留的。正如我们在这里看到的,我们需要在变量名之前使用美元符号($),以便在指定的位置使用它。相比之下,赋值看起来像这样:
mikannse7@htb[/htb]$ ./script.sh ARG1 ARG2 ARG3 ... ARG9 |
这意味着我们已经自动将相应的参数分配给了这个地方的预定义变量。这些变量称为特殊变量。这些特殊变量充当占位符。如果我们现在再次查看代码部分,我们将看到在何处使用了哪些参数。
CIDR.sh
Code:
|
有几种方法可以执行我们的脚本。但是,在使用脚本中定义的解释器执行脚本之前,我们必须首先设置脚本的执行特权。
CIDR.sh - Set Execution Privileges
mikannse7@htb[/htb]$ chmod +x cidr.sh |
CIDR.sh - Execution without Arguments
mikannse7@htb[/htb]$ ./cidr.sh |
CIDR.sh - Execution without Execution Permissions
mikannse7@htb[/htb]$ bash cidr.sh |
Special Variables
特殊变量使用内部字段分隔符(IFS)来标识参数结束和下一个参数开始的时间。Bash提供了各种特殊的变量来帮助编写脚本。其中一些变量是:
| IFS | Description |
|---|---|
$# |
此变量保存传递给脚本的参数的数量。 |
$@ |
此变量可用于检索命令行参数列表。 |
$n |
每个命令行参数都可以使用其位置有选择地检索。例如,第一个参数位于$1。 |
$$ |
当前正在执行的进程的进程ID。 |
$? $n |
脚本的退出状态。此变量对于确定命令是否成功很有用。值0表示成功执行,而1表示失败的结果。 |
在上面显示的那些中,我们在if-else条件中有3个这样的特殊变量。
| IFS | Description |
|---|---|
$# $n |
在这种情况下,我们只需要一个变量,需要分配给domain变量。此变量用于指定我们要使用的目标。如果我们只提供一个参数,那么$#变量的值将为1。 |
$0 $n |
这个特殊的变量被分配了执行脚本的名称,然后在“Usage:”示例中显示。 |
$1 $n |
用空格分隔,第一个参数被分配给该特殊变量。 |
Variables
我们还看到在if-else循环的末尾,我们将第一个参数的值赋给了名为“domain“的变量。变量的赋值没有美元符号($)。美元符号仅用于允许此变量的对应值在其他代码部分中使用。在分配变量时,名称和值之间不能有空格。
Code:
<SNIP> |
与其他编程语言相比,Bash中的变量类型(如“strings”、“integers”和“boolean”)之间没有直接的区别和识别。“变量的所有内容都被视为字符串字符。Bash根据是否只分配数字来启用算术函数。重要的是要注意,当声明变量时,它们确实包含not。否则,实际变量名将被解释为内部函数或命令。
Declaring a Variable - Error
mikannse7@htb[/htb]$ variable = "this will result with an error." |
Declaring a Variable - Without an Error
mikannse7@htb[/htb]$ variable="Declared without an error." |
Arrays
在Bash中,也可以为单个变量分配多个值。如果我们想扫描多个域或IP地址,这可能是有益的。这些变量被称为arrays,我们可以使用它们来存储和处理特定类型值的有序序列。Arrays用从index开始的0标识每个存储的条目。当我们想给数组组件赋值时,我们用与标准shell变量相同的方式来做。我们所做的就是指定用方括号括起来的字段索引。在Bash中,arrays的声明看起来像这样:
Arrays.sh
Code:
|
我们也可以使用索引单独检索它们,使用变量和花括号中的相应索引。花括号用于变量展开。
mikannse7@htb[/htb]$ ./Arrays.sh |
值得注意的是,单引号(' … ')和双引号(". ")防止数组中的各个值之间用空格分隔。这意味着单引号和双引号之间的所有空格都将被忽略,并作为分配给数组的单个值处理。
Arrays.sh
Code:
|
mikannse7@htb[/htb]$ ./Arrays.sh |
Comparison Operators
为了将特定的值相互比较,我们需要称为比较运算符的元素。comparison operators用于确定如何比较定义的值。对于这些运营商,我们区分:
stringoperatorsintegeroperatorsfileoperatorsbooleanoperators
String Operators
如果我们比较字符串,那么我们就知道我们希望在相应的值中有什么。
| Operator | Description |
|---|---|
== |
is equal to |
!= |
is not equal to |
< |
is less than in ASCII alphabetical order |
> |
is greater than in ASCII alphabetical order |
-z |
if the string is empty (null) |
-n |
if the string is not null |
这里需要注意的是,我们将给定参数($1)的变量放在双引号("$1")中。这告诉Bash变量的内容应该作为字符串处理。否则,我们会得到一个错误。
Code:
|
字符串比较运算符“< / >”仅在双方括号[[ <condition> ]]中起作用。我们可以在互联网上找到ASCII表,或者在终端中使用以下命令。我们稍后来看一个例子。
mikannse7@htb[/htb]$ man ascii |
ASCII Table
| Decimal小数 | Hexadecial十六进制的 | Character字符 | Description描述 |
|---|---|---|---|
| 0 | 00 | NUL | End of a string字符串末尾 |
| … | … | … | … |
| 65 | 41 | A一 | Capital A大写的A |
| 66 | 42 | B | Capital B资本B |
| 67 | 43 | C | Capital C资本C |
| 68 | 44 | D | Capital D大写D |
| … | … | … | … |
| 127 | 7F | DEL | Delete删除 |
ASCII代表American Standard Code for Information Interchange,表示7位字符编码。由于每个位可以取两个值,因此存在128不同的位模式,其也可以被解释为十进制整数0 - 127或十六进制值00 -7F。前32个ASCII字符代码被保留为所谓的控制字符。
Integer Operators
如果我们知道要比较什么值,比较整数对我们来说非常有用。因此,我们定义了接下来的步骤和命令,脚本应该如何处理相应的值。
| Operator | Description |
|---|---|
-eq -n |
is equal to |
-ne -n |
is not equal to不 |
-lt -n |
is less than |
-le -n |
is less than or equal to |
-gt -n |
is greater than |
-ge -n |
is greater than or equal to |
Code:
|
File Operators
如果我们想找出特定的权限或它们是否存在,文件操作符是很有用的。
| Operator | Descriptio |
|---|---|
-e -n |
if the file exist |
-f -n |
tests if it is a file |
-d -n |
tests if it is a directory |
-L -n |
tests if it is if a symbolic link |
-N -n |
checks if the file was modified after it was last read检查文件在上次读取后是否被修改 |
-O -n |
if the current user owns the file |
-G -n |
if the file’s group id matches the current user’s |
-s -n |
tests if the file has a size greater than 0 |
-r -n |
tests if the file has read permission |
-w -n |
tests if the file has write permission |
-x -n |
tests if the file has execute permission |
Code:
|
Boolean and Logical Operators
我们得到一个布尔值“false”或“true”作为逻辑运算符的结果。Bash让我们可以使用双方括号[[ <condition> ]]来比较字符串。为了得到这些布尔值,我们可以使用字符串运算符。无论比较是否匹配,我们都会得到布尔值“false”或“true“。
Code:
|
Logical Operators
使用逻辑运算符,我们可以在一个条件中定义多个条件。这意味着我们定义的所有条件必须匹配,然后才能执行相应的代码。
| Operator | Description |
|---|---|
! -n |
logical negotation NOT |
&& -n |
logical AND |
| ` |
Code:
|
Exercise Script
Code:
|
Arithmetic
在Bash中,我们有七个不同的arithmetic operators可以使用。这些用于执行不同的数学运算或修改某些整数。
Arithmetic Operators 算术运算符
| Operator操作者 | Description描述 |
|---|---|
+ |
Addition |
- |
Substraction |
* |
Multiplication |
/ |
Division |
% |
Modulus |
variable++ |
Increase the value of the variable by 1 |
variable-- |
Decrease the value of the variable by 1 |
我们可以在一个小脚本中总结所有这些操作符:
Arithmetic.sh
Code:
|
该脚本的输出如下所示:
Arithmetic.sh - Execution
mikannse7@htb[/htb]$ ./Arithmetic.sh |
我们还可以计算变量的长度。使用这个函数${#variable},每个字符都被计数,我们得到变量中的字符总数。
VarLength.sh
Code:
|
VarLength.sh
mikannse7@htb[/htb]$ ./VarLength.sh |
如果我们看一下我们的CIDR.sh脚本,我们会发现我们已经多次使用了increase和decrease操作符。这确保了while循环(我们将在后面讨论)在变量“stat”的值为1时运行并ping主机。如果ping命令以代码0结束(成功),我们会得到一条消息,表明变量host is up和“stat”以及变量“hosts_up”和“hosts_total”被更改。
CIDR.sh
Code:
<SNIP> |
Script Control
Input and Output
Input Control
我们可能会从发送的请求和执行的命令中获得结果,我们必须手动决定如何继续。另一个例子是,我们在我们的脚本中定义了几个为不同场景设计的函数。我们必须在手动检查后并根据结果决定应执行哪一个。也很可能不允许执行特定的扫描或活动。因此,我们需要熟悉如何让一个正在运行的脚本等待我们的指令。如果我们再次查看我们的CIDR.sh脚本,我们会看到我们添加了这样一个调用来决定进一步的步骤。
CIDR.sh
Code:
# Available options |
前echo行用作我们可用选项的显示菜单。使用read命令,显示带有“Select your option:”的行,并且附加选项-p确保我们的输入保持在同一行上。我们的输入存储在变量opt中,然后我们使用它来执行与case语句对应的函数,我们稍后会看到。根据我们输入的数字,case语句决定执行哪些函数。
Output Control
我们已经在Linux Fundamentals模块中学习了输出的输出重定向。然而,重定向的问题是我们没有从相应的命令中获得任何输出。它将被重定向到相应的文件。如果我们的脚本以后变得更复杂,它们可能需要更多的时间,而不仅仅是几秒钟。为了避免被动地等待脚本的结果,我们可以使用tee实用程序。它确保我们立即看到我们得到的结果,并将它们存储在相应的文件中。在我们的CIDR.sh脚本中,我们以不同的方式使用了这个实用程序两次。
CIDR.sh
Code:
<SNIP> |
当使用tee时,我们传输接收到的输出,并使用管道(|)将其转发到tee。“-a / --append”参数确保指定的文件不会被覆盖,而是补充了新的结果。同时,它向我们显示了结果以及如何在文件中找到它们。
CIDR.sh
mikannse7@htb[/htb]$ cat discovered_hosts.txt CIDR.txt |
Flow Control - Loops
控制我们的脚本流是至关重要的。我们已经了解了if-else条件,这也是流量控制的一部分。毕竟,我们希望我们的脚本能够快速有效地工作,为此,我们可以使用其他组件来提高效率并允许无错误处理。每个控制结构都是branch或loop。布尔值的逻辑表达式通常控制控制结构的执行。这些控制结构包括:
- Branches:
If-ElseConditionsCaseStatements
- Loops:
ForLoopsWhileLoopsUntilLoops
For Loops
让我们从For循环开始。在每次传递时,只对一个参数执行For循环,shell从列表中获取该参数,从增量中计算该参数,或者从另一个数据源获取该参数。只要找到相应的数据,for循环就会运行。这种类型的循环可以以不同的方式构造和定义。例如,当我们需要处理数组中的许多不同值时,经常使用for循环。这可以用来扫描不同的主机或端口。我们还可以使用它来执行已知端口及其服务的特定命令,以加快枚举过程。其语法可以如下:
Syntax - Examples
Code:
for variable in 1 2 3 4 |
Code:
for variable in file1 file2 file3 |
Code:
for ip in "10.10.10.170 10.10.10.174 10.10.10.175" |
当然,我们也可以将这些命令写在一行中。这样的命令看起来像这样:
mikannse7@htb[/htb]$ for ip in 10.10.10.170 10.10.10.174;do ping -c 1 $ip;done |
让我们再看看我们的CIDR.sh脚本。我们已经在脚本中添加了几个for循环,但是让我们继续使用这个小代码部分。
CIDR.sh
Code:
<SNIP> |
与前面的示例一样,对于数组“ipaddr”中的每个IP地址,我们发出一个“whois”请求,其输出被过滤为“NetRange”和“CIDR”。“这有助于我们确定目标位于哪个地址范围内。我们可以使用此信息在渗透测试期间搜索其他主机,if approved by the client。我们收到的结果将相应地显示并存储在文件“CIDR.txt.”
While Loops
while循环在概念上很简单,遵循以下原则:
- 只要满足条件,就会执行语句(
true)。
我们还可以组合联合收割机循环,并将它们的执行与不同的值合并。重要的是要注意,多个循环的过度组合会使代码非常不清楚,并导致难以发现和遵循的错误。这样的组合看起来像我们的CIDR.sh脚本。
CIDR.sh
Code:
<SNIP> |
while循环也适用于像if-else这样的条件。while循环需要某种计数器,以便在必须停止执行它所包含的命令时定位自己。否则,这将导致一个无休止的循环。这样一个计数器可以是一个变量,我们已经声明了一个特定的值或布尔值。当布尔值为“While“时,将运行True循环。除了计数器,我们还可以使用命令“break”,当到达这个命令时,它会中断循环,如下面的例子所示:
WhileBreaker.sh
Code:
|
WhileBreaker.sh
mikannse7@htb[/htb]$ ./WhileBreaker.sh |
Until Loops
还有until循环,这是相对罕见的。尽管如此,until循环的工作原理与while循环完全相同,但不同之处在于:
- 只要特定条件为
until,false循环中的代码就会被执行。
另一种方法是让循环运行,直到达到所需的值。“until”循环非常适合于此。这种类型的循环与“while”循环类似,但正如已经提到的,不同之处在于它会运行到布尔值为“False”。”
Until.sh
Code:
|
Until.sh
mikannse7@htb[/htb]$ ./Until.sh |
Exercise Script 练习脚本
Code: 验
|
#!/bin/bash |
HTBL00p5r0x
Flow Control - Branches
正如我们已经看到的,流控制中的分支包括if-else和case语句。我们已经详细讨论了if-else语句,并知道它是如何工作的。现在,我们将仔细研究案例陈述。
Case Statements
Case语句在其他语言中也被称为switch-case语句,如C/C++和C#。if-else和switch-case之间的主要区别是if-else结构允许我们检查任何布尔表达式,而switch-case总是只比较变量与精确值。因此,与if-else相同的条件,例如“比……更快”,不允许用于switch-case。switch-case语句的语法如下所示:
Syntax - Switch
Code:
case <expression> in |
switch-case的定义以case开头,后面是作为表达式的变量或值,然后在模式中进行比较。如果变量或值与表达式匹配,则在括号后执行语句,并以双引号(;;)结束。
在我们的CIDR.sh脚本中,我们使用了这样的case语句。在这里,我们定义了四个不同的选项,我们分配给我们的脚本,它应该如何进行后,我们的决定。
CIDR.sh
Code:
<SNIP> |
使用前两个选项,该脚本执行我们之前定义的不同函数。使用第三个选项,两个函数都将执行,使用任何其他选项,脚本将终止。
Execution Flow
Functions
我们的剧本越大,就越混乱。如果我们在脚本中多次使用相同的例程,脚本的大小将相应地增加。在这种情况下,functions是一种解决方案,可以多次提高脚本的大小和清晰度。我们将联合收割机中的几个命令组合在花括号({. })并使用我们使用functions定义的函数名调用它们。一旦定义了函数,就可以在脚本中再次调用和使用它。
Functions是脚本和程序的重要组成部分,因为它们用于为脚本或程序的不同值和阶段执行重复命令。因此,我们不必重复整个代码段,而是可以创建一个执行特定命令的函数。这些函数的定义使代码更易于阅读,并有助于使代码尽可能简短。重要的是要注意,函数必须始终在逻辑上定义before第一次调用,因为脚本也是从上到下处理的。因此,函数的定义总是脚本的at the beginning。有两种方法来定义函数:
Method 1 - Functions
Code:
function name { |
Method 2 - Functions
Code:
name() { |
我们可以选择一种方法来定义一个最适合我们的函数。在我们的CIDR.sh脚本中,我们使用第一种方法,因为使用关键字“function”更容易阅读。”
CIDR.sh
Code:
<SNIP> |
函数只能通过调用函数的指定名称来调用,正如我们在case语句中看到的那样。
Function Execution - CIDR.sh
<SNIP> |
Parameter Passing
这样的函数应该被设计为可以与固定的值结构一起使用,或者至少只能与固定的格式一起使用。就像我们已经在CIDR.sh脚本中看到的那样,我们使用了函数“network_range“的IP地址格式。参数是可选的,因此我们可以在没有参数的情况下调用函数。原则上,传递给shell脚本的参数与传递给shell脚本的参数相同。这些是$1 - $9(${n}),或者我们已经看到的$variable。每个函数都有自己的一组参数。因此它们不会与其他函数的参数或shell脚本的参数冲突。
bash脚本和其他编程语言之间的一个重要区别是,所有定义的变量总是被处理globally,除非“local”另有声明。这意味着我们第一次在函数中定义变量时,我们将在主脚本中(在函数外部)调用它。将参数传递给函数的方式与我们将参数传递给脚本的方式相同,看起来像这样:
PrintPars.sh
Code: 验证码: bash
|
mikannse7@htb[/htb]$ ./PrintPars.sh |
Return Values
当我们启动一个新进程时,每个child process(例如,执行脚本中的function)在其终止时向return code(我们通过其执行脚本的parent process)返回一个bash shell,通知它执行的状态。此信息用于确定进程是否成功运行或是否发生特定错误。基于此信息,parent process可以决定进一步的程序流。
| Return Code返回代码 | Description描述 |
|---|---|
1 |
General errors一般错误 |
2 |
Misuse of shell builtins错误使用shell内置程序 |
126 1 |
Command invoked cannot execute调用的命令无法执行 |
127 1 |
Command not found |
128 1 |
Invalid argument to exit要退出的参数无效 |
128+n 1 |
Fatal error signal “n“ 致命错误信号“n” |
130 1 |
Script terminated by Control-C脚本由Control-C终止 |
255\* 1 |
Exit status out of range退出状态超出范围 |
To get the value of a function back, we can use several methods like return, echo, or a variable. In the next example, we will see how to use “$?“ to read the “return code,” how to pass the arguments to the function and how to assign the result to a variable.
要获取函数的值,我们可以使用几种方法,如return,echo或variable。在下一个例子中,我们将看到如何使用“$?”读取“return code”,如何将参数传递给函数以及如何将结果赋给变量。
Return.sh
Code: 验证码: bash
|
Return.sh - Execution Return.sh执行
Return.sh - ExecutionReturn.sh执行
mikannse7@htb[/htb]$ ./Return.sh |
Debugging
Bash为我们提供了一个很好的机会来查找、跟踪和修复代码中的错误。debugging这个词可以有很多不同的含义。然而,Bash调试是从我们的代码中删除错误(bug)的过程。可以以许多不同的方式执行搜索。例如,我们可以使用我们的代码进行调试以检查拼写错误,或者我们可以使用它进行代码分析以跟踪它们并确定为什么会发生特定的错误。
这个过程也用于查找程序中的漏洞。例如,我们可以尝试使用不同的输入类型来引起错误,并通过汇编程序跟踪它们在CPU中的处理,这可能提供一种方法来操纵这些错误的处理,以插入我们自己的代码并强制系统执行它。这个主题将在其他模块中详细讨论。Bash允许我们使用“-x”(xtrace)和“-v”选项来调试代码。现在让我们看一个使用CIDR.sh脚本的例子。
CIDR.sh - Debugging
mikannse7@htb[/htb]$ bash -x CIDR.sh |
在这里,Bash精确地向我们展示了哪个函数或命令是用哪个值执行的。这由行开头的加号(+)表示。如果我们想查看某个特定函数的所有代码,我们可以设置“-v”选项,以更详细地显示输出。
CIDR.sh - Verbose Debugging CIDR.sh详细说明
CIDR.sh - Verbose DebuggingCIDR.sh详细说明
mikannse7@htb[/htb]$ bash -x -v CIDR.sh |
与正常调试相比,我们看到到目前为止已经处理的整个代码段,然后是已经执行的各个步骤。
