diff --git a/doc/2.8_zio.md b/doc/2.8_zio.md index f62b9b0..1b4a903 100644 --- a/doc/2.8_zio.md +++ b/doc/2.8_zio.md @@ -3,7 +3,7 @@ - [zio 简介](#zio-简介) - [安装](#安装) - [使用方法](#使用方法) -- [zio 在 CTF 中的应用](#zio-在-CTF-中的应用) +- [zio 在 CTF 中的应用](#zio-在-ctf-中的应用) ## zio 简介 diff --git a/doc/5.3_angr.md b/doc/5.3_angr.md index af6b698..c3a886e 100644 --- a/doc/5.3_angr.md +++ b/doc/5.3_angr.md @@ -40,9 +40,8 @@ $ sudo python setup.py install 其他几个 angr 官方库的安装也是如此。 -#### 一些`import angr`可能出现的问题 +#### 一些 `import angr` 可能出现的问题 如果你在安装angr之后,进入python环境,在import之后有这样的报错: - ```python Python 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609] on linux2 diff --git a/doc/6.1.3_pwn_xdctf2015_pwn200.md b/doc/6.1.3_pwn_xdctf2015_pwn200.md index acd73ea..767de48 100644 --- a/doc/6.1.3_pwn_xdctf2015_pwn200.md +++ b/doc/6.1.3_pwn_xdctf2015_pwn200.md @@ -331,7 +331,7 @@ PLT[0] 处的代码将 GOT[1] 的值压入栈中,然后跳转到 GOT[2]。这 `_dl-runtime-resolve` 的过程如下图所示: -![](../pic/6.3_dl-resolve.png) +![](../pic/6.1.3_dl-resolve.png) 重定位项使用 Elf_Rel 结构体来描述,存在于 `.rep.plt` 段和 `.rel.dyn` 段中: ```C @@ -626,14 +626,14 @@ _dl_fixup ( #### 攻击 关于延迟绑定的攻击,在于强迫动态装载器解析请求的函数。 -![](../pic/6.3_attack.png) +![](../pic/6.1.3_attack.png) - 图a中,因为动态转载器是从 `.dynamic` 段的 `DT_STRTAB` 条目中获得 `.dynstr` 段的地址的,而 `DT_STRTAB` 条目的位置已知,默认情况下也可写。所以攻击者能够改写 `DT_STRTAB` 条目的内容,欺骗动态装载器,让它以为 `.dynstr` 段在 `.bss` 段中,并在那里伪造一个假的字符串表。当它尝试解析 printf 时会使用不同的基地址来寻找函数名,最终执行的是 execve。这种方式非常简单,但仅当二进制程序的 `.dynamic` 段可写时有效。 - 图b中,我们已经知道 `_dl_runtime_resolve` 的第二个参数是 Elf_Rel 条目在 `.rel.plt` 段中的偏移,动态装载器将这个值加上 `.rel.plt` 的基址来得到目标结构体的绝对位置。然后当传递给 `_dl_runtime_resolve` 的参数 `reloc_index` 超出了 `.rel.plt` 段,并最终落在 `.bss` 段中时,攻击者可以在该位置伪造了一个 `Elf_Rel` 结构,并填写 `r_offset` 的值为一个可写的内存地址来将解析后的函数地址写在那里,同理 `r_info` 也会是一个将动态装载器导向到攻击者控制内存的下标。这个下标就指向一个位于它后面的 `Elf_Sym` 结构,而 `Elf_Sym` 结构中的 `st_name` 同样超出了 `.dynsym` 段。这样这个符号就会包含一个相对于 `.dynstr` 地址足够大的偏移使其能够达到这个符号之后的一段内存,而那段内存里保存着这个将要调用的函数的名称。 还记得我们前面说过的 GOT[1],它是一个 link_map 类型的指针,其 `l_info` 域中有一个包含 `.dynmic` 段中所有条目构成的数组。动态链接器就是利用这些指针来定位符号解析过程中使用的对象的。通过覆盖这个 link\_map 的一部分,就能够将 `l_info` 域中的 `DT_STRTAB` 条目指向一个特意制造的动态条目,那里则指向一个假的动态字符串表。 -![](../pic/6.3_link_map.png) +![](../pic/6.1.3_link_map.png) #### pwn200 获得了 re2dl-resolve 所需的所有知识,下面我们来分析题目。 diff --git a/doc/6.1.4_pwn_backdoorctf2017_fun_signals.md b/doc/6.1.4_pwn_backdoorctf2017_fun_signals.md index 5c052f2..a946419 100644 --- a/doc/6.1.4_pwn_backdoorctf2017_fun_signals.md +++ b/doc/6.1.4_pwn_backdoorctf2017_fun_signals.md @@ -18,7 +18,7 @@ #### signal 机制 -![](../pic/6.4_signal.png) +![](../pic/6.1.4_signal.png) 如图所示,当有中断或异常产生时,内核会向某个进程发送一个 signal,该进程被挂起并进入内核(1),然后内核为该进程保存相应的上下文,然后跳转到之前注册好的 signal handler 中处理相应的 signal(2),当 signal handler 返回后(3),内核为该进程恢复之前保存的上下文,最终恢复进程的执行(4)。 diff --git a/doc/6.2.3_re_codegate2017_angrybird.md b/doc/6.2.3_re_codegate2017_angrybird.md index ca5f673..26c1100 100644 --- a/doc/6.2.3_re_codegate2017_angrybird.md +++ b/doc/6.2.3_re_codegate2017_angrybird.md @@ -5,9 +5,234 @@ ## 题目解析 +看题目就知道,这是一个会让我们抓狂的程序,事实也确实如此。 ``` $ file angrybird angrybird: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=089c3a14bcd7ffb08e94645cea46f1162b171445, stripped ``` +``` +$ ./angrybird +$ +``` +一运行就退出,应该是需要程序流上有问题。 + +`main` 函数的开头有一些坑需要 patch,才能使程序正常运行,然后经过很多很多轮的运算和判断,可以看到 main 函数长达 18555 行: +``` +[0x00400600]> pd 60 @ main +/ (fcn) main 18555 +| main (); +| : ; DATA XREF from 0x0040061d (entry0) +| : 0x00400761 55 push rbp +| : 0x00400762 4889e5 mov rbp, rsp +| : 0x00400765 4883c480 add rsp, 0xffffffffffffff80 +| : 0x00400769 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40 +| : 0x00400772 488945f8 mov qword [local_8h], rax +| : 0x00400776 31c0 xor eax, eax ; 将 eax 置 0 +| : 0x00400778 83f800 cmp eax, 0 ; 比较 eax 和 0 +| `=< 0x0040077b 0f845ffeffff je sym.imp.exit ; eax == 0 时退出,所以需要将 je 换成 jne,或者把上一行的 0 换成 1 +| 0x00400781 48c745901860. mov qword [local_70h], reloc.strncmp_24 ; 0x606018 +| 0x00400789 48c745982060. mov qword [local_68h], reloc.puts_32 ; 0x606020 +| 0x00400791 48c745a02860. mov qword [local_60h], reloc.__stack_chk_fail_40 ; 0x606028 +| 0x00400799 48c745a83860. mov qword [local_58h], reloc.__libc_start_main_56 ; 0x606038 +| 0x004007a1 b800000000 mov eax, 0 +| 0x004007a6 e84bffffff call sub.you_should_return_21_not_1_:__6f6 ; 该函数中需要返回 21 +| 0x004007ab 89458c mov dword [local_74h], eax ; [local_74] = 21 +| 0x004007ae b800000000 mov eax, 0 +| 0x004007b3 e854ffffff call sub.stack_check_70c ; 栈检查函数,直接 nop 掉,或者进入函数修改逻辑 +| 0x004007b8 b800000000 mov eax, 0 +| 0x004007bd e868ffffff call sub.hello_72a +| 0x004007c2 488b15a75820. mov rdx, qword [obj.stdin] ; [0x606070:8]=0 ; 从标准输入读入 +| 0x004007c9 8b4d8c mov ecx, dword [local_74h] +| 0x004007cc 488d45b0 lea rax, [local_50h] +| 0x004007d0 89ce mov esi, ecx ; esi = 21 +| 0x004007d2 4889c7 mov rdi, rax +| 0x004007d5 e8f6fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream) ; patch 成功后就能调用 fgets +| 0x004007da 0fb655b0 movzx edx, byte [local_50h] ; 读入的第一个字符 ; fgets 的下一个地址 +| 0x004007de 0fb645b1 movzx eax, byte [local_4fh] ; 读入的第二个字符 +| 0x004007e2 31d0 xor eax, edx +| 0x004007e4 8845d0 mov byte [local_30h], al +| 0x004007e7 0fb645d0 movzx eax, byte [local_30h] +| 0x004007eb 3c0f cmp al, 0xf ; 15 ; 对处理后的输入字符做判断 +| ,=< 0x004007ed 7f14 jg 0x400803 ; 若不满足条件,跳转失败,程序退出 +| | 0x004007ef bf94504000 mov edi, str.melong ; 0x405094 ; "melong" +| | 0x004007f4 e897fdffff call sym.imp.puts ; int puts(const char *s) +| | 0x004007f9 bf01000000 mov edi, 1 +| | 0x004007fe e8ddfdffff call sym.imp.exit ; void exit(int status) +| | ; JMP XREF from 0x004007ed (main) +| `-> 0x00400803 0fb655b0 movzx edx, byte [local_50h] ; 第二轮运算 +| 0x00400807 0fb645b1 movzx eax, byte [local_4fh] +| 0x0040080b 21d0 and eax, edx +| 0x0040080d 8845d0 mov byte [local_30h], al +| 0x00400810 0fb645d0 movzx eax, byte [local_30h] +| 0x00400814 3c50 cmp al, 0x50 ; 'P' ; 80 +| ,=< 0x00400816 7e14 jle 0x40082c +| | 0x00400818 bf94504000 mov edi, str.melong ; 0x405094 ; "melong" +| | 0x0040081d e86efdffff call sym.imp.puts ; int puts(const char *s) +| | 0x00400822 bf01000000 mov edi, 1 +| | 0x00400827 e8b4fdffff call sym.imp.exit ; void exit(int status) +| | ; JMP XREF from 0x00400816 (main) +| `-> 0x0040082c c645d000 mov byte [local_30h], 0 ; 第三轮运算 +| 0x00400830 0fb645d0 movzx eax, byte [local_30h] +| 0x00400834 3c01 cmp al, 1 ; 1 +| ,=< 0x00400836 7e14 jle 0x40084c +| | 0x00400838 bf94504000 mov edi, str.melong ; 0x405094 ; "melong" +| | 0x0040083d e84efdffff call sym.imp.puts ; int puts(const char *s) +| | 0x00400842 bf01000000 mov edi, 1 +| | 0x00400847 e894fdffff call sym.imp.exit ; void exit(int status) +| | ; JMP XREF from 0x00400836 (main) +| `-> 0x0040084c 0fb655c2 movzx edx, byte [local_3eh] ; 第 n 轮运算 +| 0x00400850 0fb645b1 movzx eax, byte [local_4fh] +| 0x00400854 21d0 and eax, edx +| 0x00400856 8845d0 mov byte [local_30h], al +| 0x00400859 0fb645d0 movzx eax, byte [local_30h] +``` +第一处 patch,将指令 `je` 改成 `jne`: +``` +[0x00400600]> s 0x0040077b +[0x0040077b]> pd 1 +| `=< 0x0040077b 0f845ffeffff je sym.imp.exit +[0x0040077b]> wx 0f85 +[0x0040077b]> pd 1 +| `=< 0x0040077b 0f855ffeffff jne sym.imp.exit +``` +第二处 patch,函数 `sub.you_should_return_21_not_1_:__6f6`: +``` +[0x0040077b]> pdf @ sub.you_should_return_21_not_1_:__6f6 +/ (fcn) sub.you_should_return_21_not_1_:__6f6 22 +| sub.you_should_return_21_not_1_:__6f6 (); +| ; CALL XREF from 0x004007a6 (main) +| 0x004006f6 55 push rbp +| 0x004006f7 4889e5 mov rbp, rsp +| 0x004006fa bf64504000 mov edi, str.you_should_return_21_not_1_:_ ; 0x405064 ; "you should return 21 not 1 :(" +| 0x004006ff e88cfeffff call sym.imp.puts ; int puts(const char *s) +| 0x00400704 8b0556592000 mov eax, dword [0x00606060] ; [0x606060:4]=1 ; 修改 [0x606060:4] = 21 = 0x15 +| 0x0040070a 5d pop rbp +\ 0x0040070b c3 ret +[0x0040077b]> ?v 21 +0x15 +[0x0040077b]> s 0x00606060 +[0x00606060]> px 16 +- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF +0x00606060 0100 0000 0000 0000 0000 0000 0000 0000 ................ +[0x00606060]> wx 15 +[0x00606060]> px 16 +- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF +0x00606060 1500 0000 0000 0000 0000 0000 0000 0000 ................ +``` +第三处 patch,将调用 `sub.stack_check_70c` 的指令直接 `nop` 掉: +``` +[0x00606060]> pdf @ sub.stack_check_70c +/ (fcn) sub.stack_check_70c 30 +| sub.stack_check_70c (); +| : ; CALL XREF from 0x004007b3 (main) +| : 0x0040070c 55 push rbp +| : 0x0040070d 4889e5 mov rbp, rsp +| : 0x00400710 bf82504000 mov edi, str.stack_check ; 0x405082 ; "stack check" +| : 0x00400715 e876feffff call sym.imp.puts ; int puts(const char *s) +| : 0x0040071a 678b0424 mov eax, dword [esp] +| : 0x0040071e 83f800 cmp eax, 0 +| `=< 0x00400721 0f85b9feffff jne sym.imp.exit +| 0x00400727 90 nop +| 0x00400728 5d pop rbp +\ 0x00400729 c3 ret +[0x00400600]> s 0x004007b3 +[0x004007b3]> pd 1 +| 0x004007b3 e854ffffff call sub.stack_check_70c +[0x004007b3]> wx 9090909090 +[0x004007b3]> pd 5 +| 0x004007b3 90 nop +| 0x004007b4 90 nop +| 0x004007b5 90 nop +| 0x004007b6 90 nop +| 0x004007b7 90 nop +``` +第四处 patch 是将 `sub.hello_72a` 函数中的 `je` 改成 `jne`: +``` +[0x0040077b]> pdf @ sub.hello_72a +/ (fcn) sub.hello_72a 55 +| sub.hello_72a (); +| ; var int local_8h @ rbp-0x8 +| ; CALL XREF from 0x004007bd (main) +| 0x0040072a 55 push rbp +| 0x0040072b 4889e5 mov rbp, rsp +| 0x0040072e 4883ec10 sub rsp, 0x10 +| 0x00400732 48c745f83860. mov qword [local_8h], reloc.__libc_start_main_56 ; 0x606038 +| 0x0040073a 488b45f8 mov rax, qword [local_8h] +| 0x0040073e ba05000000 mov edx, 5 +| 0x00400743 be8e504000 mov esi, str.hello ; 0x40508e ; "hello" +| 0x00400748 4889c7 mov rdi, rax +| 0x0040074b e830feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n) ; 如果相等则返回 0 +| 0x00400750 85c0 test eax, eax +| ,=< 0x00400752 740a je 0x40075e ; 如果 eax 为 0,则跳转 +| | 0x00400754 bf01000000 mov edi, 1 +| | 0x00400759 e882feffff call sym.imp.exit ; void exit(int status) +| | ; JMP XREF from 0x00400752 (sub.hello_72a) +| `-> 0x0040075e 90 nop +| 0x0040075f c9 leave +\ 0x00400760 c3 ret +``` + +这样程序的运行就正常了,它从标准输入读入字符,进行一系列的判断,由于程序执行流非常长,我们不可能一个一个地去 patch。radare2 里输入命令 `VV @ main` 可以看到下面的东西: + +![](../pic/6.2.3_graph.png) + +使用 angr 来解决它,指定好目标地址,让它运行到那儿,在大多数情况下,这种方法都是有效的。 +``` +[0x00400761]> pd -20 @ main+18555 +| 0x00404f8e d00f ror byte [rdi], 1 +| 0x00404f90 b645 mov dh, 0x45 ; 'E' ; 69 +| 0x00404f92 d03c78 sar byte [rax + rdi*2], 1 +| ,=< 0x00404f95 7e14 jle 0x404fab +| | 0x00404f97 bf94504000 mov edi, str.melong ; 0x405094 ; "melong" +| | 0x00404f9c e8efb5ffff call sym.imp.puts ; int puts(const char *s) +| | 0x00404fa1 bf01000000 mov edi, 1 +| | 0x00404fa6 e835b6ffff call sym.imp.exit ; void exit(int status) +| | ; JMP XREF from 0x00404f95 (main) +| `-> 0x00404fab 488d45b0 lea rax, [local_50h] +| 0x00404faf 4889c6 mov rsi, rax +| 0x00404fb2 bf9b504000 mov edi, str.you_typed_:__s_n ; 0x40509b ; "you typed : %s\n" +| 0x00404fb7 b800000000 mov eax, 0 +| 0x00404fbc e8efb5ffff call sym.imp.printf ; int printf(const char *format) +| 0x00404fc1 b800000000 mov eax, 0 ; 选择一个目标地址 +| 0x00404fc6 488b4df8 mov rcx, qword [local_8h] +| 0x00404fca 6448330c2528. xor rcx, qword fs:[0x28] +| ,=< 0x00404fd3 7405 je 0x404fda +| | 0x00404fd5 e8c6b5ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void) +| | ; JMP XREF from 0x00404fd3 (main) +| `-> 0x00404fda c9 leave +\ 0x00404fdb c3 ret +``` +``` +[0x00400600]> is~puts +vaddr=0x00400590 paddr=0x00000590 ord=002 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.puts +``` +完整的 exp 如下: +```python +import angr + +main = 0x004007da +find = 0x00404fc1 +avoid = 0x00400590 # puts@plt + +p = angr.Project('./angrybird_mod') +init = p.factory.blank_state(addr=main) +pg = p.factory.simgr(init, threads=4) +ex = pg.explore(find=find, avoid=avoid) + +final = ex.found[0].state +flag = final.posix.dumps(0) + +print "Flag:", final.posix.dumps(1) +``` + +Bingo!!!(不能保证每次都有效,多试几次) +``` +$ python2 exp.py +WARNING | 2017-12-03 17:33:58,544 | angr.state_plugins.symbolic_memory | Concretizing symbolic length. Much sad; think about implementing. +Flag: you typed : Im_so_cute&pretty_:)� +``` + +其他文件在 [github](../src/writeup/6.2.3_re_codegate2017_angrybird) 相应文件夹中。 + ## 参考资料 diff --git a/pic/6.3_attack.png b/pic/6.1.3_attack.png similarity index 100% rename from pic/6.3_attack.png rename to pic/6.1.3_attack.png diff --git a/pic/6.3_dl-resolve.png b/pic/6.1.3_dl-resolve.png similarity index 100% rename from pic/6.3_dl-resolve.png rename to pic/6.1.3_dl-resolve.png diff --git a/pic/6.3_link_map.png b/pic/6.1.3_link_map.png similarity index 100% rename from pic/6.3_link_map.png rename to pic/6.1.3_link_map.png diff --git a/pic/6.4_signal.png b/pic/6.1.4_signal.png similarity index 100% rename from pic/6.4_signal.png rename to pic/6.1.4_signal.png diff --git a/pic/6.2.3_graph.png b/pic/6.2.3_graph.png new file mode 100644 index 0000000..aeee845 Binary files /dev/null and b/pic/6.2.3_graph.png differ diff --git a/src/writeup/6.2.3_re_codegate2017_angrybird/angrybird_mod b/src/writeup/6.2.3_re_codegate2017_angrybird/angrybird_mod new file mode 100755 index 0000000..c317783 Binary files /dev/null and b/src/writeup/6.2.3_re_codegate2017_angrybird/angrybird_mod differ diff --git a/src/writeup/6.2.3_re_codegate2017_angrybird/angrybird b/src/writeup/6.2.3_re_codegate2017_angrybird/angrybird_org old mode 100644 new mode 100755 similarity index 100% rename from src/writeup/6.2.3_re_codegate2017_angrybird/angrybird rename to src/writeup/6.2.3_re_codegate2017_angrybird/angrybird_org diff --git a/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py b/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py new file mode 100644 index 0000000..83f0aab --- /dev/null +++ b/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py @@ -0,0 +1,15 @@ +import angr + +main = 0x004007da +find = 0x00404fc1 +avoid = 0x00400590 # puts@plt + +p = angr.Project('./angrybird_mod') +init = p.factory.blank_state(addr=main) +pg = p.factory.simgr(init, threads=4) +ex = pg.explore(find=find, avoid=avoid) + +final = ex.found[0].state +flag = final.posix.dumps(0) + +print "Flag:", final.posix.dumps(1)