notmarek / BeFake

BeReal Python API wrapper
131 stars 29 forks source link

Unable to post BeReal #43

Closed 0utplay closed 1 year ago

0utplay commented 1 year ago

When trying to post a BeReal using the post command an error occurs as the call to Post.create_post is missing a self. TypeError: create_post() missing 1 required positional argument: 'self'

The command I executed: python3 BeFake.py post Datafolder: image

albertopasqualetto commented 1 year ago

In the code, Post class is not instantiated

valerierx commented 1 year ago

Yeah program does not work at all, completely unusable

h7ndrx commented 1 year ago

any updates on this?

notmarek commented 1 year ago

I don't have time nowadays but when I get a second i will download the latest TestFlight version and try to reverse engineer the API again😬

h7ndrx commented 1 year ago

Ok thanks @notmarek 😬

albertopasqualetto commented 1 year ago

@notmarek good to know, as I said some comments before, the problem seems to be with the Post class, I think the apis were not changed because I posted something with another website, I cannot give you more info since it happened some months ago.

notmarek commented 1 year ago

Seems like i will be able to do it tomorrow since I've fallen sick.

notmarek commented 1 year ago

Seems like they started cert pinning in the iOS app so I was unable to research anything. I might archive this project as i don't have access to an Android device or a jailbroken iOS device.

h7ndrx commented 1 year ago

There has been a fix for uploading as well as the login captcha already on a fork

ArtrenH commented 1 year ago

@notmarek In the past I was able to monitor requests using android studio emulator and this project: https://github.com/shroudedcode/apk-mitm But it didn't work the last time I used it, probably because of a stupid mistake I made, I currently don't have enough time to investigate...

ArtrenH commented 1 year ago

@h7ndrx What fork is it?

s-alad commented 1 year ago

They

Seems like they started cert pinning in the iOS app so I was unable to research anything. I might archive this project as i don't have access to an Android device or a jailbroken iOS device.

You can use AVD on android studio and root using https://github.com/newbit1/rootAVD and unpin the ssl then proxy straight to your computer.

The new post code was first found by hubertoschusch I believe in this playground.

Logging in has also been bypassed by Rvaidan, I believe he is using google cloud functions as a proxy generator or something like that. However when I use mitm to proxy login requests, its still dependent on different Certs from ios and android so Im quite unsure how he is bypassing the recaptcha.

Bereal is definitely stepping up

Heres my pythonic version of the posting that matches this project somewhat:

