某些小知识记录(持续更新)
本文最后更新于:2023年10月29日 晚上
利用checksec可以猜测题目大致考的方向
整理
没有NX,自带系统命令,直接返回地址,ret2text
没有NX,找不到system(),有可执行段,可写入shellcode,ret2shellcode
(mprotected(,,7)有对某段地址提高权限的能力,可以写入shellcode)
可以有NX,找得到system()的plt的绝对地址,没有binsh,ret2libc2
有NX,利用ROPgadget配合int 0x80调用execve,ret2syscall
有NX,利用Libc获取system(),binsh相对位置,ret2libc
未整理
注意堆栈平衡
关于alarm()反调试的绕过
目的仅为反调试的绕过方法,而非反过来利用
alarm函数被引用于unistd.h头文件,其函数原型为unsigned int alarm(unsigned int seconds)
alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理终止进程。
绕过方法:
以文本编辑器打开,搜索alarm并将其修改为isnan
关于strlen()检查字符串长度的绕过
*C库函数 size_t strlen(const char str)计算字符串str的长度,直到首个空结束字符(\000),但不包括空结束字符。
例:
char str[7]={‘1’,’2’,’3’,’\000’,’4’,’5’};
strlen(str)的返回值为3
如果文件存在对输入message长度的检查,可以利用’\000’字符绕过。
1 |
|
第一个strlen打印16,第二个打印3。(均为正常输出)
即在使用strlen的情况下,填入’\000’能够有效保护后面的字符。
使用编译器为MinGW4.9.2-tdm64-3、TDM-GCC4.9.2-tdm-3(x86),对应IDE为BloodShed Dev-C++5.11,保证str所在栈后后缓冲区且能够正常填入’\000’(无论是在x86还是x86-64条件下均正常)。
利用atoi(),并检测首字符的情况
atoi()函数会尽量去除字符串开头的所有空格,则可在字符串开头添加一个或多个空格。
利用strtol函数去控制gadget
1 |
|
该函数会在处理str字符串时截断在第一个不能转化为数字的位置,并将剩余部分用’\x00’做截断,将endptr参数赋值为str被截断位置下一个字符地址,base为基数。
该函数在处理字符串过程中,会将处理后的数字后面填’\x00’后放入rdi寄存器中,同时rax会被赋值为返回值,由此便可控制rax与rdi寄存器。
一些溢出类型
char*(定长的string)型溢出
大多为使用:
scanf(“%s”,buf);
gets(buf);
这种类型的溢出利用了函数调用后由于栈溢出造成的溢出到r,造成返回到输入地址的情况。
我们可以使用fgets()函数去避免既没有栈保护,也没有输入限制的情况。
数据(多为int)型溢出
不多说:
1 |
|
以上整形均与编译环境有关,如当为16位(DOS)环境时,int范围为-32768 ~ 32767。WIN32[x86(-64)]中为-2147483648 ~ 2147483637.
1 |
|
C Data Type | Typical 32-bit | Typical 64-bit | x86-64 |
---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | - | - | 10/16 |
pointer | 4 | 8 | 8 |
溢出形式:
mov ax,FFFFH ;ax = 1111 1111
sub ax,1 ;ax = (1)0000 0000,全部被推出去
浮点数转十六进制
在线进制转换-IEE754浮点数16进制转换 (lostphp.com)
之后补算法类型工具。
大端法,小端法
x86架构大多使用小端法,即地址顺序由大到小。
栈 与 堆
栈
Stack
栈地址从高到低,运用栈寄存器ESP和EBP去限制初始化的栈空间大小。
SS为栈的段地址寄存器,SP为栈的偏移地址寄存器。
堆
heap
堆地址从低到高,大多为后申请的空间。
关于段地址的一些小内容
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
BSS段
BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量(Global variables)和静态变量(Static variables)的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。
可执行程序包括BSS段、数据段、代码段(也称文本段)。
UNIX下可使用size命令查看可执行文件的段大小信息。
关于ROP部分的部分理解
ret2text
如
system("/bin/sh");
其对应汇编为
1 |
|
call system语句等价于
1 |
|
call指令一般都是5字节长度,所以这里保存的是下一条指令的地址。
栈溢出控制rip/eip,使IP直接指向,其实就相当于jmp system语句少了保存地址,所以我们要填一个返回地址给它。
(但是如果是调用system(“/bin/sh”)后就直接getshell了,返回地址是什么都无所谓,这种题目填0xdeadbeef来开小玩笑,后面才会用到)
以ctf-wiki上的ret2text题目为例,这种payload一般可以构造为:
payload = b'a'*offset + p32(target_addr)
ret2shellcode
ret2shellcode类型题没有调用system函数,没有程序自带的后门,需要自己写(或用shellcraft等工具辅助构造)shellcode(指用于完成某个功能的汇编代码),并让程序执行这段shellcode(即所写入区域必须要有可读可写可执行权限,一般是BSS段),getshell。
以ctf-wiki上的ret2shellcode为例,这种payload一般可以构造为:
1 |
|
ljust方法将原字符串左对齐,并使用第二个参数填充至指定长度,并返回新的字符串。
ret2shellcode的原理相当于利用汇编语言写了一段system(“/bin/sh”)的函数并且通过被ret2到而执行。
shellcode的构造受架构、系统、操作系统位数影响,使用时应在exp前部加上对应的内容,如下:
1 |
|
1 |
|
ret2syscall
ret2syscall是控制程序执行系统调用,getshell。
1 |
|
以上内容摘自系统调用 - 维基百科,自由的百科全书 (wikipedia.org)。
简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell(x86)。
execve("/bin/sh",NULL,NULL)
其中,该程序是 32 位,所以我们需要使得
- 系统调用号,即 eax 应该为 0xb
- 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
- 第二个参数,即 ecx 应该为 0
- 第三个参数,即 edx 应该为 0
ret2syscall类型题是我们第一次使用ROPgadgets工具,因为本次我们需要控制这些寄存器的值。
比如说,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。
ret2syscall类型题非常明显,多为静态编译,或者存在int 0x80(x86),或者存在syscall(x86_64),并且会非常常需要去搜找一些必要的gadget并控制加以利用。
所以首先我们需要寻找控制eax的gadgets
ROPgadget –binary ret2syscall –only “pop|ret” | grep “eax”
也可以用类似的语句来进行查询ebx等,但由于ebx->/bin/sh, ecx->0, edx->0,可以在查询一个寄存器的同时在表中找到相关的gadgets
ROPgadget –binary ret2syscall –only “pop|ret” | grep “ebx”
以ctf-wiki上的ret2syscall为例,可以选择
0x0806eb90 : pop edx ; pop ecx ; pop ebx; ret
ret2syscall类型题需要获得”/bin/sh”字符串对应地址,int 0x80的地址,那么还需要ROPgadget去查找到其位置
ROPgadget –binary ret2syscall –string “int”
以CTF-wiki上的例题为例,该题的payload可构造为:
payload = b'a' * offset + p32(pop_eax_ret) + p32(0xb) + p32(pop_edcbx_ret) + p32(0) + p32(0) + p32(binsh_addr) + p32(int_addr)
如果原题目中没有“/bin/sh”字符串,则应先调用eax为3的read函数,再去构造eax为0xb的system函数。注意,每当需要调用一个函数的时候,最后都应返回到一次“int 0x80”。
注:公共syscall函数文档 x86_syscall、x86_64_syscall、arm_syscall、arm64_syscall
x86常见syscall及调用形式
SYSCALL NAME | eax | ebx | ecx | edx | esi | edi | ebp |
---|---|---|---|---|---|---|---|
read | 3 | unsigned int fd | char *buf | size_t count | - | - | - |
write | 4 | unsigned int fd | const char *buf | size_t count | - | - | - |
open | 5 | const char *filename | int flags | umode_t mode | - | - | - |
execve | 11(B) | const char *filename | const char *const *argv | const char *const *envp | - | - | - |
mprotect | 125(7D) | unsigned long start | size_t len | unsigned long prot | - | - | - |
x86_64常见syscall及调用形式
SYSCALL NAME | rax | rdi | rsi | rdx | r10 | r8 | r9 |
---|---|---|---|---|---|---|---|
read | 0 | unsigned int fd | char *buf | size_t count | - | - | - |
write | 1 | unsigned int fd | const char *buf | size_t count | - | - | - |
open | 2 | const char *filename | int flags | umode_t mode | - | - | - |
execve | 59(3B) | const char *filename | const char *const *argv | const char *const *envp | - | - | - |
mprotect | 10(A) | unsigned long start | size_t len | unsigned long prot | - | - | - |
arm常见syscall及调用形式
SYSCALL NAME | r7 | r0 | r1 | r2 | r3 | r4 | r5 |
---|---|---|---|---|---|---|---|
read | 3 | unsigned int fd | char *buf | size_t count | - | - | - |
write | 4 | unsigned int fd | const char *buf | size_t count | - | - | - |
open | 5 | const char *filename | int flags | umode_t mode | - | - | - |
execve | 11(B) | const char *filename | const char *const *argv | const char *const *envp | - | - | - |
mprotect | 125(7D) | unsigned long start | size_t len | unsigned long prot | - | - | - |
arm64常见syscall及调用形式
SYSCALL NAME | x8 | x0 | x1 | x2 | x3 | x4 | x5 |
---|---|---|---|---|---|---|---|
read | 63(3F) | unsigned int fd | char *buf | size_t count | - | - | - |
write | 64(40) | unsigned int fd | const char *buf | size_t count | - | - | - |
openat | 56(38) | int dfd | const char *filename | int flags | umode_t mode | - | - |
execve | 11(B) | const char *filename | const char *const *argv | const char *const *envp | - | - | - |
mprotect | 226(E2) | unsigned long start | size_t len | unsigned long prot | - | - | - |
libc是什么
libc为Linux下原来的标准ANSI C库。是基本的C语言函数库,包含了C语言最基本的库函数。
那么ret2libc类型题即是利用库函数和”/bin/sh”字段去getshell。
ret2libc类型题始终绕不开利用libc库plt表(中的函数)与”/bin/sh”去getshell的过程。
ret2libc类型题主要利用了动态链接,这是与ret2syscall不同的一点,静态链接大可能是ret2syscall。
plt表与got表
如当调用printf函数时,先去plt表和got表寻找printf函数的真实地址。plt表指向got表中的地址,got表指向glibc中的地址。
当首次调用函数时,过程为:plt->got->plt->公共plt->动态连接器_dl_runtime_resolve->找到函数地址。其中,_dl_runtime_resolve函数作用为查找函数地址并返回给got表。
之后调用时,过程为:plt->got->直接获取函数地址,因为此时got表已经记录函数地址。
[StormQ’s Blog (csstormq.github.io)](https://csstormq.github.io/blog/计算机系统篇之链接(14):.plt、.plt.got、.got 和 .got.plt section 之间的区别)
ret2libc1类型题
有/bin/sh,有system函数,但system函数并没有直接调用/bin/sh字符串
此类型题源码可能为
1 |
|
很明显,我们需要在BSS段中找到”/bin/sh”所在地址和system函数所在PLT表中的地址。即先填充数组栈空间,返回到PLT表中system函数的位置。这里注意,system函数调用时会有一个对应的返回地址,所以我们需要在调用system函数后首先任意填充(32/64) / 8 = (4/8)个字节的字符,再填充一个/bin/sh所在地址作为system函数的参数。
因此,此类型的32位payload编写一般为
payload = b’a’*offset + p32(system_plt) + p32(0xdeadbeef) + p32(binsh_addr)
ret2libc2类型题
无/bin/sh,有system函数
此类型题源码可能为
1 |
|
注意,如下两种解法都是需要后续再次自行sendline(“/bin/sh”)或自行输入”/bin/sh”的,原理均为返回到输入的函数并调用该函数,使其读入输入缓冲区中的下次输入内容。
32位:
解法1:首先填充原程序通过read函数读的栈空间,先造成一次栈溢出,本次栈溢出的返回地址为read函数的地址,我们需要利用第二次的read函数去读下一次输入(*即下一次sendline的内容)并将其内容保存到一个可读可写(不需要可执行)段中的一个寄存器中,read函数也存在一个返回地址,这里首先填充函数的开头去重新执行整个程序,接下来填充read函数的三个参数,fd、读入数组变量所在首地址、最大读入数据量(如果是scanf函数或者gets函数则不需要fd、读入字量这两个参数的构造),最后是填充上返回到system的地址,再占用system函数的返回地址,最后填入system函数调用的地址。
以ctf-wiki上的ret2libc2为例(使用gets函数),该种payload在栈中部署形态为:
该段内容选自好好说话之ret2libc2_hollk的博客-CSDN博客,略作自己可以看得懂的修改。
1 |
|
payload = flat([b'a'*112, gets_plt, pop_ebx_ret, buf2, system_plt, 0xdeadbeef, buf2])
若使用read函数,其payload可以写为
payload = 'a'*112 + p32(read_plt) + p32(pop_ebx_ret) + p32(0) + p32(buf2) + p32(0x100) + p32(system_plt) + p32(0xdeadbeef) + p32(buf2)
pop_ebx_ret仅作为一个使指针上移的寄存器,弹给任何一个寄存器都没关系,只需要有返回。
ROPgadget –binary ret2libc2 –only “pop|ret”
解法2:首先填充原程序通过read函数读的栈空间,先造成一次栈溢出,本次栈溢出的返回地址为read函数的地址,我们需要利用第二次的read函数去读下一次输入(*即下一次sendline的内容)并将其内容保存到一个可读可写(不需要可执行)段中的一个寄存器中,read函数也存在一个返回地址,此时我们让read函数直接指向system函数的地址,接下来填入read函数的三个参数(fd、数组变量首地址、最大读入字量)(如果是scanf函数或者gets函数则不需要fd、读入字量这两个参数的构造),最后填入system函数调用的变量地址。
以ctf-wiki上的ret2libc2为例(使用gets函数),该种payload在栈中部署形态(ROP链)为:
1 |
|
payload = 'a'*112 + p32(gets_plt) + p32(system_plt) + p32(buf2) + p32(buf2)
若使用read函数,其payload可以写为
payload = flat(['a'*112, read_plt, system_plt, 0x00, buf2, 0x100, buf2])
patchelf
patchelf --set-interpreter ./glibc-all-in-one-master/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so --set-rpath ./glibc-all-in-one-master/libs/2.31-0ubuntu9.2_amd64 target_file
patchelf --set-interpreter ./glibc-all-in-one-master/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so --replace-needed libc.so.6 ./glibc-all-in-one-master/libs/2.31-0ubuntu9.2_amd64/libc.so.6 target_file
sudo ln ld-2.26.so /lib64/ld-2.26.so #然后设置解释器和libc,这样就能进行debug #设置解释器 patchelf --set-interpreter /lib64/ld-2.26.so ./gundam #设置libc patchelf --replace-needed libc.so.6 ~/2.26-0ubuntu2_amd64/libc-2.26.so ./gundam
p = process(['/home/xuehuzhou/2.27-3ubuntu1_amd64/ld-2.27.so', './gundam'], env={"LD_PRELOAD":'/home/xuehuzhou/2.27-3ubuntu1_amd64/libc.so.6'})
cp -r ~/ctf2021/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/.debug/ ./debug
set debug-file-directory debug/