首页
社区
课程
招聘
[原创]pwnable.kr 解题笔记(一)
发表于: 2023-4-9 11:35 11245

[原创]pwnable.kr 解题笔记(一)

2023-4-9 11:35
11245

本文编写的目的并不是单纯的给出题解,网上已经有数不清的题解了,本文想尽可能详细地根据本人的分析来解释一下每道题目背后蕴含的知识点,作为小白学习的资料,同时记录本人复习的过程。

writeup:

writeup:

分析:
传递的参数是0xdeadbeef,后面检查的是key == 0xcafebabe, 然后在get(overflowme)处,有明显的用户可控的溢出点,这里需要的是,使得输入能够覆盖到func到参数处,需要确定的关键信息有:

有了以上两个数据,就能得到key到func函数参数的距离,然后就可以很简单的构造exp了。

overflowme距离ebp是0x2c,func参数距离ebp是0x8,所以overflowme距离func参数距离是0x2c+0x8.

逆向题,UPX壳,直接upx -d可脱,手动脱壳可以简单描述为三个小向上跳转之后有一个远跳,在那边下断点dump内存即可。
脱壳之后的程序调试一下即可看见flag值。

这里详细一点分析。

首先是scanf()函数,参考RE4B解释一下。

man scanf可以看到如下描述:

The scanf() family of functions scans input according to format as de-scribed below. This format may contain conversion specifications; the results from such conversions, if any, are stored in the locations pointed to by the pointer arguments that follow format. Each pointer argument must be of a type that is appropriate for the value returned by the corresponding conversion specification.

其函数声明如下:

format之后的参数正常情况下应该是一个指向可写的内存地址,如上例中所示,首先定义了int类型的变量x,它存储在栈上,简单调试可以看到存储位置是ebp - 0x10,在调用scanf函数时,是使用lea eax, [ebp-0x10];push eax完成该调用,上述调用可以抽象为:scanf(&[ebp-0x1fb9], &[ebp-0x10])

而在本题中有:

期望的用户输入是一个数据,但是实际上的用户输入的是数据的地址,scanf会取到用户的地址,在上例中,将代码修改为scanf("%d", x);有:


入栈道是x的值而非地址。

静态分析:

开启了canary和栈不可执行。但是没有PIE和FORTIFY。

main函数没什么可分析的。

welcome函数:name距离ebp的偏移为0x70

login函数:passcode1距离ebp的偏移是0x10passcode2距离ebp的偏移是0xc,注意前面说的,
passcode传递的是地址,我们实际上修改的是这个地址上的数据,可以理解为[ebp-0x10] = *passcode1。如果我们可以控制ebp-0x10的值,我们就可以做到任意地址写了(首先在ebp-0x10处写入我们想写的地址,然后通过passcode1写入值,passcode2同理)。

动态调试:

welcome函数和login函数具有相同的ebp,同时有如下栈结构:

name[96]正好在*passcode上,所有在输入name的时候,控制后四位的数据即可控制任意地址写。

那么,怎么做才能执行system("/bin/cat flag")呢?

首先要注意栈不可执行。然后直接写入代码地址是不可行的,因为此时EIP没有办法跳转到代码地址上。
如果想要控制任意地址写到任意执行,必须想办法写入一个EIP一定会跳入的地方。在本题中PLT,GOT都是可写的,在之前的文章有介绍过,PLT表项执行的是jmp GOT的操作,而GOT表项中写入的是对应函数的实际地址,因此这里写题目中的GOT表项即可。

选择fflush即可,需要注意的是scanf接受的数据是%d,即十进制数据。

整理一下:

这里还有其他解法,不过思路是相似的。

分析:
本题利用的是伪随机数来控制生成random来解题。
rand()使用的时候是通过srand()来设置种子,正常的情况下应该在每次使用rand()的时候通过srand重新设置一次seed,但是在本题中并没有这么做,因此每次生成的时候数都是相同的。
同时A ^ B = C ==> A = B ^ C.

图片描述

下断,EAX存储的就是random

key = random ^ value

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
    if(argc<2){
        printf("pass argv[1] a number\n");
        return 0;
    }
    int fd = atoi( argv[1] ) - 0x1234;
    int len = 0;
    len = read(fd, buf, 32);
    if(!strcmp("LETMEWIN\n", buf)){
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }
    printf("learn about Linux file IO\n");
    return 0;
 
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
    if(argc<2){
        printf("pass argv[1] a number\n");
        return 0;
    }
    int fd = atoi( argv[1] ) - 0x1234;
    int len = 0;
    len = read(fd, buf, 32);
    if(!strcmp("LETMEWIN\n", buf)){
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }
    printf("learn about Linux file IO\n");
    return 0;
 
}
fd = atoi(input) - 0x1234
 
len = read(fd, buf, 32)
需要 buf == "LETMEWIN\n"
 
注意:
fd = 0 时,是stdin,标准输入流。这是可以用户控制
fd = atoi(input) - 0x1234
 
len = read(fd, buf, 32)
需要 buf == "LETMEWIN\n"
 
注意:
fd = 0 时,是stdin,标准输入流。这是可以用户控制
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
    int* ip = (int*)p;
    int i;
    int res=0;
    for(i=0; i<5; i++){
        res += ip[i];
    }
    return res;
}
 
int main(int argc, char* argv[]){
    if(argc<2){
        printf("usage : %s [passcode]\n", argv[0]);
        return 0;
    }
    if(strlen(argv[1]) != 20){
        printf("passcode length should be 20 bytes\n");
        return 0;
    }
 
    if(hashcode == check_password( argv[1] )){
        system("/bin/cat flag");
        return 0;
    }
    else
        printf("wrong passcode.\n");
    return 0;
}
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
    int* ip = (int*)p;
    int i;
    int res=0;
    for(i=0; i<5; i++){
        res += ip[i];
    }
    return res;
}
 
int main(int argc, char* argv[]){
    if(argc<2){
        printf("usage : %s [passcode]\n", argv[0]);
        return 0;
    }
    if(strlen(argv[1]) != 20){
        printf("passcode length should be 20 bytes\n");
        return 0;
    }
 
    if(hashcode == check_password( argv[1] )){
        system("/bin/cat flag");
        return 0;
    }
    else
        printf("wrong passcode.\n");
    return 0;
}
chat* x = input
strlen(x) = 20
int tmp = (int*) x
x += 4
result += x
result = 0x21DD09EC
 
得出,5个长度为4的数相加值为result
 
4X + Y = result
chat* x = input
strlen(x) = 20
int tmp = (int*) x
x += 4
result += x
result = 0x21DD09EC
 
得出,5个长度为4的数相加值为result
 
4X + Y = result
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);   // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\n");
    }
}
int main(int argc, char* argv[]){
    func(0xdeadbeef);
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);   // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\n");
    }

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-2-20 13:29 被kanxue编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//