@app.route("/signedpostinstant/<token>/<uid>/", methods=["POST"])
@app.route("/signedpostinstant/<token>/<uid>/<caption>", methods=["POST"])
def signedpostinstant(token:str, uid:str, caption:str=''):
    #==============================================================================================
    print(request.form.to_dict())
    print(request.files)
    print(caption)
    #==============================================================================================

    ispublic = json.loads(request.form.to_dict()['public'].lower())
    latitude = request.form.to_dict()['latitude']
    longitude = request.form.to_dict()['longitude']
    haslocation = json.loads(request.form.to_dict()['haslocation'].lower())
    print(ispublic, type(ispublic))
    print(latitude, type(latitude))
    print(longitude, type(longitude))
    print(haslocation, type(haslocation))

    #file manipulation
    def get_data(version):  
        version_data = io.BytesIO()
        version.save(version_data, format="JPEG", quality=90)
        version_data = version_data.getvalue()
        return version_data

    def extension(img):
        mime_type = Image.MIME[img.format]
        if mime_type != "image/jpeg":
            if not img.mode == "RGB":
                img = img.convert("RGB")
        return img

    p = request.files['primary'] 
    primary = Image.open(io.BytesIO(p.read()))
    primary = extension(primary)
    prim_data = get_data(primary) #this is the primary image file
    primarysize = str(len(prim_data))

    s = request.files['secondary']
    secondary = Image.open(io.BytesIO(s.read()))
    secondary = extension(secondary)
    sec_data = get_data(secondary) #this is the secondary image file
    secondarysize = str(len(sec_data))
    #==============================================================================================

    apiurl = f"https://mobile.bereal.com/api/content/posts/upload-url?mimeType=image%2Fwebp"
    headers = {
        "authorization": "Bearer {}".format(token),
        "accept-encoding": "gzip",
        "user-agent": "okhttp/4.10.0",
        "if-none-match": 'W/"507-M16WxEgA1LffRgMAGSRIlonfNV8"'
    }
    signed_upload_res = requests.get(url=apiurl, headers=headers)
    print("----- SIGNED UPLOAD -----")
    print(signed_upload_res)
    print(signed_upload_res.json())
    if signed_upload_res.status_code != 200: return signed_upload_res.json()
    print('----- END -----')

    signed_upload_res = signed_upload_res.json()
    signed_upload_res = signed_upload_res["data"]

    prim_path = signed_upload_res[0]["path"]
    sec_path = signed_upload_res[1]["path"]

    def intostorage(signed_res, file):
        bucket = signed_res['bucket']
        expires = signed_res['expireAt']
        image_path = signed_res['path']
        bucket_headers = signed_res['headers']
        bucket_url = signed_res['url']

        print("----- BUCKET INFO -----")
        print(bucket, "\n >>>>")
        print(expires, "\n >>>>")
        print(image_path, "\n >>>>")
        print(bucket_headers, "\n >>>>")
        print(bucket_url, "\n >>>>")
        print('----- END -----')

        ret = requests.put(url=bucket_url, headers=bucket_headers, data=file)
        print("----- BUCKET PUT RESP -----")
        print(ret)
        print(ret.text)
        print('----- END -----')
        if ret.status_code != 200: raise Exception(f"Error uploading image: {ret.status_code}, {ret.text}")
        return ret

    prim_bucket_ret = intostorage(signed_upload_res[0] ,prim_data)
    sec_bucket_ret = intostorage(signed_upload_res[1] ,sec_data)
    print("----- BUCKET RET -----")
    print(prim_bucket_ret)
    print(">>>>>")
    print(sec_bucket_ret)
    print('----- END -----')

    now = pendulum.now()
    taken_at = f"{now.to_date_string()}T{now.to_time_string()}Z"

    payload = {
        "isPublic": ispublic,
        "isLate": False,
        "retakeCounter": 0,
        "takenAt": taken_at,
        #"location": location,
        "caption": caption,
        "backCamera": {
            "bucket": "storage.bere.al",
            "height": 2000,
            "width": 1500,
            "path": prim_path,
        },
        "frontCamera": {
            "bucket": "storage.bere.al",
            "height": 2000,
            "width": 1500,
            "path": sec_path,
        },
    }
    if haslocation: payload["location"] = {"latitude": latitude,"longitude": longitude,}

    complete_res = requests.post(url=api_url+'/content/post',json=payload,headers={"content-type" : "application/json", "authorization": token},)
    print(complete_res)
    print(complete_res.json())

    return complete_res.json()
NicoWeio commented 1 year ago

Maybe throwing the BeReal APK into https://www.decompiler.com/ gives enough insight for reverse-engineering? I've made some good experiences with that approach.

valerierx commented 1 year ago

It appears that BeReal stepped up their protection against reverse engineering, it now seems to detect HTTPToolkit on a rooted phone.

Using https://modules.lsposed.org/module/io.github.tehcneko.sslunpinning works and dumps the requests. I'll post them here tomorrow

h7ndrx commented 1 year ago

Thank you @VxlerieUwU

valerierx commented 1 year ago

Here are the post upload requests and responses. I forgot to update the app tho, I'll check tomorrow if there's any differences between version 0.61.7 and 0.63.4.

Individual photo upload

Headers

cache-control: public,max-age=172800
connection: Keep-Alive
content-length: 100862
content-type: image/webp
host: storage.googleapis.com
x-goog-content-length-range: 1024,1048576

PUT https://storage.googleapis.com/storage.bere.al/Photos/userid/post/test-test.webp?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=prod-backend-fasterstore%40alexisbarreyat-bereal.iam.gserviceaccount.com%2F20230219%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20230219T215032Z&X-Goog-Expires=172801&X-Goog-SignedHeaders=cache-control%3Bcontent-type%3Bhost%3Bx-goog-content-length-range&X-Goog-Signature=signature

