본문 바로가기

자료

[VB.net/C#] 아프리카TV 채팅방 접속/채팅전송/채팅복호화

728x90

1. 모바일 아프리카TV 접속

2. F12 개발자툴을 통해 분석

3. 웹소켓 주소 확보 및 분석

4. 암호화값 해독

5. JS분석 [http://m.afreecatv.com/assets/app.6646ce82ca46e1bb64f6.bundle.js]

 

 

 채팅방 접속에 필요한 각종 값 찾기

 

1. 소켓 주소찾기

 

모바일 아프리카에서 F12 Network 창에서 아무 방이나 접속하시면 getList라는 부분을 요청하고 받아옵니다.

 

풀링크는 http://api.m.afreecatv.com/broad/chat/bridge/a/getList 인데요.

이 부분은 사용할 수 있는 소켓의 서버목록을 받아옵니다.

 

GET으로 요청하며 Json으로 반환을 받으며 이 중에서 랜덤으로 사용하시면 됩니다.

 

2. BJ정보 가지고오기

 

모바일 아프리카에서 F12 Network 창에서 아무 방이나 접속하시면 watch라는 부분을 요청하고 받아옵니다.

 

풀링크는 http://api.m.afreecatv.com/broad/a/watch 인데요.

이 부분은 BJ의 각종 방정보를 반환해줍니다.

 

POST로 요청하며 Json으로 반환을 해줍니다.

파라미터는 bj_id=비제이아이디&broad_no=비제이고유번호&agent=web 인데요.

 

BJ아이디만 입력하고 고유번호는 비워놔도 정상적으로 가지고오니 무시하셔도 됩니다.

 

3. 채팅방 로그인에 필요한 계정 암호화정보 가지고오기

 

https://login.afreecatv.com/app/LoginAction.php 로 로그인을 요청하면 계정 쿠키를 반환해줍니다.

 

그 쿠키를 분석해보면 PdboxTicket 라는 부분이 있습니다.

이 부분이 계정의 고유 암호화정보이며 이 부분은 절대 변하지않습니다.

 

4. 채팅방 암호화 정보 복호화

 

var n = new Uint8Array(e.data);

t = new TextDecoder("utf-8").decode(n) 

 

위는 아프리카 JS에서의 암호화된 정보 Decode 코드입니다.

위처럼 찾아낸 후 코드를 찾아 만들면됩니다.

 

먼저 암호화 정보는 12-41-12-62-43-71-28 이런식으로 넘어옵니다.

이 부분은 UInt8 형식이며 C#에서의 uint8은 Byte 입니다.

암호화Hex를 Byte Array화 해준 후 UTF8.GetString으로 문자를 반환받으면됩니다.

 

코드 작성

 

먼저 위에서 정보들을 가지고 와야하며, WebSocketSharp을 이용해야합니다.

 

먼저 nuget 패키지에서 WebSocketSharp 을 설치해줍니다.

 

 

그 후 Json.NET 도 설치해줍니다.

 

 

 

namespace ConsoleApplication1
{
    class Program
    {
        static CookieContainer logincookie = new CookieContainer();
        static string PdboxTicket = "undefined"; //기본값 undefined 은 비회원
 
        [STAThread]
        static void Main(string[] args)
        {
            AfreecaChat("비제이아이디");
            Console.ReadLine();
        }
 
        static async void AfreecaChat(string BJID)
        {
            bool loginstatus = await LoginAfreecaTV("아프리카TV아이디""아프리카TV비밀번호");
              if (loginstatus)
                Console.WriteLine("로그인 성공");
 
            string socketURL = await curl_get("http://api.m.afreecatv.com/broad/chat/bridge/a/getList");
            var Socketjsondata = JsonConvert.DeserializeObject<JsonParser.Socket.MainSer>(socketURL);
            socketURL = Socketjsondata.data.list[new Random().Next(0, Socketjsondata.data.list.Count)];
 
            Console.WriteLine("소켓 아이피\t: " + socketURL);
 
            string bjInfo = await curl_post("http://api.m.afreecatv.com/broad/a/watch""bj_id=" + BJID + "&broad_no=&agent=web");
            var BJInfojsondata = JsonConvert.DeserializeObject<JsonParser.BjDetail.MainSer>(bjInfo);
            string allowed_view_cnt = BJInfojsondata.data.allowed_view_cnt.ToString();
            string title = BJInfojsondata.data.broad_title;
            string user_nick = BJInfojsondata.data.user_nick;
 
            Console.WriteLine("제목\t\t: " + title);
            Console.WriteLine("닉네임\t\t: " + user_nick);
            Console.WriteLine("최대 시청자\t: " + allowed_view_cnt);
 
            string chat_no = BJInfojsondata.data.chat_no.ToString();
            string channel_info = BJInfojsondata.data.channel_info;
            string fan_ticket = BJInfojsondata.data.fan_ticket;
            string origianl_broad_no = BJInfojsondata.data.origianl_broad_no.ToString();
            string country_code = BJInfojsondata.data.country_code;
            string lang = BJInfojsondata.data.lang;
            string region_code = BJInfojsondata.data.region_code;
            string relay_ipport = BJInfojsondata.data.relay_ip + ":" + BJInfojsondata.data.relay_port;
 
            bool issend = false;
            using (var ws = new WebSocket("ws://" + socketURL + "/Websocket""chat"))
            {
                ws.OnMessage += (sender, e) =>
                {
                    string encData = e.Data;
                    string decodeData = DecodeAfreecaChat(encData);
                    //Console.WriteLine("암호화 : " + encData);
                    Console.WriteLine("받음 : " + decodeData);
 
                    /*  
                     *  - 받은채팅 명령 목록
 
                        LOGIN = 로그인
                        JOIN = 입장
                        ALERTMSG = 공지메세지
                        GETITEMADDR = 아이템
                        QUITCH = 채널퇴장메세지
                        CHATMESG = 체팅 메세지
                        BJNOTICE = BJ공지
                        DIRECTCHAT = 귓속말
                        SENDADMINNOTICE = 운영자공지
                        SETCHNAME = ??
                        SETBJSTAT = 방송종료
                        CLOSECH = ??
                        SETDUMB = 채팅금지메세지
                        BLINDEXIT = 블라인드 상태에서 탈출시도메세지
                        KICK = 강제퇴장
                        KICKCANCEL = 강제퇴장취소
                        SENDBALLOON = 별풍선메세지
                        SENDBALLOONSUB = 중계방별풍선메세지
                        VODBALLOON = VOD별풍선메세지
                        VIDEOBALLOON = 중계방 비디오풍선메세지
                        ICEMODE = 채팅 얼리기 메세지
                        SENDFANLETTER = 스티커메세지
                        SENDFANLETTERSUB = 중계방스티커메세지
                        CHOCOLATE = 초콜렛메세지
                        CHOCOLATESUB = 중계방초콜렛메세지
                        SENDQUICKVIEW = 퀵뷰선물메세지
                        SETCHINFO = ??
                        GETUSERCNT = 유저 총 수
                        GETUSERCNTEX = ??
                        CHATERROR = 채팅오류
                        GETCHINFO = ??
                        MOBBROADPAUSE = ??
                        MULTIBROAD = ??
 
                     */
 
                    if (decodeData.Contains("LOGIN"))
                    {
                        ws.Send("JOIN" + chat_no + ":" + fan_ticket + ":: & geo_cc = " + country_code + " & geo_rc = " + region_code + " & acpt_lang = " + lang + "& svc_lang = ko-KR");
                        ws.Send("CONNECTCENTER" + relay_ipport + ":" + origianl_broad_no + ":undefined: & geo_cc = " + country_code + " & geo_rc = " + region_code + " & acpt_lang = " + lang + " & svc_lang = ko-KR");
                        ws.Send("GETITEMADDR");
                    }
 
                   //ws.Send("CHAT보낼채팅내용");
                };
 
                ws.Connect();
                //mweb_ios = IOS
                //mweb_aos = ANDROID
                ws.Send("LOGIN" + channel_info + ":" + PdboxTicket + ":0:mweb_aos"); //로그인
                Console.ReadKey(true);
            }
        }
 
        static async Task<bool> LoginAfreecaTV(string id, string pw)
        {
            bool result = false;
 
            CookieContainer tempcookie = new CookieContainer();
 
            byte[] data = Encoding.ASCII.GetBytes("szWork=login&szType=json&szUid=" + id + "&szPassword=" + pw + "&isSaveId=false&isSavePw=false&isSaveJoin=false");
            HttpWebRequest postreq = (HttpWebRequest)HttpWebRequest.Create("https://login.afreecatv.com/app/LoginAction.php");
            postreq.Method = "POST";
            postreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36)";
            postreq.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
            postreq.CookieContainer = tempcookie;
            postreq.ContentLength = data.Length;
 
            Stream requestStream = await postreq.GetRequestStreamAsync();
            requestStream.Write(data, 0, data.Length);
            requestStream.Close();
 
            HttpWebResponse webResponse = (HttpWebResponse)await postreq.GetResponseAsync();
            if (webResponse.StatusCode == HttpStatusCode.OK)
            {
                tempcookie.Add(webResponse.Cookies);
                logincookie = tempcookie;
 
                try
                {
                    PdboxTicket = logincookie.GetCookies(new Uri("https://afreecatv.com"))["PdboxTicket"].Value.ToString();
                }
                catch { }
 
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
 
                string strResult = await streamReader.ReadToEndAsync();
 
                streamReader.Close();
                responseStream.Close();
                webResponse.Close();
 
                if (strResult.Contains("RESULT\":1"))
                    result = true;
            }
            return result;
        }
 
        static async Task<string> curl_get(string url)
        {
            string strResult = "";
 
            HttpWebRequest getreq = (HttpWebRequest)HttpWebRequest.Create(url);
            getreq.Method = "GET";
            getreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36";
            getreq.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
 
            HttpWebResponse webResponse = (HttpWebResponse)await getreq.GetResponseAsync();
            if (webResponse.StatusCode == HttpStatusCode.OK)
            {
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader streamReader = new StreamReader(responseStream, Encoding.Default);
 
                strResult = await streamReader.ReadToEndAsync();
 
                streamReader.Close();
                responseStream.Close();
                webResponse.Close();
 
            }
            return strResult;
        }
 
        static async Task<string> curl_post(string url, string postdata)
        {
            string strResult = "";
 
            byte[] data = Encoding.ASCII.GetBytes(postdata);
            HttpWebRequest postreq = (HttpWebRequest)HttpWebRequest.Create(url);
            postreq.Method = "POST";
            postreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36)";
            postreq.ContentType = "application/x-www-form-urlencoded";
            postreq.ContentLength = data.Length;
 
            Stream requestStream = await postreq.GetRequestStreamAsync();
            requestStream.Write(data, 0, data.Length);
            requestStream.Close();
 
            HttpWebResponse webResponse = (HttpWebResponse)await postreq.GetResponseAsync();
            if (webResponse.StatusCode == HttpStatusCode.OK)
            {
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
 
                strResult = await streamReader.ReadToEndAsync();
 
                streamReader.Close();
                responseStream.Close();
                webResponse.Close();
            }
            return strResult;
        }
 
        static string DecodeAfreecaChat(string str)
        {
            str = str.Replace("-"string.Empty);
            byte[] buffer = HexToByte(str);
            return Encoding.UTF8.GetString(buffer);
        }
 
        static byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
            {
                returnBytes[i] = Convert.ToByte(hexString.Substring(* 22)16);
            }
            return returnBytes;
        }
    }
}

728x90