post upload, url https://mobile.bereal.com/api/content/posts

Headers:

accept-encoding: gzip
authorization: Bearer key
bereal-app-language: fr
bereal-app-version: 0.61.7
bereal-device-id: deviceid
bereal-device-language: fr
bereal-os-version: 8.1.0
bereal-platform: android
bereal-timezone: Europe/Paris
connection: Keep-Alive
content-length: 445
content-type: application/json; charset=utf-8
host: mobile.bereal.com
user-agent: okhttp/4.10.0
x-datadog-origin: rum
x-datadog-parent-id: 12345678910
x-datadog-sampled: 1
x-datadog-sampling-priority: 1
x-datadog-trace-id: 12345678910

POST content:

{
  "frontCamera": {
    "bucket": "storage.bere.al",
    "path": "Photos/userid/post/test-test.webp",
    "width": 1500,
    "height": 2000
  },
  "backCamera": {
    "bucket": "storage.bere.al",
    "path": "Photos/userid/post/test2-test2.webp",
    "width": 1500,
    "height": 2000
  },
  "takenAt": "2023-02-19T21:28:43.367Z",
  "isLate": true,
  "visibility": [
    "friends-of-friends"
  ],
  "retakeCounter": 0,
  "location": {
    "longitude": xx,
    "latitude": xx
  }
}

Response:

{
  "user": {
    "id": "userid",
    "username": "username",
    "profilePicture": {
      "height": 500,
      "width": 500,
      "url": "https://cdn.bereal.network/Photos/userid/profile/userid-unixtimestamp-profile-picture.jpg"
    }
  },
  "primary": {
    "height": 2000,
    "width": 1500,
    "url": "https://cdn.bereal.network/Photos/userid/post/picture1.webp"
  },
  "secondary": {
    "height": 2000,
    "width": 1500,
    "url": "https://cdn.bereal.network/Photos/userid/post/picture2.webp"
  },
  "isLate": true,
  "lateInSeconds": seconds late,
  "moment": {
    "id": "moment-id",
    "region": "europe-west"
  },
  "id": "post id?",
  "location": {
    "longitude": xx,
    "latitude": xx
  },
  "caption": null,
  "retakeCounter": 0,
  "comments": {
    "sample": [],
    "total": 0
  },
  "realmojis": {
    "sample": [],
    "total": 0
  },
  "screenshots": {
    "sample": [],
    "total": 0
  },
  "createdAt": "same date format as before",
  "takenAt": "same date format as before",
  "visibility": [
    "friends",
    "friends-of-friends"
  ],
  "canDelete": true
}

Edit: I'm using a Nexus 5X running LineageOS 15.1 with latest magisk, zygisk LSPosed and the certificate unpinner above if you want to reproduce that.

valerierx commented 1 year ago

Btw is anyone planning on creating a GTK4 frontend using this library? I just recently got a Pinephone Pro and having BeReal on it would be awesome 😃

LCB0B commented 1 year ago

@VxlerieUwU any idea of how to fix the login ? :)

valerierx commented 1 year ago

I'll investigate and post some endpoints here soon this week. I think BeReal is making their platform much secure and harder to reverse engineer, we'll have to dump the api endpoints whenever a new update goes public.

valerierx commented 1 year ago

I have some really bad news: the login process has gotten even more complicated, with the use of SafetyNet and other google stuff. Can someone with access to a jailbroken IOS device dump the login endpoints to see if they can be used by this library?

valerierx commented 1 year ago

Uh I just tried logging in using the master branch and it just works?? Maybe someone using an Iphone can dump the newest user-agents and iOS receipt.

valerierx commented 1 year ago

Edit: I've discovered the endpoint to generate the picture upload API. Gonna add it to my private fork and make a pull request soon.

s-alad commented 1 year ago

I think I posted pythonic code for Signed picture uploads earlier if you would like to take a look

valerierx commented 1 year ago

Btw the Picture class here needs to be completely rewritten, uploads are now for both primary and secondary image at the same time, and at a seperate endpoint for Realmojis.